Commit Graph

45675 Commits

Author SHA1 Message Date
Brooklyn Nicholson
6007b3ce4f refactor(desktop): export DialogCloseButton for the boot-failure overlay
The overlay carried a byte-for-byte copy of DialogContent's close button.
2026-09-28 11:50:16 -05:00
Brooklyn Nicholson
1b8569f41f feat(desktop): filter Messaging platforms by status
A status filter sits beside the Messaging search, with a tab only for tones
some platform is in (errors, needs attention, connected, inactive). Picking a
tab moves the detail pane into the filter; a platform that leaves the filter
because you just toggled it stays open.

Messaging and the gateway menu now read platform state through one
classifier, so the same bot can no longer show two different tones.

Co-authored-by: Sonnenwerk <a.majuskel@gmail.com>
2026-09-28 11:49:02 -05:00
finn763
1bb55780ab fix(desktop): collapse a redelivered interim into its bubble (#120104, #93926)
A message.interim redelivered after the first copy sealed the stream took the
standalone-append path and painted the same commentary twice. Refresh the
newest same-text assistant row of the current turn in place instead,
comparing whitespace-normalized text.

Supersedes #120145 and #93962.

Co-authored-by: Brammiebram <64777864+Brammiebram@users.noreply.github.com>
2026-09-28 11:31:46 -05:00
Abarajithan
675f29da03 fix(desktop): settle high-overlap sealed replies 2026-09-28 11:31:46 -05:00
JoaoMarcos44
c6f964f6d4 fix(agent): preserve configured context cwd on desktop compaction 2026-09-28 11:29:49 -05:00
Brooklyn Nicholson
34e09bff78 fix(desktop): namespace the project-row drop-hover attribute
Some checks failed
Deploy Site / deploy-vercel (push) Has been cancelled
Deploy Site / deploy-docs (push) Has been cancelled
auto-fix lint issues & formatting / Generate eslint --fix patch (push) Has been cancelled
auto-fix lint issues & formatting / Apply patch (push) Has been cancelled
Skills Index Freshness Check / check-freshness (push) Has been cancelled
Build Skills Index / build-index (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
'data-drop-hover' is a generic token any component could style; rename to
'data-session-drop-hover' to match the session-drag module that owns it
(same attr constant in session-drag.ts) so no foreign CSS can hit-test it.
2026-09-28 10:56:57 -05:00
Hermes Agent
fbfe3e29f7 test(desktop): cover the project-row drag-drop target
Moves to the dropped project row, and stays a no-op over Home and the
session's own current project — the same exclusions the row's existing
"Move to project" menu applies.
2026-09-28 10:56:57 -05:00
Hermes Agent
c4d14871e1 feat(desktop): drag a sidebar session onto a project row to move it
Extends the existing pointer-based session drag (session-drag.ts) with a
new resolvable target: a sidebar project row (tagged data-sessions-project
by ProjectOverviewRow). Dropping a session there calls the same
moveSessionToProject RPC the row's own "Move to project" context-menu item
already uses, so this is a faster path onto an existing capability rather
than a new one. Home and the session's current project are excluded as
targets, matching the context-menu's own filtering. The hovered project
row is highlighted imperatively (a data-drop-hover attribute + an
outline-sidebar-ring style) to keep the sidebar out of React re-render
churn during pointer travel.
2026-09-28 10:56:57 -05:00
webtecnica
f4ac4aa6b8 fix: Make MoA strictly opt-in in model pickers (#63353)
Add _raw_config_has_enabled_moa_preset() guard to _moa_provider_row()
so the virtual MoA provider row only appears in model pickers when the
user's raw config.yaml explicitly enables at least one MoA preset.

The synthesized 'default' preset from normalize_moa_config({}) (which
every user gets via DEFAULT_CONFIG defaults) must not be treated as a
user choice — a user selecting 'openai-codex:gpt-5.6-sol' should never
see a transient switch to provider=moa.

The guard already existed in _filter_explicit_provider_rows() but was
missing from _moa_provider_row(), which is the entry point for the
non-explicit picker paths (TUI full picker, dashboard picker, etc.).

Adjust three tests that iterated CANONICAL_PROVIDERS (which includes
'moa') or used count-based assertions that assumed MoA was always
present.
2026-09-28 10:56:47 -05:00
Siddharth Balyan
e408d36339 fix(desktop): break the combo.ts <-> actions.ts import cycle (#126361)
2ffa4977ba made combo.ts import keybindActionAllowedInEditableTarget
from actions.ts, while actions.ts already imported IS_MAC from
combo.ts. When actions.ts is evaluated first, its KEYBIND_ACTIONS
table reads IS_MAC before combo.ts has initialized it. The dev
renderer throws "Cannot access 'IS_MAC' before initialization" and
#root stays empty.

actions.ts now reads the platform from the leaf module
@/lib/platform (isMacPlatform), which chords.ts and other keybind
code already use. actions.ts no longer imports combo.ts, so the
cycle is gone. The three defaults resolve at module load exactly as
before.
2026-09-28 15:00:41 +00:00
Siddharth Balyan
8f21606116 Hiding reasoning no longer hides tool rows, and tool_progress: off no longer hides reasoning (salvage #125948) (#126479) 2026-09-28 14:18:37 +00:00
Siddharth Balyan
66873f6fc3 Two tui_gateway server tests no longer fail on signed-in hosts or race the ctime clock (salvage #93665) (#126490) 2026-09-28 14:18:10 +00:00
kshitijk4poor
c8173e0387 fix(state): sweep turn-lease rows left behind by dead holders
A turn that dies without releasing (SIGKILL, crash, parent killed with
children in flight) leaves its session_turn_leases row forever when
nobody resumes that conversation. Acquisition only reclaims the row of
the conversation it is acquiring.

Each acquisition now deletes rows that expired more than 24h ago, in the
same write transaction, through the existing expires_at index (which had
no reader). Recent expiries are kept, so a live owner with a starved
refresher can still renew its own row on its next write.

On a copy of a real state.db lease table, one acquisition removed the four
rows expired 16 to 25 days ago and kept the row expired 15.6h ago and the
live rows.
2026-09-28 19:33:44 +05:30
Brooklyn Nicholson
7dbbb0f4a6 fix(desktop): report the current commit in updater status 2026-09-28 08:58:55 -05:00
teknium1
be84a141f6 test(e2e/pm): drop the #124214 gate in test_stamps_and_doctor
hermes doctor reports a dashboard web surface that cannot import.
2026-09-28 06:42:00 -07:00
teknium1
a4420be2de fix: skip the extras lookup when nothing configured is missing; pin probe encoding
The update build called declared_extras() on every run, which reads
pyproject.toml even when no configured feature lacks its SDK (and product
build fixtures have none). The doctor web-surface probe decodes with UTF-8
like every other text-mode subprocess call.
2026-09-28 06:42:00 -07:00
teknium1
de7ed4cc3b test(e2e): drop the #124049, #124418 and #124635 gates
execute_code imports third-party deps on a PM install, the PM runtime restages
through a pip mirror, and a launch on a read-only install tree fails once naming
the permission problem; the cells now assert it.
2026-09-28 06:42:00 -07:00
JoaoMarcos44
0b105e0714 fix(pm): report unwritable install state once 2026-09-28 06:42:00 -07:00
Yuan Li
80e834b129 fix(pm): preserve transport knobs under no_config isolation, strip only index redirects
The isolation filter now drops only settings that redirect WHERE packages
resolve from (index URLs, find-links, strategy, index credentials); transport
knobs (UV_NATIVE_TLS / UV_INSECURE_HOST / UV_HTTP_TIMEOUT) survive — corporate
networks need them to reach the pinned wheel URLs at all.

Addresses the review note on the no_config scope in the #125071 triage.
2026-09-28 06:42:00 -07:00
Yuan Li
32aeb9ef7b fix(pm): honor no_config isolation for bridged index settings in uv runs
stage_runtime (the pinned pm/ self-repair builder) passes no_config=True,
but that only appended uv's --no-config, which removes config FILES. The
pip-mirror bridge (pm.index_config) reaches uv as env vars instead, so a
user's pip.conf index-url still rode into the runtime sync via
runtime_environment() -> _base_environment(env=None). Re-resolving the
official-index pm/uv.lock against a mirror changes the resolution, and
uv sync --locked refuses — breaking the top-level update / doctor /
pm-repair recovery paths on any machine with a pip mirror (#124418).

When no_config is set, strip forwarded index/transport knobs from the
child env: a fully-pinned graph never needs a mirror to resolve, and the
default path keeps the bridge so mirrored/air-gapped application installs
still work.
2026-09-28 06:42:00 -07:00
funky-xamarin
7a09408e7c fix(execute-code): preserve PM runtime dependencies for own kernel 2026-09-28 06:42:00 -07:00
teknium1
e13f4212c2 fix(gateway): launcher-started gateways persist an identifiable argv (#124029)
<bootstrap>` launcher, but the persisted record still carried raw sys.argv,
which under that launcher is ["-c", "gateway", "run"]. _record_looks_like_gateway
could never accept it, so readers that fall back to the record (unreadable live
cmdline on Windows/EACCES, pid-file validation) still treated a healthy gateway
as foreign, and _gateway_code_root found no checkout path in it.

_build_pid_record now records the argv `python -m hermes_cli.main` would have
(argv[0] = the checkout's hermes_cli/main.py) when argv[0] is "-c"; the rest of
argv (including --external-supervisor) is unchanged.

The handoff E2E `head` relaunch cell already passes on main after #124965, so
its RELAUNCH_GATES entry is removed (the n1 entry is #124649, a different bug).
2026-09-28 06:42:00 -07:00
teknium1
a153adcc2e fix(update): install the SDKs of configured gateway platforms instead of only warning (#124228)
A PM environment build keeps the recorded extras selection and never reads
config, so a home with platforms.discord (or telegram) enabled came out of
`hermes update` without the SDK: the update child detected it
(_configured_features_missing_deps) and only printed a warning, and the
gateway's lazy install at connect time is the only recovery — which fails
silently when lazy installs are off or the process is not on the selected
generation.

The update-build child (fresh, selected interpreter) now maps each missing
configured feature to the pyproject extra named after it (platform name ->
extra, MCP -> mcp), keeps only declared, platform-supported extras, and adds
them with an explicit pm.sync_venv. Detection reuses the gateway's own
connected-platform semantics (config + env credentials), so it installs exactly
what the gateway will try to load on restart. Features it could not install
still get the warning; an install failure never fails the update.

Flips tests/e2e/core/upgrade/pm/test_configured_features.py::
test_configured_gateway_platform_has_its_sdk_after_update.
2026-09-28 06:42:00 -07:00
teknium1
f80cf708d4 test(doctor): trim the web-surface probe tests to two invariants
Keep the drifted fastapi/starlette pair (reported as an issue naming the
repair) and the probe never lazy-installing into the environment it
diagnoses; the absent-extra case is covered by the E2E drift cell. Also
fix the exit-code detail spacing and PEP 8 spacing after the check.
2026-09-28 06:42:00 -07:00
kokhlo
cf2fa69eba fix(doctor): report a dashboard web surface that dies at import
The optional `web` extra pins fastapi/starlette in lockstep, but the standalone
starlette security pin ships in several other extras on its own — so a managed
venv can end up with starlette 1.x beside an older fastapi. `hermes dashboard`
then dies constructing `FastAPI(...)` with `TypeError: Router.__init__() got an
unexpected keyword argument 'on_startup'`: not an ImportError, so the module's
own lazy-install fallback never fires, and the process exits before printing a
single line. `hermes doctor` had no probe covering this surface, so a dead
dashboard stayed invisible until the user needed it as a recovery path.

Add a doctor check that imports `hermes_cli.web_server` in a subprocess with
lazy installs disabled (read-only probe; it must never repair what it is
diagnosing) and reports the failing line plus the repair hint, or a soft warn
when the optional web extra is simply absent.

Fixes #124214
2026-09-28 06:42:00 -07:00
kshitijk4poor
e992d7ca10 refactor(codex): record webSearch status only when codex sends one
Codex webSearch items carry id/query/action; defaulting a missing status
to "completed" wrote a value codex never reported. Keep the provenance
marker, add status only when present, match sibling specs' ensure_ascii,
list webSearch in the module docstring, and pin args parity with the live
bridge (_codex_item_to_args) instead of a literal.
2026-09-28 19:11:05 +05:30
h4ck200711
4e0d638023 fix(codex): keep Codex web searches in stored history as tool calls
Codex app-server webSearch items fire the live tool bubble under
codex_web_search_<itemId>, but the projector had no entry for them, so the
stored transcript kept an opaque "[codex webSearch] {json}" note instead.
Any transcript refresh or reopen then replaced the search card with raw
JSON.

The projector now records webSearch as a web_search tool_call under the
same id and name the live bubble uses. The tool result is
{"provider": "codex", "status": ...}, so a later non-Codex turn can tell
Codex ran the search.

Salvaged from #66995: only the webSearch projection is kept, reworked
onto the _TOOL_PROJECTIONS table and the live web_search name. The MCP
elicitation trust change is not included.
2026-09-28 19:11:05 +05:30
kshitijk4poor
bb18f049a2 chore: map h4ck200711 contributor email (PR #66995 salvage) 2026-09-28 19:11:05 +05:30
alt-glitch
8790d031eb fix(analytics): daily buckets use local days, matching /insights
/api/analytics/usage grouped sessions with date(started_at, 'unixepoch'),
which is the UTC calendar day. The Desktop Command Center and the web
Analytics page show that string as a calendar date, and the CLI /insights
command buckets the same sessions by the machine's local day
(datetime.fromtimestamp in agent/insights.py). For a user east of UTC, a
session started at 02:00 IST was counted on the previous day.

Add SQLite's 'localtime' modifier so the day is the machine-local day,
the same one /insights uses. SQLite applies it per row through the C
library, so each session gets the UTC offset in force at its own start
time and the buckets stay correct across a DST change.

Refs #23982
2026-09-28 19:08:27 +05:30
alt-glitch
318f6ac63f fix(cron): order execution, delivery and incident history by instant, not by text
The cron SQLite stores write hermes_time.now().isoformat(), an ISO string
that carries the local UTC offset. That offset changes at a DST transition
and after a timezone config change, so the text order of these columns is
not their time order. Across a fall-back, 01:10-05:00 is 20 minutes after
01:50-04:00 but sorts before it. latest_execution(), latest_executions()
(which fills job["latest_execution"] for `hermes cron list` and the
dashboard), list_executions() and its before_claimed_at cursor, the
delivery queue's pending-claim order, delivery and execution pruning, and
list_incidents() all returned the older row first.

Every ORDER BY and the cursor comparison on these columns now use
julianday(col), which SQLite resolves to the same instant for any offset.
The raw text stays as the next sort key, so rows within the same
millisecond (julianday's resolution) keep their microsecond order, and the
cursor compares the same (instant, text) key on both sides. Stored values
and the schema are unchanged, so existing rows with mixed offsets order
correctly without a migration.

latest_executions() moves from a correlated per-row subquery to one
ROW_NUMBER() window. With julianday() in the ORDER BY the index on
claimed_at no longer serves the per-row LIMIT 1, and the correlated form
took about 90 ms at 1000 rows; the window form takes about 1 ms.

Refs #86520
Thanks to #86889 (zhao0112) for the executions-ledger diagnosis.
2026-09-28 19:07:43 +05:30
alt-glitch
8a2fb31fbc fix(time): session start in the system prompt uses its own date's UTC offset
_session_start_like localised the naive session-id stamp with
datetime.now().astimezone().tzinfo, which is a fixed offset for today.
A stamp from the other DST half got the wrong offset: under
Europe/London on a summer day, session 20260115_003000_* rendered as
"Conversation started: Wednesday, January 14, 2026". dt.astimezone()
applies the offset in force at the stamp itself, as
gateway/message_timestamps.py already does.

Also replace the two remaining datetime.utcfromtimestamp() calls
(deprecated since Python 3.12) with fromtimestamp(..., timezone.utc).
The output strings are unchanged. tui_gateway/methods_session.py runs
with tui_gateway/server.py's globals, so server.py now imports timezone.
2026-09-28 19:04:53 +05:30
alt-glitch
57d7a15c28 fix(oauth): Qwen OAuth status card shows the token expiry
The qwen-oauth row in _PROVIDER_STATUS (hermes_cli/web_routers/oauth.py)
read three keys that get_qwen_auth_status (hermes_cli/auth_qwen.py) never
returns: expires_at, access_token and auth_store_path. The getter returns
expires_at_ms, api_key and auth_file. So the Accounts card for a logged-in
Qwen user always had no expiry, an empty token preview, and the generic
"Qwen CLI" label.

The row now reads the getter's keys. The epoch-ms expiry is converted to
an aware UTC ISO string, the same shape the nous and minimax cards send
and the type the web and desktop clients declare for expires_at.

The other rows (nous, openai-codex, minimax-oauth, xai-oauth) were checked
against their getters and already read keys the getters return.
2026-09-28 19:03:26 +05:30
alt-glitch
fc4dbe32df fix(logs): hermes logs --since/--level handle unstamped lines and MCP output
`hermes logs --since` and `--level` passed every line that had no leading
timestamp. A traceback's frames are written without one, so an old error
printed its frames without the header that was filtered out, and
`--component` dropped the frames of a matching record. `hermes logs` now
reads each unstamped line as part of the record above it: the line gets
that record's verdict for every filter, in the tail read and in `-f`.
Lines before the first stamp in the read window have an unknown time and
level, so they are dropped when `--since` or `--level` is set.

mcp-stderr.log had no parseable stamp at all: the banner started with
`=====` and server output was copied raw, so `hermes logs mcp --since`
printed the whole file. The stderr tee already reads each server's
stderr in a thread, so it now writes one line at a time, each prefixed
with the asctime-shaped local stamp the Python logs use. The banner
starts with the same stamp. The stamp comes from new public
`timestamp()`/`stamp_line()` in hermes_cli/stderr_timestamp.py, which
stays stdlib-only. The desktop MCP log view accepts both banner shapes.

A test now requires a real writer's sample line for every LOG_FILES
entry to parse with `_parse_line_timestamp`. The docs no longer say the
`timezone` key changes log timestamps; log lines use the machine's
local time.
2026-09-28 19:02:20 +05:30
alt-glitch
2d9d4abbef fix(desktop): desktop.log stamps lines in local time, like the Python logs
desktop.log stamped each line with an ISO-8601 UTC time in brackets
(`[2026-09-28T07:48:19.466Z]`), while agent.log, gui.log and errors.log use
Python's local-time asctime (`2026-09-28 13:18:46,062`). Reading one
incident across logs/ meant converting clocks by hand, and
`hermes logs desktop --since 1h` returned every desktop line, because its
timestamp regex expects the Python shape at the start of the line and
lines it cannot parse pass the filter.

formatLogStamp() now emits the Python shape in local time, and
formatDesktopLogLine() puts it first: `2026-09-28 13:18:46,062 [hermes] ...`.
The #84405 comment already said the stamp matched the Python convention;
now it does.
2026-09-28 19:02:20 +05:30
Brooklyn Nicholson
71cb7d60f8 fix(desktop): add model visibility recovery action
Edit Models gains a "Reset to defaults" action: it clears the persisted
visible/known model snapshots so models frozen hidden by the old
allowlist adoption come back, while custom models stay stored and shown.
Copy added in all supported locales.

Original work by @atomic-pancake-49 in
https://github.com/NousResearch/hermes-agent/pull/122055.

Fixes https://github.com/NousResearch/hermes-agent/issues/122053
2026-09-28 08:23:14 -05:00
alt-glitch
96aa40a49f fix(gateway): launchd gateway stdout lines are timestamped
Under launchd the plist shell appends the gateway's stdout to gateway.log
(hermes_cli/gateway_launchd.py:237,367), the same file the gateway logging
handler writes (hermes_logging.py:371). print() output such as the WhatsApp
"Bridge started on port" line therefore landed there with no timestamp, and
`hermes logs gateway --since` kept those lines because it cannot date them.

stderr_timestamp already sits between launchd and the gateway and stamps
stderr into gateway.error.log. It now also pipes the child's stdout and writes
each line, stamped in the same asctime shape, to its own stdout. The file
targets, the plist and the detached fallback (gateway_launchd.py:296-299) are
unchanged, so installed plists pick this up without a reinstall. Lines that
already start with a stamp pass through unchanged.

The stdout reader is a daemon thread. After the child exits the wrapper waits
at most 5 s for it, so a grandchild that inherited stdout cannot keep the
launchd job alive past the gateway's exit; the exit-code mapping is unchanged.
The wrapper stays stdlib-only.
2026-09-28 18:43:12 +05:30
teknium1
e824425e8c fix(review): gateway restart's orphan reap is best-effort and runs on every restarting path (#125394)
The relocated `_reap_unsupervised_gateway_orphans()` in `_cmd_restart` was a
bare call, so a reaper exception (OSError on the process scan) aborted
`hermes gateway restart` before `_service_call` — the best-effort wrapper the
Desktop backend had (`except Exception: pass`) was dropped with the move, and
so was the test proving it. Wrap it like the sibling in
`stop_profile_gateway()` (logger.debug) and restore the invariant as
`test_cli_restart_reap_failure_does_not_block_restart` against `_cmd_restart`.

Also move the reap to just after the refusal guards / `--all` branch so the
external-supervisor hand-back and the manual fallback (kind is None) are swept
too, not only the service-manager branch. The s6 dispatch and
`profile_lifecycle` returns stay unswept on purpose: s6 supervises the slot,
and profile_lifecycle only handles a profile whose `find_gateway_pids()` is
empty, so there is nothing for the scan to find.
2026-09-28 06:06:17 -07:00
teknium1
3df15fd727 fix(desktop): trim connection-scoped tile salvage to the tab-persistence invariants (#120106, salvage #121983)
Keep the (connectionId, profile) tile keying and its two invariant tests;
drop the post-switch remembered-route re-navigation (separate scope, hook
latch juggling) and the two extra delete-path tests.
2026-09-28 06:06:17 -07:00
Teo | Nexcore
70f869464b fix(desktop): delete tabs in the active connection scope 2026-09-28 06:06:17 -07:00
Teo | Nexcore
68f724b3b8 fix(desktop): restore connection-scoped tabs and chat on backend switch 2026-09-28 06:06:17 -07:00
teknium1
a0e71746f4 fix(gateway): restart refuses before it stops anything (#125394)
The Desktop backend's `_spawn_gateway_restart` reaped "orphan" gateways
BEFORE spawning `hermes gateway restart`; the child then applied its
multiplexer refusal (`_guard_named_profile_under_multiplexer`, exit 78) and
started nothing, so a refused restart still cost the profile its gateway
(the reporter's 'refused restart, heartbeat stopped between two attempts').

Move the #77276 reap into the child, after the refusal guard, on the
service-manager branch (the manual fallback already reaps inside
stop_profile_gateway(); `--all` and the externally-supervised hand-back do
their own stop). The backend now stops nothing itself.

Test changes: `test_reap_failure_does_not_block_spawn` asserted the reap ran
in the backend (the bug); it is replaced by the two invariants: the backend
spawns without reaping, and the CLI reaps only after the guard.
2026-09-28 06:06:17 -07:00
teknium1
10708bc117 fix(bot-chat): rebuild a stale Bot Chat's tools through the surface builder (#124211, salvage #124307)
Trim the salvaged detector/delivery pair to the salvage bar and fix the
delivery half. The PR resolved the refresh selection as
platform_toolsets.<surface> (desktop/tui), a key no config path writes,
so _get_platform_tools fell back to the constructed hermes-<surface>
composite that resolves to 0 tools (a cold resume went 30 -> 1 tool).

Delivery now goes through the builder new desktop/TUI sessions use
(tui_gateway.server._load_enabled_toolsets(platform) +
_load_disabled_toolsets) as an explicit refresh_agent_mcp_tools
override, so the refreshed set equals a fresh session's on that surface;
only desktop/tui agents are long-lived, every other surface builds a
fresh agent per process/turn and is left alone. Drops the
toolsets.py/tui_gateway policy relocation and the raw-value fallback in
the fingerprint; keeps the detector (platform_toolsets +
agent.disabled_toolsets replace the dead tools.enabled_toolsets key)
and the tools re-pin after the refresh.

Tests: 2 invariants in tests/agent/test_bot_chat_toolset_refresh.py
(desktop -> builder consulted, disabled override passed, re-pinned;
cli -> untouched) and the existing fingerprint axis test now edits
the key `hermes tools enable/disable` writes.
2026-09-28 06:06:17 -07:00
finn763
fcda937c5d fix(bot-chat): deliver toolset changes to the canonical Bot Chat
The capability epoch watched tools.enabled_toolsets, a key no surface
writes; real hermes tools enable/disable edits (platform_toolsets.* plus
agent.disabled_toolsets) never flipped it. Even when stale, the refresh
rebuilt only the prompt and never tools[]. Watch the real keys and
rebuild + re-pin the tool snapshot on Bot Chat capability refresh.
Closes #124211
2026-09-28 06:06:17 -07:00
teknium1
28365a5bec fix(tui_gateway): session.set_hidden requires an explicit hidden flag (#122190, salvage #122568)
Trim the salvaged handler/test prose to the salvage bar and make the
contract match the handler: SessionSetHiddenParams.hidden is required
(no default=True), so the generated TS/OpenRPC types stop advertising an
optional flag whose absence used to hide the whole compression lineage.
Regenerated apps/shared/src/gateway-contract.*.
2026-09-28 06:06:17 -07:00
finn763
43d09822ae fix(tui): require explicit hidden flag on session.set_hidden
A missing hidden param defaulted to True, so any caller that dropped
it silently hid the session plus its whole compression lineage
(set_session_hidden fans out via _set_lineage_column). Related to #122190.
2026-09-28 06:06:17 -07:00
teknium1
c44fa8600b fix(tui_gateway): create-time Bot Chat composer pick survives resume (#123805, salvage #125911)
Trim the salvaged change to the salvage bar:

- Tighten the comment at the new `_create_session` marker site to the WHY:
  it records the same provenance `_apply_model_switch` records for a mid-chat
  pick, so `_stored_session_runtime_overrides` can tell a chat-scoped pick from
  an unmarked profile-following row instead of dropping model AND provider.
- Replace the contributor's record-shape test in the 22k-line
  test_tui_gateway_server.py with one end-to-end invariant next to the existing
  Bot Chat composer tests: `session.create` under owning profile B (launch
  profile A has a different model) records B's model as the marker, the first
  row write (`_ensure_session_db_row`) persists it into B's real state.db, and
  the resume read under B's scope restores the picked model and provider.

Red on origin/main (KeyError: the record has no `composer_override_profile`,
so the row is written marker-less and resume falls back to the profile
default); green on head.

Class check (write paths that set a model override on a follow_profile_config
session): `_apply_model_switch` (mid-chat composer pick / /model via
config.set model), the three `session.resume` paths and `_persist_live_session
_runtime` / `_session_row_model_config` already carry the marker on main
(#116889, 53ac548f36); `session.create` was the only writer that did not.
Branch children (`_branch_live`, `session.branch_stored`) do not inherit
`follow_profile_config` or `model_override`, so they are out of the class.
2026-09-28 06:06:17 -07:00
Nagisa-3000
865fea8061 fix(desktop): preserve bot composer selections on resume 2026-09-28 06:06:17 -07:00
teknium1
fc51ca12a2 fix(desktop): trim dead-tile retirement to the salvage bar (#125678, salvage #125706)
Same outcome as the salvaged commits — a restored tab whose id every
profile answered 404 for in calm conditions is dropped instead of being
re-probed on every launch — with less machinery:

- one set of scope listeners per backfill run instead of six per tile;
  retired ids are tracked by storedSessionId, so a tile array rebuilt by
  the backend-identity guard between start and gateway-open cannot make
  the retire silently never fire.
- the "profile inventory known" gate lives in probeStoredSession, where
  the ladder decides what its evidence proves, instead of in the caller.
- the nine-case test file is folded into session-tile.test.ts as two
  invariants: retire on all-profile 404 (red on origin/main), keep on
  5xx / network failure / switch in flight / profile A→B→A / connection
  switch / stashed draft / unknown inventory.
2026-09-28 06:06:17 -07:00
funky-xamarin
aa492fb76e fix(desktop): keep dead tiles out of reopen history 2026-09-28 06:06:17 -07:00
funky-xamarin
93ef2e2d09 fix(desktop): release cancelled tile backfill subscriptions 2026-09-28 06:06:17 -07:00