chore: upstream sync 2026-04-26 (423 commits)#13
Merged
Conversation
…gging as truncated When the streaming path (chat completions) assembled tool call deltas and detected malformed JSON arguments, it set has_truncated_tool_args=True but passed the broken args through unchanged. This triggered the truncation handler which returned a partial result and killed the session (/new required). _many_ malformations are repairable: trailing commas, unclosed brackets, Python None, empty strings. _repair_tool_call_arguments() already existed for the pre-API-request path but wasn't called during streaming assembly. Now when JSON parsing fails during streaming assembly, we attempt repair via _repair_tool_call_arguments() before flagging as truncated. If repair succeeds (returns valid JSON), the tool call proceeds normally. Only truly unrepairable args fall through to the truncation handler. This prevents the most common session-killing failure mode for models like GLM-5.1 that produce trailing commas or unclosed brackets. Tests: 12 new streaming assembly repair tests, all 29 existing repair tests still passing.
…NousResearch#15356) Extends _repair_tool_call_arguments() to cover the most common local-model JSON corruption pattern: llama.cpp/Ollama backends emit literal tabs and newlines inside JSON string values (memory save summaries, file contents, etc.). Previously fell through to '{}' replacement, losing the call. Adds two repair passes: - Pass 0: json.loads(strict=False) + re-serialise to canonical wire form - Pass 4: escape 0x00-0x1F control chars inside string values, then retry Ports the core utility from NousResearch#12068 / PR NousResearch#12093 without the larger plumbing change (that PR also replaced json.loads at 8 call sites; current main's _repair_tool_call_arguments is already the single chokepoint, so the upgrade happens transparently for every existing caller). Credit: @truenorth-lj for the original utility design. 4 new regression tests covering literal newlines, tabs, re-serialisation to strict=True-valid output, and the trailing-comma + control-char combination case.
- update faq answer with new `backup` command in release 0.9.0 - move profile export section together with backup section so related information can be read more easily - add table comparison between `profile export` and `backup` to assist users if understanding the nuances between both
skill_view response went to the model verbatim; duplicating the SKILL.md body as raw_content on every tool call added token cost with no agent-facing benefit. Remove the field and update tests to assert on content only. The slash/preload caller (agent/skill_commands.py) already falls back to content when raw_content is absent, and it calls skill_view(preprocess=False) anyway, so content is already unrendered on that path.
Follow-up to @iRonin's Ctrl+D EOF fix. If the input text is empty but the user has pending attached images, do nothing rather than exiting — otherwise a stray Ctrl+D silently discards the attachments.
When display.busy_input_mode is 'queue', the runner-level PRIORITY block in _handle_message was still calling running_agent.interrupt() for every text follow-up to an active session. The adapter-level busy handler already honors queue mode (commit 9d147f7), but this runner-level path was an unconditional interrupt regardless of config. Adds a queue-mode branch that queues the follow-up via _queue_or_replace_pending_event() and returns without interrupting. Salvages the useful part of NousResearch#12070 (@knockyai). The config fan-out to per-platform extra was redundant — runner already loads busy_input_mode directly via _load_busy_input_mode().
…ression
Before this, typing during /compress was accepted by the classic CLI
prompt and landed in the next prompt after compression finished,
effectively consuming a keystroke for a prompt that was about to be
replaced. Wrapping the body in self._busy_command('Compressing
context...') blocks input rendering for the duration, matching the
pattern /skills install and other slow commands already use.
Salvages the useful part of NousResearch#10303 (@iRonin). The `_compressing` flag
added to run_agent.py in the original PR was dead code (set in 3 spots,
read nowhere — not by cli.py, not by run_agent.py, not by the Ink TUI
which doesn't use _busy_command at all) and was dropped.
…writes _submit_anthropic_pkce() retrieved sess under _oauth_sessions_lock but wrote back to sess["status"] and sess["error_message"] outside the lock. A concurrent session GC or cancel could race with these writes, producing inconsistent session state. Wrap all 4 sess write sites in _oauth_sessions_lock: - network exception path (Token exchange failed) - missing access_token path - credential save failure path - success path (approved)
…r too Extends PR NousResearch#15171 to also cover the server-side cancellation path (aiohttp shutdown, request-level timeout) — previously only ConnectionResetError triggered the incomplete-snapshot write, so cancellations left the store stuck at the in_progress snapshot written on response.created. Factors the incomplete-snapshot build into a _persist_incomplete_if_needed() helper called from both the ConnectionResetError and CancelledError branches; the CancelledError handler re-raises so cooperative cancellation semantics are preserved. Adds two regression tests that drive _write_sse_responses directly (the TestClient disconnect path races the server handler, which makes the end-to-end assertion flaky).
…setting _load_auth_store() caught all parse/read exceptions and silently returned an empty store, making corruption look like a logout with no diagnostic information and no way to recover the original file. Now copies the corrupt file to auth.json.corrupt before resetting, and logs a warning with the exception and backup path.
…search#15218) ``run_conversation`` was calling ``memory_manager.sync_all( original_user_message, final_response)`` at the end of every turn where both args were present. That gate didn't consider the ``interrupted`` local flag, so an external memory backend received partial assistant output, aborted tool chains, or mid-stream resets as durable conversational truth. Downstream recall then treated the not-yet-real state as if the user had seen it complete, poisoning the trust boundary between "what the user took away from the turn" and "what Hermes was in the middle of producing when the interrupt hit". Extracted the inline sync block into a new private method ``AIAgent._sync_external_memory_for_turn(original_user_message, final_response, interrupted)`` so the interrupt guard is a single visible check at the top of the method instead of hidden in a boolean-and at the call site. That also gives tests a clean seam to assert on — the pre-fix layout buried the logic inside the 3,000-line ``run_conversation`` function where no focused test could reach it. The new method encodes three independent skip conditions: 1. ``interrupted`` → skip entirely (the NousResearch#15218 fix). Applies even when ``final_response`` and ``original_user_message`` happen to be populated — an interrupt may have landed between a streamed reply and the next tool call, so the strings on disk are not actually the turn the user took away. 2. No memory manager / no final_response / no user message → preserve existing skip behaviour (nothing new for providerless sessions, system-initiated refreshes, tool-only turns that never resolved, etc.). 3. Sync_all / queue_prefetch_all exceptions → swallow. External memory providers are strictly best-effort; a misconfigured or offline backend must never block the user from seeing their response. The prefetch side-effect is gated on the same interrupt flag: the user's next message is almost certainly a retry of the same intent, and a prefetch keyed on the interrupted turn would fire against stale context. ### Tests (16 new, all passing on py3.11 venv) ``tests/run_agent/test_memory_sync_interrupted.py`` exercises the helper directly on a bare ``AIAgent`` (``__new__`` pattern that the interrupt-propagation tests already use). Coverage: - Interrupted turn with full-looking response → no sync (the fix) - Interrupted turn with long assistant output → no sync (the interrupt could have landed mid-stream; strings-on-disk lie) - Normal completed turn → sync_all + queue_prefetch_all both called with the right args (regression guard for the positive path) - No final_response / no user_message / no memory manager → existing pre-fix skip paths still apply - sync_all raises → exception swallowed, prefetch still attempted - queue_prefetch_all raises → exception swallowed after sync succeeded - 8-case parametrised matrix across (interrupted × final_response × original_user_message) asserts sync fires iff interrupted=False AND both strings are non-empty Closes NousResearch#15218 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DeepSeek V4 thinking mode requires reasoning_content on every assistant message that includes tool_calls. When this field is missing from persisted history, replaying the session causes HTTP 400: 'The reasoning_content in the thinking mode must be passed back to the API.' Two-part fix (refs NousResearch#15250): 1. _copy_reasoning_content_for_api: Merge the Kimi-only and DeepSeek detection into a single needs_tool_reasoning_echo check. This handles already-poisoned persisted sessions by injecting an empty reasoning_content on replay. 2. _build_assistant_message: Store reasoning_content='' on new DeepSeek tool-call messages at creation time, preventing future session poisoning at the source. Additional fix: 3. _handle_max_iterations: Add missing call to _copy_reasoning_content_for_api in the max-iterations flush path (previously only main loop and flush_memories had it). Detection covers: - provider == 'deepseek' - model name containing 'deepseek' (case-insensitive) - base URL matching api.deepseek.com (for custom provider)
…gression tests Extracts _needs_kimi_tool_reasoning() for symmetry with the existing _needs_deepseek_tool_reasoning() helper, so _copy_reasoning_content_for_api uses the same detection logic as _build_assistant_message. Future changes to either provider's signals now only touch one function. Adds tests/run_agent/test_deepseek_reasoning_content_echo.py covering: - All 3 DeepSeek detection signals (provider, model, host) - Poisoned history replay (empty string fallback) - Plain assistant turns NOT padded - Explicit reasoning_content preserved - Reasoning field promoted to reasoning_content - Existing Kimi/Moonshot detection intact - Non-thinking providers left alone 21 tests, all pass.
…elper The three google-workspace scripts (setup.py, google_api.py, gws_bridge.py) each had their own way of resolving HERMES_HOME: - setup.py imported hermes_constants (crashes outside Hermes process) - google_api.py used os.getenv inline (no strip, no empty handling) - gws_bridge.py defined its own local get_hermes_home() (duplicate) Extract the common logic into _hermes_home.py which: - Delegates to hermes_constants when available (profile support, etc.) - Falls back to os.getenv with .strip() + empty-as-unset handling - Provides display_hermes_home() with ~/ shortening for profiles All three scripts now import from _hermes_home instead of duplicating. 7 regression tests cover the fallback path: env var override, default ~/.hermes, empty env var, display shortening, profile paths, and custom non-home paths. Closes NousResearch#12722
Multiple overlapping Slack attachment improvements: 1. Upload retry with backoff on transient errors (429, 5xx, connection reset, rate_limited, service unavailable). New _is_retryable_upload_error helper covers three upload paths: _upload_file, send_video, send_document. Up to 3 attempts with 1.5s * attempt backoff. 2. Thread participation tracking: successful file uploads now add the thread_ts to _bot_message_ts, mirroring how text replies are tracked. This lets follow-up thread messages auto-trigger the bot (same engagement rules as replied threads). 3. Thread metadata preservation in the image redirect-guard fallback (send_image → send text fallback) and in two gateway.run.py send paths (image + document fallback calls). 4. HTML response rejection in _download_slack_file_bytes. Parallels the existing check in _download_slack_file. Guards against Slack returning a sign-in / redirect page as document bytes when scopes are missing, so the agent doesn't get HTML-as-a-PDF. 5. File lifecycle event acks (file_shared / file_created / file_change). These events arrive around snippet uploads. Acking them silences the slack_bolt 'Unhandled request' 404 warnings without changing behavior. 6. Post-loop message type classification so a mixed image+document upload classifies as PHOTO (or VOICE if no image), falling back to DOCUMENT. Previously, the per-file classification in the inbound loop could be overwritten unpredictably. 7. Expanded text-inject whitelist in inbound document handling to cover .csv, .json, .xml, .yaml, .yml, .toml, .ini, .cfg (up to 100KB) so snippets and config files are directly visible to the agent, not just cached as opaque uploads. Paired with new MIME entries in SUPPORTED_DOCUMENT_TYPES in base.py. Squashed from two commits in NousResearch#11819 so the single commit carries the contributor's GitHub attribution (the original commits were authored under a local dev hostname).
MCP stdio servers are spawned via the SDK's stdio_client, which on Linux uses start_new_session=True (setsid). When a cron job is cancelled mid-way (timeout, agent finish, exception), the subprocess often escapes the SDK's teardown and survives as a session leader. Because setsid() detaches the child from the gateway's process group / cgroup tree, systemd does not reap it on service restart either — so every cron tick that touches an MCP tool leaks a dangling server process. Fix: * tools/mcp_tool.py — _run_stdio now wraps the whole stdio+session context in try/finally. On any exit path (clean, exception, cancellation), PIDs still alive are moved from the active _stdio_pids set into a new _orphan_stdio_pids set. Orphan detection is done via os.kill(pid, 0) — a cheap liveness probe that never signals the target. * tools/mcp_tool.py — _kill_orphaned_mcp_children gains an include_active=False flag. Default behaviour now only reaps the orphan set so concurrent sessions (other parallel cron jobs or live user chats) are never disrupted. The existing shutdown path passes include_active=True to keep the previous "kill everything" semantics after the MCP loop is stopped. * cron/scheduler.py — the cleanup hook is moved from run_job()'s finally (which would race with parallel siblings after NousResearch#13021) into tick() after the ThreadPoolExecutor has joined every future. At that point there are no in-flight sessions from this tick, so sweeping the orphan set is always safe. Net effect: zero regression for healthy sessions, and orphan MCP servers no longer accumulate between gateway restarts. Made-with: Cursor
…usResearch#16279) Enter while the agent is busy can now inject the typed text via /steer — arriving at the agent after the next tool call — instead of interrupting (current default) or queueing for the next turn. Changes: - cli.py: keybinding honors busy_input_mode='steer' by calling agent.steer(text) on the UI thread (thread-safe), with automatic fallback to 'queue' when the agent is missing, steer() is unavailable, images are attached, or steer() rejects the payload. /busy accepts 'steer' as a fourth argument alongside queue/interrupt/status. - gateway/run.py: busy-message handler and the PRIORITY running-agent path both route through running_agent.steer() when the mode is 'steer', with the same fallback-to-queue safety net. Ack wording tells users their message was steered into the current run. Restart-drain queueing now also activates for 'steer' so messages aren't lost across restarts. - agent/onboarding.py: first-touch hint has a steer branch for both CLI and gateway. - hermes_cli/commands.py: /busy args_hint updated to include steer, and 'steer' is registered as a subcommand (completions). - hermes_cli/web_server.py: dashboard select widget offers steer. - hermes_cli/config.py, cli-config.yaml.example, hermes_cli/tips.py: inline docs updated. - website/docs/user-guide/cli.md + messaging/index.md: documented. - Tests: steer set/status path for /busy; onboarding hints; _load_busy_input_mode accepts steer; busy-session ack exercises steer success + two fallback-to-queue branches. Requested on X by @CodingAcct. Default is unchanged (interrupt).
Extends the existing channel_skill_bindings mechanism (previously
Discord-only) to Slack, so a channel or DM can auto-load one or more
skills at session start without relying on the model's skill selector
for every short reply.
Motivation: Mats's German flashcards DM pushes a cron-driven card
5x/day; he responds with one-word guesses like 'work'. Previously each
reply required the main agent to decide whether to load german-flashcards
(full opus turn just to pick a skill). With the binding configured per
Slack channel, the skill is injected at session start and grading runs
directly.
Changes:
- Extract resolve_channel_skills() from DiscordAdapter._resolve_channel_skills
into gateway.platforms.base (now shared across adapters).
- DiscordAdapter._resolve_channel_skills delegates to the shared helper
(behavior preserved — existing test suite still passes unchanged).
- SlackAdapter: resolve channel_skill_bindings on each message and attach
auto_skill to MessageEvent. gateway/run.py already handles auto-skill
injection on new sessions; this just wires Slack through it.
- gateway/config.py: accept channel_skill_bindings in slack: block of
config.yaml (was Discord-only).
- Tests: new tests/gateway/test_slack_channel_skills.py with 11 cases
covering DM/thread/parent resolution, single-vs-list skills, dedup,
malformed entries. Discord suite unchanged.
- Docs: add 'Per-Channel Skill Bindings' section to Slack user guide.
Config example:
slack:
channel_skill_bindings:
- id: "D0ATH9TQ0G6"
skills: ["german-flashcards"]
NousResearch#3015) `delete_session()` and `prune_sessions()` only removed SQLite records, leaving .json/.jsonl transcript files on disk forever. Over time this causes unbounded disk growth (~27MB/day observed). Changes: - Add `_remove_session_files()` static helper that cleans up `{session_id}.json`, `.jsonl`, and `request_dump_{session_id}_*.json` - `delete_session()` accepts optional `sessions_dir` param and removes files for the deleted session and its children - `prune_sessions()` accepts optional `sessions_dir` param and removes files for all pruned sessions after the DB transaction - Wire up CLI `hermes sessions delete` and `hermes sessions prune` to pass `sessions_dir` - File cleanup is best-effort (OSError silenced) so DB operations are never blocked by filesystem issues - Fully backward-compatible: `sessions_dir=None` (default) preserves existing behavior
…egression tests - TestAutoMaintenance gains 3 tests: auto-prune deletes transcript files when sessions_dir is passed, preserves them when it isn't (backward- compat), and never touches active-session files during prune. - FakeDB helpers in test_sessions_delete.py accept **kwargs so they don't break when delete_session signature gains sessions_dir.
…env var is set Previously, setting SLACK_BOT_TOKEN in .env would unconditionally enable the Slack gateway adapter regardless of `slack.enabled: false` in config.yaml. This caused spurious "SLACK_APP_TOKEN not set" errors when the token was used only by skills (e.g. cron jobs that send Slack messages) rather than for the Hermes messaging gateway. Now, enabled: false in config.yaml is respected — the token is stored so skills can still use it, but the gateway adapter is not activated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Slack Bolt posts are not editable like CLI spinners; medium-tier new still emitted a permanent line per tool start (issue NousResearch#14663). - Built-in slack default: off; other tier-2 platforms unchanged. - Adjust /verbose isolation test for off to new cycle. - Migration tests: read/write config.yaml as UTF-8 (Windows locale).
When the gateway intercepts a pending /update prompt and the user sends a recognized slash command (/new, /help, ...), the command now dispatches normally AND the detached update subprocess is unblocked by writing a blank .update_response. _gateway_prompt reads '' → strips → returns the prompt's default (typically a safe 'n' / skip), so the update process exits cleanly instead of blocking on stdin until the 30-minute watcher timeout. Also clears _update_prompt_pending[session_key] on this path so stray future input for the same session isn't re-intercepted. Extends PR NousResearch#15849 with tests for the new cancel-write + a regression test pinning the legacy behavior of unrecognized /foo slash commands still being consumed as the response.
_web_ui_build_needed() in PR NousResearch#14914 checked web_dir/"dist" as the sentinel, but vite.config.ts sets outDir: "../hermes_cli/web_dist" so the build output lands in hermes_cli/web_dist/, never in web/dist/. The sentinel was therefore always missing → _web_ui_build_needed always returned True → npm install + Vite build ran on every startup → OOM on low-memory VPS persisted unchanged. Fix: derive dist_dir as web_dir.parent / "hermes_cli" / "web_dist" so the sentinel points to the actual build output directory. Fixes NousResearch#14898
Convert the airtable skill from 'skills.config.airtable.api_key' (config.yaml, wrong bucket for a secret) to 'prerequisites.env_vars: [AIRTABLE_API_KEY]' (~/.hermes/.env), matching every other bundled skill that authenticates with an API token. Why the original shape was wrong: - metadata.hermes.config is for non-secret skill settings (paths, preferences) per references/skill-config-interface.md. Storing a bearer token under skills.config.* also triggered the documented 'hermes config migrate' nag-on-every-run problem. - The Quick Reference's 'AIRTABLE_API_KEY=...' bash line couldn't read skills.config.airtable.api_key anyway — it's a yaml path, not an env var. Follow-up polish on the same pass: - Added version/author/license frontmatter to match notion/linear. - Added prerequisites.commands: [curl]. - Setup section now specifies the PAT format (pat...) that replaced legacy 'key...' API keys in Feb 2024, plus the three required scopes (data.records:read/write, schema.bases:read) and the per-base Access list requirement. - Clarified PATCH vs PUT and pagination (100 records/page cap). - Swapped verification from 'hermes -q ...' (non-deterministic) to a curl /v0/meta/bases call that returns a verifiable HTTP status code.
Adds NOTION_API_KEY, LINEAR_API_KEY, TENOR_API_KEY, and AIRTABLE_API_KEY to OPTIONAL_ENV_VARS so: - They persist to ~/.hermes/.env via save_env_value like every other key Hermes knows about, instead of being ad-hoc variables the user has to hand-edit the dotfile for. - load_env() / reload_env() populate os.environ from .env on every startup — the user sets the key once, skills keep working across restarts without losing access. - hermes setup / hermes config show surface them as known optional vars with the correct signup URL (linear.app/settings/api, airtable.com/create/tokens, etc.). These four entries use category="skill" (new) rather than "tool". tools/environments/local.py auto-adds every category=tool/messaging entry to _HERMES_PROVIDER_ENV_BLOCKLIST, which stops env passthrough from leaking provider credentials into the execute_code sandbox (GHSA-rhgp-j443-p4rf). Skill API keys are the opposite case — the point is for the agent's subprocess to see them so curl can read Authorization headers — so they must be outside the blocklist. The new category is inert for that check. All four entries are advanced=True: they show up in 'hermes config' and 'hermes status' displays, but do not nag users who have never touched those skills during setup checklists. E2E verified: save_env_value → reload_env → os.environ populated → skill_view reports setup_needed=False → env_passthrough registers the key for subprocess inheritance.
- scripts/release.py: map sonoyuncudmr@gmail.com -> Sonoyunchu so the check-attribution CI job and release notes credit Soynchu correctly. - website/docs/reference/skills-catalog.md: add the airtable row to the productivity bundled-skills table.
Expand the airtable skill from bare CRUD to a full Hermes-shaped cookbook matching the linear/notion neighbors, and trim the description to fit the 60-char system-prompt cutoff. Hermes-specific additions: - Explicit 'use the terminal tool with curl — not web_extract or browser_navigate' guidance, matching the same note in linear. - Note that AIRTABLE_API_KEY flows from ~/.hermes/.env into the subprocess automatically via env_passthrough, so curl calls don't need to re-export it. - Prefer 'python3 -m json.tool' (always present) over jq (optional) for pretty-printing, with -s on every curl to keep output clean. - Read-before-write workflow that resolves record IDs via filterByFormula instead of guessing. Cookbook expansion (new vs original): - Field-type reference table (text, select, multi-select, attachment, linked record, user) with the exact write-shape Airtable expects. - typecast flag for auto-coercing values / auto-creating select options. - performUpsert PATCH for idempotent sync by merge field. - Batch create/delete endpoints (10-record cap per call). - Sort + fields query params with URL-encoding (%5B / %5D). - Named-view query that applies saved filter/sort server-side. - Full pagination loop template (while loop with offset). - Common filterByFormula patterns (exact match, contains, AND/OR, date comparison, NOT empty). - Rate-limit backoff guidance (Retry-After header, per-base budget). - Airtable error-code reference (AUTHENTICATION_REQUIRED, INVALID_PERMISSIONS, MODEL_ID_NOT_FOUND, INVALID_MULTIPLE_CHOICE_OPTIONS) so the agent can map failures to user-actionable fixes instead of just retrying. Also: description trimmed from 183 chars (truncated to 60 in system prompt, losing 'filter/upsert/delete' trigger terms) down to 59 chars that render whole: 'Airtable REST API via curl. Records CRUD, filters, upserts.' Catalog row updated to match. SKILL.md grew from 115 to 228 lines — still under the 500-line soft cap and below the linear skill (297 lines) which serves the same role for GraphQL.
Brings in 423 upstream commits since the v0.11.0 sync, including: - new bundled skills (Airtable, Spotify, Apple, etc.) - gateway slack channel_skill_bindings, model picker - new busy_input_mode "steer" option - browser supervisor + hardline blocklist hardening - many provider/adapter additions (Azure detect, Copilot, Gemini schema) Conflict resolutions: - cli-config.yaml.example, cli.py, hermes_cli/config.py, hermes_cli/tips.py: keep dy-main's queue-mode default for display.busy_input_mode while adopting upstream's new "steer" option and updated tip strings. - gateway/run.py: keep dy-main's queue default for _busy_input_mode class default and _load_busy_input_mode fallback; preserve dy-main's local-client-bridge stop() during shutdown alongside upstream's idle agent-cache cleanup. - gateway/session.py: keep both dy-main's resolve_repo_context() and upstream's _discord_tools_loaded() helpers. - gateway/platforms/base.py, gateway/platforms/discord.py: drop the duplicate parent_chat_id kwargs the auto-merge inserted into build_source's signature and call sites. - run_agent.py: take upstream's cleaner fallback_chain local-var rewrite for the model-switch fallback prune. - tests/gateway/test_restart_drain.py: extend the busy_input_mode loader test to exercise queue (default), interrupt, and steer plus unknown-value fallback to queue on this fork. - website/docs/user-guide/cli.md: keep dy-main's queue-default framing while documenting the new steer mode and /busy command.
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
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.
Summary
maintoupstream/main(423 upstream commits) and merges the result intody-main.cli-config.yaml.example,cli.py,gateway/run.py,gateway/session.py,hermes_cli/config.py,hermes_cli/tips.py,run_agent.py,tests/gateway/test_restart_drain.py,website/docs/user-guide/cli.md.gateway/platforms/base.pyandgateway/platforms/discord.pywhere a duplicateparent_chat_idkwarg was inserted by git intobuild_source's signature and one call site.Notable upstream additions pulled in
channel_skill_bindings, Discord model picker + slash command updates, browser supervisor, hardline blocklist, schema sanitizer.steer(upstream defaultinterrupt) — kept dy-main'squeuedefault but added support forsteereverywhere.Conflict notes
display.busy_input_modedefault staysqueue(dy-main), withinterruptandsteerboth accepted. Unknown values fall back toqueue.gateway/run.pykeeps dy-main's local-client bridgestop()AND adopts upstream's idle agent-cache cleanup (additive).gateway/session.pykeeps bothresolve_repo_context()(dy-main) and_discord_tools_loaded()(upstream) helpers.run_agent.pytakes upstream's cleanerfallback_chainlocal-var rewrite for model-switch prune.tests/gateway/test_restart_drain.pyupdated to exercise queue/interrupt/steer plus unknown-→queue fallback.Test plan