360 Commits
Author SHA1 Message Date
unsupportedpastels fcb3085b9b feat(desktop): Daybreak control in the model route editor
The route dialog offers a Daybreak select (Off / On) only when the
installed model_map backend declares the `daybreak` route key and this
account's Codex catalog lists Daybreak for the chosen model (the same
capability the main model menu's Daybreak switch uses). An older plugin
that would ignore the key never gets the control.

- Switching to a model or provider without Daybreak clears the opt-in.
- A stored opt-in the plugin cannot run, or whose model is no longer
  listed, stays visible with a warning so it can be turned off.
- Turning it off removes the key; the route list marks Daybreak routes.
- Strings in every locale.
2026-11-05 15:44:58 -05:00
Austin Pickett a5f20c5c04 i18n(tui): mirror the /queue management keys into the bundled TUI packs 2026-11-05 12:34:55 -05:00
Austin Pickett c91bd19840 chore(tui): regenerate i18n key manifest for /queue verbs 2026-11-05 12:34:55 -05:00
Teknium 1a71782f17 feat(cli): backtrack picker — Esc Esc / /rewind to edit & resend or fork from an earlier prompt
Classic CLI gains a Codex-style backtrack picker. Esc Esc on an idle, empty composer with no
overlay (or /rewind) lists earlier prompts newest first with a preview and turn number.

- Enter: prefill the prompt for editing. Nothing is cut until the edit is submitted; the
  submission rides to the process loop as a BacktrackResend, which re-validates the picked
  turn (same session, same text) and then runs the same durable rewind /undo uses. Esc Esc,
  Ctrl+C, an empty submit, or submitting a slash/! command cancels and cuts nothing.
- f: fork. Runs the existing /branch path, then rewinds only the CHILD to just before the
  picked prompt and prefills it; the parent's rows stay active.
- /rewind N and /rewind fork N do the same without the picker.

Esc Esc with a draft still discards it; mid-turn Esc Esc is unchanged.

undo_last's rewind body moves into CLISessionMixin._rewind_to_user_ordinal (shared by /undo
and the picker; same compaction-carrier handling, agent patching and memory rewound hook).
The picker lives in a new hermes_cli/cli_backtrack.py mixin; cli_tui_mixin.py is untouched.
CommandDef rewind is cli_only (desktop: terminal); desktop registry and parity table regenerated.
2026-11-04 23:06:14 -08:00
Teknium 185a5cce10 feat(tui): rewind picker forks from here (f) and jumps the transcript to the picked prompt
The /rewind picker (Esc Esc on an idle, empty composer) gains Codex-style
backtrack navigation:

- f = Fork from here: a `session.mutate branch` through the last stored row
  before the picked prompt (the same primitive Desktop's message-level branch
  sends), switch to the child, prefill the prompt unsent. The original session
  is untouched. The first prompt has nothing before it, so it opens a plain new
  session with the prompt prefilled.
- The list shows a preview (first lines + prompt number) of the selection.
- While the list is open the transcript scrolls to the selected prompt and
  marks it, so Up/Down jumps between earlier user messages.
2026-11-04 23:06:14 -08:00
Teknium 5790154f11 fix(i18n): mirror the new TUI keys into the bundled locales/*.tui.yaml packs
The keys this branch added to the TUI catalog (locales/_keys.tui.json) had no
translation in the bundled per-language packs, so
test_bundled_tui_pack_mirrors_tui_key_export failed for every language.
2026-11-04 23:06:14 -08:00
TekniumandSaguaroDev 86be843ef5 feat(tui): /rewind picker (Esc Esc) and a 'No, and tell Hermes instead' approval row
The Ink TUI could only undo or retry the newest turn. `/rewind` (and Esc Esc on an idle,
empty composer) now lists every earlier text prompt; Enter opens the picked one for editing and
Enter again cuts the session back to just before it and sends the edit. Nothing is cut until the
edit is sent, and Esc steps back (edit -> list -> closed).

The cut is the same revision-fenced `session.mutate rewind` that /undo and /retry use, aimed at
the picked row: the owner's form of the `prompt.submit truncate_before_row_id` Desktop's
edit-and-resend sends. A media turn keeps its number but is not offered (no lossless replay,
the /retry rule). Salvages the /rewind UX of #25074 / #23445 onto main's rewind primitives.

Approval prompts gain "No, and tell Hermes what to do instead" wherever Deny is offered. Picking
it keeps the command held while the user types; Enter steers the instruction into the running
turn first (so it rides on the blocked tool result), then answers the gateway with a plain deny.
A refused steer (the turn already ended) keeps the words as the next prompt, the /steer fallback.
Esc on the typing row returns to the choices; Esc on the choices still denies outright.

Co-authored-by: SaguaroDev <74339271+SaguaroDev@users.noreply.github.com>
2026-11-04 23:06:14 -08:00
Teknium 18563653f5 i18n(tui): translate the /plugins update result strings into every bundled TUI pack
The nine slashCmd.ops.pluginsUpdate keys the TUI update-all route added were in the en source and
the key snapshot but missing from the 16 locales/*.tui.yaml packs (test_i18n_tui_packs).
2026-11-04 21:58:12 -08:00
Teknium 1fbff2eb3c chore(tui): regenerate the TUI copy-key snapshot
The /plugins update routing change added a TUI string; CI i18n:keys:check reds on a stale snapshot.
2026-11-04 21:58:12 -08:00
Teknium c91c57b1e2 feat(desktop): Update all and Auto-update on the Plugins tab
The Plugins tab header gets an "Update all" button (plugins.manage
update_all: the unattended path; widened pins and caution scans come
back as needs review and are named in the toast) and an Auto-update
switch for the selected profile (plugins.manage auto_update, i.e.
plugins.auto_apply). The switch hides on an older backend that lacks
the action. Copy in all eight overlays; locale keys regenerated.
2026-11-04 21:58:12 -08:00
Teknium f24a54c3d7 feat(desktop): MCP consent line names the env vars and working directory a stdio server adds; a string args value is one argument 2026-11-04 21:44:24 -08:00
Teknium 3dbb1303fe feat(desktop): show what an MCP consent row runs or contacts before approval
The manage_connections MCP card asked the user to Install / Enable / Authorize
a server by name only. Each MCP target now carries a backend-built `launch`
line ({kind: stdio|remote, target}) in connection.request: the catalog
manifest's transport for install, the configured mcp_servers entry for
enable/authorize. The card renders it under the row as plain text
("Runs: npx -y ...", "Connects to: https://...") until the row resolves.

The line is scrubbed in the backend (tools/connectors/mcp_launch.py): values
of secret-named flags (--api-key=..., --token ...), credential query params
and URL userinfo, long opaque URL path segments, then the standard egress
redaction; control characters are flattened and the text is capped at 400
chars after scrubbing. The env map and headers are never part of it. The
model's tool result drops the field.

i18n: launchCommand / launchRemote in all nine locales, _keys.desktop.json
regenerated. Gateway contract regenerated.
2026-11-04 21:44:24 -08:00
Teknium 9767033cad fix(tui): /save refusal names the missing capability (older gateway or sidecar without the session authority), not just an outdated gateway 2026-11-04 21:42:39 -08:00
Teknium 2ac12f6c92 fix(tui): /save on a gateway without session.export refuses options it cannot honour
A TUI attached with HERMES_TUI_GATEWAY_URL to a listener that is not a
session authority (an older or remote gateway) fell back to the legacy
session.save RPC for every /save. That RPC ignores the format, filename
and redact arguments and writes default-named, unredacted JSON on the
gateway host, so `/save md notes.md redact` silently produced a raw JSON
copy, secrets included, on another machine.

Match the CLI (_save_on_older_gateway) and Desktop (saveLegacyRefused):
only a plain /save (json) may use the legacy RPC, and its notice now says
the file is raw JSON on the gateway host. A redact flag, a filename or a
non-JSON format is refused with a message naming why and what to do.
The /save help line now lists the arguments.
2026-11-04 21:42:39 -08:00
Teknium f94194ba73 feat(feishu): interactive clarify cards + native choice picker
Feishu already rendered exec approvals and update prompts as interactive
cards, but the clarify tool fell back to the base numbered-text prompt and
finite-choice slash commands (/reasoning, /fast, usage-limit switch) fell
back to text status cards. This implements the generic adapter contracts
from BasePlatformAdapter instead of a Feishu-only path:

- send_clarify(): multiple-choice questions become a card with one numbered
  button per option plus "Other (type answer)"; a tap answers through
  resolve_gateway_clarify and the callback response settles the card inline
  for every client; "Other" calls mark_awaiting_text. Open-ended questions
  use the base text prompt. Answers come from the pending clarify entry, so
  a tap after a timeout or /new reports the prompt expired.
- retire_clarify_card(): the gateway's hook for a clarify that ended without
  a tap (timeout, reset, superseding prose, typed answer) PATCHes the card
  button-less; the card is a shared card (update_multi) so the update
  reaches every recipient.
- send_choice_picker(): one-tap picker card; the tap settles the card and
  runs on_choice_selected on the adapter loop, then sends its confirmation.
- Every click passes the approval path's _validate_card_action (operator
  authorization incl. the gateway pairing check, callback-vs-card chat
  match); state pops are first-writer-wins, so a double or stale tap is
  refused.

Behaviour lives in plugins/platforms/feishu/adapter_choice_cards.py (mixin);
adapter.py stays at main's length (card-action routing became a table).
New platform.feishu.clarify/picker strings in all 17 locales, reusing each
locale's existing Telegram/Discord wording.
2026-11-04 21:29:10 -08:00
Teknium 0ea7b3cf30 feat(tui): expand a collapsed paste to read or edit it, in the composer and the transcript
A large paste collapses to a [[ … [N lines] … ]] chip, and until now the
user could neither see nor change what was behind it, and the sent message
showed only the chip.

Composer: Ctrl+P expands the chip under the cursor (or the last one) back
into ordinary editable text; the composer shows a window of rows that
follows the cursor so a 60-line paste never pushes the caret off screen.
Ctrl+P again, or Enter, folds the span back into a chip whose payload is
the text as edited, so what the model receives is the edited full text,
through the same expandTokens path as before.

Transcript: a sent user row keeps showing the chip and gets one accordion
per paste ("Pasted text #1 · 60 lines"). Click toggles one; Ctrl+P with no
chip in the composer toggles them all. Payloads ride from submit to the
row through a small FIFO keyed by chip label, so a busy-time row painted
later still gets its own payload.
2026-11-04 21:07:03 -08:00
Teknium 5d8e6fcd4a feat(tui): /new mid-turn keeps the running session working in the background
Community feedback comparing against other agent TUIs asked for /new to open a
fresh session without pausing the one that is busy. On main /new refused mid-turn
("interrupt the current turn before trying to switch sessions").

- /new while a turn runs starts a new LIVE session (the switcher's `+ new` path):
  nothing is closed or interrupted, so no destructive confirm. The old session
  keeps its turn and stays in the session switcher (Ctrl+X). /new on an idle
  session and /clear are unchanged (the idle one is closed and stays resumable,
  so live agents do not pile up one per /new).
- Approval/clarify/sudo/secret/vault requests from a session that is not on
  screen no longer open over the focused session's view: they stay open on the
  backend and come back as open_requests when that session is activated.
- The live-session poll announces a background session this view showed when
  its turn finishes or it starts waiting for an answer.
2026-11-04 20:54:25 -08:00
Teknium e08cf6f308 feat(desktop): compact confirmation names the chat it will compact
With a Bot group room in front, the panel targets the primary chat behind
it (consistent with the meter's numbers), but the dialog only said "this
chat". The confirm title now carries the target chat's title, falling back
to the generic question for untitled chats. New key questionNamed in en,
types and all 8 overlays; _keys.desktop.json regenerated.
2026-11-04 20:53:13 -08:00
Teknium 9ad2c02acc refactor(desktop): compact button runs the shared /compress pipeline
Trim #148607 to the minimal mechanism. The context panel's Compact
conversation button now confirms through the shared confirm() dialog and
then calls the session delegate's executeSlash('/compress', ...), the same
entry the overflow card's Compress button already uses. Owner routing,
stale-runtime recovery, transcript replacement, progress notices, the
deferred-completion edge and the in-flight coalesce all stay in slash.ts.

Drops the parallel compress-session module, the controller action, the
terminal-observer store and the slash.ts rewrite (~1.9k lines), and keeps
the panel copy in all locales.

The chat is captured at click time and re-checked after the confirmation
resolves (focused runtime id, busy, already compacting), so switching chats
or starting a reply while the dialog is open compacts nothing.
2026-11-04 20:53:13 -08:00
Teknium be01eb2a95 feat(cli): full-resolution images in VS Code when its image support is on
VS Code's terminal draws images only with terminal.integrated.enableImages
on, and nothing in the environment says whether it is. `auto` therefore
used half-blocks there even for users who had turned images on.

At startup (same pre-prompt_toolkit window as the light-mode OSC 11 probe)
the classic CLI now sends one XTSMGRAPHICS sixel query fenced by DA1. Only
VS Code's image addon answers XTSMGRAPHICS, so a reply means images are on
and `auto` uses the full-resolution iTerm2 path; DA1 alone means off.

Timeout, measured live in VS Code 1.145 (30 runs per deadline): the reply
lands in ~7 ms; at 8 ms some runs missed it and the late reply leaked into
the next read, at 10 ms and above every run was right with no leak. The
deadline is 50 ms for headroom; the read returns as soon as the DA1 fence
arrives, so a normal start pays ~7-10 ms. Skipped over SSH (a remote round
trip can exceed the deadline and leak into the prompt) and on Windows.

When the probe says images are off, roughly one launch in five shows a tip
instead of the random one: "VS Code can show full-resolution images here:
turn on terminal.integrated.enableImages in settings." A pinned
display.inline_images value skips both the probe and the tip.
2026-11-04 13:38:09 -08:00
kshitijk4poor a285c4f4ba chore(config): finish the auxiliary.skills_hub removal
- locales/_keys.desktop.json: drop settings.model.tasks.skills_hub.{label,hint}.
  The committed key list mirrors the English catalog
  (apps/desktop/scripts/i18n-keys.test.mjs runs `--check`), and the rows were
  removed from en_aux_tasks.ts.
- zh-Hans configuring-models still had the Skills Hub row claiming that
  `hermes skills search` uses the slot; the developer-guide provider-runtime
  lists (EN + zh) still named "skills hub operations" as an aux task.
- Pin the removal: no built-in surface (DEFAULT_CONFIG, the `hermes model`
  picker, the dashboard REST allowlist, the web and desktop rows) offers the
  slot, and a config that still carries an auxiliary.skills_hub block loads.

Refs #40703, #142403
2026-11-05 02:36:29 +05:30
Victor Kyriazakos 2111815fab feat(context): label a defaulted context window "fallback" wherever it is shown
The gateway session line printed "◆ Context: 128K tokens (detected)" for a Bedrock
model no source knew. _resolve_gateway_model_context recognised only the generic 256K
DEFAULT_FALLBACK_CONTEXT as a default, so Bedrock's static 128K was reported as
detected. The CLI surfaces showed the number with no source at all.

agent/context_pin.py already labels a model.context_length pin "(pinned)" on those
surfaces (#66168). This commit extends the same helper with a "fallback" marker:

- is_fallback_context_length(window, model=, provider=, base_url=) is True only when
  the window equals the resolver's default for that route and no local source knows
  the model. On Bedrock the default is BEDROCK_DEFAULT_CONTEXT_LENGTH, and the checks
  are the table/family step, a provider-confirmed cache row and model_overrides. The
  generic route uses DEFAULT_FALLBACK_CONTEXT, the catalog, models.dev and
  custom_providers. It does only local reads, memoised for 60s because the status bar
  repaints often. It fails open and never probes.
- Labelled surfaces: the gateway session line (via context_source "default", now
  worded "fallback, not a detected limit"), CLI and gateway /usage, the wide status
  bar (new cli.status_bar.ctx_fallback key in all 17 locales), the welcome banner,
  and the CLI and gateway /model switch summary.

No new config key. The override is unchanged: model.context_length, or
model_overrides.<provider>.<model>.context_window.
2026-11-04 12:56:04 -05:00
Bear Huddleston 6de94fe423 Merge pull request #148408 from NousResearch/feat/web-access-start-progress
feat(desktop): Web Access Start shows its step and elapsed time
2026-11-03 13:34:22 -06:00
Teknium f61682590b fix(relay-link): Portal-managed agents link from the Portal; only a new account reads as linked
- On a Portal-managed agent (connector /manage/access/list answers
  managedBy: portal) a code minted with the gateway bearer carries no owner, so
  its redeem binds no account (gateway-gateway scopeService: a managed agent
  takes only the Portal setting). The gateway now refuses to mint there
  (managed_by_portal) and the Channels card on both surfaces lists the linked
  accounts read-only with a 'Link accounts in Nous Portal' button.
- Success is a new platformUserId in the refreshed list, never an 'inactive'
  code before expiry: 'inactive' also means used by an already-linked account
  or unknown. That case now shows a neutral note; expiry still says expired.
- Desktop keys the link card by connection + profile + platform, so a code,
  poll or account list never carries over to another backend.
2026-11-03 09:45:07 -08:00
Teknium fcdca2dbd4 fix(desktop): Team Gateway-managed platforms and profile-scoped link calls
- A platform the relay switches off but the shared bot does not carry (Slack,
  Matrix outside GATEWAY_RELAY_PLATFORMS) now reads 'Managed by Team Gateway'
  with the toggle disabled and the opt-out explained, matching the dashboard
  (#141175); before, its Enable toggle saved and reverted at every restart.
- The link calls carry the settings profile so a profile pointed at another
  host links against that host.
2026-11-03 09:45:07 -08:00
Teknium 179c90f547 fix(cloud): Telegram on Team Gateway is set up by linking an account to the shared bot
On a Hermes Cloud instance with Team Gateway, every direct messaging adapter is
switched off at gateway start (config_env._relay) and the connector's shared bot
is the only way Telegram, Discord and Slack reach the agent. The Channels page
and the Desktop messaging page still offered "Create with QR" and bot-token
setup, which make a bot the relay never runs: users finished setup, restarted,
and the bot never answered (#141546, #138101).

The instance now mints the connector's one-time `/link <code>` itself:

- gateway/relay/link.py calls /manage/link, /manage/link/state,
  /manage/link-status and /manage/unlink with the per-gateway bearer, and
  builds the shared bot's DM link from GATEWAY_RELAY_BOT_IDS.
- The per-gateway secret lives only in the gateway process, so the gateway
  answers a new `relay-link` control-socket verb and the dashboard routes
  (/api/messaging/relay*) go through it.
- /api/messaging/platforms reports relay_fronted / relay_exclusive per platform.
- Dashboard Channels and Desktop Messaging show a Team Gateway card (link
  button, QR of the bot, the exact message to send, linked accounts with
  Unlink) and hide the direct setup that cannot work under an exclusive relay.
2026-11-03 09:45:07 -08:00
BearHuddleston 18451d8ae3 chore(desktop): list the Web Access progress copy in the desktop i18n key file
The pane's new strings (settings.gateway.webAccess.startElapsed and startStages.*) were missing from the generated locales/_keys.desktop.json, which scripts/i18n-keys.test.mjs checks against the English catalog. Regenerated with npm run i18n:keys.
2026-11-03 13:44:32 +00:00
unsupportedpastelsandFroRaut 60f5029394 feat: Daybreak switch for catalog-listed ChatGPT subscription models
OpenAI Daybreak (Trusted Access for Cyber) reduces cyber refusals for
approved accounts. Codex's model picker has a Daybreak toggle; Hermes had
no way to request it.

The signed-in account's Codex /models catalog already lists, per model,
which cyber programs that account may use
(available_access_programs.cyber). Hermes reads that catalog for Fast and
Ultrafast; it now keeps the cyber list from the same fetch.

- agent.daybreak (default false) is one profile-wide switch, like Codex's.
- Desktop shows a Daybreak switch in a model's options submenu, next to
  Fast, only when this account's catalog lists daybreak_blue for that
  model. Unknown or unreachable catalog: hidden.
- Each Responses request adds access_programs.cyber=daybreak_blue only
  when the switch is on AND the request's credential and model are listed.
  Checked per request, so a /model switch, credential rotation or expired
  catalog never carries it to a model or account without it.
- The gateway re-reads the switch every turn, so a flip applies to the
  next turn of a running chat without rebuilding its agent.
- The opt-in codex app-server runtime does not send it, so the switch is
  hidden there.

Co-authored-by: FroRaut <79215537+Froraut@users.noreply.github.com>
2026-11-02 15:06:27 -05:00
Teknium 0738525b14 chore(desktop): regenerate i18n key manifest after dropping settings.plugins.enable/disable 2026-11-01 21:02:47 -08:00
Teknium 22af40a27e fix(desktop): localize the SSH terminal disconnected line
Review P3-4: an SSH teardown now keeps the tab and printed a hardcoded English
line beside the localized gateway messages. Move it to
rightSidebar.terminalDisconnectedBody in en and all eight overlays.
2026-11-01 18:35:23 -08:00
TekniumandAddel Hamoudhy 0d521cd383 feat(desktop): each terminal tab owns the gateway it was opened on
A terminal tab now records the gateway (connection id, plus the profile the
SSH tunnel is keyed by) it was opened on, and its PTY spawns on THAT host
whichever gateway the window shows later. Building on #143491's start-payload
gateway, the window-backend route now only serves tabs that recorded none.

- New tabs take the gateway picked in Sessions at first start; URL/token and
  cloud gateways have no shell transport, so their tabs are This device and
  say so. The + in the terminal rail gets a chevron (and a right-click menu)
  to open a terminal on any SSH gateway or This device, side by side.
- Each tab shows where it runs: a gateway glyph on its rail square and
  "zsh · NAS" in its tooltip/aria label. This device stays unlabeled until
  tabs run on more than one host.
- Restored tabs reopen on their recorded gateway. A removed gateway, or one
  that lost its shell transport, ends in an exited tab that says so instead
  of a local shell under the remote's name.
- An SSH teardown (gateway edited or removed) leaves its tabs disconnected
  ("press Enter") instead of closing them; Enter re-resolves the tab's own
  gateway. The output gate keeps the first exit verdict so the kill's own
  exit cannot overwrite it.
- The resolver moves out of main.ts into electron/terminal-gateway-target.ts;
  the window-route lookup reuses it.

Tab ownership and persistence follow #85241's TerminalEntry shape.

Co-authored-by: Addel Hamoudhy <ahamoudhy@gmail.com>
2026-11-01 18:35:23 -08:00
Teknium 722d4fac96 feat(desktop): lock a skill from the Capabilities pane
Adds the user-facing half of the per-skill lock (hermes curator lock):

- PUT /api/skills/lock {name, locked, profile?}: resolves the skill and
  keys the record by its directory (what the write guards resolve), in
  the selected profile; 404 for an unknown skill, 500 when the sidecar
  write fails.
- GET /api/skills rows carry locked (best-effort; a corrupt sidecar
  lists everything unlocked instead of failing the list).
- Desktop: Lock/Locked text toggle with a state-specific hint in the
  skill detail pane for every provenance (optimistic, rolls back on
  error), plus a lock glyph on locked rows (list rows, cards, detail
  header). Strings in all bundled locales.
2026-11-01 15:47:21 -08:00
Teknium 13bd44e4a4 fix(desktop): every gateway stays connected; picking one is instant, with no switch prompt
Desktop treated registered gateways as one-at-a-time: picking another gateway
ran a "switch" (barrier, session-list wipe, a "Switch to This device? This
opens a fresh session on this computer..." confirm), and a gateway that was
unreachable when Desktop started never showed up in the All profiles session
list after it came back.

- store/gateway-persistence: every registered gateway's home socket is dialed
  at launch, pinned against the idle pruner (retainGatewayForRelay now takes
  includeLocal so This device is kept too when a remote is primary), redialed
  every 30 s while down, and the session list is re-pulled when one connects.
- selectConnection is a live activation like a profile pick: the target is
  opened first (a dead gateway fails there and touches nothing), then the old
  runtime is unbound and a fresh draft started synchronously right before the
  target is published (#93937 invariant kept). No barrier, no list wipe, no
  machine-context reset; an in-flight Settings apply is superseded.
- This device prompts only when picking it would start the installer; the
  switchDevice* copy is gone and "Switch to" reads "Open". Other gateways'
  rail squares use the normal unselected weight instead of an "at rest" dim.
- Docs: multi-connection-desktop.md describes always-connected gateways.
2026-11-01 14:27:53 -08:00
Teknium 6513a4f591 fix(tui): page long approval commands so every line is reviewable before a choice
The Ink approval panel showed the first 10 wrapped lines and said the rest
was "(full text above)", which was not true: a dangerous line 11 never
reached the screen. The panel now pages the command with v (wrapping back
to the top) and says which lines are on screen and how to see the rest.
prompt.approval.moreLines is replaced by pageMore/pageEnd in every bundled
TUI pack, so no translation keeps the false claim.
2026-10-31 23:05:03 -07:00
Teknium 9bba78efa5 chore(desktop): regenerate i18n key catalog after dropping continuationOrigin 2026-10-31 22:46:51 -07:00
Teknium 66c48cb158 feat(cli): /model reset in the classic CLI, help hint and queued-pick display (salvage #74899)
- Classic CLI (and the Desktop slash worker, which runs this handler): `/model reset` switches
  back, session-scoped (never --global, even under model.persist_switch_by_default), to
  config.yaml's model read fresh at the command, through the normal typed path (guards, confirm,
  session-row persist), and drops an armed --once restore. Already on the default: says so; no
  model.default: says there is nothing to reset to.
- The /model args_hint lists `reset`.
- TUI: a `/model reset` queued mid-turn shows the default it will adopt, not the word "reset",
  and skips the pick-time selection guard (adopting the configured default is not a new pick).
- cli.model.reset_none / reset_no_default in all 17 locales; slash-command and messaging docs.
- Tests: parser invariants, gateway reset of a store-only override after a restart, TUI reset
  that adopts config without re-pinning.
2026-10-31 22:08:49 -07:00
Shannon Sands 5bb7bdc7c4 feat: /model reset — clear a session's model pin without losing history (NS-563)
A session /model override persists (gateway session store, TUI/Desktop session row) and
shadows every later change to the configured default. Until now the only way back was /new,
which also drops the conversation.

`/model reset` (no flags) clears the override and returns the session to the configured default:
- shared parser: `reset` is its own request (`ModelSwitchRequest.is_reset`); combined with any
  flag it is rejected (`MODEL_SWITCH_ERR_RESET_WITH_FLAGS`). Not aliased to "default" (MoA preset).
- messaging gateway: clears the in-memory and persisted override and any armed --once restore,
  evicts the cached agent, leaves a next-turn model note; channel_overrides > config.yaml applies.
- TUI gateway (`config.set model`, Desktop's slash mirror): drops the pin and the one-turn
  restore, adopts config.yaml through the config-sync switch (no new pin, never written back) and
  persists the row so session.resume cannot restore the old pin.
- gateway.model.usage_reset / reset_done / reset_none in all 17 locales.

Re-ported onto the split /model modules (gateway/slash_commands_model.py,
tui_gateway/model_switch.py) for the current main.
2026-10-31 22:08:49 -07:00
Teknium 7b62fb095d fix: a switched-off local model server never suggests /retry, and names both ways to start it
The backend sentence, the TUI/dashboard hint and the Desktop/Webapp card name the Hermes desktop
app and `hermes model` -> Local models instead of the Desktop app alone; the card says Retry only
while the server can still come up (retryable), and the Python hint keys its no-retry wording on
the surface's code and retryable flag rather than rewriting the text. The localRuntimeUnavailable
TUI keys are translated into every bundled locales/*.tui.yaml pack.
2026-10-30 08:02:40 -07:00
Teknium b19ca8c974 chore(tui): regenerate locales/_keys.tui.json for the local_runtime_unavailable copy 2026-10-30 08:02:40 -07:00
Teknium dc15316219 fix(desktop): the chat card names the local model server, with no Desktop-only pane
local_runtime_unavailable gets its own card copy in every locale instead of the
'custom model server' endpoint body. The card is shared with the Webapp, so it names
no pane; the Desktop notice keeps Settings → Providers → Local models beside its button.
2026-10-30 08:02:40 -07:00
Teknium 18d8b4a65d feat(webapp): flash the tab and show browser notifications for attention events
The Webapp marked a blocked or finished session only with a mark in the tab
title, and its notify bridge asked for notification permission from inside
the event (no user gesture, so browsers silently refuse) and ignored clicks.

- While the tab is hidden or unfocused, every event that passes the existing
  native-notification gate (Settings > Notifications, per kind) alternates the
  tab title with the notification's title and badges the favicon until the
  user returns to the tab.
- The bridge shows a browser Notification only when permission is granted;
  permission is asked from a click (new Settings row, or Send test), never on
  load. Over plain HTTP (no secure context) the row says so and the title
  flash carries the signal alone.
- Clicking a browser notification focuses the tab and opens the session
  through the existing onFocusSession path Desktop's OS notifications use.
2026-10-30 07:46:50 -07:00
Teknium 0e0b2328e0 feat(desktop): Stop a running cron job and choose its missed-run policy in the editor
A job whose newest ledger attempt is claimed/running shows Stop in its header. A click calls
POST /api/cron/jobs/{id}/cancel and holds "Stopping…" while the view polls the job list until that
attempt is terminal, then toasts "Run cancelled" (or that the run finished first / nothing was in
flight). A cron.changed refresh makes a run started elsewhere show its Stop too, and while this
window's own Run-now is waiting the run counts as live until an attempt claimed since the click has
finished, so the button never flickers back.

The editor gains "Missed runs" (use the default / run once when back / skip them), sent only when
the user changed it; the dashboard create endpoint now accepts catch_up as update already did.
Strings live in types_cron_control.ts plus one <lang>_cron_control.ts per locale.
2026-10-30 05:54:35 -07:00
kshitijk4poor ef35e8b9e1 feat(desktop): error card v2 — severity, one action, DS buttons, details copy block
The inline error card on a failed turn is rebuilt to the Error System
Overhaul design (Figma "Error handling" frame + Nous DS buttons / Copy):

- Severity: blocking (user can't keep chatting until they act; rust tint
  and icon) vs degraded (chat still works; white card, grey icon, close X).
  errorCardSeverity() derives it from the card's main action.
- One main action and at most one second option per failure
  (errorCardActions / CODE_ACTIONS), rendered as Nous DS secondary + ghost
  buttons. New actions: Continue (truncated), Open provider settings
  (privacy block), setup guides for TLS / local servers, Open model
  settings (agent init). Plan usage limits keep switch-to-model / credits.
- "View error details" toggle reveals the raw error in a DS Copy-style
  slab with a copy glyph, with Copy details / Open logs / Send diagnostics
  right below it.
- Copy rewritten: titles of at most 5 words that name the provider or
  model, one-to-two-sentence bodies that say the fix. Translated into all
  8 desktop locales.
- One event, one surface: no error toast for the session on screen (the
  card says it); out of credits is the composer banner, and the card only
  speaks when that banner isn't up for the chat. Background sessions keep
  their toasts.
2026-10-30 12:26:13 +05:30
Teknium c2dcdf5474 chore(i18n): regenerate the desktop key manifest for zones.tabActions
Refs #145774
2026-10-28 20:37:58 -07:00
Teknium e0c52c4fbf fix(plugins): catalog installs keep the inspected commit; older backends ask; partial installs need Force
Three follow-ups to the install dialog holding an install to the commit it showed.

- Catalog picks. The backend reloads the live catalog entry at Install, so a
  re-pin between the dialog's inspection and the click installed (and, through
  the one checkout, published the desktop half of) a tree nobody reviewed;
  the renderer sent no inspected_sha for catalog sources and neither the
  catalog install nor reuse of a disabled tree read it. Now the catalog
  install compares the commit it actually checked out with the inspected sha
  peeled through the clone (annotated-tag pins still install), a disabled tree
  is reused only at that commit (or its recorded tag pin), and catalog dialogs
  send it: the backend's inspect sha in the Webapp, the shown reviewed pin on
  native Desktop (whose Electron probe reads the branch tip). After a re-pin
  refusal the native dialog re-reads the catalog for the new pin. The record
  stays the catalog's pinned record. Electron's desktop clone uses the same
  reviewed commit and peels a tag pin.
- Older backends. A backend that refuses inspected_sha at admission is no
  longer retried without it behind the user's back: the dialog explains that
  this backend cannot guarantee the reviewed version is what gets installed
  (update it for that guarantee) and installs only on an explicit
  "Install anyway" (profile kept); Cancel installs nothing.
- Partial installs. Native Desktop on a remote backend could install the agent
  half at commit A, fail the desktop half (source moved), re-review to B and
  let a second Install put the desktop half down at B beside the agent half at
  A. That is now reported as a partial install (agent half in at A, desktop
  half not); Install stays disabled until Force reinstall, which replaces both
  halves with the version shown.
2026-10-28 17:21:32 -07:00
Teknium e435276eaa chore(desktop): regenerate the i18n key snapshot for the stale-review notice 2026-10-28 17:21:32 -07:00
kshitijk4poor 50f57a131f fix: a stale Discord approval click leaves the card to the request's observer
Item 1 of the review is only partly fixed by the stale-click commit
before this one. Clearing `_backend_settled` lets the settle observer
retire the card, but the click still edited the same card with
"Approval expired — command was not run" through its own interaction
response. Nothing serializes that edit with the observer's. If the
click's edit lands last, it hides the real outcome and reattaches the
disabled controls. Discord serializes the interaction payload before
awaiting the response, so a later embed change can't repair it.

When a click on a card with a request id resolves nothing, that request
has already settled through another path. Since the earlier commits, its
observer is always registered (it is registered before the send, and a
request gone before that already retires the card). So the click now
only answers the clicker privately ("no longer pending") and leaves the
card edit to the observer.

A click whose lookup raises recorded nothing, and its request may still
be pending. It used to stamp the card "expired — not run" and disable it
for good, and that edit could race the observer too. Now it re-opens the
card for a retry, tells only the clicker ("Couldn't record your choice.
Click again, or answer with /approve or /deny." — a new string in every
locale), and leaves the final edit to the observer. A card without a
request id has no observer, so its no-op click keeps the old "expired"
edit.

Tests:
- The stale-click regression now asserts that the click never edits the
  card. It fails on the previous commit.
- A new test drives a failed lookup on a really pending request with its
  observer attached: the card stays live, and a later text answer
  retires it with the real outcome.
- A third test pins the expired edit on a card with no request id.
2026-10-28 15:28:40 +05:30
kshitijk4poor b5caaf7b44 fix: a Discord approval card that settles mid-send shows how it ended
The card registered its settle observer only after `channel.send`
returned. If the request ended during the send (wait timed out, /stop,
session closed, withdrawn), the registration found no entry and the card
retired with the default reason "resolved". The card then said "This
approval has already been resolved" for a command that never ran.

Build the view first and register the observer before sending. A reason
that arrives while the card is still in flight is held on the view
(`pending_retire`) and applied as soon as the card exists. Only a
request that was already gone before the card was built still has an
unknown outcome. That card now says "⌛ This approval is no longer
pending (it timed out or was answered elsewhere)", a new outcome-neutral
string, translated in every locale. It no longer claims either
"resolved" or "not run".

Building the view outside `_build` also removes the `view_holder` list
that only existed to get the view back out. The view never depended on
the channel.

The test now covers the three orderings (after, during, before the send)
and asserts the footer. Two of them fail on the previous commit.
2026-10-28 15:28:40 +05:30
Teknium 3f4d067752 chore(i18n): regenerate desktop keys for restartFailedBecause
Refs #144648
2026-10-27 20:55:07 -07:00
Teknium 428809549a fix(memory): never call an unsaved provider active; refresh readiness after setup
Review on #145574:
- The Settings summary labelled the unsaved memory.provider draft "Active
  memory provider" even when GET /api/memory named another one. The draft
  now shows as "Selected memory provider" (not active yet, naming what
  sessions keep using), and the active row and recall copy come only from
  status.active. A config autosave that touches `memory` refetches.
- Readiness refreshes after an inline credential save, a full-config modal
  save and OAuth completion ($memoryStatusRevision); a failed status read
  shows "Memory status unavailable" with Retry instead of dropping the badge.
- The built-in row reflects memory.memory_enabled / user_profile_enabled in
  Settings ("On by default" where the config isn't loaded), and recall is
  claimed only for a ready provider. Web dashboard copy drops "always on".
- isExternalMemoryProvider matches the backend's full sentinel set,
  including `default`.

Refs #144648 (WA-22)
Refs #145175
2026-10-27 20:32:14 -07:00