Sync upstream NousResearch → fork (2026-06-13, 773 commits) - #10
Closed
Fede654 wants to merge 774 commits into
Closed
Sync upstream NousResearch → fork (2026-06-13, 773 commits)#10Fede654 wants to merge 774 commits into
Fede654 wants to merge 774 commits into
Conversation
…MES_HOME (NousResearch#44267) * fix(gateway): refuse to write service definitions with a temp-dir HERMES_HOME A test/E2E harness that exports HERMES_HOME=/tmp/... and touches any gateway service write path (install, start self-heal, restart's refresh_systemd_unit_if_needed) bakes the throwaway home into the production systemd unit / launchd plist. The gateway then restarts 'healthy' but pointed at an empty temp home — no platforms enabled, deaf to every message (live incident 2026-06-11: /tmp/hermes-e2e-41264 poisoned the unit during a PR-review E2E probe; the post-update restart produced a 7-hour zombie gateway). The existing safety belt only sniffed pytest-shaped markers (/pytest-of-, /hermes_test). Add a structural guard: _temp_home_in_service_definition() extracts HERMES_HOME from the generated systemd unit or launchd plist and refuses the write (with actionable guidance) when it resolves under tempfile.gettempdir(), /tmp, /var/tmp, or the macOS /private variants. Wired into all five write sites: systemd refresh + install, launchd refresh + install + start self-heal. * test: patch unit generator in install tests tripped by temp-home guard CI runs hermetic with HERMES_HOME under a tmp dir, so the real generate_systemd_unit() output now (correctly) trips the new temp-home write guard in three install tests. Patch the generator with synthetic non-temp content — same pattern the existing pytest-marker guard tests use.
…or in cron modals (NousResearch#44231) Headless/VPS users (dashboard-over-Tailscale, no comfortable SSH) could list/toggle/install skills and create/edit cron jobs, but not author a custom skill or link one to a cron job — the UI set WHEN a job runs, but not WHICH skill it uses. - Skills page: 'New skill' button + per-row edit pencil open a SKILL.md editor dialog (frontmatter + body, server-side validation via the same _create_skill/_edit_skill path as the agent's skill_manage tool). - New endpoints: GET /api/skills/content, POST /api/skills, PUT /api/skills/content — all profile-scoped via _profile_scope(), which now also retargets tools.skill_manager_tool's import-time SKILLS_DIR binding. - Cron page: skills multi-select in both create and edit modals (parity with hermes cron --skill / edit --add-skill); CronJobCreate gains a skills field; job cards show an attached-skills badge. update_job already accepted skills in updates. - Tests: 17 new endpoint tests (content read, create/edit validation + profile scoping + auth gate, cron skills round-trip).
…usResearch#44245) * fix(gateway): gate oversized Telegram voice/audio before download Adds a pre-download size check to the Telegram voice and audio inbound paths. Files that exceed _max_doc_bytes (default 20 MB) are rejected before get_file() is called, preventing silent OOM-style stalls on large uploads. A human-readable note is appended to the event text so the model can explain the limit to the user. Also extends 403 entitlement detection in recover_with_credential_pool to cover two additional cases: 'oauth authentication is currently not allowed for this organization' and Anthropic anthropic_messages-mode 403s, both of which should be treated as entitlement failures rather than transient errors. Tests: 7 new cases in test_telegram_voice_v0_regressions.py covering the size gate (accept, reject, note text) and the STT-failure notice path. Salvaged from NousResearch#40487 (cryptopafi) — cherry-picked the Telegram voice policy and 403 entitlement fixes; LiveKit/Discord/uv.lock workstreams left for separate PRs. * test(gateway): drop orphaned voice tests not backed by this PR The cherry-picked test file from NousResearch#40487 included 3 tests for STT-failure notice and voice-mode (_handle_voice_command 'on' -> voice_only) behavior that this PR intentionally does NOT salvage (those belong to the LiveKit/ voice-policy workstreams left in NousResearch#40487). They fail on both this branch and clean main because the feature code isn't present. Keep only the 2 tests backed by code actually in this PR: - test_telegram_audio_size_gate_rejects_oversized_media_before_download (covers the _telegram_media_size_allowed guard this PR adds) - test_voice_tts_is_explicit_audio_reply_opt_in (matches current main) Removed now-unused imports (MessageEvent, MessageType, AsyncMock).
…43977) Co-authored-by: Omar Baradei <omar@kostudios.io>
* fix(desktop): Harden local file tree paths Normalize Electron local path handling across file tree, preview, media, and git-root flows. Reject malformed and Windows device paths, recheck sensitive files after realpath resolution, and preserve external symlink traversal with stable renderer errors. * fix(desktop): Address file tree review feedback
…InvokeModelWithResponseStream (NousResearch#44293) IAM policies scoped to bedrock:InvokeModel only (a common least-privilege setup) reject converse_stream() with AccessDeniedException. The agent loop hard-prefers streaming and the denial never matched the 'stream not supported' auto-fallback, so InvokeModel-only users looped on AccessDenied forever. - agent/bedrock_adapter.py: new is_streaming_access_denied_error() detector (ClientError code check + wrapped-SDK message match); call_converse_stream() falls back to converse() on denial. - agent/chat_completion_helpers.py: bedrock_converse streaming branch retries inline via converse() and sets _disable_streaming so later turns skip the doomed stream attempt; the chat-completions retry block also recognizes the denial for the AnthropicBedrock SDK path (message pre-check avoids importing bedrock_adapter — and its lazy boto3 install — for unrelated providers). Both paths print a one-line notice telling the user which IAM action restores streaming.
… Claude Code creds; hide deprecated tool-progress env vars (NousResearch#44286) Two dashboard fixes: 1. The 'Anthropic API Key' OAuth catalog entry's status fn read ~/.claude/.credentials.json (which has its own dedicated claude-code entry) and never checked ANTHROPIC_API_KEY at all. It now checks the Hermes PKCE file, then the registry env-var order (ANTHROPIC_API_KEY -> ANTHROPIC_TOKEN -> CLAUDE_CODE_OAUTH_TOKEN) via get_env_value, so keys from .env, the shell, or Bitwarden (injected into the process env by load_hermes_dotenv) are all reported, with a '(from Bitwarden)' source suffix when applicable. 2. Deprecated HERMES_TOOL_PROGRESS / HERMES_TOOL_PROGRESS_MODE removed from OPTIONAL_ENV_VARS so the keys page and setup checklists stop offering them. Moved to _EXTRA_ENV_KEYS so .env sanitization and reload_env still recognize them for existing users (gateway back-compat fallback unchanged).
- nous_subscription: gate the STT managed-default flip on openai-audio
entitlement and skip when a local backend (faster-whisper or custom
command) works; new _local_stt_backend_available() helper + tests
- whatsapp_cloud: WHATSAPP_CLOUD_{DM_POLICY,ALLOW_FROM,GROUP_POLICY,
GROUP_ALLOW_FROM} env overrides so both adapters can run in parallel;
normalize allowlist entries (JID/punctuation) to bare wa_id
- whatsapp_cloud: wrap per-message event build in try/except (dedup-marked
wamids would be silently dropped on Meta's batch retry otherwise)
- whatsapp_cloud: validate media_id before URL/filename interpolation,
delete transient .ogg after voice upload, FIFO-cap interactive-button
state dicts and per-chat wamid cache
- whatsapp_common: '# **Title**' headers no longer double-wrap asterisks
- setup wizard: read access token / app secret via getpass on TTYs
- docs: new WHATSAPP_CLOUD_* gating env vars
…hide skills (NousResearch#44342) Real-world failure with the original index pruning: under the default auto posture, an agent-created ops skill in a demoted category vanished from the prompt's skill index mid-project, and the agent silently fell back to a stale sibling skill instead. The "discovery-only" premise didn't hold — models do not reach for skills_list to rediscover what the index stops showing them, and agent-created skills are the model's accumulated project memory (runbooks, pitfalls, operating rules). Gating pruning behind the opt-in focus mode was the wrong fix too: users opening a worktree don't know the config exists, so the index-noise win would effectively never ship. Instead, the coding posture now DEMOTES non-coding categories rather than hiding them: each demoted category renders as a single names-only line ("gaming [names only]: allthemons10-ops, mc-backup") with a footer note explaining the omitted descriptions. Every skill name stays in the prompt, so memory-anchored recall ("load <name>") keeps working in every mode, while the description noise is still cut. Applies in auto/on/focus alike; the general posture demotes nothing. Deny-list semantics unchanged — unknown/custom categories and coding-adjacent ones keep full entries. API renamed to match the honest semantics: hidden_skill_categories → compact_skill_categories, build_skills_system_prompt(hidden_categories=) → compact_categories=.
…g.yaml path (NousResearch#44374) The Config page read config_path from /api/status, which is machine-global and always reports the profile the dashboard process was started under. After switching profiles with the global switcher, the header kept showing the old profile's path (e.g. /root/.hermes/profiles/worker_1/config.yaml) even though reads/writes correctly targeted the new profile. Fix: /api/config/raw now returns the resolved path alongside the YAML (resolved inside _profile_scope, so it follows ?profile=). ConfigPage prefers that scoped path and only falls back to /api/status for old servers. ProfileKeyedRoutes already remounts the page on switch, so the header refreshes immediately.
…ousResearch#44387) The coding posture's names-only demotion of non-coding skill categories (NousResearch#44342) applied under the default auto mode, silently changing the skill index for every user in a git repo. Index changes must be opt-in: demotion now only fires under agent.coding_context=focus, alongside the toolset collapse. auto/on leave the skill index untouched; focus semantics are unchanged (demoted, never hidden; deny-list keeps coding-adjacent and custom categories at full entries).
Hermes can propose automations and let the user accept them with one tap via /suggestions, instead of making them assemble cron jobs by hand. Every proposal — wherever it originates — flows through one surface. Sources (the 'where suggestions come from'): - catalog: curated starter automations (daily briefing, important-mail monitor, weekly review, workday-start reminder) via /suggestions catalog - recipe: installing a skill that carries a metadata.hermes.recipe block registers a suggestion instead of auto-scheduling - usage / integration: reserved for the background-review detector and account-connect triggers (sources defined; emitters land next) Pieces: - cron/suggestions.py — the store. add/list/accept/dismiss, dedup+latch by key (dismissed proposals never re-offered), pending cap so it can't become a nag wall. Accepting calls the existing cron.jobs.create_job — there is NO second job engine. Mirrors jobs.py storage (atomic writes, lock, 0600). - cron/suggestion_catalog.py — the curated set. The important-mail monitor entry is where the old proactive-monitor poll->classify->surface engine lives now (cron/scripts/classify_items.py + the 'monitor' aux task), as ONE catalog automation rather than a standalone feature. - tools/recipes.py — recipe<->job bridge; register_recipe_suggestion() makes a recipe source 'recipe' of this surface. recipe_to_job_spec() is the single translation both the direct and suggestion paths share. - hermes_cli/suggestions_cmd.py — shared /suggestions handler (CLI + gateway never drift); /suggestions [accept N|dismiss N|catalog|clear]. - Wired: CommandDef + CLI dispatch (cli.py) + gateway dispatch (gateway/run.py) + aux 'monitor' task (config.py) + recipe-install hook (skills_hub.py). Consent-first throughout: nothing auto-schedules; acceptance is always explicit; dismissals latch. Supersedes NousResearch#41122 (proactive-monitor) and NousResearch#41127 (recipes): both fold in here as a catalog entry and a suggestion source respectively. Tests: store (dedup/cap/accept/dismiss/latch), catalog seeding+idempotency, recipe->suggestion bridge, command handler, aux config. E2E: recipe SKILL.md -> parsed -> suggested -> accepted -> real cron job persisted to jobs.json.
…every surface A 'recipe' is a one-place definition of an automation that every surface renders natively. The slot schema (cron/recipe_catalog.py) is the single source of truth; four renderers consume it, and all paths end at the same cron.jobs.create_job — no second job engine. Form where there's a screen, conversation where there's a chat line: - Dashboard / GUI app: a Recipes sub-tab on the Cron page renders each recipe's typed slots as a form (time-picker, enum dropdown, free-text); submit POSTs /api/cron/recipes/instantiate which fills + creates the job. - CLI / TUI / messengers: /cron-recipe lists the catalog, shows a recipe's fields, or fills + creates from a pasted 'key slot=val' command. The shared handler (hermes_cli/cron_recipe_cmd.py) names any missing/invalid slot so the agent can ask a targeted follow-up. - Docs: a generated Cron Recipes catalog page (website, .mdx + React cards) shows each recipe with a copy-paste command and a 'Send to App' button. - Desktop: a hermes:// URL scheme (Electron single-instance lock + setAsDefaultProtocolClient + open-url/second-instance) routes hermes://cron-recipe/<key>?slot=val into the chat composer pre-filled. Typed slots (time/enum/text/weekdays) with defaults: users never type raw cron — recipes parameterize time-of-day and weekday sets and translate to cron expressions; a free-text 'schedule' slot is the full-flexibility escape hatch. Consent-first throughout: nothing schedules without an explicit submit or send. Core: - cron/recipe_catalog.py — CronRecipe + RecipeSlot, 5 curated recipes, recipe_form_schema / recipe_slash_command / recipe_deeplink / recipe_catalog_entry renderers, fill_recipe (validate + translate to create_job kwargs). - hermes_cli/cron_recipe_cmd.py — shared /cron-recipe handler (CLI + TUI + gateway never drift). CommandDef + dispatch in commands.py / cli.py / gateway/run.py. Dashboard: GET /api/cron/recipes + POST /api/cron/recipes/instantiate (web_server.py), CronRecipes.tsx gallery+form, Segmented sub-tab on CronPage, api.ts methods + types. Desktop: hermes:// scheme end to end (main.cjs deep-link router + ready-queue, preload onDeepLink/signalDeepLinkReady, global.d.ts types, desktop-controller composer prefill, electron-builder protocols key). Docs: extract-cron-recipes.py generator wired into prebuild.mjs, cron-recipes-catalog.mdx + CronRecipesCatalog React component, sidebar entry. Generated index json gitignored like skills.json. Tests: 23 core (catalog/slots/schedule-resolution/validation/renderers/command handler/generator) + 5 web_server endpoint tests. E2E verified end to end: slot fill -> create_job -> persisted job with correct schedule/deliver/origin.
Reworks the chat-line UX: pick a recipe by name and the agent asks you for
what it needs, one question at a time, instead of forcing you to hand-type a
slot=val command line.
- /cron-recipe -> lists the catalog
- /cron-recipe <name> -> forgiving name match (exact/prefix/substring/
fuzzy; ambiguous lists candidates), then seeds
the agent with a natural-language fill request
built from the recipe's typed slots + schedule
and prompt templates. The agent asks for each
value one at a time and calls the EXISTING
cronjob tool. No new tool.
- /cron-recipe <name> slot=val -> unchanged deterministic path (fill_recipe ->
create_job) for the dashboard/docs/power user.
Mechanism (no new plumbing, invariant-safe — the seed enters as a normal user
turn, never a synthetic injection):
- shared handler returns RecipeCommandResult{text, agent_seed}; match_recipe()
and build_recipe_seed() are the new shared pieces.
- gateway: dispatch rewrites event.text to the seed and falls through to the
agent (the same pattern /steer uses).
- CLI: handler sets a one-shot self._pending_agent_seed; the interactive loop
consumes it right after process_command() and runs it as the next turn.
The typed-slot schema stays the single source of truth (still validates the
form/inline path via fill_recipe); the agent path just renders those slots into
the questions to ask. Docs updated to lead with the name-then-ask flow.
…ot names, surface-aware UX Review fixes for the Cron Recipes stack before release: - hydration-move: */90 in the cron minute field silently wraps to hourly (croniter-verified) — 90/120-minute options never fired at their stated cadence. Replaced with an hour-field step (0 9-17/2 * * 1-5) and an interval_hours slot whose options (1/2/3h) all fire as labeled. - fill_recipe: reject unknown slot names. A typo'd 'tiem=07:15' used to silently create the job at the 08:00 default; now it 422s on the dashboard form and errors on the slash/deep-link paths with the valid slot list. - deliver slot: non-strict enum (options are suggestions, scheduler validates downstream) so slack/whatsapp/etc. users aren't locked out; GET /api/cron/recipes rewrites its options from cron_delivery_targets() so the dashboard form only offers configured platforms; help text no longer claims dashboard-created jobs deliver to 'the chat you set this up from' (the endpoint strips origin — they go to the home channel). - gateway: success/accept messages no longer point at /cron (cli_only); surface-aware hint instead. Conversational fill now sends the 'Setting up X — I'll ask you a couple of things…' ack before the agent turn, matching the CLI experience. - important-mail catalog entry: reference the urgency classifier by module path (python3 -m cron.scripts.classify_items) instead of baking an absolute host path into the job prompt — stale after relocation and nonexistent on remote terminal backends. cron/scripts is now a real package and ships in the wheel (pyproject packages.find). - export_recipe: interval schedules round-trip again — parse_schedule stores 'minutes' but the renderer only read 'seconds', so every interval job exported as the silent '0 9 * * *' fallback. - skills_hub install: say so when a recipe suggestion is dropped (latched dedup or pending cap) instead of printing nothing. Targeted tests: 58 cron/recipe + 261 web_server pass; E2E-validated all 14 recipes fill+parse, hydration cadences via croniter, typo rejection on slash + endpoint paths, surface-aware hints, and interval export round-trip.
…t the 50-cap CI tests the PR merged with current main, where the new /memory canonical command filled Slack's 50-slash cap: with btw/bg/reset all pinned ahead of canonicals, the last canonical (/debug) got clamped and the Telegram-parity test failed. Canonical commands must win slots over alias spellings — /new keeps its native slot and 'reset' stays reachable via /hermes reset. Also updates test_includes_aliases_as_first_class_slashes to assert the pinned-alias contract (_SLACK_PRIORITY_ALIASES survive) instead of a specific unpinned alias's survival, which was the same change-detector pattern the docstring already warned about.
Product rename across every surface: module/file names (blueprint_catalog, tools/blueprints, blueprint_cmd), slash command /cron-recipe -> /blueprint (alias /bp), dashboard API /api/cron/blueprints, desktop deep-link hermes://blueprint/<key>, docs catalog page + extract script, and the skill frontmatter block metadata.hermes.blueprint. No behavior change.
…ousResearch#44411) The coding-posture brief told GPT/Codex models to use patch mode='patch' (V4A) for structured/multi-file changes but mode='replace' "for a single small swap". That second nudge points those models at a format their first-party harness never taught them. Verified against openai/codex (current main): apply_patch is the ONLY file editor in codex-rs — zero occurrences of str_replace/old_string anywhere in the repo; the grammar (core/src/tools/handlers/apply_patch.lark) is exactly the V4A dialect our patch_parser implements; the shipped model prompts (gpt_5_codex, gpt-5.2-codex, gpt-5.1-codex-max + instruction templates) explicitly say to use apply_patch "for single file edits"; and the tool is gated per model via ModelInfo.apply_patch_tool_type, i.e. OpenAI ships V4A-for-everything as model metadata. The GPT-family line now steers to mode='patch' for all edits, single-file included. The replace-family line (Claude + open-weight) is unchanged — Claude Code's FileEdit is old_string/new_string/replace_all exact string replacement (confirmed from Anthropic's shipped sdk-tools.d.ts, the only file editor in its tool union), matching our mode='replace'.
Turn off browser spellcheck, autocorrect, and autocomplete on the main chat composer and message-edit composer so code, paths, and slash commands are not flagged or altered.
…ialog (NousResearch#43496) When a user toggles off the last visible model for a provider group, the effectiveVisibleKeys() function treated the missing provider prefix as 'never customized' and re-added the default models on the next render, causing all models to snap back to enabled. Fix: store a sentinel key (e.g. 'provider::') when the last model for a provider is toggled off. The sentinel distinguishes 'user hid everything' from 'user never customized', preventing the default-fallback path from re-adding models the user explicitly chose to hide. Fixes NousResearch#43485
…ssion's provider `_stored_session_runtime_overrides` restored the session provider from `billing_provider` when `model_config` had no explicit provider. For a `custom:<name>` endpoint that only ran normal turns (no `/model` switch), the persisted `billing_provider` is the bare billing bucket `"custom"`, which `agent_init` treats as non-routable, so `session.resume` failed with "No LLM provider configured" even though new chats and CLI `--resume` work. Only restore an explicit `model_config.provider`; skip a bare billing bucket (`auto`/`openrouter`/`custom`) so resume falls back to the configured default, matching the CLI path. Fixes NousResearch#44022
_runtime_model_config persisted the live agent's RESOLVED provider into the session row's model_config JSON. For any named providers:/ custom_providers: entry, agent.provider is the literal string "custom", so the entry name was lost (and the api_key is deliberately never persisted). On session.resume or _reset_session_agent the stored provider="custom" fed resolve_runtime_provider(requested="custom"), which cannot match a named entry — the rebuild either raised "No LLM provider configured" or silently resolved placeholder credentials against the patched-back base_url. Persist the REQUESTED/entry identity instead: a new reverse lookup find_custom_provider_identity(base_url) maps the endpoint URL back to the canonical custom:<name> menu key. _runtime_model_config stores that key; _make_agent performs the same recovery for rows persisted before the fix, falling back to passing the stored base_url as explicit_base_url so the direct-alias branch still targets the session's endpoint when no entry matches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A thinking-only assistant turn (reasoning present, empty visible text) is persisted with its reasoning fields and stays recallable from the transcript, but `_history_to_messages` dropped it as "empty" before its reasoning was attached. On desktop/TUI resume or reload the turn therefore vanished from the session view while the agent could still recall it from a fresh session -- exactly the "messages disappear when the LLM uses its thinking block, but a new session can recall them" symptom reported on NousResearch#44022. Keep an assistant turn when it carries reasoning, even with empty text, so the desktop "Thinking…" disclosure has something to render. Genuinely empty turns (no text, no reasoning, no tool calls) are still filtered out. Refs NousResearch#44022 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…h#45597) Surface direct model.provider=custom endpoints in /model picker output and keep explicit bare custom switches on the current endpoint instead of requiring a named providers/custom_providers row.
…tion auto-reset After compression exhaustion the auto-reset created a fresh session but discarded reset_session()'s return value and left the Telegram topic binding pointing at the oversized compressed child. The next inbound message in that topic healed the binding forward and switch_session'd the freshly-reset lane back onto the bloated transcript, re-triggering compression exhaustion in a loop with a new session id each time. Capture the fresh entry and re-sync the topic binding to it so the next message starts clean. No-op on non-topic lanes. Regression of the NousResearch#9893/NousResearch#10063 auto-reset fix. Fixes NousResearch#35809
Old Office formats (.xls, .doc, .ppt) were missing from the SUPPORTED_DOCUMENT_TYPES dict in gateway/platforms/base.py while their newer counterparts (.xlsx, .docx, .pptx) were included. Sending an .xls file via Telegram triggers 'Unsupported document type' and the file is silently dropped instead of being cached and forwarded to the agent. Add the three legacy MIME types so these files are handled the same way as their modern equivalents.
…allowlist
Own-policy adapters (WhatsApp, WeCom, Weixin, QQBot, Yuanbao) default dm_policy/group_policy to "open", which forwards every sender. The gateway's adapter-trust shortcut in _is_user_authorized blanket-trusted those platforms when no env allowlist was set, so an operator who enabled one with only credentials authorized the entire external network -- the fail-open SECURITY.md section 2.6 forbids ("an allowlist is required for every enabled network-exposed adapter").
Trust the adapter only when its effective policy for the chat type is an actual "allowlist" restriction (the case NousResearch#34515 was protecting). "open"/"pairing"/anything else falls through to default-deny, where {PLATFORM}_ALLOW_ALL_USERS / GATEWAY_ALLOW_ALL_USERS and the pairing flow remain the explicit opt-ins.
Keep the own-policy fail-closed hardening from PR NousResearch#45444, but still trust WeCom groups.<id>.allow_from because the adapter already checked that sender allowlist before dispatching to gateway auth.
…inds
The dashboard's public /api/status liveness endpoint is in PUBLIC_API_PATHS
and bypasses dashboard auth, yet it returned absolute hermes_home,
config_path, env_path, the gateway PID, and the internal gateway health URL.
That exceeds the shape its own allowlist documents as public ("version,
gateway state, active session count, and the dashboard auth-gate shape. No
bodies, no session content, no secrets"), leaking deployment recon to any
unauthenticated caller on a network-exposed (gated) bind.
Withhold host-local detail unless the bind is loopback / --insecure, where
the dashboard is local-only and the caller is already inside the trust
envelope -- the same split should_require_auth draws. The NAS liveness probe
and the auth-gate badge are unaffected.
Adds invariant tests for both modes (gated withholds, loopback keeps).
Advances Nico's fork from upstream merge-base fc086da (2026-06-06) to NousResearch/hermes-agent@main (2026-06-13), bringing in 773 upstream commits. 4 files conflicted; all resolved preserving Nico's fork features. Conflict resolutions: - agent/conversation_loop.py: upstream folded the bare *_attempted retry flags into TurnRetryState. Took upstream's side; kept the fork-only `kimi_auth_retry_attempted` loose local (no field in TurnRetryState), still referenced by the Kimi OAuth 401-retry path. - cli.py: adopted upstream's `_estimate_tui_input_height` helper (correct CJK width + narrow-resize handling) and passed `max_height=_input_max_lines` to preserve Nico's configurable TUI input-height cap. - gateway/run.py: upstream extracted the authorization cluster into GatewayAuthorizationMixin (gateway/authz_mixin.py, commit a706a34). Took upstream's side (methods now live in the mixin); ported Nico's only divergence — the DAEMONCRAFT allowlist / allow-all env-var entries — into authz_mixin.py's `_is_user_authorized` platform maps. - hermes_cli/main.py: upstream extracted the model-flow wizards into hermes_cli/model_setup_flows.py (commit a77efad). Took upstream's side; ported Nico's Kimi OAuth credential-resolution logic into the relocated `_model_flow_kimi` (prefer OAuth via resolve_kimi_coding_runtime_credentials, then env key, then prompt). Verification: all touched modules import clean; authz, model-flow and gateway/daemoncraft test suites pass. The 8 failing embodied_plan/mc_bit tests are pre-existing on nico/main (identical failures on a clean checkout), unrelated to this sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
|
Superseded by Nico's own upstream sync (851 commits / v0.16.0, 2026-06-14, nicoechaniz/main now at 2e637e2). This 2026-06-13 sync attempt is no longer needed — closing. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sync: upstream NousResearch → fork (2026-06-13)
Pone el fork al día con
NousResearch/hermes-agent@main. Avanza desde el merge-basefc086da8b(2026-06-06) hasta upstream del 2026-06-13, incorporando 773 commits de upstream. Después de este merge la rama queda con 0 commits de upstream pendientes.Se hizo como merge (igual que tus
Merge branch 'nousmain'previos), no rebase, para no reescribir los 123 commits del fork.Conflictos (4 archivos) y cómo se resolvieron
Lo relevante: 3 de los 4 no fueron simples colisiones de líneas, sino delete/modify — upstream hizo refactors "god-file" que movieron clusters de código a archivos nuevos. En esos casos tomé la versión de upstream y porté la divergencia del fork a la nueva ubicación, para no perder features ni dejar código muerto.
agent/conversation_loop.py*_attempteddentro deTurnRetryStatekimi_auth_retry_attemptedcomo local suelto (no tiene campo enTurnRetryState) — lo usa el retry-401 de Kimi OAuthcli.py_estimate_tui_input_height(maneja ancho CJK + resize angosto)max_height=_input_max_linespara preservar el cap configurable de altura del inputgateway/run.pygateway/authz_mixin.py(GatewayAuthorizationMixin, commita706a349b)DAEMONCRAFT_ALLOWED_USERS/DAEMONCRAFT_ALLOW_ALL_USERSa los mapas de_is_user_authorizeddel mixinhermes_cli/main.pyhermes_cli/model_setup_flows.py(commita77efada5)_model_flow_kimireubicado (prefiere OAuth víaresolve_kimi_coding_runtime_credentials, luego env key, luego prompt)Verificación
tests/tools/test_embodied_plan_tool.pyytest_mc_bit_tool.py, pero son pre-existentes enmaindel fork — fallan idénticos en un checkout limpio sin este merge (las aserciones esperan mensajes que la implementación actual del tool ya no emite). No los introduce este sync. Conviene revisarlos aparte.Notas para revisar
gateway/authz_mixin.pyyhermes_cli/model_setup_flows.py.authz_mixin.pyy el flujo OAuth de Kimi enmodel_setup_flows.py, que son los dos injertos manuales.🤖 Generated with Claude Code