Commit graph

373 commits

Author SHA1 Message Date
Rod Boev
61ea7360a0 fix(#4536): reject openai-codex provider before empty-model early return 2026-06-26 01:46:57 +00:00
Rod Boev
4d64c18832 fix(#4536): block Codex models from priority service tier 2026-06-26 01:46:57 +00:00
Rod Boev
cae551f4a6 fix(#4536): match service-tier UI gate to backend 2026-06-26 01:46:57 +00:00
Rod Boev
24cf7069a6 fix(#4536): persist OpenAI priority tier in main model settings 2026-06-26 01:46:57 +00:00
nesquena-hermes
856aa1d48d fix(#4891): show header in read-only Memory sections + Profiles help view
Gate (Codex) caught two SILENT regressions in the original fix: explicitly
hiding the header in the 'else'/'empty' branches also hid populated read views.
- _setMemoryHeaderButtons('read') now shows the header for ALL read views
  (incl. read-only Project Context / External Notes), gating only the Edit btn.
- _renderProfileConceptHelp() now uses a new 'help' header mode (shows header,
  hides all action buttons) instead of 'empty' (which hid the help title).
Adds tests/test_issue4891_detail_header_visibility.py (proven non-vacuous).

Co-authored-by: luandnh <luandnh@users.noreply.github.com>
2026-06-25 06:20:20 +00:00
luandnh
561369a261 fix: task detail actions hidden on mobile PWA
The main-view header (and its action buttons) used a CSS :has(.main-view-title:empty)
rule to stay hidden until the title populated; on some mobile browsers / PWA
WebViews that pseudo-class doesn't re-evaluate after textContent is set
dynamically, leaving the header permanently hidden. Explicitly set header
display in the read/create/edit/closed header-button setters (tasks, skills,
memory, workspaces) instead of relying on :has() reactivity.

Co-authored-by: luandnh <luandnh@users.noreply.github.com>
2026-06-25 06:08:00 +00:00
Rod Boev
ee7ff6ed33 fix(extensions): harden gallery against live registry and security review findings 2026-06-25 04:21:50 +00:00
Rod Boev
ddd5c8d936 feat(extensions): add gallery browse, install, and uninstall (#4746) 2026-06-25 04:21:50 +00:00
Brett Yelverton
dfcd94a808 fix(kanban): show unassigned tasks as explicit profile lane on mobile
- Add KANBAN_UNASSIGNED_LANE constant and _kanbanLaneKey/_kanbanLaneLabel
  helpers so unassigned tasks map to a stable internal key '__unassigned__'
  rather than a locale-dependent translated string
- _kanbanLaneNames now places 'default' first, other assignees alphabetically,
  and the Unassigned lane last
- _kanbanRenderProfileLanes filters by _kanbanLaneKey, renders label via
  _kanbanLaneLabel, and adds kanban-profile-lane-unassigned CSS class
- _kanbanTaskMeta always emits the unassigned label so sidebar shows it too
- _kanbanRenderBoard checks unfiltered total and shows a 'Tasks hidden by
  filters' state with a Clear filters button when text search hides all tasks
- Add kanban_tasks_hidden_by_filters and kanban_clear_filters i18n keys to
  all 13 locale blocks
- Add .kanban-profile-lane-unassigned header border using warning token

Fixes: on Dynasty board mobile, Ready=3 CLI but default lane showed Ready=0
because those tasks were unassigned and had no visible lane.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 03:54:32 +00:00
Rod Boev
c109ed9113 fix(plugins): align activation-state sort with badge precedence (#4713) 2026-06-25 02:57:06 +00:00
Rod Boev
e3a1540d32 fix: align sort partition with badge for inactive exclusive providers 2026-06-25 02:57:06 +00:00
Rod Boev
7c1cd99961 fix(#4713): dark-mode disabled badge contrast and active-first plugin sort 2026-06-25 02:57:06 +00:00
Rod Boev
23b3206f83 fix(#4728): guard settings default-model capture when no option is selected 2026-06-24 20:09:30 +00:00
Rod Boev
bede035a5e fix(#4728): persist provider-only default-model changes end to end 2026-06-24 20:09:30 +00:00
Rod Boev
69d202b94a fix(#4728): treat provider-only default changes as settings edits 2026-06-24 20:09:30 +00:00
Rod Boev
cdb44fb7e0 fix(#4728): sync saved default model provider into new chat state 2026-06-24 20:09:30 +00:00
luandnh
878001c231 fix: strip @provider: prefix from cron job model override
When a cron job's model/provider override was set via the form, the dropdown
option value could contain a @provider: prefix (e.g. @custom:9router:chat)
from _apply_provider_prefix in the API response. Since cron jobs store model
and provider as separate fields, the model should be just the bare model name
(e.g. chat) without the provider prefix.

- Add _cronModelBareName() helper to strip @provider: prefix
- Apply in saveCronForm for create, update, and duplicate paths
- Update CHANGELOG.md
2026-06-24 05:03:38 +00:00
nesquena-hermes
e9283c8182 fix(#2814): default _kanbanLanesByProfile=true to match server default (Opus note)
The JS module default was false (consolidated) while the server defaults
lane_by_profile=True (lanes). If the board painted before _kanbanApplyConfigDefaults
ran, there'd be a one-frame consolidated flicker before snapping to lanes. Matching
the client default to the server default removes the flicker. Co-authored-by: stefan-wisbric
2026-06-23 23:51:22 +00:00
nesquena-hermes
5253724f90 rebase(#2814): resolve i18n onto current master + add kanban_view_* keys to all 13 locales
Took master's i18n.js as base (it has all 13 locale blocks + had reorganized the
kanban keys, so the stale-branch conflict block would have duplicated them in
zh-Hant), then inserted #2814's 5 kanban_view_* keys into every locale block after
kanban_new_task — real translations for it/ja/zh/zh-Hant/fr (from the PR), English
fallback elsewhere. All 13 locales now have exactly 1 of each key; locale-parity +
kanban tests green (291 passed).
2026-06-23 23:40:45 +00:00
nesquena-hermes
e8b33066ec Merge remote-tracking branch 'origin/master' into stage/rodboev-4797-4796
# Conflicts:
#	CHANGELOG.md
2026-06-23 22:32:36 +00:00
nesquena-hermes
edbe676b0d Merge branch 'peek-4796' into stage/rodboev-4797-4796 2026-06-23 22:19:13 +00:00
nesquena-hermes
8e384548c3 fix(#4788): reword a comment apostrophe that tripped test_smooth_text_fade's brace-matcher
test_smooth_text_fade.py::test_preferences_ui_exposes_and_saves_fade_text_effect
extracts loadSettingsPanel() via a brace-matcher that does NOT skip comments. The
new structured-code wiring added a comment containing 'isn't' + ''auto' mode' —
the apostrophes were mis-read as string delimiters, scrambling the brace depth so
the matcher closed the function early (before the fade-checkbox wiring), failing
the test. Reworded to 'is not' / 'auto mode' (zero behavior change; node -c clean).
The fade feature + the structured-code feature are both unchanged. Co-authored-by: metaember
2026-06-23 21:40:08 +00:00
nesquena-hermes
48663a4af3 Merge branch 'peek-4788' into gate/4788-json-view 2026-06-23 21:19:18 +00:00
Charles Bine
dd66640fae
feat(#484): configurable default view for JSON/YAML code blocks
The JSON/YAML tree viewer (#484) hardcoded its default: valid json/yaml
fenced blocks opened in Tree view at 10+ lines (`const showTree=lineCount>=10;`).
Make that default user-configurable while preserving current behavior.

- New settings (persisted via /api/settings -> settings.json):
  - structured_code_default_view: auto | on | off  (default auto)
  - structured_code_auto_tree_lines: int 1..1000    (default 10)
- ui.js: replace the hardcode with a sanitized, unit-testable decision
  helper _structuredCodeShowTree(mode, threshold, lineCount); Tree/Raw
  toggle, Raw <pre><code>, parse-failure fallback, and YAML lazy-load
  are unchanged.
- Appearance settings: 'JSON/YAML code blocks' select + 'Auto threshold
  lines' input (disabled outside auto mode); wired through the existing
  appearance-autosave and explicit-save paths; live re-render on change.
- boot.js seeds window globals; i18n labels added to all 13 locales.
- Default (auto + 10) reproduces the original >=10 behavior exactly.
2026-06-23 13:51:45 -04:00
Rod Boev
2504eb90d7 fix(#4755): honor profile default workspace on fresh sessions 2026-06-23 12:18:44 -04:00
nesquena-hermes
505465a81e feat(#4717): empty-profile loading skeleton (phase 1.5 of #4662)
The profile-switch skeleton (#4671) always rendered a fixed content shape; for
a brand-new/empty profile that flashed a misleading 'loading conversations'
skeleton that resolved to an empty list. Track each profile's last-seen session
count (localStorage, per profile name, recorded after each non-all-profiles
render) and, when switching into a profile KNOWN to have zero sessions, render a
quiet empty-state placeholder instead of the content skeleton. Unknown profiles
keep the content skeleton (safe default); skipped under an active project/source
filter (the count is an unfiltered total). prefers-reduced-motion + light/dark
aware. Frontend-only. 7 source-structural tests; Phase-1 skeleton tests still
green.
2026-06-23 05:44:57 +00:00
nesquena-hermes
749f95d01c docs(#4671): clarify dead-assignment backstop + add four-guard contract comment (Opus polish)
Opus ship-gate non-blocking observations (no MUST-FIX), applied to lower future-maintainer
risk on the crown-jewel profile-switch surface:
- clarified the belt-and-suspenders _sessionListSkeletonActive=false at the real-paint site
- added a FOUR-GUARD CONTRACT doc-comment at the top of switchToProfile naming the four
  interacting state pieces (embargo / invalidation / skeleton-flag / _wsTreeGen + _switchGen)
  so a future edit touching one without the others doesn't silently reopen a window.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-23 01:38:25 +00:00
nesquena-hermes
1b0c64a604 fix(#4671): embargo also guards the fetch-failure path + setup moved inside try (Codex re-gate 4)
Two more edge cases from re-gate 4:
(1) SILENT: a mid-switch /api/sessions that FAILS bypassed the embargo (the drop-check
was only on the success path), so its catch cleared the skeleton + rendered old-profile
cache. Added 'if(_profileSwitchListEmbargo) return;' after the gen guard in the catch too.
(2) CORE: the embargo was set BEFORE the try, so a throw in the synchronous setup could
leak it (the guarded finally only covered the try). Moved the invalidate/embargo/skeleton/
ws-gen setup INSIDE the try so the _switchGen-guarded finally always lifts the embargo.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-23 01:18:58 +00:00
nesquena-hermes
ce0ef36ab1 fix(#4671): embargo session-list renders during the switch window (Codex re-gate 3)
Codex re-gate 3 found a third clobber variant: point-in-time invalidation isn't enough
because a renderSessionList() can START after the skeleton shows but BEFORE
/api/profile/switch returns. Since the new-profile cookie is only set by the switch
response, that GET fetches the OLD profile's rows, passes the (now-current) generation
guard, and clobbers the skeleton.

Fix: a profile-switch session-list EMBARGO. _setProfileSwitchListEmbargo(true) before
showSessionListSkeleton(); _runRenderSessionListRefresh drops ALL payloads (after the
gen guard, before apply) while embargoed; the switch lifts it immediately before its own
authoritative renderSessionList() (both branches), in the failure-restore, and in a
_switchGen-guarded finally as a safety net so it can't freeze the sidebar on an
early-return/throw. + regression test.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-23 01:11:24 +00:00
nesquena-hermes
0a64d7d7c6 fix(#4671): invalidate in-flight session-list renders at switch start (Codex re-gate 2)
Codex re-gate 2 found a remaining CORE clobber variant: a renderSessionList() already
in flight BEFORE the switch (background poll fetching the OLD profile's /api/sessions)
would resolve after the skeleton appears but before the switch's own renderSessionList()
bumped _renderSessionListGen — so it passed the generation guard, cleared the skeleton
flag in _applySessionListPayload, and painted stale rows.

Fix: _invalidateSessionListRenders() bumps _renderSessionListGen + clears
_pendingSessionListPayload / _renderSessionListQueuedRequest, called at switch start
BEFORE showSessionListSkeleton(). Any outstanding pre-switch response is now stale
(its captured _gen < current) and discarded by the existing guard; deferred/queued
applies are dropped. + regression test asserting the helper exists and runs before the
skeleton.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-23 00:56:30 +00:00
nesquena-hermes
66786143a8 fix(#4671): close 2 profile-switch render races via skeleton-flag + ws generation token
Reworked from the Codex re-gate (rebased onto fresh master so #4730's gateway-poll
work is preserved, not reverted). Two CORE races + a test-window break:

(1) Skeleton-clobber (sessions.js): renderSessionListFromCache() now bails while
_sessionListSkeletonActive — _allSessions still holds the PREVIOUS profile until
/api/sessions resolves, so a gateway-poll/SSE/timer/cache render mid-switch would
repaint the wrong profile's rows over the skeleton. _applySessionListPayload clears
the flag right before painting (fresh data in hand; superseded responses already
discarded by the generation guard); the fetch-failure path and switch-failure catch
clear it too so it can't strand. NO signature change (kept renderSessionListFromCache()
and _applySessionListPayload(sessData,projData) so the ~10 static tests that anchor on
those markers stay green).

(2) Workspace wrong-profile tree (workspace.js + panels.js): an empty-session switch
reuses the same session_id, so loadDir()'s id guard can't reject a stale pre-switch
/api/list. Added _wsTreeGen generation token + bumpWorkspaceTreeGen(), bumped
UNCONDITIONALLY at switch start (even panel-closed, since loadDir('.') still runs),
captured in loadDir() and re-checked after both awaited /api/list points.

(3) test_issue1116 proximity window 5000->6500 (applyBotName now ~char 5618, still fires).

+ regression tests: skeleton-flag bail/clear lifecycle, ws generation token, failure
restore clears flag; updated #1785 stale-session guard test for the compound condition.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-23 00:46:14 +00:00
nesquena-hermes
737b112f30 fix(#4662 review): suppress spurious switch timeout toast, dim group-label fade, re-guard in-progress branch
Addresses @rodboev + greptile review of #4671:
- panels.js switchToProfile(): pass timeoutToast:false to the /api/profile/switch
  POST so a superseded or transient-but-successful switch no longer pops api()'s
  generic 'Request timed out' error toast; the generation-guarded catch handler is
  now the single source of truth for switch failure.
- panels.js: re-check _switchGen after the awaited renderSessionList() in the
  in-progress-session branch so a rapid second switch can't have its workspace
  skeleton cleared / a stale toast popped by a slower earlier switch (mirrors the
  no-messages branch guard).
- style.css: group labels now fade in via skeletonFadeInDim (ends at opacity:.5)
  instead of the shared skeletonFadeIn (ends at opacity:1 with fill-mode:both),
  which was overriding their intended resting 50% opacity.
- 4 new static-source regression assertions locking the three fixes; CHANGELOG
  Unreleased entry updated (fixed-shape skeleton note + the three review fixes).
2026-06-22 19:10:08 +00:00
Nathan Esquenazi
ef0b4ba37a fix(#4662): clear workspace skeleton on switch-failure no-workspace path
The success path clears a stranded workspace-tree skeleton when the new
profile has no bound workspace (both switch branches), but the catch block
only restored the tree `if (S.session && S.session.workspace)` — with no
else. So a profile-switch failure while the workspace panel is open and the
(still-current) previous session has no workspace left the up-front skeleton
shimmering forever: the same skeleton-strand class the Codex/Opus gates
targeted, in the one path they didn't cover. Mirror the success-path
no-workspace handling in the failure path. Adds a static regression
assertion that the catch block clears the workspace skeleton.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:12:55 +00:00
nesquena-hermes
adcd06ff76 Phase 1 (#4662): address Codex + Opus gate findings
Codex (CORE regression): the parallelized loadDir('.') started BEFORE the
_switchGen stale-switch guard; empty-session switches reuse the session id, so a
superseded switch's /api/list could paint the wrong profile's tree. Reverted to
guard-before-workspace-render ordering (the up-front skeletons already give the
immediate cross-surface feedback; the parallel gain was marginal).

Opus findings:
- [blocker] Workspace skeleton stranded permanently when the new profile has no
  bound workspace (loadDir skipped → nothing clears #fileTree). Added
  clearWorkspaceTreeSkeleton() (only empties when a skeleton is present) and call
  it on the no-workspace path in BOTH switch branches.
- [low-med] Session-list skeleton stranded if an inline-rename input or row
  action menu was open (renderSessionListFromCache early-returns before the DOM
  swap). Dismiss _renamingSid + closeSessionActionMenu() before showing the
  skeleton.
- [low] No-op self-switch flashed skeleton→restore (activateCurrentProfile
  doesn't pre-check). Added an early-return when name === S.activeProfile.

Tests: +4 static assertions for the self-switch guard, rename/menu dismissal,
no-workspace skeleton clear, and the clear-helper definition. 22 pass.
2026-06-22 18:12:25 +00:00
nesquena-hermes
b084535b24 Phase 1 (#4662): profile-switch loading skeletons (sidebar + workspace)
Show polished loading skeletons the instant a profile switch begins instead of
leaving the previous profile's conversation list and workspace file tree visible
until their fetches resolve (~1s).

- static/style.css: theme-token skeleton system (single-line session rows with
  group labels + timestamp bar; tree rows with glyph + name + size bar); subtle
  shimmer sweep; dark-mode tokens; prefers-reduced-motion fallback.
- static/sessions.js: showSessionListSkeleton() mirrors real sidebar anatomy
  (group headers + single-line rows); _sessionListSkeletonActive flag cleared on
  real render.
- static/workspace.js: showWorkspaceTreeSkeleton() mirrors the file-tree row.
- static/panels.js switchToProfile(): clear stale content + show both skeletons
  up front; parallelize the independent list + workspace refreshes (Promise.all);
  restore real content from cache on switch failure so a skeleton never strands.

Verified live: switch fires -> skeleton instant (0 stale rows) -> resolves to new
profile's real rows. Polished across 1920/1440/1280/390 + dark mode (screenshots
pending Nathan's visual review before gate+PR).
2026-06-22 18:12:25 +00:00
nesquena-hermes
55738a50ea
Release v0.51.581 — full-screen PWA mobile sidebar drawer (#4660) (#4683)
* stage #4660 v2 (franksong2702): full-screen PWA mobile sidebar drawer — REBUILT on current master

REBUILD: the prior stage was cut before tonight's #4597/#4584/#4583 ships, so its 3-dot
diff falsely appeared to revert them (Codex CORE/SILENT flags). Rebuilt on current master
(authoritative head a035b1343): clean apply, tonight's fixes confirmed INTACT (_apply_sidebar_
state_db_overrides, bottomDistance, overflow-x:hidden all present). #4660's own change is
additive — full-screen drawer width, edge-guard, mobile close button, overlay->display:none;
does NOT touch the scroll/perf/mobile-pan lines. Nathan concept-greenlit + visual signed off.
71 mobile-layout+swipe tests pass.

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>

* fix #4660 edge-guard regression: defer preventDefault until horizontal intent + touch-action:pan-y; update 2 tests to assert corrected behavior

* fix #4660 edge-guard hit-test interception (Codex CORE): pointer-events:none + drop guard listener

Deeper fix than the prior preventDefault change: the #pwaSidebarEdgeGuard was a fixed
interactive element (z-index 198, no pointer-events:none) that intercepted hit-testing for
taps/vertical-scrolls starting in the left 24px strip (since .messages is the real scroller
under body:overflow:hidden). Now guard is pointer-events:none with NO dedicated touch
listener; the existing window-level CAPTURE handlers still catch the edge-swipe-to-open.
Removed the orphaned _onPwaSidebarEdgeGuardStart fn. Updated 2 tests to assert the new
non-interactive design. 71 mobile tests pass.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-22 01:10:42 -07:00
nesquena-hermes
1f4e70a5d4
stage #4652 (nesquena-hermes): stop /api/reasoning storm + memoize YAML config parse (fixes #4650) (#4659)
Two-layer perf fix for the /api/reasoning storm dropping streaming to ~2 tok/s:
- ui.js: syncReasoningChip() serves the cached chip on routine topbar syncs, only
  re-fetching when the model/provider key (_lastReasoningFetchKey) changes — restores
  pre-a9ce2889 behavior while keeping its model-switch-refresh intent.
- config.py: memoize _load_yaml_config_file keyed on (path, st_mtime_ns, st_size) under
  a lock; cache the RAW parsed dict and re-run cheap _expand_env_vars per call (cache stays
  correct vs os.environ and read-modify-save callers get a fresh structure; on-disk edits
  picked up via mtime/size). Code byte-faithful from PR head f868f10a1ad7. 9 own tests pass.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-21 21:45:13 -07:00
nesquena-hermes
cfe93c5ca1
stage batch #4644+#4645 (franksong2702): mobile drawer close-after-tap + transform animation (#4651)
#4644: close mobile sidebar drawer after a rail-click panel switch (was left open).
#4645: animate drawer open/close with transform:translateX instead of left offset.
Disjoint product files (panels.js vs style.css); both add tests to test_mobile_layout.py
in distinct functions (no conflict). Mobile-only, desktop unaffected. 60 mobile-layout
tests pass. Code byte-faithful from PR heads.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-21 19:35:49 -07:00
nesquena-hermes
c888dd6a79
stage #4637 (santastabber): allow toggling installed extensions (extensions phase-2) (#4649)
UI enable/disable of installed manifest extensions via a bounded fail-safe
extension-overrides.json state file. Safe boundary: no manifest edits, no asset
fetches, no sidecar proxy, no extension-owned routes. /api/extensions/toggle is
auth+CSRF gated; anchored _EXTENSION_ID_RE (no path traversal); atomic UTF-8 write
under _EXTENSION_STATE_LOCK; 32KiB/512-id DoS bounds; disabled override suppresses
asset/sidecar injection end-to-end. Nathan greenlit the extensions-phase-2 concept.
Code byte-faithful from PR head 8769b4a3902e. 49 own tests pass.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 19:15:54 -07:00
nesquena-hermes
b575aaad1e
absorb #3177 (RobinAngele): global profile switcher in titlebar — gated behind opt-in setting (#4648)
Rebased RobinAngele's titlebar profile-switcher onto current master (resolved
index.html + panels.js conflicts from ~1300 commits of drift). Added a Settings ->
Appearance opt-in toggle 'show_titlebar_profile' (default OFF) per Nathan: the
titlebar button is hidden unless explicitly enabled, so default behavior is
unchanged (composer footer keeps its profile switcher). Wired config bool key +
boot.js apply (_applyTitlebarProfileVisibility) + panels.js load/save + index.html
checkbox + 2 i18n keys across all 13 locales (zh-Hant real translation).
Live-verified: default OFF = titlebar empty; opt-in ON = button appears + persists.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: RobinAngele <RobinAngele@users.noreply.github.com>
2026-06-21 18:56:31 -07:00
nesquena-hermes
0fd9bf3c37
Release v0.51.564 — loopback sidecar diagnostics (#4612) (#4632)
* stage #4612 (santastabber): surface loopback sidecar diagnostics + CHANGELOG

Settings -> Extensions surfaces manifest-declared loopback sidecar companion services
(type:loopback, http(s) on 127.0.0.1/localhost/::1 only, sanitized origin+health_path)
as read-only health cards w/ a browser-side credentials-omitted health probe. Backend
hard-whitelists scheme + loopback host, rejects userinfo/path/query/traversal, rebuilds
output from parsed components (no echo of rejected input); frontend esc()'s every field,
whitelists badge status, fetch never reads body. Purely additive to GET /api/extensions/status.
Code applied byte-faithful from PR head 7080929f3727. 33 own tests pass.
Nathan concept-approved (loopback-companion direction).

Co-authored-by: santastabber <santastabber@users.noreply.github.com>

* fix #4612 Codex gate findings: reject encoded query/fragment in health_path + align CSP loopback allowlist

Codex SHIP-WITH-FIXES (2 reproduced SILENT findings):
1. _normalize_sidecar_health_path percent-decoded AFTER the raw query/fragment ban,
   so /health%3Ftoken=abc -> ?token=abc survived into the probed URL. Now re-rejects
   decoded ? and # + regression test (%3F/%23 skipped with sidecar_health_path_rejected).
2. The origin validator accepts https:// and [::1], but CSP connect-src only listed
   http 127.0.0.1/localhost -> accepted https/IPv6 loopback sidecars would be CSP-blocked.
   Aligned _CSP_CONNECT_BASE (added https v4/localhost + http(s) [::1]) + updated the 4
   exact-string CSP tests + a regression test asserting all loopback forms are allowed.

Co-authored-by: santastabber <santastabber@users.noreply.github.com>

* Release v0.51.564 — Release TW (loopback sidecar diagnostics; #4612)

* fix(#4612): drop browser-invalid [::1]:* CSP source (browser-smoke fail)

The CSP-loopback-alignment fix added http(s)://[::1]:* to connect-src, but CSP
host-source grammar can't express a port wildcard on a bracketed IPv6 literal —
Chromium rejects '[::1]:*' as an invalid source, which browser-smoke flags as a
console error (CI fail). Keep the valid https://127.0.0.1:*/localhost:* additions;
drop the IPv6 wildcard entries. IPv6 [::1] sidecars are still accepted by the
validator (displayed in the card); their browser health probe surfaces as
'blocked' under CSP unless an operator adds a specific-port [::1]:<port> via
HERMES_WEBUI_CSP_CONNECT_EXTRA. Tests updated to match + assert [::1]:* absent.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 13:07:32 -07:00
nesquena-hermes
b549c004cb
stage #4598 (Paladin173): composer footer control visibility toggles + rebase + contract test (#4630)
Per-control show/hide for composer footer controls (primary vs situational groups),
per-profile persist + live-apply. Visibility-only — footer layout unchanged.
nesquena-hermes deep-reviewed safe (Codex+Opus+suite 9930). Rebased onto master:
resolved api/config.py boolean-keys conflict (kept both auth_disabled_acknowledged +
the 15 hide_composer_* keys) and panels.js settings-load conflict (kept both
render_user_markdown wiring + composer-control chip render). Contributor closed the
en-only-i18n CI blocker (19 keys x 13 locales verified). Added the missing static-
contract test (sibling pattern test_sidebar_tab_visibility.py).

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: Paladin173 <Paladin173@users.noreply.github.com>
2026-06-21 12:42:42 -07:00
nesquena-hermes
675bc7d9e7 stage #4031 onto master (v0.51.558) — polish cron model picker copy + regression tests
TomBanksAU: cron model picker copy (esc()+i18n fallbacks, no_agent hint for script-only jobs) +
195-line provider-picker regression test. Core picker already shipped; this is polish + guardrails.
Co-authored-by: TomBanksAU <TomBanksAU@users.noreply.github.com>
2026-06-21 09:36:24 +00:00
nesquena-hermes
07e6a839fa stage #4599 onto master (v0.51.555) — add id-ID Edge TTS voice + fix Listen button playing-state (parity)
latipun7: adds id-ID-GadisNeural to the Edge TTS allowlist (Edge TTS is a core builtin -> parity, default-in)
+ picker label + sets speaking-state at start of _playEdgeTtsChunked so the Listen button reflects playback.
Co-authored-by: latipun7 <latipun7@users.noreply.github.com>
2026-06-21 08:35:03 +00:00
nesquena-hermes
813e5aa3b2 stage #4588 — extensions settings diagnostics panel 2026-06-21 07:10:05 +00:00
nesquena-hermes
8ea7ad582e rebase #3581 onto master (v0.51.546) for review+ship-gate — require current password before disabling auth
starship-s: sudo-mode re-auth on POST /api/settings before any change/clear/passwordless transition
of password auth, + cosmetic auth-disabled-acknowledged nav badge. Operator-hardening (rubric-named).
Security-boundary + visible UI -> Nathan visual sign-off. Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-06-21 02:26:20 +00:00
nesquena-hermes
2471e277cb rebase #3933 + stamp v0.51.544 — opt-in render markdown in user messages
rodboev. Default-off; OFF path byte-identical to master (cache key 'u' + _renderUserFencedBlocks
unchanged when window._renderUserMarkdown falsy). ON routes user text through the same renderMd
SAFE_TAGS/_isSafeUrl sanitizer as assistant msgs. Nathan override-approved (likes the idea, opt-in).
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-21 00:03:02 +00:00
nesquena-hermes
a60ba78298 stage #4517: plugins panel active-provider badge via is_active_provider (#4496) + v0.51.531 CHANGELOG 2026-06-20 03:35:44 +00:00
Rod Boev
41f2015a32 fix(#2698): close isolated profile escape hatches 2026-06-20 00:48:20 +00:00
Rod Boev
963f7c3864 fix(profiles): honor isolated HERMES_HOME profile mode (#2698) 2026-06-20 00:48:19 +00:00