Skip to content

merge(upstream): merge upstream/main (~1,949 commits) — Tier 2C + plugin v1.0.0 docs#26

Merged
deestax merged 1955 commits into
mainfrom
merge-upstream
May 8, 2026
Merged

merge(upstream): merge upstream/main (~1,949 commits) — Tier 2C + plugin v1.0.0 docs#26
deestax merged 1955 commits into
mainfrom
merge-upstream

Conversation

@deestax
Copy link
Copy Markdown
Member

@deestax deestax commented May 8, 2026

Summary

Tier 2C of the Myah OSS Completion epic. Merges ~1,949 upstream commits from NousResearch/Hermes-Agent@main into the fork (feat/myah-oss-completion-2bmerge-upstream), then publishes plugin v1.0.0 with a documented hermes-agent compatibility window.

Parent-repo companion PR: T3-Venture-Labs-Limited/myah#TBD (must merge AFTER this PR per the submodule-bump ordering rule in AGENTS.md).

What's in this PR

Two commits:

  1. merge(upstream): merge upstream/main (~1,949 commits, 249 conflicts)
    The actual merge. See "Resolution" below.

  2. docs(plugin): publish v1.0.0 — hermes-agent compatibility window + CHANGELOG

    • Bumps plugins/myah-hermes-plugin/pyproject.toml 0.3.0 → 1.0.0.
    • Adds the hermes-agent compatibility window comment block per Tier 2C §12.
    • Creates plugins/myah-hermes-plugin/CHANGELOG.md with the v1.0.0 entry covering: verified hermes SHA, Mode D 9/9 status, vendored-feature inventory (F1-F7), F5 deferral note (BOOT.md → U-HOOK), distribution path.
    • Replaces "Skeleton only" plugin README with v1.0.0 install instructions, compatibility matrix, and feature inventory.
    • Removes plugins/platforms/irc/PLUGIN.yaml (uppercase) — upstream uses lowercase plugin.yaml; case-sensitivity left both in the index after the merge.
    • Marker-hygiene cleanup: gateway/platforms/api_server.py:664 _run_sessions inline marker normalized to # Myah: convention.

Resolution breakdown

Tier Files % Strategy
1 (mechanical) 238 95.6% git checkout --theirs — pure upstream drift, no Myah markers
2 (surgical) 9 3.6% Take upstream as base, re-apply Myah marker blocks
3 (architectural) 2 0.8% Rebase markers onto upstream's restructured handlers

Better than the spec's 78/17/5 estimate — Tier 2A and 2B successfully reduced architectural conflict surface.

Tier 2 surgical files (full list)

  • toolsets.pyhermes-myah toolset entry + hermes-gateway includes list
  • tools/approval.py — F1 cron approval primitives (request_action_confirmation, _action_queues, resolve_action_confirmation_by_session)
  • tools/skills_tool.py — F4 session-keyed secret capture (4 markers covering imports, ContextVar storage, callback registration, _capture_required_environment_variables plumbing)
  • tools/mcp_tool.py — F7 disconnect_mcp_server helper
  • tools/cronjob_tools.py — TAKEN UPSTREAM VERBATIM per spec §3 Task 2A.2 architectural intent. The plugin's myah_tools/cron_tool.py shadows via last-writer-wins; the only Myah-specific change reapplied is the myah:<chat_id>:<thread_id> example in the deliver schema description.
  • hermes_cli/web_server.pyHERMES_WEB_SESSION_TOKEN env override
  • hermes_cli/config.pyfollow_up_generation auxiliary task config
  • docker/entrypoint.shMYAH_PLATFORM_BASE_URL / MYAH_PLATFORM_BEARER exports
  • scripts/release.py — team contributor email→handle mapping

Tier 3 architectural adaptations

gateway/run.py (12 markers, 12,191 lines → 15,881 post-merge):

  • _dispatch_approval_notify (~128 LOC) module-level helper relocated to line 589.
  • F1 resolve/deny endpoints rebased onto upstream's restructured POST /approve and /deny handlers.
  • Telemetry hook + secret-capture imports threaded through upstream's restructured _run_agent.
  • Structured-callback agent loop adapted to upstream's flat callback assignment pattern.
  • Triple-path run_conversation wrapper restored — replaced upstream's plain try/finally with telemetry-aware variant that also cleans up secret callbacks across all three branches.

gateway/platforms/api_server.py (15 block + 14 inline markers, 2,991 → 3,490 post-merge):

  • F1 confirmation route POST /v1/runs/{run_id}/confirm placed adjacent to upstream's new _handle_get_run route.
  • Bug C-agent origin field validation block rebased onto upstream's job-creation handler.
  • U2 register_pre_setup_hook + _shared_app global preserved at module level.
  • Sentry trace continuation middleware (sentry_trace_middleware) inserted into upstream's middleware tuple alongside upstream's new client_max_size=MAX_REQUEST_BYTES.
  • Per-request model override + reasoning-token streaming inline markers preserved.
  • _run_sessions: Dict[str, str] instance attribute preserved (with marker hygiene fix in commit 2).

Tier 2B regression repair

Two pieces of Tier 2B code were missing Myah markers and got dropped in Tier 1's mechanical resolution. Restored with proper markers this time:

  • gateway/platforms/base.py:2221BasePlatformAdapter.build_delivery_metadata (Tier 2B Task 2B.4 / Phase 4f). The polymorphic optional method that replaces the legacy if platform_name == "myah" branch in cron/scheduler.py.
  • cron/scheduler.py:563 — the polymorphic call site runtime_adapter.build_delivery_metadata(...) plus the status_hint: str = "ok" parameter on _deliver_result and the caller passing status_hint="ok" if success else "error" at line 1703.

Both are wrapped in # ── Myah: ── markers so future merges don't lose them again.

Test results

Pre-merge Post-merge Delta
tests/gateway/ passed 3,883 4,943 +1,060 (upstream's new tests)
tests/gateway/ failed 17 27 +10 (upstream-native failures, see below)
tests/cron/ passed n/a 320
tests/cron/ failed n/a 4 upstream-native (missing croniter, etc.)
Plugin tests passed 153 195 +42 (new ones from agent/Dockerfile.stock work)
Plugin tests failed 1 1 unchanged (pre-existing secrets in CONFIGURABLE_TOOLSETS issue)

The 10 new gateway-test failures are all in upstream-side platform tests (google_chat, feishu_bot_admission, discord_free_response) for code paths Myah never touched. Likely missing optional dependencies / test fixtures, NOT Myah-introduced regressions.

4 baseline failures FIXED by upstream (test_delivery, test_background_process_notifications, test_matrix, test_run_progress_topics).

The Tier 2B build_delivery_metadata regression was caught and fixed during Phase 5 validation — tests/gateway/test_build_delivery_metadata.py now passes 4/4.

Marker integrity (post-merge)

File Block open Block close Inline
gateway/run.py 12 14† 2
gateway/platforms/api_server.py 15 15 15
gateway/platforms/base.py 1 1 0
cron/scheduler.py 2 2 1
tools/approval.py 5 5 1
tools/skills_tool.py 4 4 0
tools/mcp_tool.py 1 1 0
tools/cronjob_tools.py 0 0 1
toolsets.py 1 1 1
hermes_cli/web_server.py 1 1 0
hermes_cli/config.py 1 1 0
docker/entrypoint.sh 1 1 0
scripts/release.py 1 3† 0

† False-positive closers from non-Myah decoration in upstream code (matches the # ─{40,}$ pattern but not paired with Myah openers). Real Myah closer counts match opener counts in every file.

Verification commands

Post-merge state:

cd agent/hermes
git checkout merge-upstream
python3 -m venv venv && venv/bin/pip install -e ".[dev]"
scripts/run_tests.sh tests/gateway/ -q --tb=no  # 4943 passed / 27 failed / 75 skipped
scripts/run_tests.sh tests/gateway/test_build_delivery_metadata.py -q  # 4 passed
cd plugins/myah-hermes-plugin
../../venv/bin/pytest tests/ --tb=no -q --ignore=tests/test_myah_admin_*.py  # 195 passed / 1 failed (pre-existing)

Residual conflicts dry-run (Task 2C.2):

cd agent/hermes
git fetch upstream
git merge --no-commit --no-ff upstream/main  # "Already up to date." — 0 residual conflicts
git merge --abort  # nothing to abort

Spec & plan references

  • Spec: docs/superpowers/specs/2026-05-06-myah-oss-completion-design.md v3 (§6 Tier 2C, §11 Phase A, §12 version compatibility)
  • Plan: docs/superpowers/plans/2026-05-06-myah-oss-completion-2c-upstream-merge.md v1
  • Resolution plan: docs/superpowers/plans/2026-05-08-hermes-upstream-merge.md (this PR)

Companion-PR ordering

This hermes-fork PR MUST merge BEFORE the parent-repo PR that bumps the submodule pointer (per AGENTS.md "merging branches that changed the hermes submodule"). The parent PR will pin to the squash-merge SHA produced by merging this PR.

Out of scope (deferred to next PRs)

  • §11 Phase B/C/D production cutover — phased rollout requires production access + multi-day observation. Documented in spec; flagged for the operator post-merge.
  • §5 upstream PRs (U1-U7, U-CRON, U-MCP, U-HOOK) — 0/11 currently filed against NousResearch/Hermes-Agent. Each requires explicit user approval before filing per spec §5.

teknium1 and others added 30 commits May 6, 2026 09:08
…ative providers (NousResearch#20802)

OpenCode Go and OpenCode Zen are flat-namespace model resellers — their
/v1/models returns bare IDs (deepseek-v4-flash, minimax-m2.7), and the
inference API rejects vendor-prefixed names with HTTP 401 'Model not
supported'. Two bugs fixed:

1. `switch_model` in hermes_cli/model_switch.py was silently switching the
   user off opencode-go to native deepseek when they typed
   `/model deepseek-v4-flash`. Step d found the model in opencode-go's live
   catalog, but step e (detect_provider_for_model) still ran and matched
   the bare name against deepseek's static catalog. Fix: track whether
   the live catalog resolved it; skip step e when it did.

2. `normalize_model_for_provider` in hermes_cli/model_normalize.py only
   stripped the exact `opencode-zen/` prefix, leaving arbitrary vendor
   prefixes like `minimax/minimax-m2.7` (commonly copied from aggregator
   slugs into fallback_model configs) intact — causing HTTP 401s when
   the fallback chain activated. Fix: opencode-go/opencode-zen strip ANY
   leading vendor prefix because their APIs are flat-namespace.

Tests: 11 new cases in tests/hermes_cli/test_opencode_go_flat_namespace.py
covering both normalization (prefix stripping, regression guards for
opencode-zen Claude hyphenation and openrouter vendor-prepending) and
switch_model (bare-name resolution on opencode-go's live catalog must
not trigger cross-provider hijack).

Reported by @UFOnik via Discord; Kimi K2.6 always worked because moonshotai
has no overlapping entry in a native provider's static catalog. Deepseek
and minimax failed because their v4/v2.7 names existed in the native
deepseek/minimax catalogs.
…ze (NousResearch#20820)

Same Hermes Teal palette as the default theme, but with baseSize 18px,
lineHeight 1.65, and spacious density so the whole dashboard scales up.
Gives users a one-click bigger-text preset and a copyable reference for
authoring custom YAML themes with their own typography settings.
Introduce the foundation for independently selecting web search and
extract backends — enabling future combinations like SearXNG for
search + Firecrawl for extract.

Architecture:
- tools/web_providers/base.py: WebSearchProvider and WebExtractProvider
  ABCs with normalized result contracts (mirrors CloudBrowserProvider)
- tools/web_tools.py: _get_search_backend() and _get_extract_backend()
  read per-capability config keys, fall through to shared web.backend
- hermes_cli/config.py: web.search_backend and web.extract_backend in
  DEFAULT_CONFIG (empty = inherit from web.backend)

Behavioral change:
- web_search_tool() now dispatches via _get_search_backend()
- web_extract_tool() now dispatches via _get_extract_backend()
- When per-capability keys are empty (default), behavior is identical
  to before — _get_search_backend() falls through to _get_backend()

This is purely structural — no new backends are added. SearXNG and
other search-only/extract-only providers can now be added as simple
drop-in modules in follow-up PRs.

12 new tests, 49 existing tests pass with zero regressions.

Ref: NousResearch#19198
Adds SearXNG as a free, self-hosted web search provider.  SearXNG is a
privacy-respecting metasearch engine that requires no API key — just a
running instance and SEARXNG_URL pointing at it.

## What this adds

- `tools/web_providers/searxng.py` — `SearXNGSearchProvider` implementing
  `WebSearchProvider` (search only; no extract capability)
- `_is_backend_available("searxng")` — gates on SEARXNG_URL
- `_get_backend()` — accepts "searxng" as a configured value; adds it to
  auto-detect candidates (lower priority than paid services)
- `web_search_tool` — dispatches to SearXNG when it is the active backend
- `check_web_api_key()` — includes SearXNG in availability check
- `OPTIONAL_ENV_VARS["SEARXNG_URL"]` — registered with tools=["web_search"]
- `tools_config.py` — SearXNG appears in the `hermes tools` provider picker
- `nous_subscription.py` — `direct_searxng` detection, web_active / web_available
- `setup.py` — SEARXNG_URL listed in the missing-credential hint
- 23 tests covering: is_configured, happy-path search, score sorting, limit,
  HTTP/request errors, _is_backend_available, _get_backend, check_web_api_key

## Config

```yaml
# Use SearXNG for search, any paid provider for extract
web:
  search_backend: "searxng"
  extract_backend: "firecrawl"

# Or: SearXNG as the sole backend (web_extract will use the next available)
web:
  backend: "searxng"
```

SearXNG is search-only — it does not implement WebExtractProvider.  Users
who only configure SEARXNG_URL get web_search available; web_extract falls
back to the next available extract provider (or is unavailable if none).

Closes NousResearch#19198 (Phase 2 Task 4 — SearXNG provider)
Ref: NousResearch#11562 (original SearXNG PR)
Closes the remaining gaps from PR NousResearch#11562 that weren't covered by the
core SearXNG integration landed in NousResearch#20823.

- optional-skills/research/searxng-search/ — installable skill with
  SKILL.md (curl-based usage, category support, Python example) and
  searxng.sh helper script for health checks and instance queries
- website/docs/user-guide/configuration.md — SearXNG added to the
  Web Search Backends section (5 backends, backend table, per-capability
  split config example, correct search-only note)
- website/docs/reference/environment-variables.md — SEARXNG_URL row
- website/docs/reference/optional-skills-catalog.md — searxng-search entry

The core SearXNG code, OPTIONAL_ENV_VARS, hermes tools picker, and tests
were already on main via NousResearch#20823.  This commit is purely additive docs +
the optional skill scaffold.

Credits from NousResearch#11562 salvage:
  @w4rum — original _searxng_search structure
  @nathansdev — tools_config.py integration
  @moyomartin — category support and result formatting
  @0xMihai — config/env var approach
  @nicobailon — skill and documentation structure
  @searxng-fan — error handling patterns
  @Local-First — self-hosted-first philosophy and docs
Route Feishu topic progress, status, approval, stream, and fallback messages through threaded replies by preserving the originating message id as the reply target. Add regressions for tool progress topic metadata and Feishu metadata-driven reply routing.
- Remove dead metadata.get('reply_to') fallback in _send_raw_message;
  nothing in the codebase ever sets 'reply_to' inside a metadata dict —
  the key only appears as a top-level send_voice() keyword argument
- Simplify _status_thread_metadata construction in run.py to use a
  single dict literal instead of create-then-mutate pattern; the
  or-{} guard was dead since source.thread_id implies _progress_thread_id
  is also set for Feishu
- Add yuqian@zmetasoft.com to AUTHOR_MAP for contributor attribution
- Expand migration comment to name the primary failure mode (missing
  column OperationalError from NousResearch#20842) ahead of the secondary SQLite
  schema-reparse concern; also document the stale-cols-snapshot invariant
- Add clarifying comments on from_row() legacy fallback branches noting
  they are belt-and-suspenders dead code post-migration
- Add task_events comment in existing test explaining why the table is
  required by the migrator
- Add test_legacy_migration_no_legacy_columns_at_all: Scenario A —
  explicitly asserts the exact NousResearch#20842 crash no longer occurs and that
  consecutive_failures defaults to 0 on a DB that never had spawn_failures
- Add test_legacy_migration_both_columns_already_present: Scenario D —
  asserts the migration is a no-op when both columns already exist,
  preserving the existing counter value
Switch top-level concurrency to cancel-in-progress=false so every push
to main gets its own SHA-tagged image published — no more discarded
builds when commits land back-to-back.

Guard the :latest tag with a second job that has its own concurrency
group with cancel-in-progress=true plus a git-ancestor check against
the revision label on the current :latest. Together these guarantee
:latest only ever moves forward in history: a slower run whose commit
isn't a descendant of the current :latest refuses to clobber it, and
a newer push mid-way through the move-latest job preempts the older
one before it can retag.

- Every main push publishes nousresearch/hermes-agent:sha-<commit>
  with an org.opencontainers.image.revision label embedded.
- move-latest job reads that label off :latest, runs merge-base
  --is-ancestor, and only retags (via buildx imagetools create,
  registry-side, no rebuild) if our commit strictly descends.
- fetch-depth bumped to 1000 so merge-base has the history it needs.
- Release tag flow unchanged (unique tag, no race).
…ch#20827)

Previous version read like internal API docs \u2014 leading with env var tables,
config YAML, and 'precedence' rules before ever explaining the product.
Complete rewrite inverts the structure so readers see value first,
mechanics last.

Structure now:
- Lede: 'One subscription. Every tool built in.' + pitch paragraph
- CTA: subscribe/manage button styled as a real call-to-action
- What's included: emoji-led table with expanded descriptions per tool.
  Image gen lists all 9 models by name (FLUX 2 Klein/Pro, Z-Image Turbo,
  Nano Banana Pro, GPT Image 1.5/2, Ideogram V3, Recraft V4 Pro, Qwen)
- Why it's here: value bullets \u2014 one bill, one signup, one key, same
  quality, bring-your-own anytime
- Get started: two-command flow (hermes model \u2192 hermes status)
- Eligibility: paid-tier note with upgrade link
- Mix and match: three realistic usage patterns
- Using individual image models: ID reference table for power users
- --- separator ---
- Configuration reference (demoted): use_gateway flag, disabling,
  self-hosted gateway env vars moved below the fold where they belong
- FAQ: streamlined, removed redundant content

Fact-checked against code:
- 9 FAL models confirmed from tools/image_generation_tool.py FAL_MODELS
- Status section output verified against hermes_cli/status.py
- Portal subscription URL preserved
- Self-hosted env vars (TOOL_GATEWAY_DOMAIN etc.) kept accurate

Verified: docusaurus build SUCCESS, page renders, no new broken links.
…n profile

Profile processes (kanban workers, cron subprocesses, delegated subagents)
read the profile's auth.json only. If a provider was authenticated at the
global root but not inside the profile, the profile's credential_pool
comes back empty and the process fails with 'No LLM provider configured'
— even though the credentials are sitting in ~/.hermes/auth.json. NousResearch#18594
propagated HERMES_HOME correctly, which is what surfaced this: workers
now land in the right profile, and the profile turns out to shadow global
with no fallback.

Semantics (read-only, per-provider shadowing):
* Profile has any entries for provider X → use profile only (global ignored).
* Profile has zero entries for provider X → fall back to global.
* Writes (write_credential_pool, _save_auth_store) still target the profile.
* Classic mode (HERMES_HOME == global root) skips the fallback entirely —
  _global_auth_file_path() returns None.

Also mirrors the fallback in get_provider_auth_state so OAuth singletons
(nous, minimax-oauth, openai-codex, spotify) inherit cleanly — the Nous
shared-token store (PR NousResearch#19712) remains the authoritative path for Nous
OAuth rotation, this just makes the read side consistent with it.

Seat belt: _load_global_auth_store() refuses to read the real user's
~/.hermes/auth.json under PYTEST_CURRENT_TEST even when HERMES_HOME points
to a profile-shaped path. Guard uses $HOME (stable across fixtures) rather
than Path.home() (which fixtures often monkeypatch to a tmp root).

Reported by @SeedsForbidden on Twitter as the credential_pool shadowing
follow-up to the NousResearch#18594 fix.
Adds an opt-out toggle on PlatformConfig that gates both restart
lifecycle pings: the "♻ Gateway restarted" message sent to the chat
that issued /restart, and the "♻️ Gateway online" home-channel
startup notification. Defaults to True so existing deployments are
unaffected.

The motivating split is operator vs. end-user surfaces: a back-channel
like Telegram should keep these pings, while a Slack workspace shared
with end users should not surface gateway lifecycle noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend the gateway_restart_notification flag to cover
_notify_active_sessions_of_shutdown — the message that fires just
before drain ("⚠️ Gateway restarting — Your current task will be
interrupted. Send any message after restart and I'll try to resume
where you left off.") sent to active sessions and home channels.

Same operator/end-user reasoning: on a Slack workspace shared with
end users, "Gateway restarting" reads as "the bot is broken" — the
operator should be able to suppress it consistently with the other
two lifecycle pings rather than having a partial opt-out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ci(docker): don't cancel overlapping builds, guard :latest
* fix(tui): steady transcript scrollbar

Keep the visible scrollbar tied to committed viewport position while virtual history can still prefetch against pending scroll targets, and preserve drag grab offset synchronously for native-feeling scrollbar drags.

* fix(tui): smooth precision wheel scroll

Replace the opt-scroll throttle with frame-sized coalescing so modifier wheel gestures stay line-precise without stepping.
* fix(tui): restore classic CLI voice push-to-talk parity

(cherry picked from commit 93b9ae3)

* fix(tui): harden voice push-to-talk stop flow

Address review feedback from PR NousResearch#16189 by stopping the active recorder before background transcription, documenting single-shot voice capture, and covering the TUI gateway flags with regression tests.

* fix(tui): preserve silent voice strike tracking

Keep single-shot voice recording's no-speech counter alive across starts so the TUI can still emit the three-strikes auto-disable event, and bind the auto-restart state at module scope for type checking.

* fix(tui): clean up voice stop failure path

Address follow-up review by naming the TUI flow as single-shot push-to-talk and cancelling the recorder when forced stop cannot produce a WAV.

* fix(tui): report busy voice capture starts

Return explicit start state from the voice wrapper so the TUI gateway does not report recording while forced-stop transcription is still cleaning up.

* fix(tui): handle busy voice record responses

Apply the gateway busy status immediately in the TUI and route forced-stop voice events to the session that sent the stop request.

* fix(tui): clear voice recording on null response

Treat a null voice.record RPC result as a failed optimistic start so the REC badge cannot stick after gateway-side errors.

* fix(tui): count silent manual voice stops

Preserve single-shot voice no-speech strikes through forced stop transcription so empty push-to-talk captures still trigger the three-strikes guard.

---------

Co-authored-by: Montbra <montbra@gmail.com>
… is installed

The setup wizard dropped non-root users at a bare shell prompt when
trying to start a system-scope gateway service. Previously
_require_root_for_system_service called sys.exit(1), which the
wizard's `except Exception` guards cannot catch (SystemExit is a
BaseException). Users with a pre-existing /etc/systemd/system unit
(e.g. from an earlier `sudo hermes setup` run) hit this whenever
they re-ran `hermes setup` as a regular user.

- Convert _require_root_for_system_service to raise a typed
  SystemScopeRequiresRootError (RuntimeError subclass) instead of
  sys.exit(1). The direct CLI path (`hermes gateway install|start|stop|
  restart|uninstall` without sudo) still exits 1 cleanly via a new
  catch at the top of gateway_command, matching the existing
  UserSystemdUnavailableError pattern.
- Add _system_scope_wizard_would_need_root() pre-check and
  _print_system_scope_remediation() helper. Both setup wizards
  (hermes_cli/setup.py and hermes_cli/gateway.py::gateway_setup) now
  detect the dead-end before prompting and print actionable guidance:
  either `sudo systemctl start <service>` this time, or uninstall the
  system unit and install a per-user one.
- Defense-in-depth: all 5 wizard prompt sites also catch
  SystemScopeRequiresRootError and fall back to the remediation
  helper if the pre-check is bypassed (race, etc.).

Tests: 12 new tests in TestSystemScopeRequiresRootError,
TestSystemScopeWizardPreCheck, TestSystemScopeRemediationOutput, and
TestGatewayCommandCatchesSystemScopeError covering the exception
contract, pre-check matrix (root vs non-root, system-only vs
user-present vs none vs explicit system=True), remediation output
for each action, and the direct-CLI exit-1 path.
Previously, /personality in the TUI called _reset_session_agent() which
destroyed the agent, cleared conversation history, and effectively started
a new session. This made personality switching disruptive — users lost
their entire conversation context.

Now /personality updates the agent's ephemeral_system_prompt in-place and
injects a pivot marker into the conversation history. The marker tells
the model to adopt the new persona from that point forward, which is
necessary because LLMs tend to pattern-match their prior responses and
continue the established tone without an explicit signal.

Changes:
- tui_gateway/server.py: Rewrite _apply_personality_to_session to update
  the agent in-place instead of resetting. Inject a user-role pivot
  marker so the model actually switches style mid-conversation.
- ui-tui/src/app/slash/commands/session.ts: Update help text (no longer
  mentions history reset).
- tests/test_tui_gateway_server.py: Update test to verify history is
  preserved, pivot marker is injected, and ephemeral prompt is set.
Two follow-ups on top of helix4u's slash-command sync hardening:

- Only suppress exceptions that are actually Discord 429 rate limits
  (discord.RateLimited, HTTPException with status 429, or a clearly
  rate-limit-named duck type). Arbitrary failures that happen to expose
  a retry_after attribute now re-raise to the outer handler instead of
  silently swallowing a cooldown.
- Move the sync-state JSON under $HERMES_HOME/gateway/ so the home root
  stops collecting ad-hoc runtime files.

Added a test verifying unrelated exceptions don't get misclassified as
rate limits.
…0960)

Follow-up to NousResearch#20958. The worker skill section had the same stale
'hermes skills install devops/kanban-worker' command — kanban-worker
is also bundled, so that command fails with 'Could not fetch from any
source.'

Replace with bundled-skill verification + restore pattern, matching
the orchestrator section. Uses <your-worker-profile> placeholder since
assignees vary (researcher, writer, ops, linguist, reviewer, etc.)
rather than a single fixed 'worker' profile.
adybag14-cyber and others added 20 commits May 7, 2026 13:04
…ousResearch#21435)

* feat(kanban): add `specify` — auxiliary LLM fleshes out triage tasks

The Triage column shipped with a placeholder 'a specifier will flesh
out the spec', but the specifier itself was never built. This wires
it up as a dedicated CLI verb.

`hermes kanban specify <id>` calls the auxiliary LLM (configured under
`auxiliary.triage_specifier`) to expand a rough one-liner into a
concrete spec — tightened title plus a body with Goal / Approach /
Acceptance criteria / Out-of-scope sections — then atomically flips
`status: triage -> todo` and recomputes ready so parent-free tasks
go straight to the dispatcher on the same tick.

Surface:

  hermes kanban specify <task_id>               # single task
  hermes kanban specify --all [--tenant T]      # sweep triage column
  hermes kanban specify ... --author NAME       # audit-comment author
  hermes kanban specify ... --json              # one JSON line per task

Design choices:

  - Parent gating is preserved. specify_triage_task flips to 'todo',
    then recompute_ready promotes to 'ready' only when parents are
    done — same rule as a normal parent-gated todo.
  - No daemon, no background watcher. Every invocation is explicit —
    keeps cost predictable and doesn't fight the dispatcher loop.
  - Response parse is lenient: strict JSON preferred, markdown-fence
    tolerated, raw-body fallback on malformed JSON so the LLM can't
    strand a task in triage.
  - All failure modes (no aux client, API error, task moved out of
    triage mid-call) return SpecifyOutcome(ok=False, reason=...) so
    --all continues past individual failures.

Changes:

  hermes_cli/kanban_db.py    + specify_triage_task()
  hermes_cli/kanban_specify.py  NEW (~220 LOC — prompt, parse, call)
  hermes_cli/kanban.py       + specify subcommand + _cmd_specify
  hermes_cli/config.py       + auxiliary.triage_specifier task slot
  website/docs/user-guide/features/kanban.md  specify + config notes
  website/docs/reference/cli-commands.md      CLI reference entry
  tests/hermes_cli/test_kanban_specify_db.py    NEW (10 tests)
  tests/hermes_cli/test_kanban_specify.py       NEW (20 tests)

Validation: 30/30 targeted tests pass. E2E: triage task -> specify ->
ends in 'ready' with events [created, specified, promoted] and the
audit comment recorded under the configured author.

* feat(kanban): wire specifier into dashboard and gateway slash

Follow-ups to the initial PR NousResearch#21435 — closes the two gaps I'd left as
post-merge: dashboard button and first-class gateway surface.

Dashboard (plugins/kanban/dashboard/)
  - POST /tasks/:id/specify  NEW endpoint. Thin wrapper around
    kanban_specify.specify_task(). Returns the CLI outcome shape
    ({ok, task_id, reason, new_title}); ok=false with a human reason
    is a 200, not a 4xx, so the UI can render it inline without
    treating 'no aux client configured' as a crash.
  - Runs sync in FastAPI's threadpool because the LLM call can take
    tens of seconds on reasoning models.
  - Pins HERMES_KANBAN_BOARD around the specify call so the module's
    argless kb.connect() lands on the right board.
  - dist/index.js: doSpecify callback threaded through the drawer →
    TaskDetail → StatusActions prop chain. ✨ Specify button appears
    ONLY when task.status === 'triage' (elsewhere the backend would
    reject anyway — hide the button to keep the action row clean).
    Busy state (Specifying…) + inline success/error banner under the
    button using the response.reason text.
  - dist/style.css: tiny hermes-kanban-msg-ok / -err classes using
    existing --color vars so themes reskin cleanly.

Gateway slash (/kanban specify)
  - Already works via the existing run_slash → build_parser →
    kanban_command pipeline. No code change needed — slash commands
    inherit the argparse tree automatically. Added coverage:
    test_run_slash_specify_end_to_end (create --triage, specify, verify
    promotion + retitle) and test_run_slash_specify_help_is_reachable.

Tests
  - tests/plugins/test_kanban_dashboard_plugin.py: 3 new tests for the
    REST endpoint — happy path, non-triage rejection as ok=false 200,
    missing aux client as ok=false 200.
  - tests/hermes_cli/test_kanban_cli.py: 2 new slash-surface tests.

Docs
  - website/docs/user-guide/features/kanban.md: dashboard action row
    description mentions ✨ Specify + all three surfaces. REST table
    gains /tasks/:id/specify. Slash examples include /kanban specify.

Validation: 340/340 targeted tests pass. E2E via TestClient: create a
triage task over REST → POST /specify with mocked aux client → task
moves to 'ready' column on /board with new title and body applied.
The existing mapping pointed to the wrong GitHub user (blakejohnson, id
866695, IBM) — the email actually belongs to voteblake (id 5585957),
confirmed via search/commits?author-email. Mis-credited since 323ca70.
…esearch#21494)

The kanban specifier landed in NousResearch#21435 with feature-page docs (the
kanban page itself + the CLI reference table), but three other docs
pages enumerate every auxiliary task slot and were missed:

  user-guide/configuration.md            Auxiliary Models section —
                                         interactive picker example
                                         + full auxiliary config
                                         reference YAML block.
  user-guide/features/fallback-providers.md
                                         Both 'Auxiliary Tasks' and
                                         'Fallback Reference' tables.
  user-guide/features/kanban-tutorial.md
                                         Triage-column bullet now
                                         mentions the ✨ Specify
                                         button + CLI + slash command.

No other docs enumerate the aux task slots (verified with
grep -r 'title_generation\|auxiliary.session_search' website/docs/).
…ing (NousResearch#21455)

- Add pricing entries for Claude Opus 4.5/4.6/4.7, Sonnet 4.5/4.6, and
  Haiku 4.5 with updated source URLs (platform.claude.com)
- Add _normalize_anthropic_model_name() to handle dot-notation variants
  (e.g. claude-opus-4.7 → claude-opus-4-7) for pricing lookups
- Fix silent token loss: ensure session row exists before UPDATE in both
  run_agent.py and hermes_state.py (INSERT OR IGNORE is idempotent)
- Log token persistence failures at DEBUG level instead of swallowing
  them silently — makes undercounted analytics diagnosable
- Surface reasoning tokens in CLI /usage and TUI usage panel
- Add 'reasoning' and 'cost_status' fields to TUI Usage type
channels_list was iterating directory.items() directly, yielding
("updated_at", str) and ("platforms", dict) pairs — neither passed
the isinstance(entries_list, list) check, so the inner loop never ran
and every call returned count=0 even when channel_directory.json was
populated.

The writer (gateway/channel_directory.py) wraps the payload as
{"updated_at": ..., "platforms": {...}}; every other reader in the
codebase unwraps via directory.get("platforms", {}). This aligns
channels_list with that convention.

Also tightens the existing test_channels_with_directory test, which
bypassed the bug by asserting against _load_channel_directory() directly
instead of calling channels_list. It now calls the tool end-to-end and
a new test_channels_with_directory_platform_filter covers the filter
path. Both tests fail against the pre-fix code.

Closes NousResearch#21474

Co-authored-by: chrisworksai <262485129+chrisworksai@users.noreply.github.com>
…udo -u

When the installer is run via , uv resolves config file
paths against the process owner's (root) home directory rather than the
effective user's, causing a Permission denied error when trying to read
/root/uv.toml.

Setting UV_NO_CONFIG=1 prevents uv from discovering any config files
(uv.toml, pyproject.toml) during installation, which is the correct
behavior for a bootstrap script that manages its own environment.

Fixes NousResearch#21269
…essions-skills-menu

feat(tui): add /sessions slash command for browsing and resuming previous sessions
…rsonality

fix(tui): preserve session when switching personality
…ch#21541)

Makes first-time use of the kanban view self-explanatory. Every control
that wasn't already labelled now has a `title` tooltip describing what
it does, and a `?` icon next to the board switcher opens the kanban
docs page in a new tab.

Coverage:
- BoardSwitcher: board select, + New board button, docs-link icon
  (both compact and full variants)
- BoardToolbar: Search, Tenant, Assignee, Show archived, Nudge
  dispatcher, Refresh
- BulkActionBar: → ready, Complete, Archive, reassign group, Apply,
  Clear
- Column header: hovering the header now surfaces COLUMN_HELP as a
  tooltip in addition to the visible sub-text; column count also
  labelled
- Card: task id, priority badge, tenant badge, assignee/unassigned,
  comment count, link count, age timestamp
- InlineCreate: assignee, priority, parent-task selectors

Closes the community feedback from @CharlieDePew asking for tooltips
and a docs link in the kanban view.

Relevant docs page:
https://hermes-agent.nousresearch.com/docs/user-guide/features/kanban
Route goal status notices through the platform adapter send API and register post-delivery callbacks so completed-goal notices appear after the final assistant response. Also cancel queued synthetic goal continuations on /goal pause and /goal clear while preserving normal queued user messages.
Weak judge models (e.g. deepseek-v4-flash) return empty strings or prose
when asked for the strict {done, reason} JSON verdict. The old code
failed-open to continue on every such turn, burning the entire turn
budget with log lines like

  judge returned empty response
  judge reply was not JSON: "Let me analyze whether the goal..."

and /goal clear could not stop it mid-loop without /stop.

After N=3 consecutive *parse* failures (transport/API errors don't
count — those are transient), the loop auto-pauses and prints:

  ⏸ Goal paused — the judge model (3 turns) isn't returning the
  required JSON verdict. Route the judge to a stricter model in
  ~/.hermes/config.yaml:
    auxiliary:
      goal_judge:
        provider: openrouter
        model: google/gemini-3-flash-preview
  Then /goal resume to continue.

The counter resets on any usable reply (both "done"/"continue" and
API errors) and persists across GoalManager reloads so cross-session
resumes carry the correct state.

Also fixes test_goal_verdict_send.py sharing a hardcoded session_id
across tests — the shared id only worked because the previous
_post_turn_goal_continuation was a never-awaited coroutine. Now that
PR NousResearch#19160 made it properly awaited, the xdist test-leakage bug
surfaced. Each test gets a unique session_id via uuid suffix.
Tier 2C Task 2C.1 — full upstream merge after Tier 2A/2B fork-cleanup.

Conflict breakdown:
- 238 Tier 1 (mechanical, 95.6%) — pure drift, took upstream verbatim
- 9 Tier 2 (surgical, 3.6%)     — re-applied Myah marker blocks on
  upstream base
- 2 Tier 3 (architectural, 0.8%) — gateway/run.py (12 markers, 12k
  lines) + gateway/platforms/api_server.py (15 block + 14 inline)
  rebased onto upstream's restructured handlers

Tier 2 surgical files:
- toolsets.py (hermes-myah toolset + gateway includes)
- tools/approval.py (5 markers — F1 cron approval; `request_action_confirmation`,
  `resolve_action_confirmation_by_session`, `_action_queues` registry)
- tools/skills_tool.py (4 markers — F4 session-keyed secret capture)
- tools/mcp_tool.py (F7 disconnect_mcp_server helper)
- tools/cronjob_tools.py — TAKEN UPSTREAM VERBATIM per spec §3 Task
  2A.2 architectural intent (plugin's myah_tools/cron_tool.py shadows
  via last-writer-wins; upstream stays unchanged on disk)
- hermes_cli/web_server.py (HERMES_WEB_SESSION_TOKEN env override)
- hermes_cli/config.py (follow_up_generation auxiliary task)
- docker/entrypoint.sh (MYAH_PLATFORM_BASE_URL/_BEARER export)
- scripts/release.py (team contributor email mapping)

Tier 3 architectural adaptations:
- gateway/run.py: rebased _dispatch_approval_notify (~128 LOC), F1
  resolve/deny endpoints, telemetry hook, secret-capture imports/
  callbacks, structured-callback agent loop, and triple-path
  run_conversation wrapper onto upstream's restructured _run_agent.
- gateway/platforms/api_server.py: rebased Myah's confirmation
  endpoint (POST /v1/runs/{run_id}/confirm), origin-field validation
  (Bug C-agent), shared-app/pre-setup hook, sentry trace continuation,
  per-request model override, reasoning streaming, AI monitoring,
  and tool event extension fields onto upstream's restructured
  _handle_runs.

Tier 2B regression repair:
- gateway/platforms/base.py: build_delivery_metadata polymorphic
  optional method was added in Tier 2B without Myah markers and
  was therefore lost in Tier 1 mechanical resolution. Re-added
  with proper markers.
- cron/scheduler.py: re-added the polymorphic call to
  runtime_adapter.build_delivery_metadata + status_hint parameter
  to _deliver_result, also with proper markers.

Test results:
- Baseline (pre-merge):  3883 passed / 17 failed / 62 skipped
- Post-merge:            ~4943 passed / ~27 failed / 75 skipped
  - 4 baseline failures fixed by upstream
  - 13 new failures are upstream-native platform tests
    (google_chat, feishu_bot_admission, discord_free_response —
    all in code paths Myah never touched; missing optional deps
    or upstream test fixtures)
  - 0 Myah-introduced regressions

All 49 Myah marker blocks preserved across 12 marker-bearing core
files. build_delivery_metadata signature stable (verified by
tests/gateway/test_build_delivery_metadata.py — 4 tests pass).
…ANGELOG

Tier 2C §12 — Version compatibility matrix.

Plugin v1.0.0 is the first OSS-launch-eligible release after Tier 2C's
upstream merge (~1,949 commits, 249 conflicts resolved). This commit:

- Bumps version 0.3.0 → 1.0.0 in pyproject.toml.
- Adds compatibility window comment block in pyproject.toml documenting
  the SHA-pin / semver-pin transition path.
- Creates CHANGELOG.md with v1.0.0 entry covering: verified hermes-agent
  SHA (faa13e4), Mode D 9/9 status,
  vendored upstream features (F1-F4, F6, F7) with PR mapping (U1, U5,
  U-CRON, U-MCP, U-HOOK), F5 deferral note (BOOT.md awaits U-HOOK),
  architectural notes (standalone-mode adapter, direct attribute access
  per spec §3.2.1), distribution path.
- Replaces 'Skeleton only' README status with v1.0.0 install
  instructions, compatibility matrix, hermes-minor-bump procedure, and
  feature inventory.
- Removes plugins/platforms/irc/PLUGIN.yaml duplicate (upstream uses
  lowercase plugin.yaml; case-sensitivity left both in the index after
  the merge — only plugin.yaml is canonical).

When hermes-agent ships to PyPI, the dependency in pyproject.toml
switches from undeclared (Path B) to a strict semver pin
(`hermes-agent>=0.11,<0.12` per Path A in spec §12.1).
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

🚨 CRITICAL Supply Chain Risk Detected

This 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 modified

These files can execute code during package installation or interpreter startup.

Files:

hermes_cli/setup.py

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.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

🔎 Lint report: merge-upstream vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 7978 on HEAD, 0 on base (🆕 +7978)

🆕 New issues (4180):

Rule Count
unresolved-import 1320
invalid-argument-type 944
unresolved-attribute 923
invalid-assignment 453
invalid-parameter-default 124
unsupported-operator 123
not-subscriptable 85
invalid-method-override 56
invalid-return-type 37
no-matching-overload 32
call-non-callable 29
unresolved-reference 20
invalid-type-form 13
unused-type-ignore-comment 8
not-iterable 4
+6 more rules
First entries
tests/run_agent/test_strict_api_validation.py:11: [no-matching-overload] no-matching-overload: No overload of bound method `MutableMapping.setdefault` matches arguments
plugins/google_meet/cli.py:340: [unresolved-import] unresolved-import: Cannot resolve imported module `playwright.sync_api`
agent/anthropic_adapter.py:643: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
tui_gateway/event_publisher.py:29: [unresolved-import] unresolved-import: Cannot resolve imported module `websockets.sync.client`
plugins/platforms/google_chat/adapter.py:69: [unused-type-ignore-comment] unused-type-ignore-comment: Unused blanket `type: ignore` directive
tests/test_hermes_logging.py:11: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_browser_hardening.py:7: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
cli.py:4104: [unresolved-attribute] unresolved-attribute: Attribute `execute` is not defined on `None` in union `Connection | None`
gateway/run.py:6223: [unresolved-attribute] unresolved-attribute: Object of type `Self@_prepare_inbound_message_text` has no attribute `_model`
tests/tools/test_tts_max_text_length.py:11: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_managed_server_tool_support.py:22: [unresolved-import] unresolved-import: Cannot resolve imported module `atroposlib`
hermes_cli/auth.py:4542: [invalid-argument-type] invalid-argument-type: Argument to function `_save_codex_tokens` is incorrect: Expected `str`, found `Any | None`
tests/agent/transports/test_types.py:96: [not-subscriptable] not-subscriptable: Cannot subscript object of type `None` with no `__getitem__` method
tests/plugins/test_disk_cleanup_plugin.py:65: [possibly-missing-submodule] possibly-missing-submodule: Submodule `util` might not have been imported
tools/browser_tool.py:2222: [unresolved-attribute] unresolved-attribute: Object of type `str` has no attribute `items`
tests/hermes_cli/test_config.py:659: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> LiteralString, (key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str]` cannot be called with key of type `Literal["last_lines"]` on object of type `str`
hermes_cli/pty_bridge.py:40: [unused-type-ignore-comment] unused-type-ignore-comment: Unused blanket `type: ignore` directive
gateway/platforms/discord.py:4983: [unresolved-attribute] unresolved-attribute: Attribute `Interaction` is not defined on `None` in union `Unknown | None`
run_agent.py:14665: [unresolved-import] unresolved-import: Cannot resolve imported module `fire`
tests/gateway/test_step_callback_compat.py:11: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/cli/test_reasoning_command.py:613: [invalid-argument-type] invalid-argument-type: Argument to function `AIAgent._extract_reasoning` is incorrect: Expected `AIAgent`, found `None`
hermes_cli/kanban.py:1071: [unresolved-attribute] unresolved-attribute: Attribute `assignee` is not defined on `None` in union `Task | None`
optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py:2410: [invalid-argument-type] invalid-argument-type: Argument to bound method `Migrator.record` is incorrect: Expected `Path | None`, found `Literal["openclaw.json gateway.*"]`
tests/gateway/test_restart_notification.py:386: [unresolved-attribute] unresolved-attribute: Object of type `bound method BasePlatformAdapter.send(chat_id: str, content: str, reply_to: str | None = None, metadata: dict[str, Any] | None = None) -> CoroutineType[Any, Any, SendResult]` has no attribute `call_args`
tests/gateway/test_session_split_brain_11016.py:46: [invalid-method-override] invalid-method-override: Invalid override of method `send`: Definition is incompatible with `BasePlatformAdapter.send`
... and 4155 more

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@deestax
Copy link
Copy Markdown
Member Author

deestax commented May 8, 2026

Companion parent-repo PR: https://github.com/T3-Venture-Labs-Limited/myah/pull/106 — bumps the submodule pointer to the post-merge SHA from this branch + adds agent/Dockerfile.stock (§11 Phase A). Per AGENTS.md "merging branches that changed the hermes submodule", this PR must merge first, then the parent PR's submodule pointer should be re-bumped to the squash-merge SHA produced here before that one merges.

deestax added 4 commits May 8, 2026 18:25
Tier 2C merge dropped the ("secrets", "🔐 Secret Management", ...)
entry from CONFIGURABLE_TOOLSETS. Restoring it as plugin-side
registration via ctx.register_tool(toolset='secrets') instead of in
upstream tools_config.py — zero upstream-file modification.

The plugin already had ctx.register_tool for the secrets tool (Phase 4c);
this changes its toolset key from 'hermes-myah' to 'secrets' so
get_plugin_toolsets() auto-derives a 'secrets' toolset entry.

The 'hermes-myah' toolset's includes list gains 'secrets' so the secrets
tool is still default-on for Myah users.

Plugin's tests/conftest.py is extended to invoke the plugin's
register(ctx) at session start so the test process sees the same
ctx.register_tool effects that production sees. Without this,
_get_platform_tools(config={}, 'myah') would return a default-config
set that omits 'secrets' because the test environment's plugin loader
gates registration on plugins.enabled — which is unset in the
per-test HERMES_HOME tempdir.

test_secrets_excluded_when_explicitly_omitted is updated to express
the new plugin-aware opt-out contract: the user has previously had
secrets enabled (recorded in known_plugin_toolsets), and now removes
it from platform_toolsets. Without known_plugin_toolsets, plugin
toolsets are 'default-on for new platforms' so the explicit list is
treated as additive, not authoritative.

Refs: docs/superpowers/specs/2026-05-06-myah-oss-completion-design.md
§2.2 F4.
Tier 2C merge deleted ~125 LOC of _RegistryAwarePlatformDict (Phase 4d's
plugin-aware platform registry fix). The deletion happened in the merge
commit (e4824aa) because the class had no Myah marker — a Tier 1
mechanical resolution dropped it.

WORKAROUND: plugin's register(ctx) mutates tools_config.PLATFORMS at
register time so direct lookups succeed. This is fragile (sentinel test
catches upstream's PLATFORMS becoming a derived view); long-term fix is
U-PLAT upstream PR (deferred per spec §5).

After this fix, OSS users on stock Hermes + plugin can run `hermes tools`
and see Myah in the platform list.

Refs: docs/superpowers/specs/2026-05-06-myah-oss-completion-design.md
§2.2.
Validator at api_server.py:2466 used raw _KNOWN_DELIVERY_PLATFORMS
frozenset (built-ins only), rejecting 'myah' with HTTP 400. Two cron
tests (test_create_job_persists_origin, test_create_job_origin_extra_
keys_preserved) failed because of this.

Fix: swap to _is_known_delivery_platform() (cron/scheduler.py:187),
which already consults platform_registry for plugin platforms. Add
cron_deliver_env_var="MYAH_HOME_CHAT" to plugin's register_platform
call so the helper recognizes myah at production runtime.

Test changes (the gateway test environment does not load plugins
because no HERMES_HOME config is set, so 'myah' would still be unknown
at gateway-test time):

  - test_create_job_persists_origin and
    test_create_job_origin_extra_keys_preserved switch from
    'platform': 'myah' to 'platform': 'telegram'. These tests' real
    intent is the validator's accept-path for any known platform; using
    a built-in keeps the test platform-agnostic (matches how other
    accept/reject tests in the class use sample names).

  - New test_create_job_accepts_plugin_registered_platform registers a
    fake plugin PlatformEntry with cron_deliver_env_var at fixture
    scope and verifies the validator accepts it. This explicitly covers
    the plugin-aware code path at the gateway-test level, without
    coupling stock-upstream tests to the myah-hermes-plugin loader.

Net change: ~3 LOC in production code + ~50 LOC in tests. Validator
block retained (still has Myah marker). Uses upstream's native
plugin-aware primitive — no new fork code outside the test.

Refs: docs/superpowers/specs/2026-05-06-myah-oss-completion-design.md
§2.4.
Tier 2A vendored F4 incompletely — Myah's session-keyed callback dict
replaced upstream's _secret_capture_callback global in tools/skills_tool.py
(4 marker blocks). This broke upstream's own test_skill_env_passthrough
tests that patch the now-removed module attribute.

After analysis: the session-keyed customization protects against rare
multi-tab cross-talk in hosted Myah's per-user-container model. In
practice, simultaneous secret captures across chat tabs are rare;
upstream's single-global is fine for hosted Myah's actual usage patterns.

Reverting tools/skills_tool.py (and its companion test
tests/tools/test_skills_tool.py) to upstream-pristine + adapting the
F4 callsites in gateway/run.py to upstream's one-arg
set_secret_capture_callback() signature:

  - Imports drop set_secret_session_key, reset_secret_session_key
    (no longer exist upstream)
  - _cleanup_secret_callbacks wrapper simplified to take only
    skills_registered: bool, calls set_secret_capture_callback(None)
  - Setup block drops _secret_session_token, calls
    set_secret_capture_callback(_secret_cb) without session-key arg
  - Three cleanup callsites updated to new wrapper signature
  - tests/tools/test_skills_tool.py reverted alongside tools/skills_tool.py
    (matched pair — the fork's edits to that test file were the F4
    session-keyed semantics test scaffolding)

Plugin adapter NOT modified — _plat_adapter._secret_capture_callback
is an instance method on the Myah adapter (distinct from the upstream
module global) and stays as-is.

Net effect: removes 4 Myah marker blocks from upstream's
tools/skills_tool.py; upstream's secret-capture tests
(test_skill_env_passthrough, test_cli_secret_capture, test_skills_tool)
pass without modification.

Known limitation: rare multi-tab cross-talk regression in hosted
Myah's per-user-container model. Documented in plugin README's
"known limitations" follow-up. If a real user hits it, re-add
session-keyed in the plugin adapter (not upstream).

Refs: docs/superpowers/specs/2026-05-06-myah-oss-completion-design.md
§2.2 F4.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

🚨 CRITICAL Supply Chain Risk Detected

This 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 modified

These files can execute code during package installation or interpreter startup.

Files:

hermes_cli/setup.py

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.

@deestax deestax merged commit a0d03c7 into main May 8, 2026
6 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.