Skip to content

fix: inherit agent model in Skill Forge LLM calls - #208

Closed
guix4ever wants to merge 130 commits into
EverMind-AI:mainfrom
guix4ever:fix/skill_forge_model_inheritance
Closed

guix4ever wants to merge 130 commits into
EverMind-AI:mainfrom
guix4ever:fix/skill_forge_model_inheritance

Conversation

@guix4ever

Copy link
Copy Markdown

Summary

  • pass the active agent model to the Skill Forge query rewriter
  • make the LLM gate inherit the active agent model when llm_gate_model is unset
  • preserve the explicit gate model override and document the fallback behavior
  • add regression coverage for model forwarding, inheritance, and override priority

Root cause

The context-engine factory did not pass the active agent model into either Skill Forge LLM stage. QueryRewriter had no model parameter, while LLMGateFilter received None when no gate-specific override was configured. Both calls could therefore fall back to a provider-level default that differed from agents.defaults.model, including models unavailable in the user's region.

Impact

The rewriter and gate now use the same configured model as the agent by default. Deployments that set skillForge.llmGateModel continue to use that explicit model for gate calls.

Follow-up consideration

A future change could add an optional rewrite_model override, with agents.defaults.model as its fallback. That would let operators pin a cheaper or lower-latency model for query rewriting without relying on an implicit provider default. This PR intentionally does not add a new configuration field so the fix remains focused on model inheritance.

Validation

  • uv run pytest tests/test_skill_forge_rewriter.py tests/test_skill_forge_gate.py tests/test_phase_a_default_engine.py -q — 33 passed
  • uv run --extra dev ruff check on all changed Python files
  • uv run --extra dev ruff format --check on all changed Python files
  • git diff --check

Closes #155.

0xKT and others added 30 commits June 29, 2026 00:23
Replace the hardcoded, stale commit-scope list in CLAUDE.md section 3.3
with a pointer to the README Repo layout section, and finish the
public-phase wording: drop the internal-AI-stats framing and the
GitLab-era JIRA / merge-request residue from the trailer rules, the
PR-open flow, and the PR template.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
…ate_cleanup

docs: point commit-scope at README and finish public-phase cleanup
…adius

Prompt injection can't be fully prevented, so this hardens defense-in-depth:
label untrusted content with a tamper-resistant boundary the model is told to
treat as data, and shrink the blast radius of a successful injection. Scope
follows the analysis report — high-value, low-complexity items; deeper items
(sandbox default flip, capability tokens, MCP/skill signing, memory audit log)
are deliberately deferred.

- security/trust.py: wrap_untrusted() fences external content with a per-call
  random nonce, so embedded fake close-markers can't escape the fence.
- system prompt: a guideline telling the model to treat fenced/external content
  as data, watch for "ignore the above" / "you are now" style directives, and
  confirm high-impact actions via ask_user.
- fence untrusted tool output at the single chokepoint
  (ContextBuilder.add_tool_result) and inside the subagent's own loop, plus the
  subagent result re-injected into the main agent.
- fence recalled memory and the sentinel's memory/attention context as
  unverified; the planner is told unverified content must not drive privileged
  actions (spawn / nudge).
- web_fetch: use security.network.validate_url_target (DNS-resolving SSRF check)
  instead of the previous scheme-only validator.
- DirectExecutor: pass only an env allowlist (no host secrets) rather than the
  full host environment; warn loudly when running unsandboxed (backend=none).
- subagents: per-session rolling-hour spawn rate limit (auto-recovering) to stop
  a prompt-injected re-injection loop without locking out legitimate use.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
…-turns switch

Wire the TUI /model command end to end: model.* RPC handlers
(options / save_key / disconnect / add_model / remove_model); a
config.set "model" branch that rebuilds the live provider and reassigns
the agent loop so a switch takes effect on the next turn (guarded by
is_turn_active); ProviderConfig.models for the manual picker catalogue;
the custom provider routed through LiteLLM; and a base
LLMProvider.chat_stream non-streaming fallback so non-litellm providers
degrade gracefully instead of crashing. Frontend: the model picker gains
an api_base input, inline model add/delete, and a structured switch
through config.set; openrpc-derived types regenerated.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
feat: add TUI /model — provider/model config CRUD and between-turns model switch
…ted_content

feat(security): label untrusted content and contain injection blast radius
Two related changes to the EverOS-backed memory subsystem.

Migrated three recall improvements from the legacy everclaw repo
(env vars renamed EVERCLAW_* -> RAVEN_*):
- drop current-session hits from the # Memory recall so it does not
  duplicate the live conversation already in the history window
  (over-fetch top_k*2, then filter by session_id)
- add `raven agent --message-file` for prompts beyond the shell ARG_MAX
- env-gated episodic recall + preference annotation in the consolidator
  (RAVEN_EPISODIC_TOPK / RAVEN_ANNOTATE_PREFS / RAVEN_EPISODIC_CLUSTER),
  default-off

Restructured when EverOS is invoked so native memory owns short-term and
EverOS owns long-term:
- recall is now on-miss: native runs every turn; EverOS user-track recall
  fires only when native surfaces nothing relevant to the query
- the per-turn EverOS store is removed; EverOS consolidation runs only
  (a) intra-day when a session crosses everos_consolidation_threshold_pct
  of the context window (default 80, background, non-blocking) and
  (b) nightly at everos_nightly_consolidation_hour (default 0, offline)
- backend.store gains force_flush to promote a batch immediately;
  consolidation is incremental per session (metadata watermark) so the
  two triggers never double-ingest
- native short-term compaction (maybe_consolidate_by_tokens) is unchanged

Tests: unit coverage for the threshold / nightly / on-miss paths plus a
full in-memory lifecycle smoke, and a real-everos e2e for the
force_flush single-store recall path.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
EverOS defaulted both its user-level config toml and its data root
(sqlite / lancedb / .index / ome.db) to a bare ~/.everos. Scope raven's
instance under ~/.everos/raven so config and data stay isolated from any
other EverOS consumer on the machine.

Add update_everos.configure_everos_env(): it sets EVEROS_CONFIG_FILE and
EVEROS_MEMORY__ROOT via setdefault (so an explicit operator override
still wins) before EverOS's @cache-d load_settings() first runs. The
everos backend factory and the understand_media tool factory call it as
the earliest raven-controlled EverOS entry points; _EVEROS_CONFIG now
derives from a single _EVEROS_BASE constant.

No data migration: existing ~/.everos data is left untouched and a fresh
~/.everos/raven is created lazily on first write/use.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
A fresh onboard now materializes the user-facing subset of the memory /
plugins / skillForge extension blocks into ~/.raven/config.json so the
knobs are discoverable and editable without reading the source. Seeding
is setdefault-based (non-clobbering) and runs on every onboard, so it
also backfills a pre-existing config that predates these blocks.

- memory.backend defaults to "everos" (schema default); _memory_enabled
  now gates on an llm model being present, so a fresh modelless seed is
  treated as "not yet enabled" and the skip / non-interactive guard still
  resolves it to None when memory is opted out or left unconfigured.
- skillForge.everos.enabled is seeded on; router.hub.endpoint is seeded
  to the shared Skill Hub URL with apiKey left null for the user to fill.
- plugins.config["everos-memory"] is seeded with identity wiring that
  mirrors memory.userId / agentId so recall identities match.
- internal-infra fields (embedding / reranker endpoints + their API keys,
  mass_library_db) are NOT written, so a real Bearer token never lands in
  a plaintext config; they stay at their schema defaults.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
…n_blocks

Feat/onboard default extension blocks
…ovements

feat: native short-term memory + everos long-term consolidation
…all_improvements"

This reverts commit b88ea17, reversing
changes made to 5d9887e.
revert: pr #9 native short-term memory + everos long-term consolidation
## Summary
- switch project metadata and license text to Apache-2.0 while retaining
upstream notices
- add public-ready community files: issue templates, SECURITY,
CONTRIBUTING, CODE_OF_CONDUCT, Dependabot, and a stricter PR template
- align README license copy with the public launch posture

## Repository settings updated via GitHub API
- squash-only merge enabled; merge commit and rebase merge disabled
- delete branch on merge and update branch enabled
- Discussions enabled; Projects and Wiki disabled
- Actions default workflow permissions set to read-only and PR
self-approval disabled
- description and homepage updated for Raven

## Verification
- npm ci --ignore-scripts --no-audit --no-fund (root)
- npm ci --ignore-scripts --no-audit --no-fund (ui-tui)
- npm ci --ignore-scripts --no-audit --no-fund (bridge)
- npx --no-install commitlint --from origin/main --to HEAD --config
commitlint.config.cjs
- PYTHONPATH=. python3 scripts/check_commit_messages.py
origin/main..HEAD
- uv run pre-commit run --from-ref origin/main --to-ref HEAD
- git diff --check origin/main...HEAD

## Notes
- GitHub branch protection/rulesets returned 403 while the repository is
private on the current plan. Enable required checks after making the
repository public.
- Auto-merge remained disabled after the API patch, likely due
repository/org availability or policy.
## Summary
- add lightweight overview files for top-level repository sections that
still displayed old non-conventional commit messages
- update existing benchmark and demo READMEs with public-facing guidance
- make the GitHub directory listing show a clean conventional commit
message after merge without rewriting main history

## Verification
- uv run pre-commit run --files LICENSES/README.md benchmarks/README.md
demos/README.md docs/README.md raven/README.md tests/README.md
- npx --no-install commitlint --from origin/main --to HEAD --config
commitlint.config.cjs
- PYTHONPATH=. python3 scripts/check_commit_messages.py
origin/main..HEAD
- uv run pre-commit run --from-ref origin/main --to-ref HEAD

## Note
A normal pull request cannot rename historical commits already merged
into main. This PR instead updates the affected directories with useful
public-facing overview docs so GitHub displays the new conventional
commit as the latest directory change.
Bumps
[python-socketio](https://github.com/miguelgrinberg/python-socketio)
from 5.16.1 to 5.16.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/miguelgrinberg/python-socketio/releases">python-socketio's
releases</a>.</em></p>
<blockquote>
<h2>Release 5.16.2</h2>
<p>See <a
href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">CHANGES.md</a>
for release notes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">python-socketio's
changelog</a>.</em></p>
<blockquote>
<h1>python-socketio change log</h1>
<p><strong>Release 5.16.3</strong> - 2026-06-15</p>
<ul>
<li>Catch all exceptions in redis and rabbitmq client managers <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1581">#1581</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/80bb5c9b07e6b53e45f610ec29a24124c539d41b">commit</a>)</li>
</ul>
<p><strong>Release 5.16.2</strong> - 2026-05-21</p>
<ul>
<li>Prevent unnecessary resource allocations <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1574">#1574</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/ca140fe44d0ceb3004073645222abec182d8784b">commit</a>)</li>
<li>Add zizmor to CI builds <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1570">#1570</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/664dc27ec6f34179ed1724430eda3520627fc642">commit</a>)</li>
</ul>
<p><strong>Release 5.16.1</strong> - 2026-02-06</p>
<ul>
<li>Use configured JSON module in managers <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1549">#1549</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/6229261ae6e8c01e675097242e333ee84587a544">commit</a>)</li>
<li>Admin UI fixes: remove duplicate tasks, report transport upgrades
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1c2eab13a92fac9e43663eb0b5f099eb1c40ea5b">commit</a>)</li>
<li>Switch to Furo documentation template (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/add47d8c7abca697a2804141bbf29bfb095f7d5e">commit</a>)</li>
<li>Add Python free-threading to CI <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1554">#1554</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/ccdd2004a038ae4b8171a05120c5d0787332f7ee">commit</a>)</li>
</ul>
<p><strong>Release 5.16.0</strong> - 2025-12-24</p>
<ul>
<li>Address deprecation warnings (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/b235699d9b06564753c570b76055997e9d62a938">commit</a>)</li>
<li>Drop Python 3.8 and 3.9 from CI builds (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/d0728d2f74538762dd551fa9cd0cd1fd5aedfa37">commit</a>)</li>
</ul>
<p><strong>Release 5.15.1</strong> - 2025-12-16</p>
<ul>
<li>Restore support multiple arguments via pubsub emits <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1540">#1540</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/c279f26bb8c9887c4ca99d4d81ad331c4844438c">commit</a>)</li>
</ul>
<p><strong>Release 5.15.0</strong> - 2025-11-22</p>
<ul>
<li>Retry initial Redis connection <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1536">#1534</a>
([commit <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1">#1</a>](<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad">https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad</a>)
[commit <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/2">#2</a>](<a
href="https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d">https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d</a>))</li>
<li>Correctly regenerate RabbitMQ binding after a connection failure <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1516">#1516</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/c52e93b4a328d98a968bfbdec0cfd598b73ee913">commit</a>)
(thanks <strong>Gritty_dev</strong>!)</li>
<li>Support <code>ext_type</code> in the <code>MsgPackPacket</code>
class <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1521">#1521</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/208925344a48485d2cd56e40eb74266c3bcb5311">commit</a>)</li>
<li>Support sending <code>bytesarray</code>s when using pub/sub managers
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/6c9b9974f72e2efdf62407ecab24ee6995448098">commit</a>)</li>
<li>Fix typos in documentation <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1520">#1520</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/db3f1c2a0105c30cb833ddfca8f05fe4320468fd">commit</a>)
(thanks <strong>Lê Nam Khánh</strong>!)</li>
<li>Improvements to the logging documentation (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/b423d0e38eef559b7e81acb7e32059de305f982c">commit</a>)</li>
</ul>
<p><strong>Release 5.14.3</strong> - 2025-10-29</p>
<ul>
<li>Support Python's native <code>ConnectionRefusedError</code>
exception to reject a connection <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1515">#1515</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/f3b18bde3f16437b223491d4c3e440ea37105fe3">commit</a>)</li>
<li>Push binary data to the aiopika client manager <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1514">#1514</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/194e1b7f277b5f72e1de78d3f614e7b8b6c788ac">commit</a>)</li>
</ul>
<p><strong>Release 5.14.2</strong> - 2025-10-15</p>
<ul>
<li>Restore binary message support in message queue setups <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1509">#1509</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/bab4a10f48aaae11d7f832ebe5c30ad3f85d31b3">commit</a>)</li>
<li>Fix formatting of client connection error <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1507">#1507</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/f298c9b54d76ab09ff72935937e1b9575bc45ffd">commit</a>)</li>
<li>Add 3.14 and pypy-3.11 CI tasks (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1f4cd3b025c294f25208ec3c05b5f8df6209e403">commit</a>)</li>
<li>Improve documentation of the
<code>BaseManager.get_participants()</code> method (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/33722a0d96036f005188b07b8b46a5ef091fe65f">commit</a>)</li>
</ul>
<p><strong>Release 5.14.1</strong> - 2025-10-02</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/6e2b7175c97c2785edd2ea60df026c12240c785c"><code>6e2b717</code></a>
Release 5.16.2</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/cb658291085842372a16863d83e14373f85b3d3e"><code>cb65829</code></a>
update python-engineio version</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/ca140fe44d0ceb3004073645222abec182d8784b"><code>ca140fe</code></a>
prevent unnecessary resource allocation (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1574">#1574</a>)</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/b29beef6bfd38d5a3c3b89da96b7a4574a91efcf"><code>b29beef</code></a>
tox configuration</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/e8981302a3d5b525ee73c2ec9c13859ce2f764e8"><code>e898130</code></a>
Bump ujson from 5.4.0 to 5.12.1 in /examples/server/sanic (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1573">#1573</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/05c32f5da85fe5e5d647c937c7eab0972760ca05"><code>05c32f5</code></a>
Bump qs and body-parser in /examples/server/javascript (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1572">#1572</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/287dc6715b801d319f5baa0fb727e232e728b8aa"><code>287dc67</code></a>
Bump qs and body-parser in /examples/client/javascript (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1571">#1571</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/664dc27ec6f34179ed1724430eda3520627fc642"><code>664dc27</code></a>
add zizmor to ci (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1570">#1570</a>)</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/14c62366a85cfe34e0bd69a22b905eb329873dd3"><code>14c6236</code></a>
Bump django in /examples/server/wsgi/django_socketio (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1566">#1566</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/29b2e5cdbe87e8216bc46fccb864b84ab9f55cf9"><code>29b2e5c</code></a>
Bump aiohttp from 3.13.3 to 3.13.4 in /examples/server/aiohttp (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1565">#1565</a>)
#nolog</li>
<li>Additional commits viewable in <a
href="https://github.com/miguelgrinberg/python-socketio/compare/v5.16.1...v5.16.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python-socketio&package-manager=uv&previous-version=5.16.1&new-version=5.16.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/EverMind-AI/Raven/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0xKT and others added 12 commits July 22, 2026 21:55
## Summary

The deep_research API key prompt (onboard Step 5 and `raven
deep-research
enable`) exited raven with no message when a blank key was entered, and
had
no clean way to cancel once you started configuring.

Root cause: `_prompt_api_key` validated the raw input length (`len(v) >=
8`)
but consumed the stripped value. An all-whitespace key (e.g. 8 spaces)
passed
the check, stripped to empty, and hit `typer.Exit` -- quitting raven.
This
shared helper backs 8 call sites, so the crash affected every no-back
caller.

Changes:
- Validate the stripped value. Whitespace-only input is now rejected and
re-prompts (the reported "8 spaces exits raven" case); a truly-empty
submit
  is the back/cancel signal only where a caller opts in.
- Give deep_research's key prompt a cancel path: an empty submit cancels
configuration without touching the stored key (previously only Ctrl+C,
which
killed the whole process). `_back_placeholder` / `_prompt_api_key` take
an
  optional label so the in-field hint reads "cancel" here instead of the
  default "go back", which would have been misleading.

## Type

- [x] Fix

## Verification

- `uv run pytest tests/test_cli_onboard_commands.py
tests/test_cli_deep_research_commands.py` -> 95 passed
- `uv run ruff check` on the changed files -> clean

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered

The fix only changes rejection/cancel behavior of a blank submit; a
valid key
flows through unchanged. The stripped-value validation and the
empty-submit
cancel were verified against all 8 `_prompt_api_key` call sites (2
already
used allow_back and are behavior-identical for a real key).

## Related Issues

Closes #180

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
## Change description

The one-click installers ran `uv tool install` without a lockfile, so
they
re-resolved dependencies to the newest versions allowed by
`pyproject.toml`
instead of the tested set. Once `litellm` 1.92+ shipped a Rust extension
with
Linux-only wheels, Windows/macOS installs broke: with no wheel, the
sdist builds
from source and needs a compiler (`link.exe` / a Rust toolchain).

Fix, without touching the `pyproject.toml` constraints (so `uv lock`
still
governs when litellm upgrades):

- **release.yml**: export the locked dependency set as a
`raven-constraints.txt`
release asset via `uv export --locked --all-extras --no-hashes`.
`--locked`
fails the release if `uv.lock` is stale vs `pyproject.toml`, so we never
ship
  constraints that conflict with the wheel's own metadata.
- **install.sh / install.ps1**: pass the constraints to `uv tool install
-c`.
The constraints URL is derived from the wheel URL (same release
directory) so
it always matches the wheel being installed, including the
`RAVEN_WHEEL_URL`
  escape hatch. A missing asset or a failed download degrades to an
  unconstrained install rather than aborting.
- **raven upgrade** (`upgrade_commands.py`): the self-upgrade helper
does the
same derive-and-pin, so upgrading an installed copy no longer
re-triggers the
  litellm source build either.

With the pin, litellm resolves to the locked 1.85.0 (pure-Python, wheels
on all
platforms), so no compiler is needed anywhere.

**Rollout note:** this only takes effect once a new release is cut (so
the
`raven-constraints.txt` asset exists). Existing releases have no such
asset and
the scripts degrade to the current unconstrained behavior. Recommend
cutting an
rc first and verifying a zero-compile install on real Windows and macOS
before
the stable tag.

## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Document
- [ ] Others

## Related issues

Closes #181

## Checklists

### Development

- [x] Lint rules pass locally
- [ ] Application changes have been tested thoroughly
- [x] Automated tests covering modified code pass

> Unit tests and the full CI preflight pass locally (upgrade-helper
tests cover
> both the pinned and the graceful-fallback paths). End-to-end
Windows/macOS
> installs were not run on real machines; that should happen on an rc
tag before
> the stable release.

### Security

- [x] Security impact of change has been considered
- [x] Code follows security best practices and guidelines

> The constraints URL is derived from the wheel URL, which
`_parse_release_payload`
> already validates as a trusted github.com release path; constraints
only pin
> versions, they do not execute code.

### Code review

- [x] Pull request has a descriptive title and context useful to a
reviewer

---------

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
#187)

## Change description

The bespoke `subject-case: [2, "always", ["lower-case"]]` rule forced
all-lowercase subjects and rejected proper nouns (EverOS, HTTP), so a PR
title
like "... with EverOS HTTP backend" passed the pre-merge PR-title check
but
failed commit-lint on the squash commit after merge.

This switches `subject-case` to the standard
`@commitlint/config-conventional`
value (forbid Title Case / ALL CAPS / Sentence case; allow lowercase
subjects
with proper nouns), plus one matching example in AGENTS.md. It
intentionally does
NOT `extends` config-conventional, to avoid newly enforcing body/footer
line-length rules on the squash body (= PR description).

Verified locally: proper-noun titles pass, Title Case / Sentence case /
ALL CAPS
still rejected, `style:` still rejected (type-enum unchanged), long body
lines
unaffected.

## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Document
- [ ] Others

## Related issues (if there is)

Closes #186

## Checklists

### Development

- [x] Lint rules pass locally
- [x] Application changes have been tested thoroughly
- [ ] Automated tests covering modified code pass

### Security

- [x] Security impact of change has been considered
- [x] Code follows security best practices and guidelines

### Code review

- [x] Pull request has a descriptive title and context useful to a
reviewer. Screenshots or screencasts are attached as necessary

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
## Summary

Bump version from 0.1.7 to 0.1.8. Release commit only touches
`pyproject.toml` and `uv.lock` (kept in sync via `uv lock`).

Changes bundled into this release since v0.1.7:

- feat(importer): cold-start import from other AI tools with EverOS HTTP
backend (#170)
- fix(build): pin locked dependency versions in installers (#185)
- fix(cli): stop blank api key exiting deep_research prompt (#184)
- fix: use redirect-free install.ps1 url on windows powershell 5.1
(#182)
- fix(tui): brighten selection highlight for readability (#175)
- fix(commitlint): align subject-case with conventional-commits standard
(#187)

## Type

- [x] Other

## Verification

- [x] Relevant lint / type checks pass locally

```
python3 scripts/check_commit_messages.py origin/main..HEAD  # exit 0
git diff --stat  # pyproject.toml + uv.lock, 1 line each
```

## Risk

- [x] Backward compatibility considered

Version bump only; no source changes.

## Related Issues

N/A

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
## Change description

Closes #114.

Every failed agent turn surfaces in the TUI as the same opaque line --
`error: turn_failed (code=-32099)` -- regardless of the real cause (a
missing dependency, a provider error, a Node version problem, ...). That
made #119 (missing `orjson`) impossible to diagnose from the UI and
forced a from-scratch reproduction.

Root cause: `TurnFailed` already carries the underlying error
(`error=str(exc)` in the scheduler), but the spine sink discarded it --
`raven/tui_rpc/spine.py` called `emit_error(..., "turn_failed",
"internal")` with the detail hardcoded, so `TurnFailed.error` never
reached the wire or the UI.

## Fix

Thread the failure detail end to end:
- `ui-tui/rpc-schema/openrpc.json`: add an optional `detail` string to
the `ErrorEvent` payload (single source of truth).
- `ui-tui/src/rpc/generated.ts`: regenerated from the schema (`npm run
lint:rpc` confirms it is in sync).
- `raven/tui_rpc/models.py`: add `detail: str | None = None` to
`ErrorEventPayload`.
- `raven/tui_rpc/spine.py`: `emit_error` accepts an optional `detail`
and includes it only when non-empty; the `TurnFailed` sink passes
`event.error`.
- `ui-tui/src/app/chatStream.ts`: `onError` appends the first line of
`detail` (capped) to the error line, so the user sees e.g. `error:
turn_failed (code=-32099): No module named 'orjson'`.

`code` / `message` / `reason` are unchanged, so the
`cancelled_by_client` path and existing consumers are unaffected;
`detail` is omitted when empty (back-compat).

## Tests

- `tests/test_tui_rpc_spine.py`: `emit_error` with a detail includes it
in the payload; the existing no-detail test still asserts it is omitted.
- `ui-tui/src/__tests__/chatStream.test.ts`: an error event carrying
`detail` renders `error: turn_failed (code=-32099): <detail>`.

## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Document
- [ ] Others

## Checklists

### Development
- [x] Lint rules pass locally (ruff, eslint, prettier, tsc, lint:rpc)
- [x] Application changes have been tested thoroughly
- [x] Automated tests covering modified code pass (python spine + tui
chatStream + full `npm test`)

### Code review
- [x] Pull request has a descriptive title and context useful to a
reviewer

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Change description

Follow-up to #187 (which fixed the subject-case dimension). The length
dimension
had the same "passes the PR, fails after merge" flaw:
`scripts/commit_lint.py`
capped the subject at 72 (commits) / 90 (PR titles) while commitlint
capped the
whole header at 100, so a PR title with a 73-90 char subject passed the
pre-merge
PR-title check but failed commit-lint on the squash commit after merge.

This makes commitlint `header-max-length` (100) the single length
authority (the
config-conventional standard) and removes the bespoke Python 72/90 caps.
CI now
validates the PR title as the prospective squash header `<title> (#NN)`
(in the
commit-messages job, which already has Node), so a title that passes
cannot fail
after merge; the `edited` trigger re-checks title edits. Deliberately
NOT
`extends @commitlint/config-conventional`, to avoid pulling in
body/footer
line-length rules.

Verified locally: proper-noun titles pass, a 79-char subject (header
102) is now
rejected at PR stage, header exactly 100 passes and 101 fails, Title
Case still
rejected, `tests/test_commit_lint.py` green.

## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Document
- [ ] Others

## Related issues (if there is)

Closes #195

## Checklists

### Development

- [x] Lint rules pass locally
- [x] Application changes have been tested thoroughly
- [x] Automated tests covering modified code pass

### Security

- [x] Security impact of change has been considered
- [x] Code follows security best practices and guidelines

### Code review

- [x] Pull request has a descriptive title and context useful to a
reviewer. Screenshots or screencasts are attached as necessary

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
## Change description

Fixes `raven tui` failing with a "Node version too low (found 20.20.1,
need >= 22)" error on machines that **do** have a recent Node installed
(e.g. Homebrew node 26), while `raven agent` works fine.

Two bugs in `find_node()` (`raven/cli/tui_commands.py`):

1. **No minimum-version filtering.** It returned the first candidate
whose `--version` merely parsed, regardless of `_MIN_NODE_VERSION`. The
caller then rejected it if `< 22` without ever asking for the next
candidate -- so a too-old node in an earlier priority tier shadowed a
valid newer one.
2. **PATH probed with `shutil.which("node")`,** which returns only the
*first* hit. A stale `< 22` node earlier on PATH (an old
`/usr/local/bin/node`, an nvm/asdf shim, etc.) hid a newer node later on
PATH.

Together: a user with node 26 on PATH but a 20.x earlier on PATH got
"node too low". `raven agent` was unaffected (no Node needed), which
made it look like a TUI/streaming problem.

### Fix

- Enumerate **every** `node` on PATH (not just the first).
- Return the first candidate that meets `_MIN_NODE_VERSION`, in priority
order (RAVEN_NODE -> venv -> PATH -> private runtime).
- Keep the highest below-minimum candidate so, when nothing qualifies,
the caller still reports the real version (`found 20.20.1, need >= 22`)
instead of a bare "not found".

### Tests

Added to `tests/test_cli_tui_bootstrap.py`:
- `test_find_node_skips_below_min_and_scans_all_path` -- a 20.20.1
earlier on PATH + a 26.5.0 later => `find_node` returns 26.5.0.
- `test_find_node_reports_below_min_when_nothing_qualifies` -- only a
20.20.1 => returns it (so the caller can show the version).

## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Document
- [ ] Others

## Checklists

### Development
- [x] Lint rules pass locally (`ruff check` / `ruff format`)
- [x] Application changes have been tested thoroughly
- [x] Automated tests covering modified code pass

### Code review
- [x] Pull request has a descriptive title and context useful to a
reviewer

## Related issues

Closes #198

## Follow-up fix included (appended commit)

`test_find_node_discovers_windows_private_runtime` was failing on
non-Windows hosts (it mocked `sys.platform="win32"`, but `find_node`
keys its
Windows branches off `os.name == "nt"`, so the mock never took effect).
This
PR now also fixes it, replacing the earlier "flagged for a separate fix"
note:

- Add a `_is_windows()` seam that `find_node` consults for its venv,
PATH, and
  private-runtime platform checks.
- The test patches `_is_windows` instead of `os.name` -- patching
`os.name`
directly is not viable, since pathlib selects `PosixPath`/`WindowsPath`
by
  `os.name` and would raise `NotImplementedError` on a POSIX host.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary

`raven onboard` was unreadable on light-background terminals: the
palette was
hardcoded for a dark background and the CLI had no light/dark detection,
so
near-white / bright-yellow text vanished on white (issue #169,
reproduced on a
released build).

This adds `raven/cli/_theme.py`: light/dark detection (env `RAVEN_THEME`
/
`RAVEN_TERM_BACKGROUND` hint / OSC 11 background probe / `COLORFGBG`,
falling
back to dark) plus a scheme-aware palette, wired into the onboarding
wizard and
the shared questionary style.

- Dark rendering is byte-for-byte unchanged (dark token values are the
current
  literals; verified by a rendering diff and a parity test).
- Light values clear WCAG AA 4.5:1 on white; most mirror
`ui-tui/src/theme.ts`.
- Detection runs on first themed render, never at import, so plain
`raven ...`
  commands do not probe the terminal.
- Also drops two hardcoded `white` table columns (`skill` / `cron`) that
were
  invisible on light backgrounds.

Scope is deliberately limited to what fails on light: named ANSI colors
(`red`/`green`/`cyan`...) elsewhere are terminal-adaptive and were left
as-is.

## Type

- [x] Fix

## Verification

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally

```
uv run pytest tests/test_cli_theme.py            # 44 passed (new)
uv run pytest tests/test_cli_onboard_commands.py # 64 passed
uv run pytest tests/test_cli_deep_research_commands.py \
  tests/test_cli_skill_commands.py tests/test_cli_cron_commands.py \
  tests/test_cli_smoke.py                        # 142 passed
uv run ruff check / ruff format --check          # clean
```

Note for reviewers: the OSC 11 terminal probe can only be exercised on a
real
TTY (not in CI). Please confirm on a light-background terminal that
`raven onboard` is now legible, and on a dark terminal that it is
unchanged
(`RAVEN_THEME=dark` / `RAVEN_THEME=light` force a scheme).

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

Dark output is unchanged; the new env vars are additive; rollback is a
single
commit revert.

## Related Issues

Closes #169

---------

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
## Summary

Internal LLM calls (main loop and sub-agents) had only an httpx per-read
timeout, or none on the streaming path. A backend that trickles bytes
without finishing resets that per-read timer forever, so tasks hang
indefinitely with no error (about 1 in 6 tasks in a 1000+ batch
rollout).

This adds a configurable wall-clock cap enforced with `asyncio.wait_for`
on every provider call:

- New `agents.defaults.llm_call_timeout` (default 600s), carried on
`GenerationSettings` and threaded to every provider construction (eager,
lazy, and knn per-model).
- litellm, custom, azure and codex providers all honor it. Streaming
paths use a per-chunk idle cap, so a long but steadily-progressing
generation is not cut off while a mid-stream stall raises after
`llm_call_timeout` seconds of silence.
- `classify_error` now treats the builtin `TimeoutError` raised by
`wait_for` as a retryable `network` error, so the existing retry and
model-fallback ladder engages (previously it fell to `unknown` and never
retried).
- The streaming loop converts a timeout into a structured error response
and closes the stream, instead of letting it crash the turn.
- `PerModelProvider` now propagates generation settings to its per-model
sub-providers, so knn-routed calls honor the configured timeout,
temperature and max_tokens.

## Type

- [x] Fix

## Verification

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally

Commands run:
- `uv run pytest` over the provider / config / agent-loop test files
(litellm timeout, azure, codex, fallback chain, per-model, lazy,
agent-loop stream, gateway, stream, config loader): 74 passed.
- `bash .claude/scripts/preflight_ci.sh`: all checks passed (commitlint,
commit-message lint, large-files, pre-commit, ruff lint, focused tests
58 passed).

New tests cover: provider-layer wall-clock and per-chunk idle timeouts,
the `classify_error(TimeoutError)` regression, the streaming
structured-error path, and per-model generation propagation.

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

The default of 600s matches the previous hardcoded litellm cap, so
behavior does not regress; the cap is now a wall-clock bound (closing
the trickle-stall hole) and is configurable. New config field with a
default, no schema or API breaking change. Rollback is a single-commit
revert.

## Related Issues

Fixes #150

Refs #183 (this PR incidentally fixes the knn generation-propagation
leak tracked there) and #149 (the timeout surfaces as a classified
error; the run-level structured termination reason is to be coordinated
with that issue).

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
## Summary

Bump the package version from 0.1.8 to 0.1.9 (patch release). All five
PRs merged since v0.1.8 are fixes, no new features:

- #204 fix: add configurable llm_call_timeout to bound stalled LLM calls
- #199 fix(cli): make onboard readable on light-background terminals
- #189 fix(cli): pick the first node >= 22 across all PATH entries
- #196 fix(commitlint): unify commit/PR length to header-max-length
- #190 fix(tui): surface the real turn-failure detail, not just the code

## Type

- [ ] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [x] Other

## Verification

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

Bump is limited to `pyproject.toml` and `uv.lock` (`uv lock` sync).
Commit message lint passes locally.

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

Version-only change; no code or behavior change.

## Related Issues

N/A

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
## Summary

- Replace the legacy anonymous Star History SVG with the official
sealed-token embed in both English and Chinese READMEs.
- Support light and dark color schemes for the Star History chart.
- Keep the raw GitHub PAT out of the repository. The committed sealed
token is the public embed value generated by Star History.

## Type

- [ ] Fix
- [ ] Feature
- [x] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- [ ] Relevant tests pass locally
- [ ] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

- `git diff --check origin/main...HEAD` passed.
- `make check-large-files` passed.
- The interactive Star History chart rendered with the configured token.
The embed endpoint also accepted and decrypted the sealed token, but a
direct cold request intermittently returned HTTP 500 when its
server-side GitHub fetch exceeded 10 seconds.

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

The chart remains dependent on the remote Star History service rendering
reliably on GitHub.

## Related Issues

Tracks #202
## Summary

Two-part refresh of the proactivity eval, plus a small product fix.

**1. pbench: uniform prompt re-test.** All three agents re-tested with
the
same minimal task prompt (no persona, no decision principles -
`runners/prompts/uniform/`), each driven black-box through its own CLI
on
the same backend (OpenRouter `qwen/qwen3.5-27b`). The README main table
is
now the 3-run mean under this protocol. New `hermes_cli_pbench.py`
drives
pbench through the installed hermes CLI; legacy per-agent persona
templates
are kept only to reproduce the ablation appendix.

**2. longrun: matched-model rerun.** Full rerun of the 6-persona x
30-day x 3-agent benchmark as a single batch: raven, hermes, and
openclaw
all on OpenRouter `qwen/qwen3.5-27b`, with `claude-sonnet-4.5` as user
simulator and judge, so the comparison isolates framework architecture
rather than model quality. Supporting harness changes:

- `run_meta` trajectory stamp + scorecard `soft_dnd` field so every
artifact
  self-identifies its run mode.
- MCP cron bridge gains `repeat` (daily/weekdays/weekly) for openclaw.
- Scorer: decision-attribution for Type A (cron provenance + novelty
window),
  sonnet judge for Type B, weekend quota aligned to factory default.
- All three READMEs (main + data/longrun en/zh) refreshed to the new
results
  with per-dimension caveats disclosed.

**3. Product fix.** Adds `tools.exec.extra_deny_patterns`: operators or
eval
harnesses running the agent un-sandboxed can append host-specific deny
regexes. The shipped default deny list is unchanged; the eval injects
osascript / `open -a` blocks via config only.

## Type

- [x] Fix
- [x] Docs
- [x] Other

## Verification

- `uv run pytest tests/test_sandbox_unit.py -q` - 68 passed (also re-run
  after rebasing onto latest `main`).
- `uv run ruff check` / `uv run ruff format --check` on all changed
Python
  files - clean.
- pbench: 3 independent n=120 runs per agent under the uniform prompt
  (2026-07-22); README table reports the mean with per-run F1 range.
- longrun: 18 legs completed with zero LLM/API errors; scored via
  `longrun_scorecard.py --all --compare --aggregate` (18/18 scorecards,
  single judge pass).

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

`extra_deny_patterns` is append-only on top of the built-in deny list
and
defaults to empty, so shipped behavior is unchanged; both commits revert
cleanly and independently.

## Related Issues

N/A

---------

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
Co-authored-by: Elliot Chen <2340896+cyfyifanchen@users.noreply.github.com>
@guix4ever
guix4ever marked this pull request as ready for review July 24, 2026 06:14
cyfyifanchen and others added 4 commits July 26, 2026 09:48
## Summary

- Remove the Star Us and Star History section from the English README.
- Remove the corresponding Star section from the Chinese README.
- Remove both sections from their tables of contents.

The embedded Star History chart still does not render reliably, so the
promotional section is being removed instead of retaining a broken
README surface.

## Type

- [ ] Fix
- [ ] Feature
- [x] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- `make check-commits` passed.
- `PR_TITLE='docs: remove star history section' make check-pr-title`
passed.
- `make check-large-files` passed.
- Python test target passed: 58 tests.
- The isolated ModelPicker test passed.
- The full TUI run passed 901 tests and failed 1 unrelated ModelPicker
timing assertion. The failure reproduces only in the full suite and is
outside this README-only diff.

- [ ] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A
## Summary

Add first-class MiniMax Token Plan OAuth support for both Global and CN
regions.

This change adds:

- `minimax_global` and `minimax_cn` provider entries with curated
MiniMax-M3 and M2.7 models.
- OAuth 2.0 device authorization with PKCE S256 and strict state
validation.
- The shared MiniMax public client ID `coding-plan-cli` with the `openid
profile coding_plan` scope.
- Region-specific OAuth and Anthropic-compatible API endpoints.
- Proactive access-token refresh with a five-minute buffer,
cross-process locking, transient retry handling, and atomic
refresh-token persistence.
- Separate Global and CN credential files, isolated from existing Codex
OAuth storage.
- Runtime token refresh and MiniMax authentication headers for both
normal and streaming requests.
- CLI login commands, onboarding, provider status/test/reset support,
TUI setup detection, and model-picker entries.
- English and Chinese documentation updates.

Public model references remain under `minimax-global/` and
`minimax-cn/`. The runtime translates them to the Anthropic-compatible
LiteLLM route only when sending a request, so provider selection
continues to use the correct MiniMax OAuth credential.

The implementation accepts MiniMax expiry values expressed as relative
seconds, Unix epoch seconds, or Unix epoch milliseconds. OAuth-returned
verification and resource URLs are restricted to the expected HTTPS
hosts before use.

## Type

- [ ] Fix
- [x] Feature
- [x] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- `uv run pytest tests/test_minimax_oauth.py
tests/test_cli_provider_commands.py tests/test_provider_catalog.py
tests/test_config_update_providers.py tests/test_tui_rpc_model.py
tests/test_tui_rpc_setup.py tests/test_cli_status_commands.py -q -x` -
143 passed.
- Focused MiniMax onboarding tests - 3 passed.
- `uv run ruff check raven tests` - passed.
- `uv run ruff format --check raven tests` - 761 files already
formatted.
- `make check-large-files` - passed.
- `make check-commits` - passed.
- Two independent read-only reviews completed; the final review found no
blocking issues.
- The full test collection was not run because the local environment
does not include the optional `dingtalk_stream` channel dependency. All
provider, OAuth, onboarding, TUI setup, status, and configuration tests
relevant to this change passed.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

## Risk

- OAuth credentials are stored in region-specific files with restrictive
permissions and atomic replacement.
- Refresh operations use a cross-process lock and persist rotated
refresh tokens before exposing them to the runtime.
- OAuth verification and API resource URLs are validated against
region-specific host allowlists.
- Existing `minimax` API-key configuration remains unchanged; the new
OAuth providers use separate slugs and storage.
- Rollback consists of reverting this change. Existing API-key provider
configuration and credentials are not migrated or modified.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

- Live OAuth smoke test: MiniMax Global MiniMax-M3 returned "MiniMax
OAuth works" successfully after normalizing the Anthropic-compatible
resource URL.

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary

CI ran `make test-python` over 4 hardcoded files, so ~268 of 272
top-level test files never executed on any PR. A bare `uv run pytest`
could not stand in for it either: collection aborted with 7 errors
(channel-adapter tests importing optional SDKs at module level), and
`testpaths` pulled `tests/integration/` into every run.

This makes the whole unit suite the blocking CI gate.

- `Makefile`: `test-python` runs the whole unit tree instead of a
hardcoded file list.
- `ci.yml`: the `python` job splits into `lint-python` and a `unit` job
running the whole suite on ubuntu / py3.12; the matrix block is kept so
more versions are one list entry away.
- `pyproject.toml`: register `integration` / `e2e` markers, enable
`--strict-markers` / `--strict-config`, exclude `tests/integration` via
`norecursedirs`, and default to `-m "not integration and not e2e"` so a
bare `pytest` is safe.
- Guard the 7 channel-adapter tests with `pytest.importorskip`; CI
installs `--all-extras`, so they run there rather than skip.
- Relocate 9 integration / real-resource files out of the flat unit tree
into `tests/integration/`, renaming the token_wise benchmarks to the
`real_llm` kind required by AGENTS.md section 5.2.

Turning the suite on surfaced 44 pre-existing failures, all fixed here:

- 29 from `asyncio.get_event_loop().run_until_complete()` in three
producer test files, which picks up a closed loop once other async tests
have run; now `asyncio.run()`.
- 2 stale `run_turn` stubs missing the `inline_tool_stream` keyword the
production callers pass.
- 12 from cross-test state leaking through
`raven.config.loader._current_config_path`, litellm's global logging
handlers, and an ambient dependency on a populated `~/.raven` in the TUI
init-error tests.
- 1 flaky concurrency test whose timing bound left only 0.05s of margin.

The full run also recovered 38 genuine unit tests under
`tests/tui/autotest` that a directory-level exclusion would have
orphaned; they are selected normally now, with the 24
subprocess-spawning e2e tests deselected by marker.

Deliberately out of scope: an integration / nightly CI tier (real-LLM
runs are hard to make meaningful in CI and cost money -- to be designed
separately), and the naming and assertion debt tracked in #223.

## Type

- [x] CI / tooling

## Verification

```
uv run --all-extras pytest -q
# 4611 passed, 24 deselected in 193s

bash .claude/scripts/preflight_ci.sh
# ALL PREFLIGHT CHECKS PASSED (commitlint, large files, pre-commit, ruff, tests)
```

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

No production code changes -- test files, test configuration, and CI
workflow only.

The repository ruleset currently declares no required status checks, so
renaming the job blocks nothing. If the `unit` job should become a merge
gate, add `unit (py3.12 / ubuntu-latest)` to the ruleset separately.

## Related Issues

Closes #222

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary

Two packaging defects, plus the CI simplification the second one
unblocks.

**`raven[sandbox]` could not be installed on Windows.** boxlite
publishes wheels for macOS and Linux only -- it is a microVM runtime
built on KVM / Hypervisor.framework, and its PyPI classifiers say so --
but our declaration carried no platform marker, so the install failed
outright:

```
error: Distribution `boxlite==0.9.5` can't be installed because it doesn't have
a source distribution or wheel for the current platform
```

The code already degrades gracefully when boxlite is absent:
`sandbox/interfaces.py` keeps the shared types importable without it,
and both call sites probe with `try: import boxlite / except
ImportError`. So a Windows user should have gotten a Raven that reports
the sandbox as unavailable, not a failed install. `python-socks` and
`matrix-nio` already carry the same `sys_platform != 'win32'` marker;
boxlite now does too.

**The `retrieval` extra had no consumer.** It declared torch and
transformers, which nothing imports -- a whole-tree grep finds them only
in that declaration, two comments, and a doc note, with no dynamic
loading and no doc telling users to install it. It dates back to the
initial commit. Keeping it cost a multi-hundred-megabyte download per
environment and added a native crash surface of the kind `cli/_exit.py`
documents. Removed via `uv remove`; the lockfile drops from 220 to 194
packages.

**CI follows.** With torch out of the extras, `--all-extras` now
resolves to what the hand-maintained `channels + tools + sandbox` list
covered, so the unit job goes back to it and stays correct as extras
change. Every job also gains `timeout-minutes`.

A `windows-latest` cell was added to measure whether the marker fix was
enough to run the suite there. It is not: the install now succeeds, but
the suite hit 121 failures by 28 percent, across sixteen files, from
systemic platform assumptions (assertions hardcoding forward slashes,
fcntl locking, socket and subprocess differences). That is a port, not a
fix, so the cell was dropped. This matches comparable projects:
hermes-agent runs every job on ubuntu and substitutes a blocking static
check for Windows-unsafe primitives, and openclaw's PR-blocking workflow
is ubuntu-only with Windows behind workflow_dispatch. The dedicated
Windows self-upgrade job still covers the install path.

The macos cell is dropped for the same reason, one level milder: it
reported the same failures as ubuntu on every run while ubuntu
additionally caught two Linux-specific problems it could not, and
development happens on macos anyway. The unit job is now a single ubuntu
cell, with the matrix block kept so another entry is one list item away.

## Type

- [x] Bug fix

## Verification

```
uv run --all-extras pytest -q
# 4611 passed, 24 deselected -- same count as before, torch was never used

uv run python -c "import importlib.metadata as m; m.version('torch')"
# PackageNotFoundError, and boxlite still installs on this macOS host

bash .claude/scripts/preflight_ci.sh
# ALL PREFLIGHT CHECKS PASSED
```

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

`raven[retrieval]` stops being a valid extra. Nothing in the repo or the
docs referenced it, and installing it only pulled torch into the
environment without any code path using it, so no behaviour changes.

The Windows matrix experiment is described above and left out of the
final state; both dependency fixes stand on their own.

## Related Issues

Closes #225

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
@0xKT

0xKT commented Jul 29, 2026

Copy link
Copy Markdown
Member

Heads-up on a naming change that will collide with this branch.

A test-debt cleanup (#223) renames tests/test_phase_a_default_engine.py to tests/test_context_engine_factory.py -- the old name carried a phase code that AGENTS.md section 5.1 disallows. This PR is the only open one touching that file, so once the rename lands you will need to rebase and move your changes onto the new filename; the content is otherwise untouched by the rename.

Sorry for the churn. Nothing to do until the rename merges.

arelchan and others added 2 commits July 29, 2026 15:10
## Summary

The TUI already read `update_behind` / `update_command` out of the
session init
bundle, but nothing on the Python side ever filled those fields, so the
hint
never appeared. This wires up the missing half and moves where it shows.

**The check**

`raven/cli/update_notice.py` compares the running version against a
small
cache in `~/.raven/update_check.json`:

- `raven tui` calls `maybe_refresh_async()` once per launch. It spawns a
daemon
thread only when the cache is missing or older than 24h, so a normal
launch
  touches the network at most once a day and never blocks startup.
- The gateway only *reads* that cache when it builds the session info
bundle
  (`_default_session_info`). Reading is pure and fast; no network on the
session-create path, which is why the check is cached rather than live.
- Every failure path is swallowed: no cache, unparseable version,
corrupt JSON,
  network error, rate limit. An update hint must never break startup.

The tradeoff is staleness by one launch: the first launch after a
release
lands refreshes the cache, and the hint shows on the next launch.

**Where it shows**

The status bar's right slot, in the warn color, replacing the cwd/branch
label
while an update is available. No extra line, no layout shift.

The old banner block in `branding.tsx` is removed. The banner is the
first
thing on screen at startup and an upgrade nudge does not deserve that
weight.
`update_behind` is now a flag rather than a commit count, so the text is
version-agnostic (`Update available - run raven upgrade`) instead of the
old
`N commits behind`.

## Type

- [ ] Fix
- [x] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

```
uv run ruff check raven/ tests/                  # All checks passed
uv run ruff format --check raven/ tests/         # 760 files already formatted
uv run pytest tests/test_cli_update_notice.py \
  tests/test_cli_tui_commands.py \
  tests/test_tui_rpc_session.py -q               # 95 passed
cd ui-tui && npm run type-check                  # clean
cd ui-tui && npm test                            # 904 passed | 3 skipped
cd ui-tui && npm run lint                        # 0 errors
cd ui-tui && npx prettier --check "src/**/*.{ts,tsx}"   # clean
```

New tests cover the cache read/write and refresh throttle
(`tests/test_cli_update_notice.py`), the status-bar slot swap
(`ui-tui/src/__tests__/statusRule.test.tsx`), and the banner no longer
rendering the block (`branding.test.tsx`).

Also exercised the five edge cases by hand: absent cache, newer cached
release, cache equal to current, unparseable version string, and a
corrupt
cache file. Only the second one returns a notice; the rest return
`None`.

The lint warning reported on `appChrome.tsx`
(`react-hooks/exhaustive-deps`,
line 275) is pre-existing on `main` and untouched by this change.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

The GitHub releases fetch reuses
`upgrade_commands._fetch_latest_release`, so
no new network surface or credential handling. The cache file holds a
version
string and a timestamp only.

Wire-compatible: `update_behind` / `update_command` were already
optional
fields in the info bundle, and a client that never receives them behaves
as
before. Rollback is reverting this commit; the feature degrades to its
current
state, which is showing nothing.

To see it locally:

```
printf '{"latest_version":"9.9.9","checked_at":%s}' "$(date +%s)" > ~/.raven/update_check.json
```

## Related Issues

N/A

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary

Reworks the TUI transcript so a turn reads as a sequence of steps
instead of
interleaved thinking blocks and tool lines. Every model call becomes one
"episode" holding its reasoning, narration and tools.

**Rendering**

- A step with no narration collapses to one summary line ("reasoning for
8s,
  read 2 files"); a narrated or running step shows a reasoning fold, the
  narration as prose, then its tool rows.
- Reasoning expands while it streams and folds once the step produces
anything
  visible (narration, a tool, or the answer text).
- A run of same-name calls renders as a tree: one header plus a child
row per
  call, so a burst of parallel searches no longer fills the screen.
- In-flight tools report elapsed time; finished ones keep their measured
span.
- Rows are bound to the transcript width and clipped with an ellipsis.
Without
the bound, long details overflowed the container and the terminal
soft-wrapped
  them, which looked like stray blank lines and edge-cut text.

**Tool display contract**

Tools now own how they appear. `Tool.display_call` and the new
`ToolResult`
(`model_text` plus optional `display_text`) separate what the model
reads from
what the transcript shows:

- `ask_user` renders its question and the answer instead of a raw
arguments
blob, pairing each question with its answer when several are asked at
once.
- Tools with no explicit verb derive a readable one from their own name,
so a
newly added tool renders sensibly with no changes to the verb table (it
is an
  overrides list, not a required registry).
- The call preview picks the query, question or command out of the
arguments
  instead of the first value, which could be a numeric flag.

**Also**

- Episodes are the default transcript style; `/transcript legacy` stays
as an
  escape hatch and the legacy path is untouched.
- An interrupt commits the collapsed episode view instead of dumping the
raw
  expanded segments.
- The virtual-height estimate accounts for episode rows. Without it the
  transcript reserved too few rows and left stale cells on screen.
- Markdown table columns fit the available width with CJK-aware
clipping.
A cell wider than its column is truncated with an ellipsis and cannot be
expanded in the TUI (it used to soft-wrap, which kept every character
but
looked broken), and inline markup inside a cell is flattened to plain
text
  rather than rendered.
- The deep-research offer states that a regular search is the user's
chosen
path, not a failure, so the model stops narrating it as a broken
fallback.

**Also in this diff, beyond the stated scope**

- The queued-message panel is restyled: the `queued (N)` header is gone
and the
edit hints moved to a trailing line. The count stays derivable from the
window
  plus `...and N more`, and the edited row is still highlighted.
- User messages get their own tier: an accent chevron and bold text, so
a prompt
  reads as a prompt next to the assistant's prose.
- `transcriptGutterWidth` takes the tool glyph, so the user and
assistant gutters
  are derived from one place instead of two hardcoded widths.

## Review follow-ups

Addressed in the second commit, by the reviewer's IDs:

- **R1** an interrupt with no `episode.start` dropped the accrued
segments and
tool trail; it now falls back to the legacy trail exactly like the
completion
  path, with a regression test.
- **R2** the `ToolResult` / `display_call` contract had no Python
coverage. Adds
`tests/test_ask_user_tool.py` and a loop test asserting
`add_tool_result` gets
  `model_text` while `tool.complete` carries `display_text`.
- **R3** the transcript-mode comment was wrong on all three claims.
- **R4** `ToolRegistry.execute` now unwraps at the boundary and returns
`ToolOutput`, a `str` subclass carrying the display string, so the
sentinel
action executor, subagent manager, curator and tracing can no longer
receive a
  dataclass. `isinstance(.*ToolResult)` hits the registry only.
- **R7** drops `episodeLabel`, `turnSummary`, `toolLine` and
`episodeIndex`.
- **R8** the height estimate counts the inter-step margin row.
- **R9** (mid-turn `/transcript` switch) and **R10** (clarify answer
beyond the
200-char preview) are left for a follow-up: both add state or a wire
field
  rather than fixing this diff.

## Type

- [ ] Fix
- [x] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

```
cd ui-tui && npm run type-check          # clean
cd ui-tui && npm test                    # 927 passed | 3 skipped
cd ui-tui && npm run lint                # 0 errors, no warnings in touched files
cd ui-tui && npx prettier --check "src/**/*.{ts,tsx}"   # clean
uv run ruff check raven/ tests/          # All checks passed
uv run ruff format --check raven/ tests/ # 758 files already formatted
make test-python                         # 4621 passed, 2 pre-existing failures
cd ui-tui && npx vitest run              # 925 passed | 3 skipped
```

New tests cover the episode commit path (including interrupt), the tool
summary
and verb fallback, the argument preview, and the rendered layout.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

The wire gains one optional `tool.start` field (`display`); older
payloads
without it fall back to the generic preview. `execute` may now return
either a
string or a `ToolResult`, so existing tools are unaffected. Rollback is
`/transcript legacy` for the view, or reverting this commit.

Known follow-ups, not addressed here: occasional screen garbling seen
when
messages are queued (root cause not yet identified), and `web_search`
being
registered even without an API key.

## Related Issues

N/A

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
@0xKT 0xKT mentioned this pull request Jul 29, 2026
6 tasks done
@guix4ever

Copy link
Copy Markdown
Author

Got it, thanks for the heads-up. I'll rebase onto the latest main and move my test changes to tests/test_context_engine_factory.py once #223 lands.

0xKT and others added 3 commits July 29, 2026 21:26
## Summary

A read-only audit of all 298 test files found the suite in good shape --
exactly one genuinely fake test -- with the real debt sitting in file
names and a scattering of assertions that passed for the wrong reason.
This clears that, and fixes the bugs found while clearing it.

**Three dead tests dealt with.** Two removed because neither could ever
assert anything:

- `test_chat_e2e.py` was a module-level skip over three
`NotImplementedError` stubs. The wire-up it waited for has shipped
(`tui_commands` instantiates `SubscriptionEmitter`) and the proposal doc
it cited is gone.
- `test_tui_exit_e2e.py` asserted that building the agent loop leaves
lancedb's thread live. It does not: the loop is constructed but never
started, and the memory plugin talks to everos over HTTP, so lancedb is
not even imported. The assertion could not hold in any configuration.

The third, `slashParity.test.ts`, shelled out to python for a
`raven_cli.commands.COMMAND_REGISTRY` that no longer exists, and skipped
all three of its cases when the import failed -- three skips on every
run. The parity it wanted needs no python: `createSlashHandler.ts:45-51`
routes a slash locally when `findSlashCommand` resolves it and otherwise
hands it to `slash.exec`, a CLI subprocess a mutation cannot reach the
live session from. It now asserts that predicate directly.
`NATIVE_MUTATING_COMMANDS` and the three-way `classifyRoute` went with
it: all six names in that set are also in the local registry, so the
native branch could not change any outcome.

**19 files renamed** off phase, ticket and bug codes (AGENTS.md section
5.1), including one reversed CLI name and two integration files moved
onto a legal `<kind>`. Reference updates ride along:
`.pre-commit-config.yaml` names one of these files in a
`detect-private-key` exclude, and three docs pages point at them.

**Assertions that passed for the wrong reason, tightened**: eight
over-broad `pytest.raises(Exception)` narrowed to the type each site
actually raises; an `except` clause that swallowed the `AssertionError`
its own fakes use as a tripwire; a `hasattr` probe replaced by binding a
broker and checking it stuck; and a recall assertion that read as a pass
on an empty result set.

**Dead weight removed**: ten unused locals, three dead code blocks, six
`importorskip` guards on hard dependencies, two hardcoded `/tmp` paths,
and ticket codes left in docstrings after the renames.

**Two coverage gaps the deletions exposed are now closed**:

- `tests/test_cli_exit.py` pins the hard-exit guard, which had no tests
at all.
- The TUI chat e2e gains a multi-turn case. It reads the persisted
session rather than the screen, because a prompt is echoed into the
transcript the moment it is typed -- screen-scraping for a planted word
passes even when history is broken.

**Three timing bugs fixed in the TUI e2e tests.** They keyed off the
banner, which paints about seven seconds before the app accepts input,
and submitted the moment text was typed. Dropped prompts made the
pipeline look dead in one test, and made a negative assertion (`assert
not leak_detected`) pass vacuously in another -- no prompt means no
streaming means no leak.

**Gaps in the two features that landed during review**, small enough to
close here: the `session.py` wiring that fills `update_available` /
`update_command` (removing it left the suite green),
`_upgrade_command_works` (a fixture stubbed it to `True` everywhere), an
`episodeSummary` case with an empty test body, and `clipToWidth` /
`hasMeaningfulReasoning`, which had no coverage at all. The larger items
from that audit are tracked in #229.

**Upstream review attributions dropped from ui-tui comments.** 36
mentions of another project's review thread (#19835 as "Copilot round-N
review"), plus #18994, #19194 and #14045 -- none resolve to anything
here, and AGENTS.md section 1.1 rules out comments pointing at
information only visible elsewhere. The rationale each carried is kept;
four `describe()` titles lose a trailing `(#18994)`. Three went further
and documented a python counterpart that does not exist: `platform.ts`
credited key-spelling normalization to `raven_cli/voice.py` (no such
module -- `raven/tui_rpc/methods/_stubs.py` answers `voice.toggle` with
"voice not supported in Raven v0.1"), and `useConfigSync.ts` plus its
test credited an `interrupt` default to `raven_cli/config.py` and
`tui_gateway/server.py::_load_busy_input_mode` (`busy_input_mode`
appears nowhere in python; neither module exists).

**One flaky test fixed, caught by this PR's own CI run.**
`test_stdio_no_executor_does_not_raise` set `command="true"` and let
`connect_mcp_servers` really spawn it, so the result depended on how a
live child process raced the MCP handshake -- it failed once on CI in
the full-suite run while passing 30 out of 30 standalone runs locally.
It also asserted only that whatever came back was not a
`SandboxInitError`. The transport now raises instead of running (the
idiom its sibling test already uses) and the test asserts the stdio
branch was reached.

Review follow-ups, folded in: the hard-exit test now skips rather than
asserting no lancedb thread exists process-wide, `tests/conftest.py` no
longer credits the pytest segfault to a lancedb thread the suite never
starts (raven imports lancedb nowhere), and the status-bar patterns the
two TUI e2e tests wait on live in one module instead of a hand-mirrored
copy per file.

Every new or changed assertion was checked by mutating the code under
test and confirming the assertion fails.

## Type

- [x] Others

Test files, test configuration, and comments. The only non-test files
touched are ui-tui comments and four `describe()` titles -- no behaviour
change.

## Verification

```
uv run --all-extras pytest -q
# 4654 passed, 25 deselected

cd ui-tui && npx vitest run
# 936 passed, no skips

uv run --all-extras pytest tests/tui/autotest -m e2e -q
# passes locally with tui-use installed and ui-tui built

bash .claude/scripts/preflight_ci.sh
# ALL PREFLIGHT CHECKS PASSED (including the TUI lane)
```

Mutation checks, each confirming the new assertion fails when the code
under test is broken: the `session.py` nudge wiring removed;
`clipToWidth` replaced with identity; `hasMeaningfulReasoning` forced to
`true`; a never-sent prompt added to the multi-turn session assertion.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

No behaviour changes: the non-test diff is comments only. The renames
touch git history for 19 test files; #208 is the only open PR affected
and has been notified.

Two known items are deliberately left alone: `test_dogfood_whitelist.py`
is flaky when the whole file runs (tracked in #228, unchanged here), and
the `lancedb_finalization_hazard` gate in `commands.py` never fires
today -- its test docstring says so explicitly, so the coverage does not
read as an argument for keeping it.

## Related Issues

Closes #223

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary

`test_dogfood_whitelist.py` was reported as flaky: run the file and a
few cases fail, a different few each time. It was not flaky. Three
defects stacked, and each one hid the next.

**Every one of the 14 output assertions was vacuous.** The expected
pattern is matched against the whole screen, which includes the composer
echoing the command just typed. 13 of the 14 were satisfied by that echo
alone -- `skill list` on `skill`, `cron show` on `cron`, `sandbox list`
on `sandbox`. The fourteenth, `status` (`OpenRouter|Model:`), was
satisfied by the welcome frame's own status line. This is a pure string
property, no timing involved.

**Readiness keyed off the banner**, which paints before the app accepts
input. Typing right after it, the composer was observed still showing
its placeholder -- the keystrokes were dropped entirely. Defect 1 is why
that was invisible: a dropped command still satisfied the assertion.

**Ctrl+C is a ladder, not an exit key.** In `useInputHandlers.ts` a busy
UI cancels the turn, a non-empty composer clears the input, and only
then does it quit. The test pressed it once, so any command that left
the UI busy timed out in `expect_exit`. That is the reported flakiness,
and pressing until the process exits takes the file from `3 failed, 4
passed, 4 xfailed, 3 xpassed` to `0 failed`.

The three together also explain the experiment recorded in #228 as
inexplicable: fixing readiness alone made one case fail
*deterministically*, because the command then really submitted and
really ran a turn, so the UI was busy when the single Ctrl+C arrived.

Ruled out with instrumentation rather than argument: a pytest plugin
logging `tui-use list` at every setup and teardown shows no session ever
leaked between cases, and `tui-use info` retains `Status: exited`
indefinitely, so `expect_exit` cannot lose a code it already had.

## What else the audit turned up

**Three commands in the whitelist do not exist.** `skill refresh` and
`skill stats` (the CLI has only `skill list` / `skill get`), and `cron
show`, whose rename to `cron get` shipped long ago. All three were
answered with "unknown command" and passed anyway.

**The same three defects were in every other file in the tier**, so they
are fixed there too, and the seven `xfail(strict=False)` markers are
gone -- their reason blamed a Raven overlay-exit defect, but with a
correct exit sequence all seven pass.

**The README was the source.** Its reference example keyed readiness off
the banner, asserted `OpenRouter|Model:`, and exited with a single
Ctrl+C. Every file that copied it inherited all three. It now documents
the correct sequence, states plainly what the tier cannot do, and drops
eight references to documents that no longer exist.

**The tier is now scoped to what only it can prove.** Dogfood covers
four output shapes instead of 12 commands: `createSlashHandler.ts`
routes every dispatched slash through the same `slash.exec` call, and
the Python side already has 31 dedicated tests, so per-command
repetition proved nothing the first case did not.
`test_e2e_tui_status_slash.py` is deleted (same spawn, same command,
same assertion as the dogfood `status` case) and so is
`test_e2e_raven_chat_cli.py`, an xfail-strict placeholder spawning a
`raven chat` that is neither implemented nor planned -- its tripwire
moves to `tests/test_cli_smoke.py`, where it costs nothing and actually
runs.

**The Ctrl+C ladder moves into vitest.** `decideCtrlC` is now a pure
function, the shape this file already used for
`applyVoiceRecordResponse`, so the rungs are pinned without a PTY or a
live turn. Hook behaviour is unchanged: `isTurnActive()` is still only
consulted on the busy path.

A static sweep of all 301 test files for assertions their own input can
satisfy found exactly one more, in `test_session_fork_help`, which
asserted `"fork"` against `fork --help` output whose usage line echoes
the subcommand name.

## Type

- [x] Others

Tests, test configuration and comments, plus one behaviour-preserving
extraction in `ui-tui/src/app/useInputHandlers.ts`.

## Verification

```
uv run --all-extras pytest -q
# 4655 passed, 13 deselected

cd ui-tui && npm test
# 943 passed

uv run --all-extras pytest tests/tui/autotest -m e2e
# 13 passed, no xfail, no skips, 2m36s   (was 22 passed / 4 xfailed / 3 xpassed in 4m21s)

bash .claude/scripts/preflight_ci.sh
# ALL PREFLIGHT CHECKS PASSED (including the TUI lane)
```

The dogfood file was run three consecutive times: 12 passed each, no
flakes. Every new assertion was mutation-checked: restoring `skill
refresh` fails on missing output; pointing a pattern at `OpenRouter`
trips the idle-screen guard; spawning `sh -c 'cat > /dev/null'` makes
the cat-echo assertion fail where the old one passed; registering a
`chat` command trips the new tripwire; three separate mutations of the
Ctrl+C ladder each turn vitest red.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

The only non-test change is extracting `decideCtrlC` out of the
`useInputHandlers` Ctrl+C branch; the branch order and the guard on
`isTurnActive()` are preserved, and three mutations confirm the new
tests fail when it is not.

Left alone deliberately: no CI job runs this tier, so these runs rest on
local execution -- the repo's existing gap, now stated in the README
rather than implied. The tier also still lives at
`tests/tui/autotest/tests/`, a third location AGENTS.md section 5 does
not sanction; splitting it into `tests/` and `tests/integration/` needs
its own PR, since it is renames plus a fixture move with no bearing on
this fix.

## Related Issues

Closes #228

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
@gloryfromca

Copy link
Copy Markdown
Member

This PR was auto-closed by a history rewrite, not by a maintainer

On 2026-09-12 the main branch of this repository was replaced with a linearized history: all merge commits were folded into a single line and author emails were unified. Every commit received a new SHA, so your branch and main no longer share a common ancestor and GitHub can no longer compute a diff. That is why this PR was closed automatically; the API refuses to reopen it.

Your work is safe in your fork. Nothing in your repository was modified.

To restore this PR, rebase onto the new main and open a fresh one. The pre-rewrite main is preserved as a tag, which makes the rebase exact:

git remote add upstream https://github.com/EverMind-AI/Raven.git   # if you don't have it
git fetch upstream 'refs/tags/backup/pre-linearize-20260912:refs/tags/old-main'
git fetch upstream main
git rebase --onto upstream/main $(git merge-base <your-branch> old-main) <your-branch>
git push --force-with-lease

Then open a new PR and reference this one. Thank you for the contribution, and sorry for the disruption.

KevinHuSh pushed a commit to KevinHuSh/Raven that referenced this pull request Sep 14, 2026
## Summary

A read-only audit of all 298 test files found the suite in good shape --
exactly one genuinely fake test -- with the real debt sitting in file
names and a scattering of assertions that passed for the wrong reason.
This clears that, and fixes the bugs found while clearing it.

**Three dead tests dealt with.** Two removed because neither could ever
assert anything:

- `test_chat_e2e.py` was a module-level skip over three
`NotImplementedError` stubs. The wire-up it waited for has shipped
(`tui_commands` instantiates `SubscriptionEmitter`) and the proposal doc
it cited is gone.
- `test_tui_exit_e2e.py` asserted that building the agent loop leaves
lancedb's thread live. It does not: the loop is constructed but never
started, and the memory plugin talks to everos over HTTP, so lancedb is
not even imported. The assertion could not hold in any configuration.

The third, `slashParity.test.ts`, shelled out to python for a
`raven_cli.commands.COMMAND_REGISTRY` that no longer exists, and skipped
all three of its cases when the import failed -- three skips on every
run. The parity it wanted needs no python: `createSlashHandler.ts:45-51`
routes a slash locally when `findSlashCommand` resolves it and otherwise
hands it to `slash.exec`, a CLI subprocess a mutation cannot reach the
live session from. It now asserts that predicate directly.
`NATIVE_MUTATING_COMMANDS` and the three-way `classifyRoute` went with
it: all six names in that set are also in the local registry, so the
native branch could not change any outcome.

**19 files renamed** off phase, ticket and bug codes (AGENTS.md section
5.1), including one reversed CLI name and two integration files moved
onto a legal `<kind>`. Reference updates ride along:
`.pre-commit-config.yaml` names one of these files in a
`detect-private-key` exclude, and three docs pages point at them.

**Assertions that passed for the wrong reason, tightened**: eight
over-broad `pytest.raises(Exception)` narrowed to the type each site
actually raises; an `except` clause that swallowed the `AssertionError`
its own fakes use as a tripwire; a `hasattr` probe replaced by binding a
broker and checking it stuck; and a recall assertion that read as a pass
on an empty result set.

**Dead weight removed**: ten unused locals, three dead code blocks, six
`importorskip` guards on hard dependencies, two hardcoded `/tmp` paths,
and ticket codes left in docstrings after the renames.

**Two coverage gaps the deletions exposed are now closed**:

- `tests/test_cli_exit.py` pins the hard-exit guard, which had no tests
at all.
- The TUI chat e2e gains a multi-turn case. It reads the persisted
session rather than the screen, because a prompt is echoed into the
transcript the moment it is typed -- screen-scraping for a planted word
passes even when history is broken.

**Three timing bugs fixed in the TUI e2e tests.** They keyed off the
banner, which paints about seven seconds before the app accepts input,
and submitted the moment text was typed. Dropped prompts made the
pipeline look dead in one test, and made a negative assertion (`assert
not leak_detected`) pass vacuously in another -- no prompt means no
streaming means no leak.

**Gaps in the two features that landed during review**, small enough to
close here: the `session.py` wiring that fills `update_available` /
`update_command` (removing it left the suite green),
`_upgrade_command_works` (a fixture stubbed it to `True` everywhere), an
`episodeSummary` case with an empty test body, and `clipToWidth` /
`hasMeaningfulReasoning`, which had no coverage at all. The larger items
from that audit are tracked in EverMind-AI#229.

**Upstream review attributions dropped from ui-tui comments.** 36
mentions of another project's review thread (#19835 as "Copilot round-N
review"), plus #18994, #19194 and #14045 -- none resolve to anything
here, and AGENTS.md section 1.1 rules out comments pointing at
information only visible elsewhere. The rationale each carried is kept;
four `describe()` titles lose a trailing `(#18994)`. Three went further
and documented a python counterpart that does not exist: `platform.ts`
credited key-spelling normalization to `raven_cli/voice.py` (no such
module -- `raven/tui_rpc/methods/_stubs.py` answers `voice.toggle` with
"voice not supported in Raven v0.1"), and `useConfigSync.ts` plus its
test credited an `interrupt` default to `raven_cli/config.py` and
`tui_gateway/server.py::_load_busy_input_mode` (`busy_input_mode`
appears nowhere in python; neither module exists).

**One flaky test fixed, caught by this PR's own CI run.**
`test_stdio_no_executor_does_not_raise` set `command="true"` and let
`connect_mcp_servers` really spawn it, so the result depended on how a
live child process raced the MCP handshake -- it failed once on CI in
the full-suite run while passing 30 out of 30 standalone runs locally.
It also asserted only that whatever came back was not a
`SandboxInitError`. The transport now raises instead of running (the
idiom its sibling test already uses) and the test asserts the stdio
branch was reached.

Review follow-ups, folded in: the hard-exit test now skips rather than
asserting no lancedb thread exists process-wide, `tests/conftest.py` no
longer credits the pytest segfault to a lancedb thread the suite never
starts (raven imports lancedb nowhere), and the status-bar patterns the
two TUI e2e tests wait on live in one module instead of a hand-mirrored
copy per file.

Every new or changed assertion was checked by mutating the code under
test and confirming the assertion fails.

## Type

- [x] Others

Test files, test configuration, and comments. The only non-test files
touched are ui-tui comments and four `describe()` titles -- no behaviour
change.

## Verification

```
uv run --all-extras pytest -q
# 4654 passed, 25 deselected

cd ui-tui && npx vitest run
# 936 passed, no skips

uv run --all-extras pytest tests/tui/autotest -m e2e -q
# passes locally with tui-use installed and ui-tui built

bash .claude/scripts/preflight_ci.sh
# ALL PREFLIGHT CHECKS PASSED (including the TUI lane)
```

Mutation checks, each confirming the new assertion fails when the code
under test is broken: the `session.py` nudge wiring removed;
`clipToWidth` replaced with identity; `hasMeaningfulReasoning` forced to
`true`; a never-sent prompt added to the multi-turn session assertion.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

No behaviour changes: the non-test diff is comments only. The renames
touch git history for 19 test files; EverMind-AI#208 is the only open PR affected
and has been notified.

Two known items are deliberately left alone: `test_dogfood_whitelist.py`
is flaky when the whole file runs (tracked in EverMind-AI#228, unchanged here), and
the `lancedb_finalization_hazard` gate in `commands.py` never fires
today -- its test docstring says so explicitly, so the coverage does not
read as an argument for keeping it.

## Related Issues

Closes EverMind-AI#223

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
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.

Skill Forge rewriter/gate ignore agents.defaults.model, fall back to region-blocked provider default