3050 Commits
Author SHA1 Message Date
personal82555 5e801d74d5 feat(selfmedia): add 自媒体中心 native panel to sidebar
Browser smoke / changes (push) Canceled after 0s
Browser smoke / browser-smoke (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (chromium, reconnect-scene-redraw, tests/browser_reconnect_scene_redraw.py) (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (historical-transcript-hydration, tests/browser_historical_transcript_hydration.py) (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (normal, normal, tests/browser_conversation_lifecycle.py) (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (terminal-error, terminal-error, tests/browser_conversation_lifecycle.py) (push) Canceled after 0s
Tests / changes (push) Canceled after 0s
Tests / lint (push) Canceled after 0s
Tests / test (3.11, 0) (push) Canceled after 0s
Tests / test (3.11, 1) (push) Canceled after 0s
Tests / test (3.11, 2) (push) Canceled after 0s
Tests / test (3.11, 3) (push) Canceled after 0s
Tests / test (3.11, 4) (push) Canceled after 0s
Tests / test (3.12, 0) (push) Canceled after 0s
Tests / test (3.12, 1) (push) Canceled after 0s
Tests / test (3.12, 2) (push) Canceled after 0s
Tests / test (3.12, 3) (push) Canceled after 0s
Tests / test (3.12, 4) (push) Canceled after 0s
Tests / test (3.13, 0) (push) Canceled after 0s
Tests / test (3.13, 1) (push) Canceled after 0s
Tests / test (3.13, 2) (push) Canceled after 0s
Tests / test (3.13, 3) (push) Canceled after 0s
Tests / test (3.13, 4) (push) Canceled after 0s
- New 'selfmedia' panel registered in MAIN_VIEW_PANELS with sidebar
  rail/nav buttons, main view container and mobile sidebar panel
- Native in-origin rendering (no sandboxed iframe) so api() calls
  carry session cookie + CSRF and actually load cron data
- Dashboard: stats cards, self-media cron job detection by keywords,
  one-click job trigger, recent activity feed, 30s auto-refresh
- Quick actions open a brand-new conversation and send the command
  there (发布公众号/选题/写文章/多平台发布/热门排行)
2026-10-17 21:56:24 +08:00
nesquena-hermes 016a04c0b6 Merge PR #7789 into stage/0924-n 2026-09-24 19:01:31 +00:00
nesquena-hermes 96921c2cfc Merge PR #7577 into stage/0924-l 2026-09-24 17:53:18 +00:00
carlotestor 075f95aaf2 fix(console): retry a late profile-mismatch 409 once when focus returned mid-request 2026-09-24 17:09:08 +00:00
carlotestor 6ebd12c251 fix(console): re-arm profile-paused approval/clarify pollers on focus/visibility 2026-09-24 14:55:27 +00:00
carlotestor 96bdeeb9cf Merge remote-tracking branch 'origin/master' into HEAD 2026-09-24 14:54:20 +00:00
nesquena-hermes 38c5c69b63 Merge PR #6717 into stage/0924-i 2026-09-24 14:26:32 +00:00
nesquena-hermes 73d08d3cfb test: follow the #6717 render-scheduler signature; guard the burst reset for node harnesses
_scheduleMessageVirtualizedRender now takes (force, request), so the two
structural probes in test_issue4346_vscroll_footer_jitter.py matched nothing.
renderMessages() calls _resetMessageVirtualMeasurementBurst(), which node
harnesses that extract renderMessages() alone (test_compaction_current_summary_fallback.py)
don't define; guard it with typeof like the file's other optional helpers.
2026-09-24 13:43:12 +00:00
nesquena-hermes 1cb16f6037 Merge PR #6895 into stage/0924-h 2026-09-24 13:25:15 +00:00
webtecnica c784ef7a12 fix(ui): preserve endpoint-style custom slugs host:port in model parsers (#6884) 2026-09-24 08:50:30 -03:00
webtecnica 0cbf16a51c fix(perf): treat only non-consecutive repeat cycle keys as oscillation (#6717) 2026-09-24 08:44:04 -03:00
nesquena-hermes c2c8ef5fce Merge PR #7301 from laitekin/fix/7299-hidden-session-poll 2026-09-24 11:26:41 +00:00
latekin 08dbd87a36 Merge branch 'master' into fix/7299-hidden-session-poll 2026-09-24 18:20:37 +08:00
nesquena-hermes 19908b5db4 Merge PR #7720: scope MCP status, inventory and /reload-mcp to the request profile 2026-09-24 09:58:07 +00:00
nesquena-hermes 10faa69155 Merge PR #6746 from happy5318/fix/mobile-enter-newline 2026-09-24 08:05:12 +00:00
tancouandClaude Fable 5.1 dbf0179c53 fix(mcp): report runtime_scope on /api/notes/sources and surface it in the Notes drawer
Review follow-up on #7720 (Greptile P2): the external Notes drawer reads the
same profile-bound MCP inventory as the MCP panel but dropped the
``runtime_scope`` label, so a profile whose runtime scope could not be
confirmed rendered its note sources as inactive/configured with no
explanation. ``/api/notes/sources`` now returns ``runtime_scope`` and the
drawer shows the existing ``mcp_runtime_scope_unavailable`` notice (same key,
no new locale strings) when it is ``unavailable``.

Tests: the skewed-routing unit test now also asserts the notes response
carries ``runtime_scope: "unavailable"`` with its sources inactive.
docs/troubleshooting.md mentions the drawer next to the panel.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 09:53:02 +02:00
tancouandClaude Opus 5 90b814f673 fix(mcp): scope MCP status, inventory and /reload-mcp to the request profile
Two profiles configuring a same-named MCP server shared one connection and
the MCP settings panel showed another profile's runtime ("Active, N tools"
next to an empty inventory); /reload-mcp stopped every profile's servers and
rediscovered from the process-default config.

- api.profiles._set_hermes_home() is the single writer of the process-profile
  home: it records get_process_profile_home() and hands it to Hermes Agent's
  hermes_constants.pin_process_hermes_home when available, at startup and on
  switch_profile(process_wide=True), so the per-turn HERMES_HOME mirror no
  longer makes every turn look like the process profile (bare-name,
  cross-profile MCP keys) and the anchor cannot drift from HERMES_HOME.
- Run get_mcp_status/registry reads and /reload-mcp inside
  api.mcp_runtime.mcp_runtime_scope(): request profile's Hermes-home override
  and secret scope (root included), no os.environ mutation, restored on exit.
- One ledger predicate, ledger_key_serves_view() (owned via
  _server_scope_keys/key scope, or adopted via _server_tool_scopes), is shared
  by status, inventory and the reload summary. The Agent's launch-profile view
  is process-wide and name-indexed, so filter_runtime_status_to_view()
  rebuilds connected/tools/status/error from the entries serving the profile:
  the root profile never shows a routed profile's connection (nor lets its own
  parked failed-spawn task borrow a routed profile's live same-named one).
- Inventory lists only tools in the profile's own registry slot for servers it
  configures; responses carry runtime_scope.
- Fail closed when the agent's routing does not match the resolved profile:
  hide runtime data (runtime_scope "unavailable", shown as a notice in the MCP
  panel, 14 locales) and refuse /reload-mcp.
- /reload-mcp shuts down only the profile's own ledger names (parked
  included), drops its own connect cooldown/error entries so a failed spawn is
  retried (the wildcard cleared every cooldown; a scoped shutdown only clears
  the live keys it tears down), rediscovers under the profile's home and
  counts live connections only. The bare wildcard remains for agents without
  profile-scoped MCP.
- Signature introspection is centralized (accepts_keywords) and falls back to
  the bare call instead of blanking every status; ledger key helpers resolve
  to the Agent's tools.mcp_tool_scope when present; tools.mcp_tool is
  resolved by full name so stubbed agents still work.
- ARCHITECTURE.md §4.10 and docs/troubleshooting.md document the boundary.
- Tests: agent-free contract (models adoption, parked tasks, cooldowns and the
  process-wide root view) and real-Agent integration (fake stdio MCP server,
  skips without hermes-agent + mcp SDK).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 09:53:02 +02:00
happy5318 bbebe23f7e fix(mobile): preserve tablet hardware-keyboard Enter behavior 2026-09-24 15:26:06 +08:00
nesquena-hermes 16acb63a6d fix(notifications): auto-request 'default' permission at most once per page
A pending approval/clarify prompt re-surfaces on every 1.5s poll tick, and
with permission still 'default' (second browser/device) each tick issued a
non-gesture Notification.requestPermission() and toasted 'notifications
denied'. Auto requests are now made once per page; the explicit Send-test
path still always asks. Release-gate hardening on top of #7493.
2026-09-24 03:15:09 +00:00
nesquena-hermes 1c4072228e Merge PR #7493 into stage/0924-a 2026-09-24 03:15:02 +00:00
nesquena-hermes 0b8d6d6fd8 Merge PR #7628 into stage/0924-a
# Conflicts:
#	static/messages.js
2026-09-24 03:15:02 +00:00
nesquena-hermes 57eb9fc1f4 Merge master into stage/7765-subagent-project
# Conflicts:
#	CHANGELOG.md
2026-09-23 21:56:19 +00:00
carlotestor fbb0843acf fix(console): stale profile-mismatch response must not stop the replacement poller
Each approval/clarify fallback poller captures its own interval handle; a 409
(or finally) from a request issued by a replaced poller no longer stops or
clears the in-flight guard of the poller that superseded it.
2026-09-23 19:11:25 +00:00
carlotestor c713c6efc2 fix(console): stop git-info 404s, profile-mismatch 409 poll loops and the wasted pwa-startup preload
- /api/git-info falls back to state.db metadata for sessions without a WebUI
  sidecar (CLI, delegated subagents), mirroring /api/list, instead of 404ing.
- Approval and clarify fallback pollers stop on session_profile_mismatch 409
  instead of re-requesting every 1.5s/3s; live cards stay standing.
- Drop <link rel=preload> for pwa-startup.js: the preload scanner resolves it
  before the dynamic <base href> is written, so on /session/<id> it fetches
  /session/static/... while the script loads /static/..., a wasted duplicate
  request plus a 'preloaded but not used' warning on every load.
2026-09-23 18:29:11 +00:00
nesquena-hermes 3f85d3bbc4 Merge PR #7765 into stage/7765-subagent-project 2026-09-23 18:23:46 +00:00
nesquena-hermes d7530ab90a Merge PR #7779 into stage/7779-cron-provider-pin 2026-09-23 18:23:39 +00:00
carlotestor 4632ab65e9 Merge remote-tracking branch 'origin/master' into HEAD 2026-09-23 16:34:03 +00:00
carlotestor 35921eccbb fix(sidebar): scope project inheritance to subagents, resolve lineages once
Forks and other child sessions keep their own project_id, so a fork moved to
No project stays Unassigned. A per-render memoized, cycle-safe resolver is
shared by partitioning, reference scoping and the Unassigned chip.
2026-09-23 15:28:47 +00:00
Cushing Whitney e1f90aed09 fix(cron): preserve provider-only pin on untouched save; honor explicit clear
Editing a cron job with the model picker at "Default" sent provider: null
unconditionally, wiping provider-only pins (provider set, no model — the
pattern for self-hosted LLM endpoints). The combined picker cannot express
that state, so such jobs always rendered as "Default" and were silently
re-resolved to the bare global default on any save, breaking dispatch.

#4030 deliberately made clearing the picker mean "no overrides"; that
contract is preserved for explicit clears and model+provider jobs, while
provider-only pins survive saves where the picker was never touched.

- _cronProviderForClear(prevDetail, pickerTouched) decides the provider
  submitted on clear: touched picker -> null; untouched + no model on the
  job -> prev.provider.
- _cronModelPickerTouched is set by a change listener added when the picker
  populates, and reset at openCronEdit/openCronCreate/duplicateCurrentCron.
- Tests: delegation + flag lifecycle locked in, and the helper driven
  through node for provider-only / model-pinned / no-override / no-snapshot
  inputs (6 passed).

Refs: #4030
2026-09-23 14:10:22 +00:00
carlotestor 35c9b8ae96 fix(sidebar): Unassigned chip resolves child projects through reference rows
The chip check now reuses the ancestor map built by _partitionSidebarSessionRows,
so a child whose project parent is only a hidden reference row is not counted
as unassigned. Document project inheritance for subagent sessions in README.
2026-09-23 12:59:36 +00:00
webtecnica 4b791bd820 fix(stream): require a current live assistant row to seed the replay floor
The replay-authority predicate accepted any non-empty assistant message,
but a new turn copies the full transcript into INFLIGHT.messages. In an
established conversation that content is the previous turn's reply, so
`canSeed` came back true with both stale cursor halves preserved and the
server kept skipping the range that rebuilds the body.

Message-based evidence now counts only from a current `_live` assistant
positioned after the latest user boundary; a historical assistant row
fails closed to floor 0 and an empty event id.

Refs #7640 (blocker on #7651)
2026-09-23 09:29:00 -03:00
webtecnica 03013606fe Merge remote-tracking branch 'origin/master' into fix/7640-transparent-replay-body 2026-09-23 09:20:31 -03:00
nesquena-hermes aebcd96ff8 Merge PR #7730 into stage/7730-first-visit-locale 2026-09-23 10:22:45 +00:00
carlotestor d04737ec87 fix(sidebar): show delegated subagents inside their parent's project
Subagent child sessions come from state.db and never carry a project_id,
so the project filter dropped them. Resolve a child's project from its
nearest ancestor for the project filter, reference scoping and the
Unassigned chip.
2026-09-23 09:25:33 +00:00
nesquena-hermes 86d5b36898 Merge master (exp-v0.52.349) into stage/7708-media-backtick
# Conflicts:
#	CHANGELOG.md
2026-09-23 08:50:25 +00:00
nesquena-hermes a028961133 Merge PR #7708 (contributor head 6455aa9b2b) 2026-09-23 06:22:22 +00:00
laitekin a0e5e32e46 Merge current master while preserving recoverable hidden polling (#7299) 2026-09-23 12:18:23 +08:00
happy5318 6455aa9b2b fix(media): two-pass MEDIA: scan (pre-pass for wrapped, bare class without backtick exclusion) (#7708 round 2)
#7708 re-gate (9/22 23:47), both findings:

**Finding 1 (SILENT)**: A bare path that contains a backtick
(MEDIA:/tmp/report`final.png) is truncated at the first backtick,
so the server stops authorizing it. The round-1 fix traded one bug
for another: adding the backtick to the bare-token exclusion set
dropped the inline-code closing backtick (good) but truncated any
bare path with a backtick in the filename (bad — POSIX and Windows
both allow backticks).

**Finding 2 (SILENT)**: TTS now reads filename fragments aloud
(MEDIA:/tmp/report(1).png -> 'a file).png') because the
parenthesis-exclusion was added to a site that should not need it.
Inline code is already stripped at the top of the TTS flattener.

**Fix shape** (maintainer-suggested two-pass scan):

1. Pre-pass: `MEDIA:([^`\s]+)` -> MEDIA:$1 — strips the
   inline-code backticks off the wrapped form so the bare class
   below sees a plain MEDIA:path.
2. Bare class: MEDIA:([^\s\)\]]) (original, backtick
   removed from the exclusion set) — captures the full filename
   even when the path itself contains a backtick.
3. TTS: restore the original MEDIA:[^\s]+ (no paren/bracket
   exclusion either). Inline code is stripped at the top of the
   flattener so the closing backtick of an inline-code wrapper has
   already been eaten by the time the TTS regex scans the text.

Applied to all 7 capture sites:
- api/routes.py:20453 (_MEDIA_TOKEN_RE, findall site 20513)
- api/media_snapshots.py:507 (media_re, findall site 533)
- static/ui.js:7695 (linkifier, replace site)
- static/ui.js:9142 (TTS flattener — separate shape)
- static/messages.js:4848 (chunk finalize, exec)
- static/messages.js:4896 (combined walk, exec loop)
- static/messages.js:4922 (partial tail, exec)

**Tests** (24 total, all green):
- 11 source-shape guards: each JS/PY site must carry the bare-token
  class literal AND the wrapped-form pre-pass literal
- 4 behaviour tests against the live source: wrapped form strips
  closing backtick, bare form captures plain path, bare form with
  backtick in filename captures the full path (the new regression),
  trailing paren is still excluded
- 2 round-1 regression guards documenting the trade-off that drove
  the two-pass compose
- 1 composed two-pass test (pre-pass + bare class on 4 inputs)
- 2 node --check syntax guards
- 1 site-count guard: 5 JS + 2 PY = 7 capture sites, all guarded

**Verification**:
- pytest tests/test_issue7359_media_backtick_strip.py -v -> 24/24 pass
- python3 scripts/ruff_lint.py --diff origin/master -> 'no new
  violations on added/modified lines. OK.'
- node --check on the two modified JS files -> both OK
2026-09-23 12:03:33 +08:00
Frank Song bbca954c5c fix: gate idle recovery on exact runtime status
(cherry picked from commit 35597715018e598bf24f0b708cbd32b039533a99)
2026-09-23 11:57:31 +08:00
Frank Song ca2153ec54 fix: bound sidebar idle recovery for missing terminal events
(cherry picked from commit 1be0e114b32eefe14f94837ed3cf78aadc14ca9b)
2026-09-23 11:57:31 +08:00
Frank Song 5dcda6f537 fix: preserve terminal handoff across idle sidebar refresh
(cherry picked from commit 85f1c2d34b62b0ade85f51ed2cf8c1cf56f5ab9e)
2026-09-23 11:57:31 +08:00
happy5318 8d0cdaefbe fix(i18n): preserve explicit saved English on first-visit bootstrap
Closes #7622.

Round-3 follow-up to the maintainer review at
https://github.com/nesquena/hermes-webui/pull/7730#issuecomment-5786493511.
The round-2 'primary === "en"' heuristic overrode users who had
genuinely picked English: when the server-stored 'language' was 'en'
the resolver treated it as the schema default and skipped it, falling
through to localStorage / the browser hint.  This round addresses all
three maintainer findings:

1. BRICK — server stops injecting the schema default.  Drop
   '"language": "en"' from api/config.py:_SETTINGS_DEFAULTS so a
   fresh install returns settings['language'] == None.  Existing
   settings.json files that already carry 'language: "en"' still
   report 'en' (the field is just absent from the defaults dict; the
   per-file load path is unchanged).  'language' is added back to
   _SETTINGS_ALLOWED_KEYS so the Settings modal save path still
   validates and persists explicit user picks (the BCP-47 regex is
   unchanged).

2. CORE — browser-language detection is now in one guarded helper.
   static/i18n.js exposes _detectBrowserLanguageHint(), used by
   loadLocale(), boot.js, and panels.js.  A throwing navigator
   accessor in some embedded webviews can no longer abort boot or
   settings hydration before the model/provider/plugin/extension
   sections are populated.

3. CI RED — restore the test shape.  boot.js and panels.js call
   resolvePreferredLocale(s.language, localStorage.getItem('hermes-lang'),
   _detectBrowserLanguageHint()) inline so
   tests/test_language_precedence.py::test_boot_and_settings_panel_use_shared_locale_precedence
   matches the expected argument list.  The TestComposedResolverPrecedence
   tests in tests/test_issue7622_initial_locale_from_browser.py are
   re-pinned to the round-3 contract (server primary is None for a
   fresh install; an explicit 'en' primary from a user-saved file
   beats a non-English browser hint).

Tests (22/22 i18n-related pass; 833/833 settings+i18n+locale pass; ruff
forward-gate --diff origin/master is clean):
- test_language_precedence.py: 8/8 (added guarded-helper, server-default,
  composed-resolver, and cross-file first-visit tests)
- test_issue7622_initial_locale_from_browser.py: 14/14 (re-pinned to
  round-3 contract; added explicit-saved-English regression test)
- test_login_locale.py and friends: unchanged behaviour for the
  save/load round trip
2026-09-23 11:21:51 +08:00
nesquena-hermes 394ff790e5 Merge master (exp-v0.52.344) into stage/7436-reconnect-replay
# Conflicts:
#	CHANGELOG.md
2026-09-23 03:21:30 +00:00
nesquena-hermes c5663e86a7 Merge PR #7436 from atchisonbrent: skip unchanged tool-card replay on reconnect 2026-09-23 02:29:39 +00:00
nesquena-hermes a6d6b3da85 Merge PR #7374 from Thireus: fix duplicate assistant answer render (#2051) 2026-09-23 02:28:29 +00:00
nesquena-hermes 087a2df4a2 Merge PR #7716 into stage/rm-343-batch 2026-09-23 00:43:14 +00:00
happy5318 b471e7d1b8 fix(i18n): fall back to browser language on first visit (#7622)
Closes #7622.

The renderer's `loadLocale()` was strictly a localStorage-or-English
fallback — non-English speakers had to dig into Settings after every
fresh install.  This change consults `navigator.languages[0]` (or
`navigator.language` when the ordered list is missing) ONLY when no
stored preference exists, so:

  - first visit on a zh-CN browser → UI lands on `zh` immediately
  - first visit on an en-US browser → still `en` (regression guard)
  - first visit on a kl-GL browser (unsupported) → falls back to `en`
  - any second visit (or after the user picked a language in
    Settings) → the stored value wins, the browser hint is never
    consulted again for that browser profile

Tests (8/8 pass): a node driver loads the real `static/i18n.js`,
mocks `localStorage` and `navigator`, and asserts the language
that `loadLocale()` ultimately passes to `setLocale(lang)`.  The
test covers the four core happy/sad paths plus two pinning tests for
the 'stored wins' contract.

`resolveLocale()` already strips the region subtag (zh-CN → zh,
en-US → en, fr-FR → fr), so the `navigator.language` value is
reduced to a key in `LOCALES` without any extra work in this PR.
The fix is strictly additive: `loadLocale()`'s precedence chain
becomes (navigator hint) → (stored) → 'en' on first visit, and
remains (stored) → 'en' on every subsequent visit.
2026-09-23 07:59:27 +08:00
laitekin 99f22391d7 Merge remote-tracking branch 'origin/master' into fix/7299-hidden-session-poll 2026-09-22 23:59:23 +08:00
laitekin 3cc825d044 fix(streaming): preserve recovery after ambiguous profile 404s (#7299) 2026-09-22 23:59:23 +08:00
nesquena-hermes 0378ed4e34 fix(routes): preserve live cards on cross-profile 409 collisions
Gate follow-up for #7710. Three front-end handlers treated EVERY 409 as a
domain-specific stale/expired signal, so the new session_profile_mismatch
response would have:

- messages.js: hidden a LIVE clarify card and mislabelled it expired
- ui.js: retired a LIVE compression-recovery card with a false note
- commands.js: rendered a false 'Compression failed' error state

Each now routes through _sessionProfileMismatchFromError() first and leaves
the card standing. Also captures the status in the import_cli test mock so a
wrong status can no longer pass (the mock swallowed it into *_).
2026-09-22 10:21:22 +00:00