Follow-up on the cherry-picks from @Dilligaf371's #340 and
@bootcrowns's #202. Both fail ruff's E501 (88-char line limit) on
main:
- ``src/openjarvis/agents/executor.py:325`` (from #340) — the
``self._system is not None and getattr(..., "tool_executor", None)
is not None`` guard was 101 chars. Wrapped the condition.
- ``src/openjarvis/telemetry/gpu_monitor.py:55-60`` (from #202) —
five new GPU_SPECS entries (Jetson Orin NX 16GB/8GB, AGX Orin,
Snapdragon X Elite/Plus) were 90-93 chars. Wrapped each
GpuHardwareSpec constructor call onto its own block.
- ``tests/telemetry/test_gpu_monitor.py`` (from #202) — removed a
spurious blank line between imports and the first ``#`` comment
block, picked up by ``ruff check --fix`` (rule I001).
No behavior change — pure formatting.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands 5 high-priority contributor PRs as one bundle, with original authorship preserved on every commit, plus follow-up commits from me to make each PR CI-green and non-regressive. See PR #362 for the full per-author commit breakdown and credits.
Credits: @tomaioo (#235), @Dilligaf371 (#339), @TX-Huang (#293, #294, #295).
Follow-up on @TX-Huang's PR #295. The new
test_soul_md_content_reaches_engine_in_simple_agent test has one
line at 92 chars that trips ruff's E501 (88 char limit). Wrap the
ternary onto multiple lines so the file passes ruff check on CI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`SystemPromptBuilder` is fully implemented and tested in
`openjarvis.prompt.builder`, and `BaseAgent.__init__` accepts a
`prompt_builder` kwarg. But no production code path ever instantiates
the builder, so the persona-files feature documented in
`MemoryFilesConfig` (SOUL.md / MEMORY.md / USER.md) had no effect.
Users could write a fully-customized `~/.openjarvis/SOUL.md` and the
file was never read.
This PR wires it up in `_run_agent` (called by `jarvis ask --agent
<name>` and the new fallback from #294):
```python
if "prompt_builder" in inspect.signature(agent_cls.__init__).parameters:
agent_kwargs["prompt_builder"] = SystemPromptBuilder(
agent_template=config.agent.default_system_prompt or "",
memory_files_config=config.memory_files,
system_prompt_config=config.system_prompt,
)
```
The `inspect` guard means agents that override `__init__` without
forwarding `prompt_builder` (e.g. OrchestratorAgent, which has its
own tool-aware system prompt) opt out automatically and keep
working unchanged. SimpleAgent and any future agent that inherits
`BaseAgent.__init__` directly picks up the persona files.
Also fixes a latent bug in `SystemPromptBuilder._load_file`: it
called `path.read_text()` with no encoding, which on Windows falls
back to the system code page (cp950 / cp932 / cp949) and raises
`UnicodeDecodeError` on any non-ASCII persona content. Pin to UTF-8.
## Tests
- Add `test_soul_md_content_reaches_engine_in_simple_agent` — writes
sentinel SOUL.md / MEMORY.md / USER.md to tmp_path, runs the
command, and asserts each sentinel appears in the SYSTEM message
passed to engine.generate.
- Add `test_orchestrator_keeps_its_own_system_prompt` — exercises
the `inspect`-based opt-out so OrchestratorAgent doesn't crash
on the unexpected kwarg.
Run: `pytest tests/cli/test_ask_agent.py tests/cli/test_ask_router.py
tests/cli/test_ask_e2e.py tests/agents/ tests/prompt/`
The 6 remaining failures (test_base_agent, test_loop_guard,
test_manager, test_native_openhands) are pre-existing on
origin/main and unrelated to this change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up on @TX-Huang's PR #294. The default-agent fallback now
routes `jarvis ask "..."` (no --agent) through `config.agent.default_agent`,
which dataclass-defaults to `"simple"`. The autouse `_clean_registries`
fixture in tests/conftest.py clears AgentRegistry between tests, so
the fallback then fails with `Unknown agent: simple` and every
CLI-driven test_energy_wiring test exits with code 1.
These tests exercise engine-level instrumentation, not agent dispatch
— ``test_engine_wrapped_with_instrumented``, the energy-monitor
lifecycle tests, and the end-to-end pipeline tests all care that the
engine gets wrapped and telemetry lands in SQLite, regardless of
whether the call goes through an agent. Set ``cfg.agent.default_agent
= ""`` in ``_energy_config`` to keep these tests on the direct-engine
path they were originally designed for. The dedicated
``test_agent_mode_uses_instrumented_engine`` (which explicitly passes
``--agent``) is unaffected.
Same pattern PR #294 already uses in tests/cli/test_ask_router.py
for the two MagicMock-config tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`jarvis ask "..."` (no `--agent` flag) routed straight to
`engine.generate()` regardless of `agent.default_agent` in the user's
config. As a result the persona stack — `agent.default_system_prompt`,
SOUL.md, MEMORY.md, USER.md — was silently bypassed for the most
common command.
Behavior now:
- `--agent X` → use agent X (unchanged)
- `--agent ""` (empty) → explicit opt-out, direct-to-engine mode
- (omitted) → fall back to `config.agent.default_agent`,
which dataclass-defaults to `"simple"`,
so persona settings finally take effect
Tests:
- Rename `test_no_agent_uses_direct_mode` to
`test_no_agent_flag_falls_back_to_config_default_agent` and update
its docstring to document the new behavior.
- Add `test_explicit_empty_agent_opts_out_of_agent_mode`.
- Add `test_no_agent_with_blank_config_default_uses_direct_mode` to
cover the case where the user clears `default_agent`.
- Update `_patch_engine` (test_ask_router) and `_patch_ask`
(test_ask_e2e) to re-register `SimpleAgent` after the autouse
`_clean_registries` conftest fixture, since the agent path now
runs in tests that previously short-circuited to direct mode.
- Add explicit `cfg.agent.default_agent = ""` to two
`test_ask_router` tests that mock load_config with a MagicMock
(so `cfg.agent.default_agent` doesn't auto-create as a truthy mock).
Note: `tests/cli/test_ask_context.py` has 2 unrelated pre-existing
failures on origin/main (memory backend returns None on Windows);
those are out of scope for this PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On Windows the default Python stdout encoding follows the system
ANSI code page (cp950 for zh-TW, cp932 for ja, cp949 for ko).
`click.echo()` then raises `UnicodeEncodeError` whenever a CJK
character lands in CLI output — `jarvis ask` returning Chinese
crashes with `'cp950' codec can't encode character '义'`.
Reconfigure `sys.stdout` and `sys.stderr` to UTF-8 with
`errors='replace'` at the `main()` entry point. Scoped to
`win32` so other platforms are untouched. Two unit tests verify
the reconfigure happens on Windows and doesn't on Linux.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`_total_ram_gb()` had branches for Darwin (sysctl) and Linux
(/proc/meminfo) but no Windows path, so `jarvis init` reported
"0.0 GB RAM" on every Windows host. The downstream
`recommend_model()` then fell back to VRAM-only sizing, often
selecting a smaller tier than the system can actually run.
Add a Windows branch that calls `GlobalMemoryStatusEx` via
ctypes — no new dependency. Add a platform-skip-aware test for
each OS branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up on @Dilligaf371's PR #339 fix. Adds a regression test that
spawns a subprocess which consumes stdin without ever writing to
stdout, then issues `send_notification` from a worker thread. If the
override is missing, the thread blocks forever on `proc.stdout.readline()`
and the 2-second join timeout fires.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: close KnowledgeStore connections in connectors_router endpoints
KnowledgeStore opens a persistent SQLite connection in __init__ but was
never closed at the three instantiation sites in connectors_router.py,
leaking one file descriptor (plus its WAL handle) per call. The worst
offender is _connector_summary(), which is invoked per-connector on
every GET /connectors poll from the frontend — after ~100 polls the
backend hits the default macOS per-process FD limit (256) and asyncio's
accept() starts failing with OSError: [Errno 24] Too many open files.
Add __enter__/__exit__ to KnowledgeStore (close() already existed) and
wrap all three instantiation sites in `with` blocks so the connection
is released deterministically when the scope exits.
- connectors/store.py: add context manager protocol
- server/connectors_router.py: use `with KnowledgeStore() as store:`
in _connector_summary, _ingest, and _run_sync
* test: cover KnowledgeStore context manager + connectors_router close leak
- test_store.py: add two tests for the new __enter__/__exit__ protocol
verifying the connection closes on normal exit and on exceptions.
- test_connectors_router.py: add a regression test that monkeypatches
KnowledgeStore.close to count invocations and asserts every store
opened by GET /v1/connectors is paired with a close.
Also fix a pre-existing bug in the test_connectors_router fixture: the
router is created with prefix="/v1/connectors" internally (line 92 of
connectors_router.py), and the fixture was wrapping it again with
prefix="/v1", producing "/v1/v1/connectors". All 6 existing router
tests were failing with 404 before this fix. 5 of them now pass; the
remaining failure (test_trigger_sync) is an unrelated KeyError on a
missing response field and is out of scope for this PR.
* test: drop connectors_router regression test — skipped in CI anyway
The regression test added in the previous commit relies on FastAPI
being importable, but CI's dev-extra install does not include fastapi
(it lives in the `server` optional group). All tests in
test_connectors_router.py silently skip on CI with "fastapi not
installed", so the regression test would never actually execute there.
Revert test_connectors_router.py to the upstream main version. The
fixture bug I fixed (double prefix) and the connection-leak regression
test both deserve a separate PR scoped to test infrastructure — that
PR should either add fastapi to dev deps, split server tests into
their own CI job, or both.
The KnowledgeStore context manager tests in test_store.py are kept
because they have no fastapi dependency and will run in CI.
* feat(mining): add Pearl model conversion workflow
* test(mining): tolerate missing Docker device request type
* docs(mining): record Gemma and Qwen conversion evidence
* docs(mining): record Qwen local validation evidence
---------
Co-authored-by: krypticmouse <herumbshandilya123@gmail.com>
The desktop app's chat-completions stream relies on a raw browser
fetch from the Tauri webview to the FastAPI backend, so it is
subject to CORS. The default allowlist only contained
`tauri://localhost`, which is the production webview origin on
macOS, Linux, and iOS. On Windows and Android, Tauri 2 serves the
app from `http://tauri.localhost` (or `https://tauri.localhost`
when `windows.useHttpsScheme` is enabled), so the preflight for
`POST /v1/chat/completions` was rejected and the streaming fetch
threw `TypeError: Failed to fetch` before any byte was read.
Symptom users reported: in the Logs tab the request appears to
succeed up to "Request sent", followed immediately by
"Stream error: Failed to fetch" and "Response: 22 chars" -- which
is exactly the length of the synthesized fallback string
`Error: Failed to fetch` written by InputArea.tsx when streamChat
throws.
Add `http://tauri.localhost` and `https://tauri.localhost` to both
default origin lists (`ServerConfig.cors_origins` and the
`create_app` fallback), and add a regression test that drives a
real preflight against `/v1/chat/completions` for each of the
three Tauri origin schemes.
Browser model loading kept working because it is routed through
the Rust `tauriInvoke('fetch_models')` command, which is a
server-to-server HTTP call not subject to browser CORS -- only
streaming chat goes through the webview's fetch.
* feat: add Apple Contacts connector (macOS AddressBook)
Reads directly from ~/Library/Application Support/AddressBook/AddressBook-v22.abcddb
in read-only mode. Extracts names, phone numbers, emails, postal addresses, URLs,
social profiles, and notes for each contact. Requires Full Disk Access like
iMessage and Apple Notes connectors.
- New connector: src/openjarvis/connectors/apple_contacts.py
- Registered in connectors/__init__.py
- Added frontend catalog entry (PIM category) and icon mapping
- MCP tools: contacts_search, contacts_get_contact
* test: add comprehensive tests for Apple Contacts connector
15 tests covering: is_connected (exists/missing), sync yields all real
contacts (skips system rows), extracts all field types (phone, email,
address, URL, social, notes), cleans Apple label markup, org-only
contacts, minimal contacts, since filter, sync_status tracking,
structured metadata, disconnect, mcp_tools, registry, empty DB,
missing DB. Uses fake SQLite database — no real Contacts DB needed.
* fix: scan iCloud/Exchange source databases for all contacts
The main AddressBook database only contains locally-created contacts.
Synced contacts (iCloud, Exchange, etc.) live under
Sources/<UUID>/AddressBook-v22.abcddb. Now scans all source databases
and deduplicates by ZUNIQUEID across sources.
Adds tests for multi-source scanning and cross-source deduplication.
Update all human-readable strings (docstrings, comments, descriptions,
log messages, TOML config descriptions) to correctly say
"DeepResearchBench" instead of "LiveResearchBench" for the
deep_research_bench benchmark. Class names and file paths are
unchanged for backward compatibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- openjarvis.__version__ now comes from importlib.metadata.version("openjarvis")
instead of a hardcoded string, so it stays in sync with pyproject.toml on every
release. Tests assert against openjarvis.__version__ rather than a literal.
- Bump actions/checkout@v4 → @v6 and astral-sh/setup-uv@v4 → @v8 across every
workflow ahead of the 2026-06-02 Node.js 20 deprecation on GitHub Actions runners.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>