hermes-agent/hermes_cli
Siddharth Balyan 7668d289d6
Terminal-billing client hardening: shared wire types, wire-layer tests, dead RPC removal (#61067)
* refactor(shared): move terminal-billing wire types to @hermes/shared

The billing/subscription wire shapes (plus UsageBarData/UsageModelData,
which they reference) move verbatim from ui-tui/src/gatewayTypes.ts into
apps/shared/src/billing-types.ts so the desktop app can share the same
gateway contract. gatewayTypes.ts re-exports every moved name from the
new @hermes/shared/billing subpath, so no ui-tui consumer changes.

The subpath export keeps DOM-less ui-tui from pulling the barrel (whose
WebSocket helpers need the DOM lib). ui-tui also now declares its
@hermes/shared dependency explicitly instead of relying on workspace
hoisting.

* test(cli): pin nous_billing wire-layer status-to-exception mapping

The HTTP layer's error handling had zero coverage through _request:
only 2xx parsing and request shaping were tested, and the mapping cases
in test_remote_spending_gate_contract.py hit _raise_for_error directly.

Adds 19 tests driving _request via a monkeypatched urlopen: the
401-refresh-retry path (success, terminal plain/session_revoked,
idempotency-key preservation, base re-resolution), 403 variants through
the wire, 429/503 retry-after, non-JSON error bodies, 404/502
fallbacks, and URLError normalization.

Two behaviors are pinned as findings rather than fixed: a JSON-body
retryAfter hint is ignored unless the Retry-After header is present,
and a bare socket.timeout propagates uncaught (real urllib wraps
timeouts in URLError before this layer).

* fix(cli): normalize read-phase timeouts to the typed billing error

urlopen wraps connect-phase timeouts in URLError (already mapped to
network_error), but a timeout during resp.read() raises a bare
TimeoutError that escaped the typed-BillingError contract and reached
callers as an unhandled exception. Catch it narrowly and normalize.
The boundary test now asserts normalization instead of documenting the
leak.

* fix(shared): stop typing mutation success payloads as error payloads

BillingMutationResponse.payload was declared BillingErrorPayload, but on
ok:true the gateway passes through the raw NAS success body (rail,
changeType, cancelAtPeriodEnd, ...). The TUI never reads it so nothing
broke, but the shared contract now feeds the desktop app too — widen the
field deliberately and document both shapes.

* feat(shared): typed billing refusal and charge-failure unions

- BillingRefusalCode covers every code the gateway serializes today, with a
  (string & {}) arm so unknown future codes (the NAS W3 card-health family)
  stay assignable — consumers keep their unknown-code fallback.
- ChargeFailureReason models the four NAS terminal reasons plus the raw
  subscription_payment_intent_requires_action code NAS leaks pre-#711.
- billing.state now carries the server-derived can_change_plan the gateway
  emits; capability comments updated (canChangePlan is capability-based, not
  an OWNER/ADMIN role gate).

* feat(shared): closed Known* halves for the refusal and charge-failure unions

- KnownBillingRefusalCode / KnownChargeFailureReason are closed literal sets,
  so classification tables, copy maps and tests can be Record-exhaustive and
  break at compile time when a code is added but not mapped. The wire types
  keep the (string & {}) open arm for unknown future codes.
- Add network_error (client-originated transport code the gateway already
  serializes) to the known set.
- Export the union types from the root barrel alongside the other billing
  names.

* feat(shared): canonical billing refusal policy and charge-settlement driver

- billing-policy.ts: one exhaustive Record<KnownBillingRefusalCode,
  BillingRefusalPolicy> classifying every known code (recovery kind,
  mid-poll ambiguity, idempotency-key reuse) with a documented unknown-code
  fallback. Surfaces keep their own copy; the behavior classification now
  has a single home that breaks the build when a new code goes unmapped.
- charge-settlement.ts: the settlement poll state machine (2s cadence,
  5-minute cap, bounded retry-after backoff, ambiguous-on-revocation) as a
  pure dependency-injected driver returning a discriminated outcome.
- The TUI's pollCharge becomes a thin renderer over the shared driver —
  byte-identical output, and the desktop poller can now share the same
  machine instead of a drifting copy.
2026-07-18 07:05:58 -07:00
..
dashboard_auth fix(mcp): complete OAuth through hosted dashboards 2026-07-17 04:50:47 -07:00
proxy security(gateway): set explicit client_max_size on 3 uncapped aiohttp servers (#59180) 2026-07-05 14:48:28 -07:00
subcommands fix(cron): harden execution attempt ledger 2026-07-17 04:58:35 -07:00
__init__.py chore: release v0.18.2 (2026.7.7.2) (#60651) 2026-07-07 20:11:08 -07:00
_parser.py feat(cli): restore cwd on resume (--no-restore-cwd) 2026-07-12 05:08:13 -04:00
_subprocess_compat.py revert(windows): roll back terminal-popup PRs #53791 #53810 #53829 (#53853) 2026-06-27 15:59:00 -07:00
active_sessions.py fix(tui): preserve live session identity across compression (#49041) 2026-06-24 00:54:18 +05:30
auth.py fix(auth): detect configured providers absent from registry (#66017) 2026-07-16 22:56:45 -04:00
auth_commands.py fix(auth): make xAI OAuth pools multi-account resilient 2026-07-17 11:37:25 +05:30
azure_detect.py fix(security): secure Azure catalog probes 2026-07-11 12:28:55 +05:30
backup.py fix(cron): widen UTF-8 BOM tolerance to backup/curator jobs.json readers 2026-07-18 02:31:20 -07:00
banner.py feat(install): warn pip/Homebrew installs are unsupported (CLI, TUI, desktop) (#57225) 2026-07-07 21:13:19 -07:00
blueprint_cmd.py refactor(cron): rebrand Cron Recipes -> Automation Blueprints 2026-06-11 10:49:47 -07:00
browser_connect.py fix: /browser connect times out when another app squats the CDP port 2026-07-18 02:49:28 -07:00
build_info.py
bundles.py
callbacks.py
checkpoints.py
claw.py revert(windows): roll back terminal-popup PRs #53791 #53810 #53829 (#53853) 2026-06-27 15:59:00 -07:00
cli_agent_setup_mixin.py fix(cli): sanitize terminal escapes when replaying stored history (/resume recap, /status recap) 2026-07-17 04:53:38 -07:00
cli_billing_mixin.py feat(tui+cli): change your Nous plan from the terminal (/subscription, /topup, terminal-billing UX) (#51639) 2026-07-18 14:30:24 +05:30
cli_commands_mixin.py fix: /browser connect times out when another app squats the CDP port 2026-07-18 02:49:28 -07:00
cli_output.py
clipboard.py revert(windows): roll back terminal-popup PRs #53791 #53810 #53829 (#53853) 2026-06-27 15:59:00 -07:00
codex_models.py feat(openai): complete gpt-5.6 E2E — codex catalog + 272K compaction auto-raise 2026-07-10 00:47:51 +05:30
codex_runtime_plugin_migration.py
codex_runtime_switch.py refactor(codex-runtime): tidy reapply-migration control flow 2026-07-01 23:51:54 +05:30
colors.py
commands.py feat(tui+cli): change your Nous plan from the terminal (/subscription, /topup, terminal-billing UX) (#51639) 2026-07-18 14:30:24 +05:30
completion.py
config.py Merge pull request #66470 from NousResearch/bb/picker-dialog-latency 2026-07-18 00:58:29 -04:00
console_engine.py refactor(console): remove hosted-context command blocking from Hermes Console (#66144) 2026-07-17 04:33:34 -07:00
container_boot.py fix(container): keep named multiplex gateway slots down (#65368) 2026-07-16 14:30:05 +10:00
context_switch_guard.py fix(cli): log instead of swallow preflight-warning errors; consistent TUI warning field 2026-06-21 16:31:56 +05:30
copilot_auth.py fix(copilot): prefer endpoints.api for base URL, guard empty chat base URL 2026-06-30 03:27:41 -07:00
cron.py fix(cron): harden execution attempt ledger 2026-07-17 04:58:35 -07:00
curator.py feat(curator): add hermes curator usage — all-skills usage view 2026-07-06 12:26:21 -07:00
curses_ui.py
dashboard_register.py fix(cli): correct stale hermes auth login nous hints to hermes auth add nous (#53929) 2026-06-27 21:30:37 -07:00
debug.py fix: remove dead f-string prefixes via ruff F541 (216 sites) (#52336) 2026-07-05 13:42:46 -07:00
default_soul.py fix(soul): installers seed the real default persona, upgrade legacy empty templates (#52246) 2026-06-24 18:56:26 -07:00
dep_ensure.py fix(security): centralize credential-safe subprocess env (#29157) 2026-06-27 20:45:31 -07:00
diagnostics_upload.py feat(debug): drop dead confirm step from --nous upload (stateless NAS) 2026-06-30 17:29:23 -07:00
dingtalk_auth.py refactor(setup): route dependency installs through the canonical uv→pip→ensurepip ladder 2026-07-07 04:09:35 -07:00
doctor.py feat(providers): Support DeepInfra as an LLM provider 2026-07-14 02:59:39 +05:30
dump.py fix(cron): accept UTF-8 BOM when reading jobs.json 2026-07-18 02:31:20 -07:00
env_loader.py fix(secrets): make 1Password bootstrap token reliable outside systemd 2026-07-06 04:58:07 -07:00
fallback_cmd.py
fallback_config.py fix(auth): honor per-entry key_env when resolving fallback providers 2026-07-16 07:19:36 -07:00
gateway.py fix(gateway): preserve external supervisor ownership 2026-07-16 05:08:56 -07:00
gateway_enroll.py feat(gateway): generic OIDC client-credentials relay provisioning (NAS-free) (#60730) 2026-07-08 16:55:32 +10:00
gateway_windows.py revert(windows): roll back terminal-popup PRs #53791 #53810 #53829 (#53853) 2026-06-27 15:59:00 -07:00
goals.py fix(auxiliary): route direct-create aux callers through call_llm (#65029) 2026-07-15 07:39:17 -07:00
gui_uninstall.py feat: uninstall the Chat GUI without removing the agent (CLI + desktop UI) (#40355) 2026-06-06 18:22:38 -07:00
hooks.py feat(agent): add pre_verify hook and verify-on-stop coding guidance 2026-06-30 00:59:29 -05:00
input_sanitize.py fix(input): strip bracketed-paste leaks before prompt persistence (#62557) 2026-07-15 07:39:42 -07:00
inventory.py fix(model): keep configured provider authoritative 2026-07-11 05:43:07 -07:00
journey.py fix(cli): render /journey color instead of leaking raw ANSI 2026-07-01 16:25:48 -05:00
kanban.py docs(kanban): clarify unblock status routing 2026-07-17 15:47:39 -07:00
kanban_db.py refactor(kanban): unify attachment size cap on KANBAN_ATTACHMENT_MAX_BYTES 2026-07-16 07:33:14 -07:00
kanban_decompose.py fix(auxiliary): route direct-create aux callers through call_llm (#65029) 2026-07-15 07:39:17 -07:00
kanban_diagnostics.py fix(kanban): clear failure/crash diagnostics while a retry is in flight 2026-07-09 16:13:59 -05:00
kanban_specify.py fix(auxiliary): route direct-create aux callers through call_llm (#65029) 2026-07-15 07:39:17 -07:00
kanban_swarm.py refactor(kanban): fold worker/orchestrator skills into injected guidance (#50473) 2026-06-21 17:06:48 -07:00
logs.py fix: remove dead f-string prefixes via ruff F541 (216 sites) (#52336) 2026-07-05 13:42:46 -07:00
main.py fix(cli): pass TUI Python env from dashboard chat (salvage #44797) (#66581) 2026-07-17 23:04:33 +00:00
managed_scope.py fix(managed-scope): honor managed scope in all standalone config loaders 2026-06-19 07:46:33 -07:00
managed_uv.py revert(windows): roll back terminal-popup PRs #53791 #53810 #53829 (#53853) 2026-06-27 15:59:00 -07:00
mcp_catalog.py feat(mcp): enforce exact version pins across the whole MCP catalog 2026-07-15 04:56:25 -07:00
mcp_config.py fix(mcp): close hosted OAuth lifecycle gaps 2026-07-17 04:50:47 -07:00
mcp_picker.py
mcp_security.py fix(security): close hermes-0day MCP-persistence attack surface 2026-06-21 19:05:27 -07:00
mcp_startup.py fix(mcp): late-refresh must see desktop/dashboard discovery thread owner (#55514) 2026-06-30 02:08:37 -07:00
memory_oauth.py feat(memory): Honcho OAuth connect — desktop and CLI flows + token refresh (#44335) 2026-06-22 19:16:47 -05:00
memory_providers.py fix(desktop): show Hindsight memory provider (#37546) 2026-06-18 16:48:47 -05:00
memory_setup.py refactor(setup): route dependency installs through the canonical uv→pip→ensurepip ladder 2026-07-07 04:09:35 -07:00
middleware.py fix(middleware): single-use next_call guard + deepcopy-safe request copies 2026-06-06 23:07:25 +05:30
migrate.py
moa_cmd.py feat(moa): support per-slot reasoning effort 2026-07-14 21:08:22 -07:00
moa_config.py fix(moa): surface stale presets without retries 2026-07-17 13:49:12 -07:00
model_catalog.py feat(models): catalog-labeled silent default — GLM-5.2 marked "default": true in the model catalog 2026-07-15 00:10:31 -07:00
model_cost_guard.py fix(model): require confirmation for expensive model selections 2026-06-10 00:24:06 -07:00
model_normalize.py fix(gemini): strip native self prefixes before generateContent (#36141) 2026-06-13 13:47:08 -07:00
model_setup_flows.py feat(pricing): refresh Fireworks snapshot to 2026-07, cover full serverless catalog + cached picker pricing 2026-07-16 04:24:14 -07:00
model_switch.py fix(model-picker): show exhausted-pool providers in interactive /model picker (#66584) 2026-07-17 19:16:48 -04:00
models.py fix(models): add kimi-for-coding-highspeed to kimi-coding provider list 2026-07-16 19:35:16 -07:00
nous_account.py feat(billing): /credits command — balance + portal top-up handoff (#44776) 2026-06-12 08:51:10 +00:00
nous_auth_keepalive.py fix Nous auth refresh for idle agents 2026-06-21 22:43:48 -07:00
nous_billing.py Terminal-billing client hardening: shared wire types, wire-layer tests, dead RPC removal (#61067) 2026-07-18 07:05:58 -07:00
nous_subscription.py fix(browser): validate agent-browser is runnable, not just present (#51740) 2026-06-24 00:14:49 -07:00
onepassword_secrets_cli.py fix(secrets): remove unused masked_secret_prompt import from onepassword CLI 2026-07-06 04:58:07 -07:00
oneshot.py fix(delegate): declare stateless channel in one-shot and cron so delegate_task returns results 2026-07-18 00:05:25 -07:00
pairing.py
partial_compress.py feat(commands): /compact alias + --preview/--dry-run flags for /compress (#3243 salvage) 2026-07-02 05:10:31 -07:00
pets.py feat(pets): generation RPCs, non-blocking gallery + gateway plumbing 2026-06-24 13:48:38 -05:00
platforms.py
plugins.py fix(approval): emit observer hooks for smart verdicts 2026-07-13 02:00:09 -07:00
plugins_cmd.py fix(dashboard): unblock basic auth plugin when setting password interactively (#54489) (#63786) 2026-07-16 19:49:39 -04:00
portal_cli.py fix: remove dead f-string prefixes via ruff F541 (216 sites) (#52336) 2026-07-05 13:42:46 -07:00
profile_describer.py fix(auxiliary): route direct-create aux callers through call_llm (#65029) 2026-07-15 07:39:17 -07:00
profile_distribution.py revert(windows): roll back terminal-popup PRs #53791 #53810 #53829 (#53853) 2026-06-27 15:59:00 -07:00
profiles.py fix: remove dead f-string prefixes via ruff F541 (216 sites) (#52336) 2026-07-05 13:42:46 -07:00
projects_cmd.py feat(projects): add per-profile project store 2026-06-25 16:40:26 -05:00
projects_db.py feat(projects): add per-profile project store 2026-06-25 16:40:26 -05:00
prompt_size.py fix(prompt-size): respect enabled/disabled toolsets per platform 2026-07-05 19:13:20 -07:00
provider_catalog.py feat(moa): expose MoA presets as selectable virtual models (#46081) 2026-06-25 13:52:06 -07:00
providers.py fix(model-switch): override stale api_mode with host-mandated mode on OpenAI-direct switch 2026-07-17 15:47:08 -07:00
psutil_android.py
pt_input_extras.py
pty_bridge.py fix(pty-bridge): mark os.killpg/getpgid windows-footgun-ok (POSIX-only module) 2026-06-08 07:03:12 -07:00
pty_session.py feat(pty): periodic reaper wired into dashboard lifespan 2026-07-07 15:15:37 -07:00
relaunch.py revert(windows): roll back terminal-popup PRs #53791 #53810 #53829 (#53853) 2026-06-27 15:59:00 -07:00
runtime_provider.py fix(bedrock): streaming fallback to Converse API + image base64 decode + bearer token routing 2026-07-15 09:59:38 -07:00
secret_prompt.py feat(memory): improve OpenViking setup UX 2026-06-17 01:04:26 +08:00
secrets_cli.py fix(secrets): fail early with clear error when bitwarden setup runs without TTY (#40571) 2026-06-06 18:36:40 -07:00
security_advisories.py
security_audit.py
security_audit_startup.py style(security-audit): add explicit encoding to read_text calls (ruff PLW1514) 2026-06-21 19:05:27 -07:00
send_cmd.py fix(managed-scope): honor managed scope in config→env bridges too 2026-06-19 07:46:33 -07:00
service_manager.py fix(s6): dot-prefix gateway staging dir so svscan ignores it mid-build (#54834) 2026-06-29 21:33:00 +10:00
session_export.py feat: add prompt-only session export 2026-07-07 13:29:58 -07:00
session_export_html.py fix(session-export): escape html tool call names 2026-07-09 19:54:02 -07:00
session_export_md.py feat(sessions): full prune-filter set + --redact on sessions export 2026-07-07 12:36:41 -07:00
session_filters.py feat(sessions): full filter surface for prune + bulk archive subcommand (#59327) 2026-07-05 22:04:52 -07:00
session_listing.py feat(gateway): add /sessions search <query> (#57685) 2026-07-03 13:44:00 -07:00
session_recap.py fix(cli): sanitize terminal escapes when replaying stored history (/resume recap, /status recap) 2026-07-17 04:53:38 -07:00
setup.py feat(gateway): default session auto-reset to off (mode: none) (#60194) 2026-07-07 05:11:10 -07:00
setup_whatsapp_cloud.py fix(whatsapp-cloud): review follow-ups for #43921 2026-06-11 07:51:01 -07:00
skills_config.py fix(cli): normalize malformed skills config in get_disabled_skills (#61797) 2026-07-09 19:57:54 -07:00
skills_hub.py fix: widen UTF-8 BOM tolerance to all sibling frontmatter parsers 2026-07-17 04:52:02 -07:00
skin_engine.py
slack_cli.py fix(slack): complete agent view workspace routing 2026-07-14 13:58:36 -07:00
sqlite_util.py feat(projects): add per-profile project store 2026-06-25 16:40:26 -05:00
status.py fix(cron): accept UTF-8 BOM when reading jobs.json 2026-07-18 02:31:20 -07:00
stdio.py
suggestions_cmd.py fix: remove dead f-string prefixes via ruff F541 (216 sites) (#52336) 2026-07-05 13:42:46 -07:00
telegram_managed_bot.py Add CLI Telegram QR onboarding 2026-06-05 03:20:10 -07:00
timeouts.py
tips.py feat(browser): store full snapshots on truncation; make eval denylist opt-in (#65923) 2026-07-16 23:41:26 -07:00
tools_config.py fix(tools): don't report platform-restricted toolsets as enabled 2026-07-16 01:17:11 -07:00
toolset_validation.py fix(config): surface invalid platform_toolsets instead of silently dropping tools (#38798) 2026-06-26 14:07:43 +05:30
uninstall.py feat: add uninstall dry-run mode 2026-07-07 05:12:24 -07:00
urllib_security.py fix(security): order sanitizer after installed hooks 2026-07-11 12:28:55 +05:30
voice.py
web_git.py feat(desktop): auto-fetch remote base branch before worktree add 2026-07-13 14:53:34 -04:00
web_server.py fix(dashboard): keep custom themes visible after embedded chat starts (#60601) 2026-07-18 00:34:54 -04:00
webhook.py fix(webhook): make dual-stack bind exclusive 2026-07-16 12:36:51 +05:30
win_pty_bridge.py feat(windows): enable dashboard /chat tab via ConPTY (win_pty_bridge) + tests (#42251) 2026-06-08 11:32:43 -07:00
write_approval_commands.py refactor(memory,skills): replace tri-state write_mode with boolean write_approval (default off) (#43354) 2026-06-09 23:21:14 -07:00
xai_retirement.py fix(config): guard xai migration writer + drop gratuitous annotation 2026-07-05 23:00:34 +05:30