Follow-up for salvaged #24279:
- cli-config.yaml.example: document compression.threshold_tokens
(commented-out, default null = disabled)
- contributors/emails: map maly.dan@gmail.com -> DanielMaly
- tests: should_compress() fires at the absolute cap below the pct
threshold (first-fires-wins); DEFAULT_CONFIG ships None and 0/None
are behavior-neutral incl. across update_model(); the small-context
pct floor is unaffected by the cap and re-derives correctly on
model switch
Compaction summaries persist across sessions and re-enter every subsequent
summarizer prompt, but every redact_sensitive_text() call in
context_compressor.py used default mode: a no-op under
security.redact_secrets:false, and opaque OAuth-callback / URL-userinfo
credentials passed through even when enabled. The stored _previous_summary
also re-entered the iterative-update prompt unredacted.
Add _redact_compaction_text() — redact_sensitive_text(force=True,
redact_url_credentials=True) — and thread it through all compaction text
boundaries: serializer input (content + tool args), deterministic fallback
summary, summarizer LLM output, manual + auto focus topics, the latest-user
task snapshot, and _previous_summary re-entry.
Note: force=True at this boundary intentionally overrides
security.redact_secrets:false — that opt-out targets live tool output, not
persisted summaries.
Salvages the compaction half of #49556 (the redact.py strict-URL half
landed independently via 75af6dc57/62a00a739). Addresses #43666 item 2.
Co-authored-by: AndrewMoryakov <topazd2@gmail.com>
Follow-up to the salvaged contributor commit, closing the three gaps
flagged in the sweeper review:
1. Init ordering: assign compression.model_thresholds to a selected
plugin context engine BEFORE the initial update_model() call in
agent_init.py, so the initial model's override applies from init
(previously it only took effect after the first /model switch).
Base-class ContextEngine.update_model() now snapshots the
pre-override percent once so repeated switches fall back to the
engine's configured threshold, not a previous model's override.
2. DEFAULT_CONFIG: add compression.model_thresholds (empty map) to
hermes_cli/config.py — additive key, no _config_version bump.
3. Docs: document the key in
website/docs/developer-guide/context-compression-and-caching.md
(yaml example, parameter table, dedicated section) and update the
plugin-boundary note in context-engine-plugin.md to state the
explicit context-engine contract for model_thresholds.
Adds tests/run_agent/test_per_model_threshold_init_ordering.py:
plugin-engine AIAgent init regression (override applies at init,
empty map unchanged), DEFAULT_CONFIG key presence, floor interaction
on the model-switch path (override below the small-context floor is
raised to the floor; above the floor wins), and base-class config
snapshot across repeated switches. Also maps @bennybuoy in
contributors/emails/.
Follow-up for salvaged PR #65453: extend the regression test to dispatch a
real memory_tool add through the store the tool executor wires in, assert the
write persists to memories/MEMORY.md, and assert the external memory provider
(MemoryManager) is still skipped under skip_memory=True. Also map the
contributor email for attribution CI.
Fixes#65429.
Consolidated defensive layer for the invalid_blocks / msg_too_long bug
class (#56615, #62054, #53693): one malformed or oversized block fails
the ENTIRE chat.postMessage / chat.update call, so approval cards never
update and messages silently drop.
New block_kit.sanitize_blocks() is applied at every call site that
attaches blocks (send/edit via _maybe_blocks, send_exec_approval,
send_slash_confirm, and the approval/slash-confirm chat.update button
handlers). It:
- truncates section/context text to the 3000-char cap with an ellipsis
(covers interaction payloads where Slack's HTML-escaping of < > &
inflates text past the limit budgeted at send time)
- truncates header text to its 150-char cap
- drops empty blocks (no text / elements / rows)
- replaces null table column_settings entries with {} and trims
default trailing entries (Slack requires every entry be an object)
- caps the payload at Slack's 50-block maximum
- returns None when nothing valid remains so callers fall back to the
plain-text payload; never raises
Also registers contributor mappings for the salvaged PRs in this
cluster (tw0316, kamonspecial, sowork-skills).
Follow-up to the salvaged #28840 change: forward original timestamps in
_persist_branch_seed (TUI first-turn branch persist), add behavioral
round-trip tests for branch copy and compression-style replace, TUI
protocol tests asserting session.branch and _persist_branch_seed forward
timestamps, and register the contributor email mapping.
The salvaged estimator ran a per-character Python loop on every
estimate_tokens_rough() call — a ~28,000,000x slowdown vs (len+3)//4 on a
1MB ASCII tool output (measured ~3.0s per call). Gate it:
- str.isascii() O(1) fast path keeps pure-ASCII text bit-identical to the
classic (len+3)//4 rule at ~1.3x baseline cost (0.23us vs 0.17us per
1MB call).
- Non-ASCII text counts dense CJK chars via a compiled character-class
regex in C (len(text) - len(re.sub(''))): ~352ms/1MB hangul vs ~2.1s
for the per-char loop.
- Non-ASCII-but-non-CJK text (accents, Cyrillic, emoji) keeps the classic
rule.
Also: parity tests against the per-char reference implementation, and
updated two stale expectations that encoded the old behavior (CJK now
counted ~1 token/char; short string content now ceil-divided instead of
floored to 0). The continuity test now detects merged-into-tail summaries
via _is_context_summary_content.
Reworks the salvaged command module into a CommandSource(SecretSource)
registered as the third bundled source, composing with Bitwarden and
1Password through the apply_all() orchestrator — enable any combination
simultaneously. The original PR's secrets.provider single-selector is
deliberately dropped: multi-source is first-class and a mutually
exclusive provider switch would regress that.
- fetch() only fetches; precedence/override/conflicts/environ writes stay
in the orchestrator. ErrorKind classification + remediation hints.
- apply_command_secrets() kept as a legacy shim (parser/security helpers
unchanged: HERMES_SECRET_KEY data-only key passing, cross-key misroute
guard, base64-padding disambiguation, timeout + output cap, structured-
fields-only failure logging, stderr discarded).
- Dispatch tests rewritten for the registry path incl. an explicit
two-sources-compose test; selector tests removed with the selector.
- cli-config.yaml.example + docs page (command.md), secrets index entry.
- contributors mapping for mvalentin@valensys.net -> 0xr00tf3rr3t.
Preserve ordered structured turns across OpenViking's 100-message batch limit and resume retries from the first unconfirmed message.
Based on the OpenViking batching work from commit 1a567f7067 in #58981.
- Update test_observed_group_context_preserves_slash_command_text_for_dispatch
to assert user_id is preserved for COMMAND messages (new correct behavior)
- Add _coerce_allow_set helper to handle both list and comma-separated
string allowlist inputs (prevents character-by-character iteration bug)
- Include 'channel' in chat_type checks for group-scoped authorization
- Add _telegram_extra fallback for group_allowed_chats (consistent with
group_allow_from fallback)
- Add AUTHOR_MAP entry for nyaruko@hermes -> tsuk1nose
Adds an allow_session flag to the gateway approval payload so adapters
can render the session tier independently of the permanent tier. Matrix
gains a session reaction (🌀) and a reaction legend; pure-tirith prompts
now offer once/session/deny instead of collapsing to once/deny.
Salvaged from PR #67312, adapted to the allow_permanent semantics that
landed in #68597 (Always offered when any dangerous-pattern warning is
persistable; pure-tirith prompts stay session-max).
* feat(desktop): configure repository discovery
* fix(config): preserve additive default migration
* fix(desktop): stabilize session-actions-menu gateway mock for repo-scan subscribe
projects.ts now runs $gateway.subscribe(syncReposScanning) at module load, and
nanostores fires the subscriber synchronously. session-actions-menu.test.ts
reaches projects.ts transitively via the session store but mocked
@/store/gateway without $gateway, crashing the whole desktop vitest suite
("No \ export is defined"). Simply adding $gateway: atom(null) exposed a
second issue: the synchronous subscriber calls the mock's activeGateway()
during the transitive import, before the module-level const initializes (TDZ).
Hoist the mock fns via vi.hoisted() so activeGateway is defined before the
hoisted vi.mock factory runs, and add $gateway: atom(null) to the mock. Mirrors
the self-contained mock pattern already used in projects.test.ts. Also maps the
PR author's commit email for attribution.
Supersedes #67630; incorporates review feedback from that PR.
Co-authored-by: Rudimar Ronsoni <rudimar@outlook.com>
---------
Co-authored-by: Rudimar Ronsoni <rudimar@outlook.com>
Co-authored-by: Austin Pickett <austinpickett@users.noreply.github.com>
The Codex backend returns the per-account model catalog only when the
ChatGPT-Account-Id header is present. Without it, GET /backend-api/codex/models
responds 200 OK with {"models":[]} and the picker silently degrades to the
hardcoded fallback list — which is stale or wrong for the active plan
(no GPT-5.6 family, wrong context windows).
This was the upstream bug behind slow first responses and HTTP 520/120s SSE
hangs: Hermes was sending invalid slugs because the probe never saw them in
the catalog, and Codex's request builder also depends on the same JWT claim
that's now being threaded through both probe paths.
Fixes the probe-side paths in hermes_cli/codex_models.py and
agent/model_metadata.py by extracting chatgpt_account_id from the OAuth JWT
(mirroring the request-side logic already in auxiliary_client.py) and sending
it as a header.
Verified live:
- _fetch_models_from_api now returns the 10-model catalog (gpt-5.6-sol,
gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4, gpt-5.4-mini,
gpt-5.3-codex-spark, 3x -pro variants) instead of [].
- _fetch_codex_oauth_context_lengths resolves all 8 account models to 272K
context (matches direct API probes of the same account).
- end-to-end: hermes chat -m gpt-5.6-sol -q 'Reply with one word: pong'
returns 'pong' cleanly via the openai-codex route.
Same class of bug as PR #64760.
Follow-up to the salvaged #52552 and #53083 commits:
- Rework the Matrix PLATFORM_HINTS entry around what the adapter actually
emits: headings, numbered lists, blockquotes, strikethrough-free markdown
all render (the adapter converts them to sanctioned HTML). Keep the
genuinely valuable guidance: no Markdown tables (Element X / Beeper /
mobile clients don't render HTML tables — cells collapse into one line),
no spoilers/checkboxes/~~strikethrough~~ (not converted by
python-markdown), prefer descriptive link text.
- Regression test: hint must steer models away from tables.
- Fix test_long_response_split_preserves_thread_context to derive its
payload size from the adapter's configurable limit instead of assuming
the old hardcoded 4000.
- Document matrix.max_message_length in the Matrix docs page.
- Contributor mapping for RKelln.
The cherry-picked resolve_provider_full 0.5 step returned a generic
openai_chat ProviderDef for ANY registry ID, hijacking single-entry
alias rewrites like copilot -> github-copilot away from their overlay
transports (test_explicit_copilot_switch_uses_selected_model_api_mode
regression). Restrict the early return to names where MULTIPLE registry
providers collapse to one canonical (kimi-coding + kimi-coding-cn +
kimi + moonshot -> kimi-for-coding) — the only case where alias
resolution actually loses information.
Also maps Almurat123's contributor email.
Follow-up widening for salvaged PRs #67115, #67685, #67620:
- _PROVIDER_MODELS: add kimi-k3 atop kimi-coding / moonshot / opencode-go
curated lists (kimi-coding-cn covered by cherry-picked #67620)
- setup.py _DEFAULT_PROVIDER_MODELS: kimi-k3 for kimi-coding(-cn) + opencode-go
- model_metadata: align DEFAULT_CONTEXT_LENGTHS kimi-k3 entry to 1,048,576
(matches endpoint-scoped override, models.dev, and OpenRouter live metadata)
- anthropic_adapter: classify the bare Coding Plan slug 'k3' (and k3.x/k3-*)
as Kimi family so adaptive thinking applies on proxied endpoints
- moonshot_schema: is_moonshot_model matches bare 'k3' so tool-schema
sanitization runs on the chat-completions path
- contributor mappings for githubespresso407, datachainsystems, Punyko8
Tests: 582 passed across 11 targeted files; hermetic E2E verifies picker
order (kimi-k3 first), no dupes, and 1M context resolution.
The copy-paste config sample had reasoning_effort: low active, which
would silently downshift effort for anyone pasting the block. Keep it
commented like other optional keys. Also add the contributor email
mapping for the salvage.