* feat(desktop): add custom endpoint settings (supersedes #42745)
Salvages PR #42745 (elashera:custom-endpoints-desktop), which could no
longer merge cleanly against main. Re-integrated the work onto current
main and reconciled the conflicts:
- Settings nav: wired the new 'Custom Endpoints' provider sub-view into
main's data-driven navGroups/OverlayNav layout (PR predated that
refactor) and added it to PROVIDER_VIEWS.
- providers-settings: kept BOTH main's LocalEndpointRow affordance and
the PR's fuller CRUD panel; unified ProvidersSettingsProps to carry
onClose + onConfigSaved + onMainModelChanged.
- web_server: kept main's _normalize_main_model_assignment + api_key
propagation AND the PR's provider base_url lookup in
_apply_model_assignment_sync.
- model_switch: dropped the PR's bare direct-custom-config picker block;
main already implements it (source='model-config', with live model
discovery). Updated the salvaged test to assert main's behavior.
- Merged additive import/type blocks in hermes.ts and types/hermes.ts.
Backend endpoints, i18n labels (en/ja/zh/zh-hant), and the
custom-endpoints-settings.tsx panel carried over. 28 custom-endpoint
tests pass.
Co-authored-by: elashera <emilio.jesus.lasheras.romero@nttdata.com>
* chore(contributors): map elashera's commit email
Salvage of #42745 (superseded by #67759) preserves @elashera's
authorship, whose corporate commit email had no contributor mapping.
Adds contributors/emails/ mapping so check-attribution passes.
Verified: GitHub user 'elashera' id=135239963 matches their own
noreply commit email (135239963+elashera@users.noreply.github.com).
---------
Co-authored-by: elashera <emilio.jesus.lasheras.romero@nttdata.com>
* fix(desktop): allow write-build-stamp from non-git checkouts
Stop hard-failing npm pack when neither GITHUB_SHA nor git HEAD is
available (ZIP installs / broken .git). Emit an explicit fallback stamp
instead so local Windows desktop builds can finish (#50823).
* fix(desktop): treat fallback stamps as unpinned; harden Windows install
Keep all-zero fallback commits out of -Commit/--commit pins and fetch
install.ps1 by branch instead. After bootstrap, pin the marker to the
checkout HEAD so isBootstrapComplete accepts it. On Windows, force ZIP
checkout, seed GITHUB_SHA (ASCII-only install.ps1), and avoid the pack
stamp failure.
* fix(install): pin core.autocrlf=false before ZIP-path checkout (#50823 review)
The ZIP-fallback path added in #67643 runs `git checkout -f FETCH_HEAD`
before core.autocrlf gets pinned (which only happened later, on the
shared clone-path config). On Git for Windows -- where core.autocrlf
defaults to true -- that renormalizes the repo's LF text files to CRLF in
the working tree during checkout, leaving the freshly-created managed
checkout dirty versus HEAD and aborting the next `hermes update`. That is
the exact "dirty tree the user never touched" failure the surrounding
code already guards against (install.ps1:1461-1469, 1750-1753).
Move the `config core.autocrlf false` pin to run immediately after
`git init`, before the fetch/checkout. The later idempotent pin on the
shared clone path is retained so git-clone installs are unaffected.
Addresses teknium1's review on #67643 and supersedes it, preserving the
original author's two commits.
Co-authored-by: HexLab98 <8422520+HexLab98@users.noreply.github.com>
* chore(contributors): map austinpickett commit email for attribution
The check-attribution CI gate flagged austinpickett@users.noreply.github.com
as an unmapped commit-author email (introduced by the autocrlf fix commit
on this PR). Add the per-email mapping file as the gate instructs (the
legacy AUTHOR_MAP in scripts/release.py is frozen).
---------
Co-authored-by: HexLab98 <liruixinch@outlook.com>
Co-authored-by: austinpickett <austinpickett@users.noreply.github.com>
Co-authored-by: HexLab98 <8422520+HexLab98@users.noreply.github.com>
Kimi's Anthropic-compatible endpoints (api.moonshot.cn/anthropic,
api.kimi.com/coding) implement the adaptive thinking contract — they
accept thinking.type=adaptive + output_config.effort (all of low,
medium, high, xhigh, max verified live) and return thinking blocks, and
the replay-validation 400s that originally motivated dropping the
parameter (#13848) no longer occur.
_supports_adaptive_thinking() now returns True for Kimi-family models,
so they get thinking={type: adaptive, display: summarized} +
output_config.effort via ADAPTIVE_EFFORT_MAP instead of nothing, and
the blanket drop of the thinking parameter for Kimi-family endpoints is
removed. MiniMax and other non-adaptive third parties keep the manual
budget_tokens path; Claude behavior is unchanged.
Salvaged from #42654 by @digitalbase (earliest report of the leak, June 9):
the desktop sidebar and cron overlay showed EVERY profile's jobs because
GET /api/cron/jobs defaults to profile=all and the desktop never sent the
param — profileScoped() (landed in #67493) routes the backend process but
adds no endpoint filter on local pools.
- hermes.ts: getCronJobs(profile?) appends ?profile= when given; omitting
the arg keeps the legacy unfiltered path. profileScoped() still rides
along for process routing.
- use-session-list-actions.ts: sidebar cron refresh passes the sidebar's
profile scope (concrete profile → own jobs; ALL_PROFILES → 'all').
- app/cron/index.tsx: the cron overlay's refresh uses the same scope so
the overlay and sidebar (shared $cronJobs atom) always agree.
- Tests: list ?profile= contract in hermes-cron-scope.test.ts; sidebar
scoping in use-session-list-actions.test.tsx.
Reworked onto current main per the sweeper review: threaded through the
existing profileScoped()/list-param seams instead of the original PR's
pre-refactor call sites (DesktopController has since delegated to
use-session-list-actions).
Salvaged from #59888 by @isfttr: the profileScoped() fix itself landed
via #67493 (salvaged from the earlier #49948), but this PR contributed a
contract test locking all 9 cron helpers to the active gateway profile —
omitted when none is set (single-profile users unaffected), attached when
one is active. Keeps the multi-profile/remote cron routing from silently
regressing.
Refactor the cherry-picked #40338 backend half:
- Move option merging from import-time _SCHEMA_OVERRIDES mutation to a
per-request overlay in GET /api/config/schema — options now reflect the
current config.yaml (no restart needed) and the module-level
CONFIG_SCHEMA is never mutated. The endpoint gains an optional
?profile= param scoped via _config_profile_scope.
- Keep builtin display order first, customs appended (drop the
sorted(set(...)) re-sort) — matches desktop enumOptionsFor.
- Only command-type provider blocks count (type absent or 'command' plus
non-empty command string), enumerated from the canonical
<kind>.providers.* location AND the legacy top-level <kind>.<name>
fallback — the same dual resolution as _get_named_provider_config /
_get_named_stt_provider_config. Builtin-name collisions are excluded
case-insensitively against the RUNTIME builtin sets (not the display
shortlist), mirroring apps/desktop/src/app/settings/helpers.ts
commandProviderNames (#67209).
- Drop the plugin.yaml 'provides: [tts]' manifest scan — that convention
does not exist (manifests carry provides_tools/provides_hooks only);
plugin TTS/STT providers register at runtime via
ctx.register_tts_provider(). Instead, opportunistically include names
from agent.tts_registry / agent.transcription_registry when plugins
happen to be loaded in this process.
- Current tts.provider/stt.provider value preserved in options.
- Tests: custom command provider merge (tts+stt), builtin-order
preservation, EDGE collision exclusion, non-command block exclusion,
current-value preservation, per-request freshness, legacy top-level
block support.
_make_tui_argv() called _ensure_tui_workspace(tui_dir) unconditionally
before checking for a prebuilt bundle. That function sys.exit(1)s when
ui-tui/ doesn't exist, which it never does on a pip/pipx install — the
wheel ships hermes_cli/tui_dist/entry.js but never ships ui-tui/ at all
(that directory only exists in a git checkout).
Every dashboard Chat tab connection on a pip/pipx install therefore
hard-exited before ever reaching _find_bundled_tui(), surfacing as the
unhelpful "Chat unavailable: 1" banner despite having a fully valid
bundled entry.js on disk.
Move the bundled-wheel/HERMES_TUI_DIR shortcut ahead of the workspace
check. --dev is unaffected (it never uses the bundled path and still
requires the workspace), and the checkout-without-bundle path is
unaffected (bundled lookup returns None, falls through to the existing
git-restore/npm-install/build flow).
Adds a contributors/emails mapping for the original author.
Fixes#56665
Co-authored-by: lucaskvasirr <lucaskvasir@duck.com>
Fixes the two review defects that kept PR #29923 open, plus docs:
- gateway: exclude --once from the session-store write-through. The
once-override lived only in memory before, but the write-through
persisted it, so a gateway restart before the finally-restore
rehydrated a supposedly one-turn model permanently.
- TUI: skip _sync_agent_model_with_config while a one-turn restore is
pending. The once-model is deliberately not pinned as a session
model_override, so the config sync saw a model mismatch and clobbered
the once-override back to the config model before the turn ran.
- tests: real _handle_model_command drive asserting --once never
touches set_model_override while --session still does; restore-pop
idempotency.
- docs: /model --once in configuring-models.md with an honest
prompt-cache cost note (one-shot switch breaks the cached prefix
twice; wins for short sessions and cheap-to-expensive escalation).
Salvaged from PR #25653 by @Black0Fox0 — the config-key and env-wiring
halves of that PR landed via #67018; this carries the surviving dashboard
schema override so browser.headed renders as a labeled boolean toggle.
Description updated to reflect the merged cleanup-skip behavior.
* feat(attribution): conflict-free contributor mappings via contributors/emails/ directory
The AUTHOR_MAP dict in scripts/release.py was a merge-conflict magnet:
every concurrent salvage PR appended entries to the same lines of the
same file, so parallel PRs re-conflicted on every merge to main.
New system: one file per email under contributors/emails/ — filename is
the commit-author email, first non-comment line is the GitHub login.
File additions never conflict, so any number of PRs can add mappings
concurrently.
- scripts/release.py: AUTHOR_MAP is now LEGACY_AUTHOR_MAP (frozen)
merged with the directory at import time (directory wins). All
existing consumers (resolve_author, contributor_audit.py) unchanged.
- scripts/add_contributor.py: idempotent CLI to add a mapping; refuses
conflicting reassignments (incl. against the legacy map), validates
email/login shapes.
- contributor-check.yml: attribution gate now accepts a mapping file OR
a legacy entry; failure message prints the exact add_contributor
command. Also auto-resolves bare <login>@users.noreply.github.com
emails is intentionally NOT added (kept id+login form only, matching
previous behavior).
- contributor_audit.py: guidance now points at add_contributor.py.
- tests/scripts/test_contributor_map.py: 12 tests covering loader,
merge precedence, CLI idempotency/conflict/validation, subprocess E2E.
* feat(ci): one-shot per-file flake retry in the parallel test runner
A failing test FILE is re-run once in a fresh subprocess. Pass-on-retry
counts as green but is loudly reported in a '⚠ FLAKY' summary section
(with both attempts' output preserved) so the flake gets fixed instead
of eating a full-run rerun. Deterministic failures fail both attempts —
regressions cannot be laundered green.
- --file-retries N / HERMES_TEST_FILE_RETRIES (default 1, 0 disables)
- E2E verified: simulated first-run-fail flake goes green with banner;
deterministic failure still exits 1; retries=0 restores old behavior.
This converts the dominant CI failure mode (one timing-sensitive test
flaking a 4600-test shard, requiring a manual 10-minute rerun and an
agent triage loop) into a self-healing retry that costs one file's
runtime.
* test(approval): loosen wall-clock perf bounds 0.15s -> 2.0s
These guard against catastrophic regex backtracking (seconds-to-minutes
class), but 0.15s is within scheduler-stall noise on loaded shared CI
runners — test_max_accepted_separator_free_input_is_fast failed a CI
shard this week on runner load alone. 2.0s still catches the regression
class with zero flake surface.
* fix(ci): job timeouts everywhere + retries on all network installs
Reliability pass over every workflow:
- timeout-minutes on all 21 jobs that lacked one (a hung job previously
burned the 6-hour default runner budget)
- ./.github/actions/retry wrapped around every network-fetching install
that lacked it: pip installs (deploy-site, skills-index), npm ci
(deploy-site website, upload_to_pypi web + ui-tui), uv sync (docker
test deps). Deterministic build steps (npm run build) deliberately
NOT retried — split into separate steps so a real build failure fails
fast instead of retrying 3x.
* docs(agents): document the file-retry flake policy
* fix(ci): curl retries on deploy hook + skills-index probe
* fix(ci): kill the remaining transient-failure classes in workflows + Dockerfile
From the workflow reliability audit:
- tests.yml: duration-cache restore had NO restore-keys while saves use
run_id-suffixed keys — the cache never matched once, so LPT slicing
always ran blind and unbalanced slices pushed heavy files toward the
per-file timeout. One-line restore-keys fixes slice balancing.
- Label gates (lint ci-reviewed, supply-chain mcp-catalog-reviewed):
'gh pr view || true' turned an API blip into 'label absent' → false
BLOCKING failure. Now 3x retry, and API failure is reported as an API
failure instead of a missing label.
- detect-changes action: compare API retried before failing open (was
silently running all lanes on any blip).
- uv-lockfile-check: 'uv lock --check' resolves against PyPI — retried
so registry blips don't read as 'lockfile stale'.
- docker.yml merge job: imagetools create retried (Docker Hub eventual
consistency on just-pushed digests).
- Dockerfile: apt-get Acquire::Retries=3; s6-overlay ADDs converted to
curl --retry 3 (ADD cannot retry; checksums still enforced); npm
--fetch-retries=5; playwright chromium fetch retried 3x.
- Advisory artifact uploads (per-slice durations, ci-timings report)
get continue-on-error so an artifact-service blip can't fail a green
test slice.
* fix(tests): kill the two root-cause flakes — leaking pre-warm timer + env-dependent provider list
- test_tui_gateway_server.py: session.create / non-eager session.resume
arm a 50ms threading.Timer (_schedule_agent_build) that outlives its
test and fires into the NEXT test's _make_agent mock, racily
corrupting captured state (the recurring session_resume shard
failures). Replaced the per-test whack-a-mole stub with a module-wide
autouse fixture; the 3 worker-lifecycle tests that genuinely need the
deferred build opt back in via @pytest.mark.real_agent_prewarm (new
marker in pyproject).
- test_api_key_providers.py: PROVIDER_ENV_VARS is now derived from the
live PROVIDER_REGISTRY instead of a hand-list that had drifted
(missing HF_TOKEN / DEEPINFRA_API_KEY) — resolve_provider('auto')
tests failed on any machine with HF_TOKEN exported. E2E-verified with
HF_TOKEN/DEEPINFRA_API_KEY set: 42/42 pass.
* test: de-flake 30 timing-sensitive test files for loaded CI runners
Root-cause fixes from the flake audit (session-DB mining + repo sweep):
Event-based sync instead of sleep-sync:
- title_generator: mock sets threading.Event, wait(10) replaces
sleep(0.3) hoping the daemon thread got scheduled
- docker zombie_reaping / profile_gateway: poll-for-state helpers
replace fixed 1-3s sleeps (s6 transitions + SIGCHLD reaping are async)
- process_registry tree test: select()-bounded readline replaces an
unbounded blocking read (parent wedge now fails THIS test with a clear
message instead of an opaque rc=124 file kill); SIGTERM grace 1s->2s
(the 1s partition window mid-interpreter-startup is how a child PID
escaped the live-system guard in CI)
Timeout raises (loaded 8-way-sliced runners see ~5s scheduling floors;
all of these complete in ms-to-1s when healthy so the raises cost
nothing on green runs):
- subprocess/thread waits <= 2s raised to 10-15s across mcp_tool,
mcp_circuit_breaker, mcp_reconnect_retry_reset, mcp_parked_self_probe,
mcp_cancelled_error_propagation, registry, clarify_gateway, interrupt,
voice_cli_integration, docker_environment, session_store_lock_io,
planned_stop_watcher, cli_interrupt_subagent, thread_scoped_output
(joins now also assert not is_alive() so stragglers fail loudly)
- wall-clock discrimination ceilings loosened where the guarded hang is
10x larger: local_background_child_hang 4s->10s, interrupt_cleanup
setup 5s->20s + pgid-exit 30s->60s, mcp_stability grandchild spinup
5s->15s, protocol/gil-starvation fast-handler 0.5s->2s,
iso_certify_seam 1.5s->5s, wait_for_mcp_discovery 0.1s->1s
- narrow assertion windows widened: honcho first-turn wait 0.4..0.65 ->
0.25..2.0 (property is bounded-not-hung, not an exact wall-clock);
compression fork-lock TTL 1s->3s (12 refresh chances per lease);
compression-lock expiry margins symmetric (ttl 0.05->0.5, sleep 1.0)
- telegram hung-DNS bound 1.0->1.4 (fake hang is 1.5s — must stay under)
* fix(tests): repair indentation from de-flake batch edit
* fix(tests): harden env isolation and replace remaining sleep-sync races
The full 42k-test run and complete npm check surfaced three more classes:
- Environment isolation: local ~/.honcho defaultHost and SSH_* variables
leaked into Python/TUI tests. Pin the default Honcho host in the
hermetic fixture, isolate the one fallback test from ~/.honcho, and
blank SSH_* around terminalSetup tests. This flipped 20 false failures
back to deterministic behavior on developer machines.
- Background-thread sleep-sync: Honcho async writer tests patched
time.sleep globally, then busy-polled with that same mocked sleep. Under
full-suite load the poller could starve the writer. Each test now waits
on an Event emitted by the exact flush/retry transition; 30/30 passed
under 15-way contention.
- Desktop streaming: the test slept 80ms and assumed a 500ms timer could
not fire before its assertion. A loaded runner descheduled the test for
>500ms and both chunks arrived. Producer controls now gate second-chunk
and completion transitions explicitly.
Also make file-retry observability complete: a self-healed flaky file now
prints BOTH attempts' full output in the FLAKY summary. Two behavioral
runner tests prove pass-on-retry is green+loud+traceback-preserving, while
a deterministic failure remains red.
* refactor(ci): use gh bot pat, better retries
refactor(ci): use retry action for PR label fetch
the retry action now captures stdout as a step output, so it can serve
double duty: retry + output capture for commands like 'gh pr view' whose
result must be consumed by later steps.
Retry action gains:
- 'stdout' output (heredoc-delimited to preserve newlines)
- tee to temp file so stdout still streams to the job log
- step id 'retry' for output reference
Both lint.yml and supply-chain-audit.yml now use the retry action
directly with 'command: gh pr view ...' and read
steps.<id>.outputs.stdout.
ci: use AUTOFIX_BOT_PAT for all gh CLI / GitHub API auth
Replace secrets.GITHUB_TOKEN and github.token with
secrets.AUTOFIX_BOT_PAT across all workflows and composite actions
that use the gh CLI or GitHub API. The PAT has consistent permissions
across fork PRs (where GITHUB_TOKEN is read-only), avoids API rate
limit sharing with the default token, and is already used by
js-autofix.yml for the same reasons.
19 sites swapped across 9 files:
- lint.yml (3): label fetch, comment post/edit, comment update
- supply-chain-audit.yml (5): scan, critical comment, unbounded dep
comment, label fetch, mcp-catalog comment
- lockfile-diff.yml (1): PR comment post/update
- skills-index-freshness.yml (1): issue creation on degraded probe
- skills-index.yml (2): index build, trigger deploy workflow
- upload_to_pypi.yml (2): release view poll, release upload
- ci.yml (1): timings report
- deploy-site.yml (2): skills index crawl
- detect-changes/action.yml (1): compare API call
---------
Co-authored-by: ethernet <arilotter@gmail.com>