Sync current Nous Hermes main into Ace patches - #39
Closed
vashkartik wants to merge 5854 commits into
Closed
Conversation
…d V4A patch path
write_file currently spawns up to 6 subprocesses per call:
1. mkdir -p (separate call before atomic write)
2. cat (to read pre-content for lint/BOM/line-ending detection)
3. _atomic_write (mktemp + write + mv — the essential one)
4. wc -c (to measure bytes written)
5. _check_lint_delta (post-write lint — also essential)
6. LSP snapshot (also essential)
This PR removes three of them without changing any observable behavior:
1. Fold mkdir -p into _atomic_write shell script (−1 subprocess/write)
The atomic write script already runs a single shell; adding mkdir -p
to it costs zero extra processes.
2. Add optional pre_content parameter to write_file (−1 subprocess/patch)
patch_replace and V4A _apply_update already read the file for fuzzy
matching. Passing that content as pre_content skips the redundant cat
inside write_file. Fully backward-compatible: callers that don't pass
pre_content still read from disk as before.
3. Replace wc -c with len(content.encode('utf-8')) (−1 subprocess/write)
We already have the content in memory; encoding it to get the byte count
is equivalent to wc -c for UTF-8 text.
4. Remove redundant _check_lint loop in apply_v4a_operations (−N subprocesses/V4A)
write_file already runs _check_lint_delta internally. The old code ran a
bare _check_lint(f) loop over all modified files — a re-read + re-lint
without post_content context. Now lint results propagate from write_file
via a four-tuple return, zeroing out the extra subprocesses.
Net effect:
- write_file: 6 → 3 subprocesses per call (new files)
- patch_replace: 6 → 5 subprocesses per call (pre_content skips cat)
- V4A multi-file patches: saves 1 subprocess per modified file
- A typical 4-file V4A patch drops from ~28 to ~16 subprocess calls
…ard compat Bug 1 (UTF-8 BOM loss on V4A UPDATE): _file_has_bom() trusted pre_content for BOM detection, but the most common pre_content provider — read_file_raw() — deliberately strips BOMs so the agent never sees U+FEFF glyphs. Passing BOM-stripped content through pre_content caused a false-negative: the method returned False and write_file() silently removed the marker on rewrite. Fix: _file_has_bom() now always probes the first 3 bytes on disk (head -c 3), ignoring pre_content for BOM purposes. pre_content is still used by two other consumers — line-ending detection and lint/LSP delta computation — neither of which is affected by BOM stripping. Bug 2 (backward compatibility): _apply_update() called write_file(path, content, pre_content=...) as a keyword argument. Duck-typed file_ops implementations that only implement the two-argument write_file(path, content) contract would raise TypeError. Fix: wrap the call in try/except TypeError, falling back to the two-argument form when the keyword is not accepted. Also declare tomli in pyproject.toml (pre-existing conditional import for pre-3.11 Python, caught by the pre-commit dep scan after staging file_operations.py). Tests: Add TestV4ABomRoundTrip with two cases: - UPDATE on BOM-bearing file preserves the marker - UPDATE on plain file does not inject a BOM Addresses teknium1 review on PR NousResearch#55661.
Content that flowed through a surrogateescape decode (backend output via patch_replace) can carry lone surrogates; a strict encode raises UnicodeEncodeError where the old wc -c path could not. Mirrors the existing sha256 verification encode.
- write_file: encode content once, share bytes between bytes_written and the sha256 verification (drops a second full-content encode per write) - patch_parser: replace the except-TypeError retry around write_file(pre_content=...) with signature-based feature detection so a TypeError raised inside a capable implementation propagates instead of triggering a duplicate write; tests for both duck-typing contracts - tests: real-ops V4A BOM round-trip + _file_has_bom disk-probe guard (the teknium1-review regression previously only covered by a fake) - comment: document dirs_created's long-standing "parent ensured" meaning
requires-python is >=3.11 so tomllib is always in stdlib; the tomli fallback branch in _lint_toml_inproc was unreachable. Removes the dependency from pyproject.toml + uv.lock and deletes the dead try/except ImportError fallback in the code.
…warning NousResearch#75017: Telegram polling conflict retry used drop_pending_updates=False, starting a new getUpdates session that immediately got 409'd by the previous still-expiring session — creating the very conflict it was trying to recover from. Switch to drop_pending_updates=True so Telegram terminates stale sessions. Also add a recovery-generation guard so the first transient getUpdates success after a retry doesn't reset the conflict counter back to 0 (defense-in-depth from PR NousResearch#75096). NousResearch#75153: The WAL-reset warning always said 'hermes update can repair' even for git/pip/system Python installs where it can't. Now uses detect_install_method() + recommended_update_command_for_method() to give a context-appropriate hint (hermes update for git, docker pull for docker, nix message for nix, generic install hint as fallback).
…-junhohong chore: contributor email mapping for junhohong
Loopback dashboard tabs now share one one-shot stale-token recovery path across REST 401s, the PTY socket, the structured event socket, and the shared JSON-RPC gateway wrapper. The shared client exposes only an optional close-event interception hook; the dashboard remains responsible for deciding that loopback 4401 means reload. Constraint: Current main delegates the web gateway to apps/shared JsonRpcGatewayClient, and NousResearch#54022 review requires a shared-client-compatible close-code hook plus direct ChatSidebar event-socket coverage. Rejected: Restore the dashboard's old direct WebSocket implementation | stale against the shared JSON-RPC client and would duplicate transport behavior. Confidence: high Scope-risk: moderate Directive: Keep stale-token policy dashboard-specific; the shared JSON-RPC client should expose close events without learning dashboard auth semantics. Tested: npm --workspace web test (21 files, 106 tests); focused stale-token tests (5 files, 14 tests); npm --workspace web run typecheck; npm --workspace @hermes/shared run lint; npm --workspace @hermes/shared run typecheck; focused web eslint; git diff --check. Not-tested: Manual browser smoke test across a real dashboard restart.
react-router v7 exports MemoryRouter from 'react-router', not 'react-router-dom'. The test was written when the repo still imported from 'react-router-dom' (4000+ commits ago).
Sibling site missed by PR NousResearch#54022 — /api/console WebSocket in HermesConsoleModal.tsx has the same buildWsUrl → stale-token → 4401 close path as the PTY and events WebSockets. Without this guard, opening the console after a dashboard restart shows 'Console closed (4401). auth: token_mismatch' with no recovery.
When Grok runs on xAI Responses, only swap to native server-side web_search when the active/configured backend is xai. For Firecrawl and other Hermes providers, keep client dispatch under a renamed wire tool so Grok cannot hijack web_search and ignore user config.
Lock in backend preference, wire-name aliasing, and normalize mapping so configured non-xai search providers stay on the Hermes client path. Also init conflict-recovery generation on the telegram bare-adapter helper so CI polling progress tests do not AttributeError.
Drop the manual web.search_backend / web.backend config-reading block that duplicated _read_config_key in web_search_registry.py. The function now delegates directly to get_active_search_provider() (which reads the same config keys via the registry's canonical resolver) and falls back to _get_search_backend() only when the registry has no providers loaded. Also updates the TestXaiWebSearchBackendPreference tests to monkeypatch the registry instead of load_config_readonly, and adds two new tests for the legacy fallback path (no provider registered -> _get_search_backend).
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
hermes model saves custom_providers models: {default: {context_length}} for
local Ollama. That dict shape was treated as an explicit catalog, so no-key
endpoints skipped live /v1/models probing and Desktop/Telegram only showed
the saved default — Refresh could not help. Keep list/string shapes as
allowlists; pin dict catalogs with discover_models: false.
…urrent-turn-scopes fix(relay): avoid concurrent turn scope corruption
…lay-model-metrics feat(observability): report model and provider usage
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Hitting ⌘1 (or cycling ⌃Tab onto the main tab) while Capabilities / Messaging / Artifacts covered the workspace looked dead: the workspace pane was already the zone's active tab behind the page, so fronting it changed nothing on screen. activateTreeTabSlot / cycleTreeTabInFocusedZone now return the activated pane id, and the keybind handlers route back to the loaded session (or the new-chat draft) when the landing pane is the workspace under a full page — the same rule openSession already applies.
…fyNative
Desktop plugins can toast in-app (host.notify) but have no sanctioned way to
reach the OS notification pipeline the app's own approval/turn alerts use, so
a plugin surfacing a genuinely notable background event (e.g. a discovery
plugin finding a match) stays invisible once the user steps away from Hermes.
Add a curated per-plugin door instead of exporting the raw dispatcher:
- ctx.notifyNative({ title, body?, silent? }) on PluginContext — attributed
to the plugin id, routed through dispatchNativeNotification so every
existing gate applies (master + per-kind prefs, post-connect baseline,
away-from-app gating, throttle).
- New 'plugin' native-notification kind with its own Settings ▸ Notifications
toggle (default on), so users silence plugins without losing app alerts.
- New optional `tag` discriminator on the notify payload keys the renderer
throttle and main-process cross-window dedupe per plugin, so two plugins
can't collapse each other's session-less notifications.
Consumer: the Index Network desktop plugin wants background opportunity
alerts; anything in ~/.hermes/desktop-plugins gets the same door.
…gets messageRenderWeight moves out of thread/list.tsx into lib/render-weight.ts. The DOM page budget already spends render cost rather than message count — the store window added next needs the same currency, and one weight function keeps the two layers from drifting apart. No behavior change.
…st (NousResearch#55191) An oversized session rebuilt an unbounded runtime repository on every store update and exhausted the renderer's V8 heap, crash-looping the window. The DOM budget in thread/list.tsx bounds what PAINTS, but every message was still normalized into the repository first, so a session only had to be heavy — not visible — to kill the renderer. selectTranscriptWindow keeps the tail that fits one render-weight page. Weight, not message count: measured against a real 1,175-session store, a 400-message cap disengages on 37 sessions that are heavy but short (one is 133 messages / 1.05MB) while firing on 92 long-but-light sessions that were never at risk. The cut aligns off branch-group boundaries. useRuntimeMessageRepository records a group's fork point the first time it sees the group, so a window starting mid-group would re-parent the surviving branches to whatever happened to precede them. Co-authored-by: HexLab <8422520+HexLab98@users.noreply.github.com>
…rom the store Show earlier spends the already-materialized DOM budget first and only asks the session store for another page once that is exhausted, so the click stays cheap and the store window stays as small as it can be. Paging has no ceiling: each expand grows the window by one budget page until the whole transcript is loaded. Branch persistence stays wired throughout — setMessages is never dropped, so switchToBranch and applyBranchVisibility keep working on a windowed session. Co-authored-by: HexLab <8422520+HexLab98@users.noreply.github.com>
Fold ctx.notifyNative into a ctx.os namespace so every way a plugin reaches outside the app window lives behind one attributed door instead of accreting one top-level ctx method per capability: - ctx.os.notify — the native-notification door from the previous commit, unchanged semantics (plugin kind pref, away-gating, per-plugin throttle). - ctx.os.openExternal / ctx.os.revealPath / ctx.os.writeClipboard — the existing window.hermesDesktop bridge capabilities, now sanctioned and result-shaped: each resolves false (never throws) when the bridge or member is missing, so a plugin branches on the result instead of sniffing the preload surface or crashing on an older shell. No new Electron surface: everything routes through bridge members the app already ships; the notification path keeps every existing gate.
…earch#22622) * fix(credential-pool): clear exhaustion state on key rotation When a user rotates an API key (e.g. via `hermes setup` after hitting a rate limit), _upsert_entry updates the access_token on the existing pool entry but preserves the stale last_status=exhausted from the old key. On the next session the pool finds the entry, sees it exhausted, and returns no usable credentials — even though the new key is valid. Fix: when access_token changes on an existing entry, reset last_status, last_error_code, last_error_reason, last_error_message, and last_error_reset_at. The exhaustion state belongs to the old key, not the new one. * chore: add pasevin@gmail.com to AUTHOR_MAP * fix: clear last_status_at on key rotation, remove unused pytest import Address review feedback from teknium1 on PR NousResearch#22622: - Add last_status_at=None to the reset block (matches all other token-sync reset paths in credential_pool.py) - Assert last_status_at is None in the regression test - Remove unused pytest import flagged by ruff + ty
…summary-blocks Reasoning steps read as separate blocks again instead of one glued paragraph
…ssion-integrity fix: preserve session history when a turn crashes
After `hermes update`, the desktop sidebar showed "No sessions yet" until the user's first message. NousResearch#72424 added sessions.last_activity_at, which list_sessions_rich now selects — but column adds only land through _reconcile_columns() in the writable _init_schema, and read-only opens skip that by design. Every sidebar read path opens state.db read-only, so each poll raised "no such column: s.last_activity_at" until the first prompt's lazy session-row persist forced a writable open and reconciled. A heal for exactly this class already existed (_open_session_db_for_profile probes the read-only handle and does a one-time writable reopen on staleness), but its probe was a hand-written four-column list that never learned last_activity_at — it went stale three days after shipping. And the batched sidebar route (/api/profiles/sessions/sidebar) bypassed the helper entirely, swallowing per-profile failures into an errors array the desktop never surfaces, so the incident produced an empty sidebar with clean logs. The fix removes the maintenance burden instead of paying it once more: - hermes_state_schema.schema_read_probe_statements() derives one `SELECT <every declared column> FROM <table> LIMIT 0` per table from SCHEMA_SQL via the existing _parse_schema_columns() — the same source of truth the writable reconciler diffs against, so any future ADD COLUMN is probed with no list to update. Column references are table-qualified: an unqualified double-quoted identifier that fails to resolve silently degrades to a string literal (SQLite's double-quoted-string misfeature) and would make the probe pass on exactly the store it exists to catch. - web_server splits the heal into a path-level _open_session_db_at_path (semantics unchanged) so the cross-profile session routes can share it; both profiles.py loops and _count_status_active_sessions (the remaining raw read-only sibling) now open through it. The heal stays a helper rather than a SessionDB classmethod on purpose: escalation-to-writable must remain an explicit caller decision — update_cmd.py opens read-only mid-update and must never write. - Exhaustion guard: if the writable heal SUCCEEDS and the re-probe still fails (a schema problem ADD COLUMN cannot express), the store is marked exhausted — warn once, skip the probe, serve reads probe-less — instead of re-running the full writable init on every poll against a possibly live DB. A FAILED writable open (transient lock) is deliberately not recorded, so the next poll retries the heal. - The per-profile swallow sites in profiles.py now also log a deduplicated warning, so a persistent read failure is loud in errors.log even though the response errors array stays invisible to the sidebar. Tests: probe/SCHEMA_SQL coverage invariants (tests/test_schema_read_probe.py), last_activity_at added to the /api/sessions heal parametrize, a sidebar-route heal test reproducing the shipped symptom (errors == [] and the session returned against a store missing the column), and an exhaustion test pinning exactly one writable open. The sidebar and last_activity_at tests fail on main.
…tale-schema-probe fix(desktop): derive the stale-schema read probe from SCHEMA_SQL
…ng standards The in-repo skill-authoring skill taught the validator's ceilings (1024-char descriptions, 'Use when ...' phrasing) instead of the repo's review standards, so agents following it produced skills that fail review: 240+ char descriptions, author 'Hermes Agent' with no human credit, no bundled-vs- optional decision, dangling related_skills, no platforms audit, no tests, no docs regen, and machine-local /home/bb/... paths baked into prose. Rewritten to teach the hardline standards from AGENTS.md: - description <= 60 chars, one sentence, ends with period - author credits the human contributor first - bundled vs optional tier decision (5+ sessions/month bar; default optional) - no router/index/hub skills - platforms: audited against actual scripts, POSIX-signal table - related_skills must resolve in-repo - Hermes-tool framing instead of raw shell prose - tests at tests/skills/ + docs regen with scope discipline - removed machine-local paths; validator limits marked as NOT the standard
…pora Inspired by virgiliojr94/book-to-skill (MIT): /learn now picks the skill shape by the source. Workflows and small sources still get one tight SKILL.md; books, paper stacks, specs, and large doc corpora get a knowledge-base layout — a lean always-loaded SKILL.md index plus one distilled file per chapter/topic under references/, loaded on demand via skill_view so query cost stays proportional to the answer. - agent/learn_prompt.py: new _KNOWLEDGE_SKILL_STANDARDS block (index + per-chapter references/, structure-not-summary distillation, never reproduce source passages, fold-in instead of duplicating) and a _SOURCE_HYGIENE block pinning extracted source text as data and dropping invisible/bidi Unicode (Trojan Source class). Clarified that the ~200-line cap and hub-skill ban apply to SKILL.md itself, not a knowledge skill's own references/ files. - tests: contracts for the knowledge-base layout, the three embedded standards blocks, and the source-hygiene coverage. - docs: skills.md documents the knowledge-base shape.
A manual cronjob run executed the job synchronously on the calling agent's tool thread. A cron job is a full agent run that routinely takes minutes to hours, so the parent turn sat inside ONE tool call the whole time: uninterruptible (the interrupt flag is only checked between loop iterations) and serial (a batch of manual runs executed one by one). A Telegram session that kicked off dozens of new jobs 'right now' was wedged for hours ignoring every interrupt. action='run' now rides the async-delegation rail delegate_task background mode uses: the at-most-once claim is taken synchronously (so paused/missing/already-firing jobs still report immediately), the run executes on the shared daemon executor, the tool returns at once with a delegation handle, and the job's outcome re-enters the conversation as a type='async_delegation' completion event through the existing completion-queue drains (CLI + gateway) — preserving message-role alternation and the prompt cache. Sync fallbacks preserved: - no routable session (direct Python callers, hermes cron run) - async delivery unsupported (hermes -z, cron child sessions, Kanban workers, stateless HTTP) - dispatch pool at capacity (claim already taken — runs inline rather than stranding it) The completion block reports ok/failure, delivery target, next scheduled run, and an excerpt of the job's saved output.
Salvaged from PR NousResearch#53395 by @izumi0uu: the fire claim's 300s TTL is routinely outlived by real cron jobs, so claim_job_for_fire alone cannot stop a manual cronjob(action='run') from double-firing a job the ticker (or another manual run) is still executing. Extract the ticker's _submit_with_guard running-set check into shared module-level helpers (try_register_running_job / release_running_job) and register manual runs through the same set — one dedupe owner, no drift. Manual runs also become visible to get_running_job_ids (the gateway shutdown drain, NousResearch#60432) and mark_running_jobs_interrupted, which previously could not see them. The background dispatch path pre-checks the running set so a mid-run job reports 'already running' in the tool response immediately instead of as a delayed error completion event; the authoritative atomic check remains in _run_claimed_job on the worker. Co-authored-by: izumi0uu <izumi0uu@gmail.com>
Pins the scheduler-boundary contract: extra_prompt is appended under '## Run Context', does not mutate job['prompt'], and the header is absent when extra_prompt is omitted. Addresses review feedback from harjothkhara on PR NousResearch#57342.
…esearch#57331) Salvaged from PR NousResearch#57342 by @liuhao1024 (with the injection-scan half from PR NousResearch#57360 by @ghedeselmabot): cronjob(action='run', prompt=...) silently discarded the prompt argument — per-run context never reached the spawned cron session. The prompt is now threaded as extra_prompt through the whole chain (cronjob run action → _try_dispatch_background_run/_execute_job_now → _run_claimed_job → run_one_job → run_job → _build_job_prompt) and appended to the stored prompt under a '## Run Context' header for that single fire only — never persisted to the job definition. It passes the same strict _scan_cron_prompt injection scan as stored prompts before firing, and works identically on the background and sync fallback paths. Test fakes across tests/cron/ updated to accept the new kwargs (sibling-test blast radius from the signature change). Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
… standalone send Fixes NousResearch#61495 When manually triggering cron jobs from a live Matrix session, delivery would fail with "Timeout context manager should be used inside a task" because the aiohttp.ClientTimeout context manager requires a proper asyncio task context. Use asyncio.wait_for() instead of aiohttp.ClientTimeout to avoid this error, following the same pattern as the Weixin platform (gateway/platforms/weixin.py). Changes: - Remove aiohttp.ClientTimeout(total=30) from ClientSession constructor - Wrap the send operation in a nested async function (_do_send) - Use asyncio.wait_for(_do_send(), timeout=30) for timeout handling - Catch asyncio.TimeoutError explicitly and return clear error message
Covers the behavior shipped in NousResearch#80807 (background dispatch for cronjob action='run') and NousResearch#80838 (per-run '## Run Context' prompt, gateway-loop delivery): immediate return with handle, completion re-entering the conversation, in-flight dedupe, transient context injection with prompt scanning, and the sync fallbacks.
…open fix) Adversarial review of the salvaged recovery found a reachable fail-open: compression continuations inherit the rotated agent's model_config verbatim (publish_compression_child callers pass agent._session_init_model_config), so a delegate subagent's continuation carries _delegate_from=<the delegate's own parent>. The marker-PRESENCE filters in reopen_orphaned_compression_session and find_live_compression_child misclassified such a REAL continuation as a delegate child: - reopen: parent 'orphaned' -> reopened while a live continuation exists -> two live heads in one lineage (verified with a live repro) - find_live: adoption misses the continuation (fail-closed, masked the fork pre-PR; the PR made it active) Fix: markers only disqualify a child when they point at the queried parent (shared _NON_CONTINUATION_CHILD_FILTER_SQL fragment, also resolving the duplicated-SQL drift risk flagged by the reuse reviewer). Both directions regression-tested: reopen fails closed on an inherited-marker continuation; find_live adopts it. Also from review: reopen-failure log raised debug->warning (the failure hard-fails the turn moments later), commit-semantics hardening comment on the lease DELETE path, blank-line nit. The three read-only projection walks (get_compression_tip, list_sessions_rich chain, resume walk) share the marker-presence shape but fail closed (skip a continuation -> resume shows the parent), and the fixed adoption path self-heals that case at turn start; left as-is.
Platform registry create_adapter() gates on check_fn before the adapter exists, so wiring the passive probe permanently blocked connect() and the existing check_teams_requirements() lazy-install never ran.
Step 5 only showed docker compose from a clone; native/systemd users hit missing compose files and PEP 668 system-pip failures.
…ive installer) PlatformEntry.check_fn served three contradictory roles: adapter-creation gate, config auto-enablement gate, and status display. Plugins had to pick one function for all three: - Active installer as check_fn (discord/slack/telegram/matrix/dingtalk/ feishu): every status display could pip-install SDKs as a side effect (the desktop 94% boot-loop class). - Passive probe as check_fn (teams, wecom_callback): create_adapter() returned None before connect() could lazy-install, so the SDK never installed (NousResearch#79812 deadlock; wecom_callback's platform.wecom_callback LAZY_DEPS entry was dead code). The split makes both call sites correct by construction: - check_fn is now contractually PASSIVE (probe only, never installs). - New optional PlatformEntry.ensure_deps_fn is the ACTIVE installer; create_adapter() runs it exactly when check_fn is False — the platform is enabled+configured and the gateway is about to connect it. - Config enablement keeps a configured platform whose deps are missing but installable; the install itself is deferred to create_adapter(). - Status surfaces (_platform_status, hermes status) read only the passive probe and can never trigger pip. Migrated all lazy-installable platform plugins to the split; platforms with no optional deps (irc/ntfy/buzz/simplex/line/a2a/...) are unchanged — no ensure_deps_fn means a False check_fn stays a hard block. wecom_callback gains a working installer for the first time. Builds on @xxxigm's NousResearch#79812 (both commits cherry-picked with authorship preserved), reworking the check_fn swap into the two-field split so the Teams fix doesn't reintroduce install-on-status.
- gateway/config.py: rewrite the stale enablement-pass header comment that still described check_fn as 'the single source of truth for are-my-env- vars-set' / 'lazy-installs it' — both false under the new contract. - teams: check_requirements docstring wrongly claimed credential checks (body checks only SDK/aiohttp presence); derive install_hint from the canonical LAZY_DEPS pins + sys.executable instead of hardcoding '~/.hermes/hermes-agent/venv/bin/pip' and version pins (wrong under HERMES_HOME overrides / profile installs; pins go stale on CVE bumps); connect() fatal-error hints now point at the venv pip instead of bare system pip (the PEP 668 trap the docs warn about). - teams docs: drop exact version pins from the two manual-install commands (LAZY_DEPS is the source of truth; unpinned installs still work and the text can't go stale). - hermes_cli/status.py: per-entry exception guard around check_fn so one raising probe can't abort the listing of all remaining plugin platforms (aligns with the other three call sites). - tests: rename test_register_check_fn_is_active_lazy_installer -> test_register_splits_passive_probe_from_active_installer (name said the opposite of what it verifies).
- matrix/dingtalk: extract deps-only installers (ensure_matrix_deps, ensure_dingtalk_deps) and register THOSE as ensure_deps_fn — the prior check_*_requirements combined credential env checks with the install, so a platform configured via PlatformConfig.extra (which is_connected accepts) would pass enablement, reach create_adapter(), and have the 'installer' veto on env-var grounds before installing anything — re-creating the NousResearch#79812 deadlock for extra-configured setups. The combined deps+credentials functions remain for setup/status callers. - matrix/feishu passive probes: use the existing lazy_deps.is_available() instead of hand-rolling 'not feature_missing(...)' (reuse finding). - teams: module docstring no longer recommends bare system pip (the PEP 668 trap purged everywhere else); docs troubleshooting row updated to match the new hint text. - wecom_callback: drop dead 'global ET, DEFUSEDXML_AVAILABLE' (ensure_and_bind mutates the module dict directly; nothing assigns). - tests: parametrized wiring contract for all 8 lazy-installable platforms — ensure_deps_fn present and distinct from check_fn (behavior contract, not identity snapshot, so renames don't churn it).
…pip=True) Fold the remaining simplify-code reuse finding: teams' _install_hint() duplicated lazy_deps' spec-fetch + quote + join (feature_install_command already builds pip commands from LAZY_DEPS). Add a venv_pip=True variant to feature_install_command — sys.executable -m pip targeting, correct in every install layout and immune to PEP 668 — and shrink the teams helper to a one-line call. Also gives matrix and the other platforms a shared derived hint to adopt later. New test mutation-checked (fails when venv_pip returns the uv form).
Owner
Author
|
Closing this automation PR as a stale prepared candidate, not as a rejected upstream sync. The protected baseline is still 4371712 while current upstream is 03fa32c (907 baseline-aware commits ahead). The 2026-08-10 04:00 preparation failed before publication on six unresolved files: |
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.
Automated fail-closed upstream sync. Locally verified head: 2be2098