Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
2195 commits
Select commit Hold shift + click to select a range
1733cb3
feat(kanban): configure worktree paths and branches
aqilaziz May 19, 2026
b5c1fe7
feat(skills): add skill bundles — alias /<name> loads multiple skills…
teknium1 May 19, 2026
e382365
feat(kanban): add scheduled status for delayed follow-ups
roycepersonalassistant May 19, 2026
dfcf48b
feat(kanban): drag-to-delete trash zone + bulk delete for task cards
Jpalmer95 May 19, 2026
c4c45f1
docs: add Korean Kanban documentation
pochi-gio May 19, 2026
1912810
fix(tests): catch up six stale tests after compression/aux/kanban cha…
teknium1 May 19, 2026
6fb57bc
fix(telegram): render full clarify choice text in message body, use s…
asdlem May 18, 2026
ab11d09
chore(release): map @asdlem for PR #27852 salvage
teknium1 May 19, 2026
7fad501
fix(telegram): default streaming transport to edit
LeonSGP43 May 15, 2026
21a15b6
fix(telegram): respect reply_to_mode for DM topic reply fallback
liuhao1024 May 11, 2026
b93996c
fix(gateway): route Telegram audio file attachments away from STT pip…
Bartok9 May 13, 2026
785993b
chore(release): map bartok9 noreply for PR #24879 salvage
teknium1 May 19, 2026
edce8a5
fix(send_message): route standalone Telegram sends through TELEGRAM_P…
pepelax May 14, 2026
b58b418
chore(release): map @pepelax for PR #25419 salvage
teknium1 May 19, 2026
362ef91
fix(kanban-dashboard): restore implementations dropped during salvage…
teknium1 May 19, 2026
f55c67a
fix(gateway): roll over Telegram tool progress bubbles
Qwinty May 15, 2026
256c4c1
fix(gateway): scope audio_file_paths outside media_urls guard
teknium1 May 19, 2026
1b3c51b
fix(gateway): keep tool-progress edits alive after Telegram flood con…
erhnysr May 13, 2026
32435df
chore(release): map @erhnysr for PR #25198 salvage
teknium1 May 19, 2026
6be579f
fix(telegram): preserve can_edit after transient network errors in pr…
Bartok9 May 18, 2026
f260aa6
fix(telegram): recover from post-update polling conflict without ente…
CryptoByz May 14, 2026
17f3254
fix(test+release): update conflict retry count for MAX=5; map @CryptoByz
teknium1 May 19, 2026
fbabd56
fix(gateway): route background-process notifications into Telegram DM…
fabiosiqueira May 17, 2026
2994bf4
chore(release): map @fabiosiqueira for PR #27212 salvage
teknium1 May 19, 2026
de4cb55
fix(telegram): route resumed DM topic sends directly
Qwinty May 18, 2026
db50af9
fix(telegram): enforce TELEGRAM_ALLOWED_USERS allowlist on inbound me…
ygd58 May 11, 2026
89d3205
fix(telegram): fail-closed auth fallback when TELEGRAM_ALLOWED_USERS …
liuhao1024 May 12, 2026
8439ddc
test(telegram): stub _is_callback_user_authorized in trigger-gating f…
teknium1 May 19, 2026
5c4b43c
fix(telegram): reset sticky fallback IP on connect failure, retry pri…
falconexe May 12, 2026
e7a3e99
test+release: align stale sticky-IP test for #24511; map @falconexe
teknium1 May 19, 2026
434d508
fix(telegram): propagate extra base_url config
LeonSGP43 May 15, 2026
cf814c9
feat(send_message): auto-detect @username mentions and create Telegra…
fonhal May 18, 2026
9a444a9
test+release: align send_message mocks for MessageEntity import; map …
teknium1 May 19, 2026
ba2572e
fix(telegram): resume typing indicator after inline approval click (#…
briandevans May 18, 2026
d69f0c1
fix(gateway): mark final voice reply as notify-worthy so Telegram del…
briandevans May 18, 2026
7b2bcba
fix: avoid Telegram group reply thread session splits
eliteworkstation94-ai May 18, 2026
b46ef2e
chore(release): map @eliteworkstation94-ai for PR #28157 salvage
teknium1 May 19, 2026
f8eeb57
fix(gateway): avoid duplicate Telegram text after auto-TTS voice replies
Zyrixtrex May 16, 2026
35781ba
chore(release): map @Zyrixtrex for PR #26754 salvage
teknium1 May 19, 2026
4b6d35b
fix(telegram): escape send_slash_confirm preview with format_message
nftpoetrist May 14, 2026
bf6a287
chore(release): map @nftpoetrist for PR #25856 salvage
teknium1 May 19, 2026
af381ef
fix(telegram): retry wrapped connect timeouts
samahn0601 May 18, 2026
e19f4c1
chore(release): map @samahn0601 for PR #27887 salvage
teknium1 May 19, 2026
ed9087f
fix(tts): keep native audio outside Telegram voice delivery
aqilaziz May 15, 2026
470edfa
chore(release): map @aqilaziz for PR #26406 salvage
teknium1 May 19, 2026
ede47a5
fix(gateway): pin Telegram DM-topic routing to user's current topic
karthikeyann May 15, 2026
fc42bb9
chore(release): map @karthikeyann for PR #26609 salvage
teknium1 May 19, 2026
df530b4
fix(send_message): add thread-not-found retry for Telegram forum topi…
kunci115 May 16, 2026
2bb04f6
test(send_message): add thread-not-found retry tests for Telegram for…
kunci115 May 16, 2026
4abaec1
test(send_message): add thread-not-found retry tests for Telegram topics
kunci115 May 16, 2026
38356cc
chore(release): map @kunci115 for PR #27098 salvage
teknium1 May 19, 2026
7682198
fix(gateway): register Telegram commands for groups
chromalinx May 15, 2026
efc3740
test+release: fix test fixture for forum_commands; map @chromalinx
teknium1 May 19, 2026
46ce345
fix(telegram): gate profile bots by allowed topics
booker1207 May 13, 2026
032d4ca
chore(release): map @booker1207 for PR #25132 salvage
teknium1 May 19, 2026
a4fb0a3
fix(cron): route Telegram cron deliveries to a dedicated topic via TE…
konsisumer May 13, 2026
77c4675
fix(telegram): route image documents (.png/.jpg/.webp/.gif) through v…
kiranvk-2011 May 13, 2026
1d37860
test+release: stub auth in test_telegram_documents fixture; map @kira…
teknium1 May 19, 2026
417a653
fix(gateway): prevent Windows Telegram /restart leaving gateway stopped
May 14, 2026
c66efcf
chore(release): map @rak135 for PR #25960 salvage
teknium1 May 19, 2026
3ec28f3
fix(telegram): preserve topic metadata on overflow edits
Qwinty May 17, 2026
9d789f3
feat(telegram): add disable_topic_auto_rename gateway flag
B0Tch1 May 17, 2026
5734c3f
chore(release): map @B0Tch1 for PR #27634 salvage
teknium1 May 19, 2026
95a0955
fix(gateway): restore Telegram DM topic thread_id after session split…
jackjin1997 May 17, 2026
721d47f
chore(release): map @jackjin1997 for PR #27239 salvage
teknium1 May 19, 2026
b38140e
fix(gateway): allow chat-scoped telegram auth without sender user_id
soynchux May 18, 2026
15e89e1
chore(release): map @soynchux for PR #27806 salvage
teknium1 May 19, 2026
16d8e44
fix(telegram): add DM topic typing fallback when message_thread_id re…
fonhal May 18, 2026
d81b888
fix(telegram): report cron topic fallback
el-analista May 14, 2026
4f6fef1
chore(release): map @el-analista for PR #25368 salvage
teknium1 May 19, 2026
1891bee
fix(telegram): wire gt: callback dispatch for gmail-triage buttons
khungate May 14, 2026
e80d308
chore(release): map @khungate for PR #25829 salvage
teknium1 May 19, 2026
b1acf80
feat(telegram): support quick-command-only menus
stevehq26-bot May 18, 2026
17b8121
chore(release): map @stevehq26-bot for PR #28015 salvage
teknium1 May 19, 2026
704872a
fix(telegram): handle channel post updates
brndnsvr May 14, 2026
84a9b81
test: address telegram channel post review
brndnsvr May 14, 2026
f1cefad
test+release: stub auth in channel_posts fixture; map @brndnsvr
teknium1 May 19, 2026
8a80eee
Quiet noisy Telegram gateway errors
May 11, 2026
bb8e9ea
chore(release): map oracle@jarviss-mbp.home for PR #24014 salvage
teknium1 May 19, 2026
ce4d857
Route Telegram multi-bot mentions exclusively
OCWC22 May 12, 2026
e90869e
Document Telegram multi-profile gateway commands
OCWC22 May 12, 2026
fbfe294
fix: ignore Telegram messages for other bots
OCWC22 May 14, 2026
da48be1
chore(release): map @OCWC22 for PR #24581 salvage
teknium1 May 19, 2026
c931dad
feat(telegram): ignore_root_dm with system command lobby
ai-hana-ai May 12, 2026
6d66ad2
docs(telegram): document ignore_root_dm feature
ai-hana-ai May 12, 2026
ce46e6b
chore(release): map @ai-hana-ai for PR #23928 salvage
teknium1 May 19, 2026
a724c3b
feat(telegram): pin incoming user message for duration of agent turn
indigokarasu May 15, 2026
6265b3a
chore(release): map @indigokarasu for PR #26636 salvage
teknium1 May 19, 2026
ad2531b
feat(telegram): skip-STT audio path + 2GB cap via local Bot API server
alber70g May 13, 2026
69b1d31
chore(release): map @alber70g for PR #25280 salvage
teknium1 May 19, 2026
1335ce9
fix(web): add scheduled column to i18n type definitions (#28549)
alt-glitch May 19, 2026
eacce70
docs: comprehensive 2-week sweep of feature/PR coverage gaps (#28497)
teknium1 May 19, 2026
6dd0b35
chore(release): pre-stage AUTHOR_MAP for May 2026 LHF batch group 9 (…
teknium1 May 19, 2026
7f40767
feat(signal): add require_mention filter for group chats
jdelmerico May 19, 2026
03f7bc0
Revert "feat(telegram): pin incoming user message for duration of age…
teknium1 May 19, 2026
22120ef
Revert "feat(telegram): support quick-command-only menus"
teknium1 May 19, 2026
bbd2b46
Revert "feat(send_message): auto-detect @username mentions and create…
teknium1 May 19, 2026
4d44304
Revert "fix(telegram): enforce TELEGRAM_ALLOWED_USERS allowlist on in…
teknium1 May 19, 2026
e2a1a2b
fix(gateway): pre-mark sessions as resume_pending before drain to pre…
LifeJiggy May 19, 2026
276e6cc
fix(matrix): implement thread_require_mention to prevent multi-agent …
justemu May 19, 2026
8c3b065
fix(cli): show active profile in TUI prompt
burjorjee May 19, 2026
6cac56f
fix(tui): preserve dunder identifiers in markdown
helix4u May 19, 2026
0b89628
test(file_ops): add regression tests for git baseline warning in writ…
LifeJiggy May 19, 2026
afffb8d
fix(dashboard): use browser scrollback for chat wheel
YuanHanzhong May 19, 2026
425aba7
fix(cli): ignore stale HERMES_TUI_RESUME env
noctilust May 19, 2026
663ee14
fix(cron): allow emoji ZWJ sequences in prompts
outsourc-e May 19, 2026
b8a9cbd
fix: tolerate unreadable gateway JSONL transcripts
MoonJuhan May 19, 2026
87c6edc
fix(skills): add timeout to Google OAuth urlopen calls
Zyrixtrex May 19, 2026
d759a67
fix: add recovery hints to loop guard warnings
ooovenenoso May 19, 2026
62573f4
fix: guard yaml.safe_load, flock unlock, TOCTOU races, and atomic writes
vanthinh6886 May 19, 2026
4039e2a
chore(release): alias xxxigm noreply for upcoming #27986 salvage (#28…
teknium1 May 19, 2026
12c3983
fix(doctor): attach codex CLI hint to OpenAI Codex auth warning for #…
xxxigm May 19, 2026
a0bd11d
fix(tests): catch up 25 stale tests after recent merges (#28626)
teknium1 May 19, 2026
aedb8ac
feat(update): syntax-validate critical files post-pull, auto-rollback…
teknium1 May 19, 2026
58591d9
feat: show names of user-modified skills in bundled skill sync summary
zccyman May 19, 2026
7f253f5
fix(acp): use tempfile.gettempdir() in workspace auto-approve
Zyrixtrex May 19, 2026
88ee58f
fix(kanban): stale reclaim must not tick failure counter (#28680)
teknium1 May 19, 2026
a3c7531
fix(telegram): address post-merge audit follow-ups (#28670, #28672, #…
teknium1 May 19, 2026
424f2cc
chore(actions)(deps): bump the actions-minor-patch group across 1 dir…
dependabot[bot] May 18, 2026
2692457
chore(actions)(deps): bump docker/login-action from 3.7.0 to 4.1.0
dependabot[bot] May 13, 2026
bbee1dd
chore(actions)(deps): bump docker/build-push-action from 6.19.2 to 7.1.0
dependabot[bot] May 8, 2026
b10b783
chore(actions)(deps): bump actions/setup-python from 5.3.0 to 6.2.0
dependabot[bot] May 15, 2026
7bcdced
fix(kanban): respawn guard defers blocker_auth instead of auto-blocki…
teknium1 May 19, 2026
c498116
chore(actions)(deps): bump actions/checkout from 4.3.1 to 6.0.2
dependabot[bot] May 18, 2026
709e37e
fix(dashboard): add scheduled kanban i18n strings (#28534)
helix4u May 19, 2026
f1254b1
fix(cli): exit prompt_toolkit cleanly on SIGTERM/SIGHUP instead of ra…
teknium1 May 19, 2026
fc90f1b
chore(deps): bump dompurify from 3.3.3 to 3.4.2 in /website
dependabot[bot] May 5, 2026
0b75d24
chore(deps): bump follow-redirects from 1.15.11 to 1.16.0 in /website
dependabot[bot] May 5, 2026
c4bcc77
chore(deps): bump lodash from 4.17.23 to 4.18.1 in /website
dependabot[bot] May 5, 2026
7f1d124
chore(deps): bump lodash-es and langium in /website
dependabot[bot] May 5, 2026
dffcb6f
chore(deps): bump python-multipart from 0.0.22 to 0.0.27
dependabot[bot] May 6, 2026
a9c38c7
chore(deps): bump python-dotenv from 1.2.1 to 1.2.2
dependabot[bot] May 12, 2026
43f8edb
chore(deps): bump fast-uri from 3.1.0 to 3.1.2 in /website
dependabot[bot] May 8, 2026
070eeaa
chore(deps): bump @babel/plugin-transform-modules-systemjs in /website
dependabot[bot] May 9, 2026
ff0a703
fix(web): consume bundled design system assets (#26391)
austinpickett May 19, 2026
ec244e5
chore(deps): bump webpack-dev-server from 5.2.3 to 5.2.4 in /website …
dependabot[bot] May 19, 2026
a3ef217
chore(deps): bump ws in /ui-tui/packages/hermes-ink (#28183)
dependabot[bot] May 19, 2026
39c41d0
chore(deps): bump mermaid from 11.13.0 to 11.15.0 in /website (#24011)
dependabot[bot] May 19, 2026
7c622b6
fix(kanban): migrate task session index after columns
quocanh261997 May 19, 2026
7552e0f
fix(kanban): also hoist idx_events_run + drop redundant inner create
kshitijk4poor May 19, 2026
5a33176
fix(discord): define view classes after lazy discord.py install
EloquentBrush0x May 19, 2026
b0af1d0
Merge pull request #28829 from NousResearch/bb/tui-no-history-truncation
OutThisLife May 19, 2026
ae74b15
chore: add erikengervall to AUTHOR_MAP (#28855)
daimon-nous[bot] May 19, 2026
273ff5c
feat(firecrawl): add integration tag for Hermes usage in browser and …
erikengervall May 19, 2026
ebe0b77
fix(model-switch): mark bare custom provider as current
LeonSGP43 May 11, 2026
57af46f
Revert "feat(firecrawl): add integration tag for Hermes usage in brow…
teknium1 May 19, 2026
2a7308b
fix(update): quarantine hermes.exe vs concurrent Windows instance (#2…
teknium1 May 19, 2026
f007ef8
fix(windows): hide cron script subprocess consoles
nekwo May 16, 2026
95683c0
fix(windows): hide local subprocess consoles
nekwo May 16, 2026
d948de3
fix(gateway): harden Windows gateway install lifecycle
nekwo May 16, 2026
a19eb54
test(gateway-windows): make ctypes.windll monkeypatch tolerant on non…
teknium1 May 19, 2026
a614209
fix(agent): set tool_name on tool-result messages at construction time
justinccdev May 19, 2026
7c2ff74
fix(tui): termux-gate scrollback preservation, touch-friendly defaults
adybag14-cyber May 19, 2026
2b41f9d
Merge pull request #28914 from justincc/fix/fix-blank-tool-names-at-m…
ethernet8023 May 19, 2026
57145ca
feat: add BrowseShSource adapter for browse.sh skills catalog
Kylejeong2 May 19, 2026
90be1be
fix: register browse-sh in per-source limits and --source choices
Kylejeong2 May 19, 2026
890b2eb
fix(browse-sh): fetch SKILL.md via /api/skills/{slug}+skillMdUrl
teknium1 May 19, 2026
3f55256
docs(skills): document browse.sh source (#28939)
teknium1 May 19, 2026
e13f242
fix(cli): preserve setup config picker writes
stepanov1975 May 8, 2026
6a159be
fix(runtime): treat 'ollama'/'vllm'/'llamacpp' aliases like 'custom' …
teknium1 May 19, 2026
784febe
perf(cli): defer openai._base_client import via sys.meta_path finder …
teknium1 May 19, 2026
544c31b
perf(agent-loop): cut 47% of per-conversation function calls via 3 ta…
teknium1 May 19, 2026
aa4e492
chore(deps): bump ws from 8.20.0 to 8.20.1 in /ui-tui
dependabot[bot] May 19, 2026
60bb98e
fix(install.ps1): pin PortableGit instead of hitting rate-limited Git…
teknium1 May 19, 2026
d9829ab
fix(model): match custom provider by active base url
helix4u May 19, 2026
28ab420
🐛 fix(cli): handle no-remote worktree cleanup
BlackishGreen33 Apr 17, 2026
c9d5ef2
🐛 fix(cli): handle missing remote tracking refs
BlackishGreen33 Apr 17, 2026
64a9a19
fix(xai-oauth): pin inference base_url to x.ai origin (#28952)
teknium1 May 19, 2026
34120a0
fix(kanban): worker-initiated block must not be auto-promoted (#28712)
xxxigm May 19, 2026
d35f893
test(kanban): cover sticky blocks for worker-initiated kanban_block (…
xxxigm May 19, 2026
448a3f9
fix(discord): transcribe native voice notes
helix4u May 19, 2026
93734c2
fix(dingtalk): transcribe native voice notes
teknium1 May 20, 2026
6cb9917
perf(compression): defer feasibility check to first compression attem…
teknium1 May 20, 2026
e2fd462
ci(tests): add pytest-timeout 60s hard cap to break suite-teardown de…
teknium1 May 20, 2026
a0c0312
feat(web): add xAI Web Search provider plugin
Jaaneek May 20, 2026
6bd4311
perf(terminal): adaptive subprocess poll cuts ~195ms off every tool c…
teknium1 May 20, 2026
43c7a1b
docs(web-search): document xAI Web Search backend (#29052)
teknium1 May 20, 2026
05f0264
fix(windows): drop duplicate creationflags kwarg in LocalEnvironment.…
jquesnelle May 20, 2026
c2a4782
fix(nix): refresh npm lockfile hashes
github-actions[bot] May 20, 2026
62713c8
Merge pull request #29059 from NousResearch/jq/fix-windows-creationfl…
jquesnelle May 20, 2026
0c6eb96
Merge pull request #28947 from NousResearch/dependabot/npm_and_yarn/u…
ethernet8023 May 20, 2026
340d2b6
docs(xai-oauth): note X Premium+ also unlocks Grok OAuth (#29055)
teknium1 May 20, 2026
697d38a
feat: auto-launch Chromium-family browser for CDP
H-Ali13381 May 8, 2026
6a6766f
test(cli): cover Brave binary CDP launch detection
H-Ali13381 May 19, 2026
5e74355
fix(lint): skip per-file shell linter when LSP will handle the file (…
OutThisLife May 20, 2026
2589656
fix(chat_completions): strip tool_name from messages for strict provi…
savanne-kham May 19, 2026
42c4288
fix(chat_completions): broaden tool_name strip docstring + AUTHOR_MAP
teknium1 May 20, 2026
edb2d91
feat(web): migrate dashboard checkboxes to @nous-research/ui + DS pol…
austinpickett May 20, 2026
6f3a020
feat(xai): detect retired xAI models (May 15, 2026)
julientalbot-ergonomia May 10, 2026
b4ba425
feat(doctor): surface xAI model retirement in hermes doctor
julientalbot-ergonomia May 10, 2026
a8a05c8
feat(cli): warn about retired xAI models at chat startup
julientalbot-ergonomia May 10, 2026
9ff98da
feat(xai): apply_migration — rewrite config.yaml in-place via ruamel …
julientalbot-ergonomia May 10, 2026
12842d3
feat(cli): hermes migrate xai [--apply] [--no-backup]
julientalbot-ergonomia May 10, 2026
5af4b73
fix(xai): align migrate retirement map with docs
Julientalbot May 10, 2026
ca192cf
Add opt-in xAI TTS speech tag pauses
julientalbot-ergonomia Apr 28, 2026
c29b4f5
perf(termux): speed up tui cold start
adybag14-cyber May 20, 2026
ce26785
refactor(session-log): delete _save_session_log and all callers
yoniebans May 20, 2026
c547392
refactor(session-log): stop initializing session_log_file attribute
yoniebans May 20, 2026
cebd480
refactor(session-log): drop branch/compress re-point of session_log_file
yoniebans May 20, 2026
9d793e8
docs(session-log): state.db is canonical; ~/.hermes/sessions/ is legacy
yoniebans May 20, 2026
6f1a5f8
refactor(session-log): delete dead _clean_session_content helper
yoniebans May 20, 2026
b6c6f65
test(session-log): pin no-session_json regression + drop trailing whi…
yoniebans May 20, 2026
6fc1989
chore(release): correct AUTHOR_MAP for jonny@nousresearch.com
teknium1 May 20, 2026
eeb747d
feat(sessions): opt-in per-session JSON snapshot writer
teknium1 May 20, 2026
88f5186
fix(tui): anchor splitReasoning unclosed-tag regex to start of input …
OutThisLife May 20, 2026
27506cc
chore(deps): bump ws from 8.20.0 to 8.20.1 in /scripts/whatsapp-bridg…
dependabot[bot] May 20, 2026
fee8810
chore(deps): bump protobufjs in /scripts/whatsapp-bridge (#28889)
dependabot[bot] May 20, 2026
419910e
chore(deps): bump idna from 3.11 to 3.15 (#28883)
dependabot[bot] May 20, 2026
57a6105
fix(deps): bump pydantic to 2.13.4 to avoid pydantic-core thread segf…
fabiosiqueira May 20, 2026
4d2df86
docs(skills): clarify external dir mutations
helix4u May 20, 2026
1d27be0
test(gateway): pin SQLite-only load_transcript behaviour
yoniebans May 20, 2026
024a8e3
refactor(gateway): drop JSONL fallback in load_transcript
yoniebans May 20, 2026
971cfaa
refactor(yuanbao): migrate recall to load_transcript()
yoniebans May 20, 2026
351fdcc
refactor(gateway): stop writing JSONL in append_to_transcript / rewri…
yoniebans May 20, 2026
b4b118c
refactor(gateway): drop _append_to_jsonl from mirror
yoniebans May 20, 2026
33a3cf5
docs(sessions): state.db is canonical for gateway messages
yoniebans May 20, 2026
c634c07
test(gateway): pin DEFAULT_DB_PATH in fixtures to prevent real state.…
yoniebans May 20, 2026
0cc1a1d
refactor(yuanbao): drop dead branch A1 message_id loop + pin missing …
yoniebans May 20, 2026
31a0100
feat(state.db): persist platform_message_id; restore yuanbao exact-id…
teknium1 May 20, 2026
2a352f9
fix(x_search): surface degraded results + validate dates
kshitijk4poor May 20, 2026
1a7bb98
fix(gateway): harden kanban and provider cleanup races
helix4u May 20, 2026
3ce1cf2
Merge pull request #29484 from kshitijk4poor/kp/x-search-degraded-flag
kshitijk4poor May 20, 2026
fc7e04e
fix(skills-hub): deduplicate search results by identifier, not name
EloquentBrush0x May 20, 2026
8f92327
fix(skills-hub): fix dedup in browse_skills() programmatic API
EloquentBrush0x May 20, 2026
c6a380e
fix(skills-hub): widen identifier-dedup to GitHubSource + fix test pa…
teknium1 May 20, 2026
c42edd8
fix(tui): clipboard copy on linux/wayland
May 11, 2026
f7441f9
fix(nix): add xclip and wl-copy
ethernet8023 May 20, 2026
1566d71
Merge pull request #29342 from NousResearch/fix/tui-linux-copy
ethernet8023 May 21, 2026
b9b6e03
fix(gateway): prioritize Telegram command menu
helix4u May 20, 2026
5672772
fix(gateway): reorder telegram menu priority — everyday commands first
teknium1 May 21, 2026
5908822
fix(security): prevent API key leakage to non-authoritative custom en…
erhnysr May 19, 2026
9514ddb
fix(security): address review feedback from pmos69
erhnysr May 20, 2026
c6a992e
fix(security): derive <VENDOR>_API_KEY from host as final credential …
teknium1 May 21, 2026
a9db0e2
Observe unmentioned Telegram group messages
Glucksberg May 20, 2026
3bbe980
chore: add Glucksberg to AUTHOR_MAP
teknium1 May 21, 2026
4ead464
fix(security): guard os.chmod(parent) against / and top-level dirs
liuhao1024 May 14, 2026
127b56a
style: docstring + whitespace cleanup on secure_parent_dir
teknium1 May 21, 2026
b4afc65
fix(xai): restore encrypted reasoning replay across turns
teknium1 May 21, 2026
24c7ce0
feat(agent): allow declaring supports_vision via user config
CNSeniorious000 Apr 30, 2026
1c76689
fix(agent): resolve supports_vision override for named custom providers
CNSeniorious000 Apr 30, 2026
32aea11
fix(agent): consult supports_vision override in auto-mode routing
teknium1 May 21, 2026
975e130
fix(cli): honour image-routing decision in quiet-mode -q --image path
teknium1 May 21, 2026
be0728c
fix: handle Discord typing indicator 429 gracefully
nycomar May 9, 2026
f722ec7
chore: add nycomar to AUTHOR_MAP
teknium1 May 21, 2026
5edb346
security(file-safety): also write-deny <root>/.env when running under…
0xsir0000 Apr 28, 2026
6c26727
fix(gateway): extend observe+attribution to location and media handlers
EloquentBrush0x May 21, 2026
c3a09f7
fix(background_review): propagate parent toolset config to keep tools…
ziliangpeng May 21, 2026
87d9239
chore: trim verbose comments/docstrings, add AUTHOR_MAP entry
alt-glitch May 21, 2026
48be2e0
test: use subprocesses for each test file (#29016)
ethernet8023 May 21, 2026
bf8627b
chore: sync with upstream main (2026-05-21)
May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 45 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,24 @@ concurrency:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y ripgrep
- name: Install ripgrep (prebuilt binary)
run: |
set -euo pipefail
RG_VERSION=15.1.0
RG_SHA256=1c9297be4a084eea7ecaedf93eb03d058d6faae29bbc57ecdaf5063921491599
RG_TARBALL=ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl.tar.gz
curl -sSfL -o "$RG_TARBALL" \
"https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${RG_TARBALL}"
echo "${RG_SHA256} ${RG_TARBALL}" | sha256sum -c -
tar -xzf "$RG_TARBALL"
sudo mv "ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl/rg" /usr/local/bin/rg
rm -rf "$RG_TARBALL" "ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl"
rg --version

- name: Install uv
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5
Expand All @@ -44,9 +55,26 @@ jobs:
uv pip install -e ".[all,dev]"

- name: Run tests
# Per-file isolation via scripts/run_tests_parallel.py: discovers
# every test_*.py file under tests/ (excluding integration/ + e2e/),
# then runs `python -m pytest <file>` in a freshly-spawned subprocess
# with bounded parallelism. No xdist, no shared workers, no
# module-level state leakage between files.
#
# Why per-file (not per-test): per-test spawn cost (~250ms × 17k
# tests = 70min CPU minimum) blew the wall-clock budget. Per-file
# spawn (~250ms × ~850 files = ~3.5min) fits while still giving
# every file a fresh interpreter — the only isolation boundary
# that matters in practice (cross-file leakage was the original
# flake source; intra-file is the test author's responsibility).
#
# Why drop xdist entirely: xdist's persistent workers accumulate
# state across files, which is exactly the leakage we wanted to
# fix. ThreadPoolExecutor + subprocess.run is ~60 lines and does
# the job with cleaner semantics.
run: |
source .venv/bin/activate
python -m pytest tests/ -q --ignore=tests/integration --ignore=tests/e2e --tb=short -n auto --timeout=30 --timeout-method=signal
python scripts/run_tests_parallel.py
env:
# Ensure tests don't accidentally call real APIs
OPENROUTER_API_KEY: ""
Expand All @@ -60,8 +88,19 @@ jobs:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y ripgrep
- name: Install ripgrep (prebuilt binary)
run: |
set -euo pipefail
RG_VERSION=15.1.0
RG_SHA256=1c9297be4a084eea7ecaedf93eb03d058d6faae29bbc57ecdaf5063921491599
RG_TARBALL=ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl.tar.gz
curl -sSfL -o "$RG_TARBALL" \
"https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${RG_TARBALL}"
echo "${RG_SHA256} ${RG_TARBALL}" | sha256sum -c -
tar -xzf "$RG_TARBALL"
sudo mv "ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl/rg" /usr/local/bin/rg
rm -rf "$RG_TARBALL" "ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl"
rg --version

- name: Install uv
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ __pycache__/web_tools.cpython-310.pyc
logs/
data/
.pytest_cache/
.pytest-cache/
tmp/
temp_vision_images/
hermes-*/*
Expand Down
44 changes: 36 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1013,17 +1013,39 @@ def profile_env(tmp_path, monkeypatch):

**ALWAYS use `scripts/run_tests.sh`** — do not call `pytest` directly. The script enforces
hermetic environment parity with CI (unset credential vars, TZ=UTC, LANG=C.UTF-8,
4 xdist workers matching GHA ubuntu-latest). Direct `pytest` on a 16+ core
developer machine with API keys set diverges from CI in ways that have caused
multiple "works locally, fails in CI" incidents (and the reverse).
`-n auto` xdist workers, in-tree subprocess-isolation plugin). Direct `pytest`
on a 16+ core developer machine with API keys set diverges from CI in ways
that have caused multiple "works locally, fails in CI" incidents (and the reverse).

```bash
scripts/run_tests.sh # full suite, CI-parity
scripts/run_tests.sh tests/gateway/ # one directory
scripts/run_tests.sh tests/agent/test_foo.py::test_x # one test
scripts/run_tests.sh -v --tb=long # pass-through pytest flags
scripts/run_tests.sh --no-isolate tests/foo/ # disable subprocess isolation (faster, for debugging)
```

### Subprocess-per-test isolation

Every test runs in a freshly-spawned Python subprocess via the in-tree plugin
at `tests/_isolate_plugin.py`. This means module-level dicts/sets and
ContextVars from one test cannot leak into the next — the historic
`_reset_module_state` autouse fixture is gone.

Implementation notes:

- The plugin uses `multiprocessing.get_context("spawn")`, which works on
Linux, macOS, and Windows alike (POSIX `fork` is not used).
- Per-test overhead is ~0.5–1.0s (Python startup + pytest collection). xdist
parallelism amortizes this across cores; on a 20-core box the full suite
finishes in roughly the same wall time as before, but flake-free.
- `isolate_timeout` (configured in `pyproject.toml`) caps each test at 30s.
Hangs are killed and surfaced as a failure report.
- Pass `--no-isolate` to disable isolation — useful when debugging a single
test interactively, or when you specifically want to verify state leakage.
- The plugin disables itself in child processes (sentinel envvar
`HERMES_ISOLATE_CHILD=1`), so there's no fork-bomb risk.

### Why the wrapper (and why the old "just call pytest" doesn't work)

Five real sources of local-vs-CI drift the script closes:
Expand All @@ -1034,23 +1056,29 @@ Five real sources of local-vs-CI drift the script closes:
| HOME / `~/.hermes/` | Your real config+auth.json | Temp dir per test |
| Timezone | Local TZ (PDT etc.) | UTC |
| Locale | Whatever is set | C.UTF-8 |
| xdist workers | `-n auto` = all cores (20+ on a workstation) | `-n 4` matching CI |
| xdist workers | `-n auto` = all cores | `-n auto` (safe — subprocess isolation prevents cross-worker flakes) |

`tests/conftest.py` also enforces points 1-4 as an autouse fixture so ANY pytest
invocation (including IDE integrations) gets hermetic behavior — but the wrapper
is belt-and-suspenders.

### Running without the wrapper (only if you must)

If you can't use the wrapper (e.g. on Windows or inside an IDE that shells
pytest directly), at minimum activate the venv and pass `-n 4`:
If you can't use the wrapper (e.g. inside an IDE that shells pytest directly),
at minimum activate the venv. The isolation plugin loads automatically from
`addopts` in `pyproject.toml`, so you get the same per-test process isolation
either way.

```bash
source .venv/bin/activate # or: source venv/bin/activate
python -m pytest tests/ -q -n 4
python -m pytest tests/ -q
```

Worker count above 4 will surface test-ordering flakes that CI never sees.
If you need to bypass isolation for fast feedback while debugging:

```bash
python -m pytest tests/agent/test_foo.py -q --no-isolate
```

Always run the full suite before pushing changes.

Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ hermes-agent/
| `~/.hermes/skills/` | All active skills (bundled + hub-installed + agent-created) |
| `~/.hermes/memories/` | Persistent memory (MEMORY.md, USER.md) |
| `~/.hermes/state.db` | SQLite session database |
| `~/.hermes/sessions/` | JSON session logs |
| `~/.hermes/sessions/` | Gateway routing index (`sessions.json`), request-dump breadcrumbs, gateway `*.jsonl` transcripts, and (optionally) per-session JSON snapshots when `sessions.write_json_snapshots: true` is set. The per-session snapshots are off by default; state.db is canonical. |
| `~/.hermes/cron/` | Scheduled job data |
| `~/.hermes/whatsapp/session/` | WhatsApp bridge credentials |

Expand Down Expand Up @@ -239,7 +239,7 @@ User message → AIAgent._run_agent_loop()

- **Self-registering tools**: Each tool file calls `registry.register()` at import time. `model_tools.py` triggers discovery by importing all tool modules.
- **Toolset grouping**: Tools are grouped into toolsets (`web`, `terminal`, `file`, `browser`, etc.) that can be enabled/disabled per platform.
- **Session persistence**: All conversations are stored in SQLite (`hermes_state.py`) with full-text search and unique session titles. JSON logs go to `~/.hermes/sessions/`.
- **Session persistence**: All conversations are stored in SQLite (`hermes_state.py`) with full-text search and unique session titles. Per-session JSON snapshots in `~/.hermes/sessions/` were superseded by the SQLite store and are off by default; opt back in with `sessions.write_json_snapshots: true` if you have external tooling that consumes the JSON files directly.
- **Ephemeral injection**: System prompts and prefill messages are injected at API call time, never persisted to the database or logs.
- **Provider abstraction**: The agent works with any OpenAI-compatible API. Provider resolution happens at init time (Nous Portal OAuth, OpenRouter API key, or custom endpoint).
- **Provider routing**: When using OpenRouter, `provider_routing` in config.yaml controls provider selection (sort by throughput/latency/price, allow/ignore specific providers, data retention policies). These are injected as `extra_body.provider` in API requests.
Expand Down
14 changes: 13 additions & 1 deletion agent/agent_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,19 @@ def init_agent(
hermes_home = get_hermes_home()
agent.logs_dir = hermes_home / "sessions"
agent.logs_dir.mkdir(parents=True, exist_ok=True)
agent.session_log_file = agent.logs_dir / f"session_{agent.session_id}.json"
# Per-session JSON snapshot writer (~/.hermes/sessions/session_{sid}.json)
# is opt-in via sessions.write_json_snapshots (default False). state.db
# is canonical — the snapshot is only useful for external tooling that
# reads the JSON files directly. See run_agent._save_session_log.
agent._session_json_enabled = False
try:
from hermes_cli.config import load_config as _load_sess_cfg
_sess_cfg = (_load_sess_cfg().get("sessions") or {})
agent._session_json_enabled = bool(_sess_cfg.get("write_json_snapshots", False))
except Exception:
pass
# logs_dir is retained unconditionally for request_dump_*.json (debug
# breadcrumb path written by agent_runtime_helpers.dump_api_request_debug).

# Track conversation messages for session logging
agent._session_messages: List[Dict[str, Any]] = []
Expand Down
117 changes: 66 additions & 51 deletions agent/agent_runtime_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1869,49 +1869,92 @@ def copy_reasoning_content_for_api(agent, source_msg: dict, api_msg: dict) -> No



def cleanup_dead_connections(agent) -> bool:
"""Detect and clean up dead TCP connections on the primary client.
def _iter_pool_sockets(client: Any):
"""Yield raw sockets reachable from an OpenAI/httpx client pool.

Inspects the httpx connection pool for sockets in unhealthy states
(CLOSE-WAIT, errors). If any are found, force-closes all sockets
and rebuilds the primary client from scratch.

Returns True if dead connections were found and cleaned up.
httpcore 1.x stores the concrete HTTP11/HTTP2 connection under
``conn._connection``; older versions exposed stream attributes directly
on the pool entry. Keep the traversal defensive because these are private
transport internals and vary across httpx/httpcore releases.
"""
client = getattr(agent, "client", None)
if client is None:
return False
try:
http_client = getattr(client, "_client", None)
if http_client is None:
return False
return
transport = getattr(http_client, "_transport", None)
if transport is None:
return False
return
pool = getattr(transport, "_pool", None)
if pool is None:
return False
return
connections = (
getattr(pool, "_connections", None)
or getattr(pool, "_pool", None)
or []
)
dead_count = 0
for conn in list(connections):
# Check for connections that are idle but have closed sockets
except Exception:
return

seen: set[int] = set()
for conn in list(connections):
candidates = [conn]
inner = getattr(conn, "_connection", None)
if inner is not None:
candidates.append(inner)
for candidate in candidates:
stream = (
getattr(conn, "_network_stream", None)
or getattr(conn, "_stream", None)
getattr(candidate, "_network_stream", None)
or getattr(candidate, "_stream", None)
)
if stream is None:
continue
sock = getattr(stream, "_sock", None)
if sock is None:
sock = getattr(stream, "stream", None)
if sock is not None:
sock = getattr(sock, "_sock", None)
get_extra_info = getattr(stream, "get_extra_info", None)
if callable(get_extra_info):
try:
sock = get_extra_info("socket")
except Exception:
sock = None
if sock is None:
wrapped = getattr(stream, "stream", None)
if wrapped is not None:
sock = getattr(wrapped, "_sock", None)
if sock is None:
# anyio-backed streams expose the raw socket through
# SocketAttribute.raw_socket when available.
wrapped = getattr(stream, "_stream", None)
extra = getattr(wrapped, "extra", None)
if callable(extra):
try:
from anyio.abc import SocketAttribute
sock = extra(SocketAttribute.raw_socket)
except Exception:
sock = None
if sock is None:
continue
marker = id(sock)
if marker in seen:
continue
seen.add(marker)
yield sock


def cleanup_dead_connections(agent) -> bool:
"""Detect and clean up dead TCP connections on the primary client.

Inspects the httpx connection pool for sockets in unhealthy states
(CLOSE-WAIT, errors). If any are found, force-closes all sockets
and rebuilds the primary client from scratch.

Returns True if dead connections were found and cleaned up.
"""
client = getattr(agent, "client", None)
if client is None:
return False
try:
dead_count = 0
for sock in _iter_pool_sockets(client):
# Probe socket health with a non-blocking recv peek
import socket as _socket
try:
Expand Down Expand Up @@ -2087,36 +2130,7 @@ def force_close_tcp_sockets(client: Any) -> int:

closed = 0
try:
http_client = getattr(client, "_client", None)
if http_client is None:
return 0
transport = getattr(http_client, "_transport", None)
if transport is None:
return 0
pool = getattr(transport, "_pool", None)
if pool is None:
return 0
# httpx uses httpcore connection pools; connections live in
# _connections (list) or _pool (list) depending on version.
connections = (
getattr(pool, "_connections", None)
or getattr(pool, "_pool", None)
or []
)
for conn in list(connections):
stream = (
getattr(conn, "_network_stream", None)
or getattr(conn, "_stream", None)
)
if stream is None:
continue
sock = getattr(stream, "_sock", None)
if sock is None:
sock = getattr(stream, "stream", None)
if sock is not None:
sock = getattr(sock, "_sock", None)
if sock is None:
continue
for sock in _iter_pool_sockets(client):
try:
sock.shutdown(_socket.SHUT_RDWR)
except OSError:
Expand Down Expand Up @@ -2154,5 +2168,6 @@ def force_close_tcp_sockets(client: Any) -> int:
"cleanup_dead_connections",
"extract_api_error_context",
"apply_pending_steer_to_tool_results",
"_iter_pool_sockets",
"force_close_tcp_sockets",
]
5 changes: 5 additions & 0 deletions agent/background_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ def _bg_review_auto_deny(command, description, **kwargs):
# parent below so memory(action="add") writes from
# the review still land on disk; the review just
# has zero side effects on external providers.
# Match parent's toolset config so ``tools[]`` is byte-identical
# in the request body — Anthropic's cache key includes it.
# (The runtime whitelist below still restricts dispatch.)
review_agent = AIAgent(
model=agent.model,
max_iterations=16,
Expand All @@ -401,6 +404,8 @@ def _bg_review_auto_deny(command, description, **kwargs):
api_key=_parent_runtime.get("api_key") or None,
credential_pool=getattr(agent, "_credential_pool", None),
parent_session_id=agent.session_id,
enabled_toolsets=getattr(agent, "enabled_toolsets", None),
disabled_toolsets=getattr(agent, "disabled_toolsets", None),
skip_memory=True,
)
review_agent._memory_write_origin = "background_review"
Expand Down
Loading
Loading