Commit graph

4720 commits

Author SHA1 Message Date
nesquena-hermes
941c8051a9 chore: CHANGELOG note for stage augmentation 9242305a 2026-05-10 17:06:27 +00:00
nesquena-hermes
9242305a81 fix(stage-329): zh-Hant locale parity for kanban_status_original_hint + extend locale parity test (Opus advisor SHIP-WITH-CAVEATS follow-up) 2026-05-10 17:06:10 +00:00
nesquena-hermes
52c1053baa chore: CHANGELOG for v0.51.35 — Release K (kanban polish + i18n DE) 2026-05-10 17:00:40 +00:00
nesquena-hermes
f44ead1404 Stage 329: PR #1996 — test(kanban): modal locale parity regression by @franksong2702 2026-05-10 16:48:44 +00:00
nesquena-hermes
b01df72727 Stage 329: PR #1995 — feat(kanban): trap focus in kanban modals + status hint by @franksong2702 2026-05-10 16:48:41 +00:00
nesquena-hermes
7ce48de817 Stage 329: PR #1993 — fix(kanban): invalidate profile cache for assignee select by @franksong2702 2026-05-10 16:48:15 +00:00
nesquena-hermes
bf98ffec9b Stage 329: PR #1991 — fix(i18n): correct German profile_skill_count pluralization by @franksong2702 2026-05-10 16:48:15 +00:00
nesquena-hermes
c6e4306d7b Stage 329: PR #1990 — fix(kanban): guard dispatcher actions while in-flight by @franksong2702 2026-05-10 16:48:15 +00:00
dobby-d-elf
fecfc5f6db fix: reanchor live context usage updates 2026-05-10 10:31:14 -06:00
Jellypowered
8aed650b4c Stitch continued session transcripts in WebUI 2026-05-10 11:10:54 -05:00
ai-ag2026
017a631b6c fix: keep explicit fork sessions out of compression lineage 2026-05-10 18:03:21 +02:00
ai-ag2026
8226328cba fix: avoid sidebar jumps when active session is visible 2026-05-10 18:00:10 +02:00
Dennis Soong
c3cf8b10e9 feat: add read-only session lineage report 2026-05-10 23:28:14 +08:00
ai-ag2026
2a34a1256e fix: prefer latest compressed session segment 2026-05-10 17:04:33 +02:00
dobby-d-elf
56d68b7511 fix: keep live context metering session-scoped 2026-05-10 08:20:37 -06:00
dobby-d-elf
a300d9a323 Drop configured provider model badges 2026-05-10 08:07:59 -06:00
dobby-d-elf
1cf0ff01b5 feat: live context window status tracking during streaming 2026-05-10 06:51:46 -06:00
Frank Song
e64e02479f Fix CLI session patch diff rendering 2026-05-10 20:44:34 +08:00
insecurejezza
f7938372ba fix: wrap markdown code blocks on mobile 2026-05-10 19:12:20 +10:00
qxxaa
f665e50738
fix: stamp profile on continuation session after context compression
When context compression fires, the agent rotates to a new session_id.
The compression migration block correctly migrates the session lock,
SESSION_AGENT_CACHE, SESSIONS dict, and the session file rename, but
does not ensure s.profile is set on the continuation session.

On the next request, _run_agent_streaming resolves the profile via:

    get_hermes_home_for_profile(getattr(s, 'profile', None))

With s.profile == None this falls back to the default profile's
HERMES_HOME. Memory tool calls then read and write the wrong profile's
MEMORY.md — confirmed by investigation: session 0dfefb (continuation
after compression from a troubleshooting profile session) read memory
at 16% / 1,184 chars with 4 entries, while the troubleshooting profile's
actual state was 72-77% / 5,000+ chars. That reading could only come
from the default profile's bank. Subsequent replace operations failed
because the target entries existed only in the troubleshooting profile.

There are two failure paths:

1. In-memory: if s.profile was None from the start (legacy session or
   one created before this fix), the continuation session object carries
   null through the current request.

2. Persistence: s.save() persists "profile": null to the continuation
   session's JSON file (profile is in METADATA_FIELDS, models.py ~408).
   On the next request, Session.load(new_sid) reads it back as null and
   get_hermes_home_for_profile(None) falls back to the default profile.

Fix: capture _resolved_profile_name at request entry (~line 2019),
immediately after profile home resolution. This is the only point where
profile context is reliable: s.profile if already set, otherwise
get_active_profile_name() — which at that point reads thread-local
storage (_tls.profile) correctly set by the HTTP handler thread via
set_request_profile(). Calling get_active_profile_name() at compression
time instead would be unsafe: the streaming thread is a separate
threading.Thread, does not inherit TLS, and the call would fall back to
the process-global _active_profile which may belong to a different
concurrent tab.

Stamp s.profile in the compression migration block immediately after
s.session_id = new_sid. Guarded by `if not s.profile` so sessions that
already have a profile set are unaffected. A logger.info line records
when the stamp fires, making future investigation straightforward.

Fixes: memory writes bleeding into default profile after compression
Reproduces: reliably on any long non-default profile session that hits
the compression threshold (default: 0.80 context fill)
2026-05-10 09:57:45 +01:00
vikarag
84a172b572 feat: add Xiaomi MiMo provider support
Add xiaomi to _PROVIDER_DISPLAY, _PROVIDER_MODELS, and _PROVIDER_ALIASES
so the WebUI recognizes Xiaomi as a first-class provider.

Models included:
- mimo-v2.5-pro (MiMo V2.5 Pro)
- mimo-v2.5 (MiMo V2.5)
- mimo-v2-pro (MiMo V2 Pro)
- mimo-v2-omni (MiMo V2 Omni)
- mimo-v2-flash (MiMo V2 Flash)

Aliases: mimo, xiaomi-mimo -> xiaomi

The hermes-agent CLI already registers xiaomi as a provider
(hermes_cli/models.py, hermes_cli/auth.py) but the WebUI was missing
the corresponding entries, causing the model dropdown to fall back to
OpenRouter and the provider list to show 'Unsupported'.
2026-05-10 17:48:37 +09:00
Frank Song
1bec8070f2 fix(1833): persist compression anchor summary for reload UI 2026-05-10 16:45:16 +08:00
eov128
9c37104c94
Add files via upload
Update Chinese language translation
2026-05-10 16:08:14 +08:00
Frank Song
2e6b3601bd fix(clarify): honor clarify.timeout config in webui prompts 2026-05-10 16:05:50 +08:00
qxxaa
7ee41c9b12
fix: patch skills module-level caches on per-request profile switch
Per-request profile switches (process_wide=False, introduced in #1700)
update os.environ['HERMES_HOME'] but skip _set_hermes_home(), which is
responsible for monkeypatching module-level caches.

Both tools/skills_tool.py and tools/skill_manager_tool.py set
HERMES_HOME and SKILLS_DIR once at import time. When a non-default
profile is active in the WebUI, os.environ['HERMES_HOME'] is correctly
updated per-turn in the _ENV_LOCK block, but the module-level
constants still point at the root profile. All agent-side skill
operations — skills_list(), skill_view(), skill_manage() — read and
write to the wrong directory.

Add the same monkeypatching that _set_hermes_home() already performs
(profiles.py line ~620) to the per-turn env setup block in
streaming.py, covering both skills_tool and skill_manager_tool.

The WebUI display half was already fixed in #1917 via
_active_skills_dir() in routes.py. This patch fixes the agent-side
half so the running agent resolves skills from the correct profile.
2026-05-10 09:02:49 +01:00
Frank Song
ba51efec26 test(kanban): assert profile-cache invalidation on profile delete 2026-05-10 15:49:14 +08:00
Frank Song
2427f1e598 test(kanban): harden locale-block parsing for quoted locales 2026-05-10 15:48:18 +08:00
Frank Song
1e1a9481b4 fix(i18n): localize /goal runtime status strings 2026-05-10 15:21:24 +08:00
Frank Song
4c95d9274e test: add kanban modal locale parity regression 2026-05-10 15:03:21 +08:00
Frank Song
42a23818b3 Fix 1974: trap focus in kanban modals 2026-05-10 14:57:51 +08:00
Frank Song
45a5253292 fix(i18n): add kanban status hint key to all locales for #1994 2026-05-10 14:49:30 +08:00
Frank Song
b67d2676e4 fix(kanban): show original status hint in edit modal 2026-05-10 14:40:46 +08:00
Frank Song
b06eb99d91 fix(kanban): invalidate profile cache for assignee select 2026-05-10 14:37:37 +08:00
Alan
025f137f28
fix: guard empty CTL_BOOTSTRAP_ARGS expansion for bash 3.2
With set -u, bash 3.2 treats empty array expansion
"\${CTL_BOOTSTRAP_ARGS[@]}" as an unbound variable.

Use ${CTL_BOOTSTRAP_ARGS[@]+"..."} pattern to skip
expansion when the array is empty.
2026-05-10 14:34:39 +08:00
Alan
630981a068
fix: use bash 3.2 compatible variable check in ctl.sh
Replace [[ -v ${key} ]] (requires bash 4.2+) with
[[ -n "${!key+x}" ]] which works on bash 3.2+ and all
POSIX-compatible shells.

macOS ships with bash 3.2, causing:
  ./ctl.sh: line 42: conditional binary operator expected
2026-05-10 14:29:07 +08:00
Frank Song
8f077d37f7 Fix German profile_skill_count interpolation 2026-05-10 14:25:08 +08:00
Frank Song
a0b757a9d4 Fix Kanban dispatch double-click race guard 2026-05-10 14:18:50 +08:00
Michael Lam
c7afae0e07 fix: lengthen auto-compression toast 2026-05-09 19:35:05 -07:00
nesquena-hermes
e4a9c5b7f5
Merge pull request #1983 from nesquena/stage-328
release: v0.51.34 — Release J (#1979 zh-Hant kanban i18n + #1981 kanban edit/dispatch/assignee)
2026-05-09 14:16:47 -07:00
nesquena-hermes
189c9bf556 release: v0.51.34 — Release J (kanban edit/dispatch + zh-Hant kanban i18n) 2026-05-09 21:13:43 +00:00
nesquena-hermes
3fbecc489c fix(stage-328): backfill #1981's 17 new kanban keys into zh-Hant locale
PR #1979 (@Michaelyklam) backfilled the existing kanban keys into zh-Hant
which was the missing locale block.  PR #1981 then added 17 NEW kanban
keys (edit_task, run_dispatcher_confirm, assignee_profiles_label,
dispatch_* result fields, etc.) but only to the 8 existing kanban-supporting
locales — zh-Hant was again left without those new keys.

This commit closes the gap fully: the 17 new keys from #1981 now exist in
zh-Hant too, with Traditional Chinese translations adapted from the
Simplified Chinese (zh) versions in the same file.

Without this commit, zh-Hant users would have:
  - The full create-task modal localized (from #1979 + #1965)
  - But the new edit-task / run-dispatcher / assignee-dropdown / dispatch
    result strings falling back to English

Adapted translations preserve the same shape and tone as the zh block.
The gap is mechanical (translation drift, not architectural) and worth
closing inline rather than leaving as another follow-up issue.

JS syntax: clean (`node -c` on i18n.js + panels.js).
Kanban tests: 34/34 pass on this stage.
2026-05-09 21:03:48 +00:00
nesquena-hermes
c67336e4e3 Stage 328: PR #1981 — feat(kanban): edit task button, real Run dispatcher, assignee dropdown by @nesquena-hermes
# Conflicts:
#	CHANGELOG.md
2026-05-09 21:02:27 +00:00
nesquena-hermes
fb128ef288 Stage 328: PR #1979 — fix(i18n): backfill zh-Hant Kanban keys by @Michaelyklam 2026-05-09 21:02:14 +00:00
Nathan Esquenazi
8e0eedd163 fix(kanban-edit): preserve real status when editing non-{triage,todo,ready} tasks
PR #1981's edit-task modal silently demotes tasks whose real status is
running/blocked/done/archived. The dropdown only offers triage/todo/ready,
so `_kanbanEditableStatusFor()` maps any other status to 'triage' for
display. If the user just edits the title and saves, the dropdown's
displayed 'triage' lands in the PATCH payload — and `_patch_task` calls
`_set_status_direct` which:
  - ends any active run with outcome='reclaimed' (worker yanked back)
  - nulls claim_lock / claim_expires / worker_pid
  - moves the task to triage

So editing a 'running' task's title would reclaim the running worker.
Editing a 'done' task would un-done it. Editing an 'archived' task would
un-archive it. All silent, no warning.

Reproducer (Node):
  Original: {status: 'running'}
  Modal display: 'triage' (mapped)
  User leaves dropdown alone → submit
  Payload: {title: 'X', status: 'triage'}  ← destructive

Fix: track the modal's initial displayed status in
_kanbanTaskModalInitialDisplayedStatus on edit-mode open. In submit's
edit branch, only include `status` in the PATCH payload when the user
actually picked a different value than what the dropdown opened with.
Create-mode resets the tracker to null so create payloads always include
status.

Verified end-to-end via Node harness:
  - edit running, untouched → no status sent ✓ (server keeps running)
  - edit running, picked ready → status:ready sent ✓ (worker reclaimed
    intentionally)
  - edit triage, untouched → no status sent ✓ (idempotent)
  - edit triage, picked ready → status:ready sent ✓
  - create new → status always sent ✓
  - edit done, untouched → no status sent ✓ (no un-done)

Adds test_kanban_edit_mode_preserves_status_when_dropdown_untouched
pinning the tracker variable, openKanbanEdit captures, submit-skip
condition, and create/close reset paths. Verified to fail pre-fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:57:31 -07:00
nesquena-hermes
c71312b2e8 feat(kanban): edit task button, real Run dispatcher, assignee dropdown
Three connected gaps in the Kanban UX, fixed together because they're
load-bearing for the actual work-queue lifecycle:

1. Edit task — the detail view had only status-transition buttons (Triage/
   Todo/Ready/Blocked/Done/Archived) plus Block/Unblock and Add comment.
   No way to edit title, body, assignee, tenant, or priority once the task
   was created. Backend already supported it via PATCH /api/kanban/tasks/<id>
   (api/kanban_bridge.py::_patch_task) — purely a UI gap.

   Now: an Edit button on the task-detail header opens the existing modal
   pre-filled with current values, switches the modal title to 'Edit task'
   and the submit button to 'Save', PATCHes instead of POSTing on submit.

2. Run dispatcher — the existing 'Preview dispatcher' button always passed
   ?dry_run=1 (nudgeKanbanDispatcher), so it was preview-only. There was
   literally no UI button anywhere in the WebUI that actually ran the
   dispatcher to claim Ready tasks and spawn workers. Users had to drop
   to the CLI.

   Now: new runKanbanDispatcher() entry point hits /api/kanban/dispatch
   without dry_run=1, after a showConfirmDialog confirmation because it
   spawns subprocess workers. Two UI surfaces: a lightning-bolt button in
   the board header (visually distinct from the dry-run preview ▶), and
   a primary 'Run dispatcher' button in the sidebar bulk bar next to a
   relabeled 'Preview' button. Toast result shows concrete numbers from
   dispatch_once(): 'Dispatched: 1 spawned, 2 skipped (no assignee)' —
   not just a generic 'OK'.

3. Assignee dropdown — the previous create modal accepted free-text
   assignee with no validation. The dispatcher (kanban_db.py:3567) only
   spawns workers when row['assignee'] is a real Hermes profile name; any
   typo or blank value made the task sit in Ready forever.

   Now: <select> populated from /api/profiles (Hermes profile names) with
   historical board assignees grouped under 'Other (CLI lanes / removed
   profiles)', plus an explicit '— Unassigned (won't auto-run) —' option.
   Default selection is the first profile, not Unassigned. Custom SVG
   chevron so the field reads visually as a dropdown. Helper text under
   the field explains the dispatcher claim contract. Soft warning if user
   explicitly picks Unassigned + Ready ('You picked Unassigned + Ready.
   The dispatcher will skip this task. Submit again to confirm, or pick
   a profile.'); proceeds on second submit.

Side effect: default new-task status changed from triage to ready, since
'ready' is what users want for tasks they intend to actually run. Triage
is still in the dropdown for tasks that need staging review.

i18n: 19 new keys translated across all 8 supported locales.

Tests: 3 new regression tests in tests/test_kanban_ui_static.py:
- test_kanban_task_detail_has_edit_button_and_modal_supports_edit_mode
- test_kanban_assignee_dropdown_uses_select_not_freetext
- test_kanban_run_dispatcher_button_exists_and_is_distinct_from_preview

Verified end-to-end in browser: created board → opened modal with profile
dropdown → created task with assignee=archivist → clicked Edit → changed
all 5 fields → saved → verified persistence → clicked Run dispatcher →
confirm dialog → confirmed → toast 'Dispatched: 1 spawned' → task moved
Ready → Running.

Test suite: 5042 passed, 11 skipped, 3 xpassed, 0 regressions in 151s.
2026-05-09 20:48:28 +00:00
Michael Lam
2aa8b1adc0 fix(i18n): backfill zh-Hant kanban keys 2026-05-09 13:40:19 -07:00
nesquena-hermes
ed776ee1a1
Merge pull request #1976 from nesquena/fix/mcp-profile-discovery
fix(profile/mcp): discover MCP tools after per-session HERMES_HOME mutation (#1968)
2026-05-09 13:29:06 -07:00
nesquena-hermes
a3af4a3c8f fix(profile/mcp): discover MCP tools after per-session HERMES_HOME mutation
Issue #1968: switching to a non-default profile in the WebUI dropdown
had no effect on which MCP servers were available. Every chat session,
regardless of profile, only saw the default profile's mcp_servers from
~/.hermes/config.yaml. Non-default profile MCP servers (postgres, custom
stdio servers, anything in <profile>/config.yaml) never registered.

Root cause: api/streaming.py:1922 called discover_mcp_tools() at the
TOP of _run_agent_streaming(), about 100 lines BEFORE the per-session
'os.environ["HERMES_HOME"] = _profile_home' mutation at line 2053.
discover_mcp_tools() reads ~/.hermes/config.yaml via get_hermes_home(),
which uses os.environ['HERMES_HOME']. So at the call site, HERMES_HOME
was still whatever the WebUI server process had at startup — the default
profile, every time.

Fix: relocate the discover_mcp_tools() call past the _ENV_LOCK block so
get_hermes_home() resolves to the session's actual profile home. Same
try/except wrapping is preserved; same idempotency semantics on
already-connected servers; same lazy-import pattern.

Caveat (out of scope, agent-side): _servers in tools/mcp_tool.py is a
process-global Dict[str, MCPServerTask] keyed only by server name. So
once profile A registers a server named e.g. 'postgres', profile B's
discovery sees 'postgres' as already connected and skips it — even if
B's config points at a different binary or DB. Concurrent multi-profile
WebUI processes will still hit 'first profile wins per server name'.
Fully fixing that requires keying _servers by (profile_home, name)
upstream in hermes-agent. This PR ships layer 1 only — fixes the
single-non-default-profile case (the headline symptom).

Tests: tests/test_issue1968_mcp_profile_discovery.py — 4 static tests
pinning the lexical ordering invariants. Verified mutation-safety: a
proof-of-concept revert (re-adding a discover call before the
HERMES_HOME mutation) makes the 'only called once' test fail.

Test suite: 5047 passed, 4 skipped, 3 xpassed, 0 regressions.

Closes #1968
2026-05-09 20:08:16 +00:00
nesquena-hermes
ba535e0c69
Merge pull request #1971 from nesquena/stage-327
release: v0.51.32 — Release I (2-PR batch: #1943 lineage segment expand + #1965 kanban modal)
2026-05-09 13:03:21 -07:00
nesquena-hermes
4ce113f324 Stage 327: PR #1965 — fix(kanban): header + button opens create-task modal (#1964) by @nesquena-hermes
# Conflicts:
#	CHANGELOG.md
2026-05-09 19:51:30 +00:00