Commit Graph
100 Commits
Author SHA1 Message Date
99bbc2054a Add arXiv badge to README (#642)
Add a red arXiv badge linking to the OpenJarvis paper (2605.17172) as
the first item in the header badge row, matching the style used on the
Intelligence-Per-Watt repo.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:42:20 -07:00
d5d8fddc94 Fix leaderboard savings lookup after provider-key rename (#635)
PR #634 renamed the Anthropic cost-comparison provider key
`claude-opus-4.6` -> `claude-fable-5` but missed one consumer:
App.tsx looks up the Anthropic entry by that key to compute the
`dollar_savings` value submitted to the leaderboard. After the rename
`per_provider.find(p => p.provider === 'claude-opus-4.6')` returned
undefined, so this path silently submitted dollar_savings = 0.

Point the lookup at the new key.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:33:38 -07:00
6240c59ca3 Update cost-comparison models: GPT-5.6 Sol, Claude Fable 5 (#634)
Refresh the cost-comparison / savings surfaces to current frontier cloud
pricing (per 1M tokens):

- OpenAI:    GPT-5.3 ($2/$10)        -> GPT-5.6 Sol ($5/$30)
- Anthropic: Claude Opus 4.6 ($5/$25) -> Claude Fable 5 ($10/$50)
- Google:    Gemini 3.1 Pro ($2/$12)  -> unchanged

Internal provider keys are renamed in lockstep (gpt-5.3 -> gpt-5.6-sol,
claude-opus-4.6 -> claude-fable-5) across the canonical CLOUD_PRICING
dict, the two server-rendered HTML pages, and the frontend color/label
maps so backend, dashboard, and UI stay consistent. Energy/FLOPs
metadata is carried over unchanged. Model catalog and eval configs are
untouched (real model/benchmark entries, not the cost comparison).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 14:48:22 -07:00
215ab76e5f docs: point the Project Site link to openjarvis.stanford.edu (#628)
The project's canonical site moved from the Scaling Intelligence Lab blog
(scalingintelligence.stanford.edu/blogs/openjarvis/) to
https://openjarvis.stanford.edu/. Update every reference to that URL:

- README: the "Project" badge and the "Project Site" link
- docs/index.md: the research write-up link
- desktop Settings: the "Project site" link (SettingsPage.tsx)
- the Twitter-bot operator prompt

The bare Scaling Intelligence Lab homepage links (the lab itself, not the
project site) are intentionally left unchanged, as are the github.io
documentation and installer URLs.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 13:27:32 -07:00
26bc7efb09 fix(packaging): make openjarvis-rust a uv-only group to unblock pip install openjarvis[desktop] (#624)
* fix(packaging): make openjarvis-rust a uv-only dependency group (unblock pip[desktop])

#615 added openjarvis-rust to the published `desktop` extra so
`uv sync --extra desktop` builds the native PyO3 extension for the desktop
app. But openjarvis-rust is not on PyPI and `[tool.uv.sources]` is stripped
from published wheel metadata, so `pip install openjarvis[desktop]` from PyPI
failed at install trying to resolve openjarvis-rust from PyPI (#584).

Move openjarvis-rust into a uv `desktop-native` dependency group (PEP 735 —
excluded from wheel metadata) and sync it in the desktop app via
`uv sync --group desktop-native`. The extension is still built from the local
path source; only the published metadata changes.

Verified: the built wheel no longer lists openjarvis-rust in any Requires-Dist
(nowhere in the metadata), and uv.lock still resolves it from the local path
source under the group. Adds tests/deployment/test_packaging.py to guard the
split (not in the published extra, present in the group, path source, and the
desktop app syncs the group).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(packaging): sync native group in install paths

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Elliot Slusky <elliot@slusky.com>
2026-07-01 13:02:13 -07:00
d32f20f9b3 fix(desktop): align @tauri-apps npm packages with the 2.11 Rust crate (#613)
Desktop release builds failed on all platforms with 'Found version mismatched Tauri packages' because @tauri-apps/api and @tauri-apps/cli were pinned at 2.10.1 while the tauri Rust crate resolved to 2.11.3. Bump both npm packages to the 2.11 line (api 2.11.1, cli 2.11.4) so they share the crate's major.minor. Plugins were already aligned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 16:51:46 -07:00
1fa80d8ecd fix(docs): wire the savings leaderboard's Supabase anon key into the docs build (#596)
The docs-site leaderboard (docs/javascripts/leaderboard.js) reads the public
Supabase anon key from window.OPENJARVIS_SUPABASE_ANON_KEY, but nothing set it,
so the published leaderboard always rendered "Leaderboard not configured yet".

Add a generated config file (leaderboard-config.js) loaded before
leaderboard.js that supplies the global, and inject its value at docs-build
time from the existing VITE_SUPABASE_ANON_KEY repo secret. The committed
default is empty, so local `mkdocs build` and fork PRs (no secret) degrade
gracefully. The anon key is public by design (Supabase RLS protects the data).

- docs/javascripts/leaderboard-config.js: empty-default global declaration.
- mkdocs.yml: load leaderboard-config.js before leaderboard.js.
- docs.yml: write the config from the secret (read via env, JSON-encoded into a
  JS string literal to avoid injection) before `mkdocs build`.
- tests/deployment/test_docs_leaderboard.py: guard the wiring + load order.

Verified with a local `mkdocs build`: the generated config ships in site/ and
loads before leaderboard.js.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:49:18 -07:00
e7c46c1985 fix(frontend): make Supabase anon key optional to unblock PyPI publishing (#589)
PyPI publishing had been broken since v1.0.3.dev851: #587 made VITE_SUPABASE_ANON_KEY a hard build-time requirement, but no such secret exists, so the frontend build aborted every publish run before the PyPI upload. Decouple package buildability from the leaderboard credential: a missing anon key now disables the savings leaderboard at runtime instead of failing the build, and auto-enables when the secret is provided. Verified: npm run build with the key unset succeeds; tsc + vitest pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:15:06 -07:00
Jon Saad-FalconandGitHub 993c24c8b9 test(server): make TestTraceRecording hermetic (env-independent) (#583)
TestTraceRecording relied on the ambient ~/.openjarvis/config.toml leaving traces.enabled at its default, so it failed on any machine with traces disabled locally (passing in CI only because the runner has no config file). Pass an explicit traces-enabled config with a tmp db_path so the tests are environment-independent and parallel-safe under pytest -n auto. Relates to #582.
2026-06-22 19:12:10 -07:00
6dbe5461bb fix(engine): drop Qwen3 control-token tool calls from Ollama responses (#578)
Qwen3 treats /think and /no_think as soft-switch control tokens. On small
models a multi-line prompt makes the model emit one as the sole tool argument
(e.g. {"command": "/no_think"}); OpenJarvis forwards Ollama's native tool_calls
verbatim, so the operative agent executes garbage. Filter control-token-only
tool calls in both the non-streaming generate() and streaming _run_stream()
paths, keeping legitimate calls like {"command": "date"}.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 11:40:25 -07:00
3e2f4bcdb4 feat(core): consolidate all state under a single env-aware home directory (#462) (#549)
Previously `core/config.py` defined `DEFAULT_CONFIG_DIR = Path.home() /
".openjarvis"` as a by-value module constant imported into ~45 modules, and 34
modules hardcoded `Path.home() / ".openjarvis"` directly. The installer honored
`OPENJARVIS_HOME` but the Python runtime ignored it, producing a split-brain
layout (some modules honored the override, the core config dir did not). Eval
dataset caches also scattered into `~/.cache/<benchmark>`.

This introduces a single env-aware resolver in `openjarvis/core/paths.py` and
routes every state/config/cache path through it. OpenJarvis now keeps ALL of
its state under ONE root, resolved in priority order:

  1. $OPENJARVIS_HOME
  2. $XDG_DATA_HOME/openjarvis   (single nested dir, when XDG_DATA_HOME is set)
  3. ~/.openjarvis               (default — unchanged, so existing installs are
                                  untouched and no data migration is required)

Implementation:
- New `core/paths.py`: get_config_dir / get_config_path / get_data_dir /
  get_cache_dir, with a source-tree rejection guard (fails loudly per
  REVIEW.md if the root resolves inside the repo).
- `core/config.py`: DEFAULT_CONFIG_DIR / DEFAULT_CONFIG_PATH are now resolved
  via the env-aware resolver at import (real attributes, so existing
  monkeypatch.setattr-based tests keep working). All dataclass field defaults
  that pointed at ~/.openjarvis converted to default_factory so they honor the
  override at instantiation.
- Routed all 34 hardcoders plus several string-literal escapees the original
  audit missed: prompt_loader / description_loader (were OPENJARVIS_HOME-only,
  no XDG), swebench_harness cache, tools/{memory,skill,user_profile}_manage
  defaults, server trace.db fallbacks, doctor_cmd hints.
- spec_search storage/paths now delegates to the unified resolver (gains XDG);
  its ConfigurationError is aliased to the core one.
- Eval dataset caches moved from ~/.cache/<name> to <root>/cache/<name>
  (~/.cache/huggingface left alone — it is HF's own cache).
- Docs + installer comment + `jarvis config path` to show resolved dirs.

Read-only macOS connectors and OS service files (LaunchAgents/systemd) are
intentionally left untouched.

Fixes #462

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:32:02 -07:00
f9d1bc8c27 fix(connectors): complete Google OAuth and register Drive in Data Sources (fixes #512) (#548)
Pasting a Google Client ID / Secret never completed OAuth: Drive (and its
Google siblings) accepted the credentials, showed no error, opened no browser,
and never appeared in Data Sources. Root cause is three coupled defects, all
reproduced at the unit level against main with a FastAPI TestClient (no Google
creds, network-free):

(A/B) POST /connect routed a `client_id:client_secret` pair into the
  connector's handle_callback, which spawned a daemon thread that popped a
  browser and ran its own localhost:8789 callback server. That thread fails
  silently in the bundled desktop context (`except Exception: pass`), so the
  connector never gained an access_token; /connect returned status "pending"
  and the UI's 20x2s poll timed out with no error.
  Fix: in POST /connect, an OAuth `client_id:client_secret` pair now persists
  the client credentials to every Google credential file and returns an
  `oauth_required` directive pointing at the in-process server flow, instead of
  the silent background thread. The Google connectors' handle_callback no longer
  spawns the browser thread for the pair case — it only persists the creds; the
  server's /oauth/start -> /oauth/callback owns the consent round-trip.

(C) The would-be-correct server flow was itself broken: under
  `from __future__ import annotations` plus a `Request` import local to the
  router factory, FastAPI could not resolve the stringized `request: Request`
  annotation. /oauth/start returned HTTP 422 (request mis-bound as a query
  param) and /oauth/callback injected None -> AttributeError on
  `request.base_url`. Fix: import `Request` at module scope and make the
  callback's `request` a required injected dependency.

A malformed/blank client pair now raises HTTP 400 with the provider setup URL
instead of a perpetual silent "pending" (REVIEW.md silent-failure discipline).

Frontend: DataSourcesPage now opens the server OAuth window when /connect
returns `oauth_required`, then polls until connected; connect errors surface the
backend detail; the Drive setup steps document the "Web application" OAuth
client + server-callback redirect URI the in-process flow requires.

Tests (run on the main venv, hermetic — no ~/.openjarvis pollution):
- test_oauth_flow.py: the three handle_callback tests now assert NO browser is
  opened and only client creds are persisted (was: assert background flow ran).
- test_connectors_router_oauth.py (new): reproduces + fixes all three defects via
  TestClient with mocked token exchange; parametrized over gdrive/gcalendar/
  gcontacts/gmail/google_tasks to prove the shared OAuth path is fixed for every
  sibling and that a single consent writes the access_token to all six Google
  credential files and flips is_connected() to True.
Full tests/connectors suite: 355 passed.

Relationship to PR #510: #510 rewrites all of these files (account-scoped
retrieval) but still carries all three defects. This fix is intentionally scoped
to the OAuth path and does not modify oauth.py, to minimize collision. A
maintainer can either merge this and rebase #510 on top, or port these changes
into #510. See PR body for details.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 19:55:22 -07:00
8ef1ab1928 fix(ci): gate secret-bearing Claude workflows by author association (fixes #218) (#547)
The Claude automation workflows (claude-issues.yml, claude-review.yml)
trigger on public, attacker-controllable events (issues, issue_comment,
pull_request_review_comment) and grant the job secrets.ANTHROPIC_API_KEY
plus a write-scoped GITHUB_TOKEN with NO author-association gate.

Because issues / issue_comment / pull_request_review_comment always run in
the base-repo context with full secret access (unlike fork pull_request,
from which GitHub withholds secrets), any external GitHub user could fire
these jobs — draining the API budget and, via contents:write +
pull-requests:write, creating branches/PRs.

Fix:
- Add an author-association gate to every human-triggered, secret-bearing
  if: clause, restricting to OWNER / MEMBER / COLLABORATOR. Uses the correct
  event payload field per trigger: github.event.issue.author_association for
  the `issues` event, github.event.comment.author_association for
  issue_comment and pull_request_review_comment. workflow_dispatch stays
  trusted (requires repo write to invoke).
- Drop unused id-token: write from both workflows (claude-code-action@v1 is
  passed github_token directly, so OIDC is unused).
- Reduce claude-issues.yml timeout-minutes 60 -> 15.

desktop.yml and take-assign.yml are intentionally NOT touched: independently
verified as not exploitable for ANTHROPIC_API_KEY (desktop.yml's only
pull_request job uses no secrets and the trigger is plain pull_request, not
pull_request_target; take-assign.yml uses only GITHUB_TOKEN with issues:write
and no checkout/no Anthropic key). claude-review.yml's stale pull_request
auto-trigger was already removed in 3f2f46e4.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 19:31:51 -07:00
28e75cb513 fix(server): inject OpenJarvis identity system prompt on the desktop chat path (fixes #540) (#546)
The OpenAI-compatible POST /v1/chat/completions endpoint — the desktop UI's
chat backend — never injected OpenJarvis's agent.default_system_prompt when the
client omits a system message. The frontend (Chat/InputArea.tsx) posts only
user/assistant turns, so the model answered from its training identity
("I'm Claude", "I am Qwen", ...). The CLI paths ground identity via
SystemPromptBuilder / BaseAgent; the engine-direct server handlers did not.

Fix:
- Add _ensure_identity_prompt(messages, app_config) in server/routes.py: returns
  messages unchanged when any has role==SYSTEM, else prepends a SYSTEM message
  with the resolved identity prompt (app.state.config.agent.default_system_prompt,
  else load_config()), wrapped in try/except that debug-logs on failure (no crash,
  no silent swallow per REVIEW.md).
- Apply it after _to_messages() in all three engine-direct handlers:
  _handle_stream, _handle_stream_tools, and _handle_direct; thread app.state.config
  through. _handle_agent is left untouched (BaseAgent already injects the default).
- Harden AgentConfig.default_system_prompt so distilled models stop claiming to be
  Claude/ChatGPT/Gemini and self-identify as OpenJarvis.

Tests (tests/server/test_routes.py, tests/core/test_config.py): identity prompt IS
prepended when no system message is present (stream / direct / tools paths) and is
NOT duplicated when the client supplies one; config wording anchors "OpenJarvis"
and "not Claude". Verified fail-on-unfixed against main (3 inject tests + config
wording test fail there).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 18:38:05 -07:00
4b9948250b feat(engine): add DeepSeek as a first-class cloud provider + fix #335 over-permissive cloud fallback (#545)
* feat(engine): add DeepSeek as a first-class cloud provider

Adds DEEPSEEK_API_KEY support to the cloud engine, wiring DeepSeek's
OpenAI-compatible API (api.deepseek.com/v1) alongside the existing
MiniMax, OpenRouter, Anthropic, and Google providers.

- Add _DEEPSEEK_MODELS list (deepseek-v4-flash, deepseek-v4-pro)
- Add _is_deepseek_model() routing predicate
- Init self._deepseek_client from DEEPSEEK_API_KEY in _init_clients()
- Add _generate_deepseek() and _stream_deepseek() methods
- Wire DeepSeek into generate(), stream(), _stream_full_openai(),
  list_models(), and health()
- Add approximate pricing entries for both models

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(engine): strict cloud model routing + deepseek can_serve branch

Builds on the DeepSeek provider (PR #504) with two routing-correctness
fixes to CloudEngine._client_for_model:

1. Add the missing DeepSeek branch so can_serve('deepseek-*') agrees with
   list_models()/health() when only DEEPSEEK_API_KEY is set (mirrors the
   minimax branch). Without it the engine advertised deepseek models via
   list_models() but refused to serve them (the #532 can_serve contract).

2. Fix #335: _client_for_model previously fell through to the OpenAI client
   for ANY unrecognized model name, so an OpenAI key (even a dummy
   sk-dummy... one) made can_serve('qwen3.5:0.8b') return True. With the
   local engine transiently down (classic post-Windows-restart Ollama not
   yet up), model-aware get_engine then mis-selected the cloud engine for a
   local model and died with "OpenAI client not available". Add a positive
   _is_openai_model predicate (gpt-/chatgpt-/o1/o3/o4 + _OPENAI_MODELS) and
   return None for unrecognized names, so can_serve declines them. generate()
   and stream() keep their OpenAI fall-through, preserving loud failure for an
   explicitly-requested unknown cloud model.

Tests: DeepSeek detection/pricing/health/list_models/generate-routing/
can_serve and a #335 regression (can_serve rejects local names with an
OpenAI key; unknown model not served even with all clients set; end-to-end
get_engine does not misroute a local model with a dummy OpenAI key).

Fixes #335

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Jen Huls <me@jenhuls.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 18:21:56 -07:00
b21463aab6 fix(evals): harden terminalbench-native harness against tmux death and setup hangs (#536)
Two failure classes hit by a downstream team:

1) tmux/task-env death (TerminalBenchTaskEnv.__enter__, terminalbench_env.py):
   create_session ran inside the spin_up_terminal generator-CM with no
   exception safety — a tmux failure leaked the docker compose project
   (down deferred to GC, never if the env was retained) and surfaced as an
   opaque mid-run death. Now: exception-safe __enter__ with an idempotent
   _teardown(), a tmux/asciinema preflight in the container BEFORE the
   agent loop (TaskEnvironmentError naming the task image + remedy), and
   fail-that-task-cleanly semantics — the failure is recorded as a harness
   error (QueryTrace.error_kind="harness_error"), the container is downed,
   and the run continues.

2) OpenHands in-container SETUP hang misattributed as a model result:
   harness.run() had no bound (terminal-bench runs installed-agent setup
   with max_timeout_sec=inf inside the per-trial agent budget), and the
   summary conversion read the nonexistent results.trial_results attr and
   hardcoded errors=0, folding zero-model-request trials into resolve-rate
   as model misses. Now: global_agent_timeout_sec / global_timeout_multiplier
   are threaded config -> backend -> Harness kwargs (default 1800 s bound on
   SETUP+RUN; configurable per [run]/[[benchmarks]] TOML), and
   summarize_benchmark_results() classifies harness/infra failures out of
   the accuracy denominator keyed on token usage (zero/missing tokens +
   unresolved = the agent never contacted the model), NOT failure_mode —
   terminal-bench 0.2.18 leaves failure_mode UNSET on success AND on
   genuine misses, so a failure_mode-based check would misflag every real
   model miss. Genuine misses (tokens>0, is_resolved=false) stay in the
   denominator.

QueryTrace gains error/error_kind (wired through to_dict/from_dict so the
fields actually reach traces.jsonl; backward-compatible loads), the
AgenticRunner flags zero-model-contact traces and TaskEnvironmentError as
harness errors, and export/summary/console output exclude harness errors
from resolve-rate while reporting them loudly.

terminal-bench stays an undeclared dep on purpose: it requires Python
>=3.12 while this project supports >=3.10,<3.14, so an unmarked extra
would break uv lock. pyproject/uv.lock untouched.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 14:30:10 -07:00
0cac61d3bb docs(evals): rewrite evaluations guide to match the real CLI surface; add openjarvis-eval alias (#534)
docs/user-guide/evaluations.md documented a standalone "openjarvis-evals"
package, a "uv sync --extra eval" install, and an "openjarvis-eval" console
script — a layout from commit bd493832 that was never an ancestor of main.
Rewrite the page against the real surface (jarvis eval / python -m
openjarvis.evals), document all 40 registered benchmark keys and 4 backends,
fix the judge-model default, correct run-all semantics, and split the option
reference into the jarvis-eval subset and the module CLI's research-only
options.

Add the one-line [project.scripts] alias
openjarvis-eval = "openjarvis.evals.cli:main" (the click group the module
CLI already dispatches to) so the long-documented command name works again.

The page was a complete orphan: add it (and the equally orphaned
benchmarks.md) to the mkdocs nav and link it from docs/index.md.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 13:50:17 -07:00
50993dfa4d fix(evals): honor --base-url/--api-key for first-party eval backends (#535)
`jarvis eval run --base-url ... --api-key ...` was silently dropped for
jarvis-direct/jarvis-agent (_build_backend only forwarded the flags to
hermes/openclaw) and ignored by terminalbench-native, which hardcoded
api_base="http://localhost:8000/v1". Worse, with --base-url set the
engine-discovery fallback silently substituted ANY healthy local engine
(observed: requested vllm + healthy endpoint at --base-url, got
OllamaEngine@localhost:11434 — the requested URL was never contacted).

Changes:
- _OpenAICompatibleEngine gains an api_key param (Bearer Authorization
  header on the httpx client; {ENGINE_ID}_API_KEY env fallback with
  hyphen-sanitized names; no header when unset).
- New non-registered OpenAICompatEngine + normalize_openai_base_url()
  (strips a single literal trailing "/v1" so request paths don't double).
- SystemBuilder.engine_instance() injects a pre-built engine; build()
  health-checks it and fails loudly naming the host instead of falling
  back to discovery. Discovery substitution after an explicit -e key now
  logs a warning.
- JarvisDirectBackend/JarvisAgentBackend accept base_url/api_key; on
  base_url they pin an OpenAICompatEngine to that endpoint with a
  fail-fast pre-flight (actionable error naming the URL and probe).
- _build_backend forwards base_url/api_key to first-party backends on
  the CLI path; _run_terminalbench_native receives --base-url as
  api_base (single /v1 suffix) and exports OPENAI_API_KEY around the
  in-process harness run (terminus-2 routes via LiteLLM).
- Suite TOML [backend.external] stays scoped to hermes/openclaw
  (suite_mode=True in the suite drivers) — first-party suite semantics
  are explicitly deferred. The config-host path is untouched.
- Help text updated on both CLI surfaces; KNOWN_BACKENDS now lists
  hermes/openclaw/terminalbench-native.

Fixes the eval-CLI endpoint gap reported by the downstream team.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 13:49:37 -07:00
4218258486 perf(serve): build the system once — drop duplicate SystemBuilder.build() (#263) (#529)
`jarvis serve` constructed every heavy component inline (engine discovery +
instrumentation, telemetry, memory, agent manager, per-agent tools) and then
called `SystemBuilder(config).build()` a second time inside the scheduler
block purely to feed `AgentExecutor.set_system()`. That second build
re-discovered and re-connected the engine, re-instrumented it, re-resolved
tools, re-opened the configured channel and re-created the agent manager —
~30-40s of fully redundant startup work (the headline remaining cost in #263
after engine probes were parallelised and the version check moved off the hot
path in #470).

Fix: assemble the executor's `JarvisSystem` from the components already built
inline instead of rebuilding from scratch. `AgentExecutor` only reads
`engine`, `model`, `config`, `memory_backend`, `tool_executor`,
`session_store` and `channel_backend` off the system; all are wired here. The
memory backend is now constructed just before the scheduler block (it was
built later) so the executor's system can reference it, and the primary
agent's resolved tool list is reused to build the scheduler's `ToolExecutor`
(preserving the MCP-discovered-tool pool the executor reads via
`tool_executor._tools`). `skill_manager` / the learning orchestrator are only
consumed by the orchestrator's `system.ask()` path, which the executor never
invokes, so they are intentionally omitted.

Tests: new `tests/cli/test_serve_single_build.py` patches
`SystemBuilder.build` and asserts it is never called during `jarvis serve`
startup, and that the executor still receives a system exposing
`tool_executor` / `session_store` / `memory_backend` (plus engine/model/config).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:10:10 -07:00
176ed3029b fix(channels): unify send() destination/reply contract (Discord #515/#516) (#528)
The channel `send()` contract was inconsistent across adapters. Almost
every adapter (Discord, Slack, email, WhatsApp, ...) treats the first
positional `channel` arg as the real DESTINATION id and `conversation_id`
as an optional reply/thread reference. Telegram alone treated
`conversation_id` as the destination (`chat_id = conversation_id or
channel`).

`JarvisSystem._on_channel_message` hard-coded the Telegram-shaped mapping
for ALL channels: `send(cm.channel, reply, conversation_id=cm.conversation_id)`.
Since inbound `ChannelMessage`s carry the channel TYPE label in `.channel`
("discord") and the real destination id in `.conversation_id`, this sent
the literal "discord" as the Discord channel id (HTTP 400
NUMBER_TYPE_COERCE, #515) and passed the channel id as a Discord
`message_reference` (MESSAGE_REFERENCE_UNKNOWN_MESSAGE, #516).

Fix: define and document ONE canonical contract on `BaseChannel.send` —
positional `channel` = destination id, `conversation_id` = inbound message
id used as a reply reference — and dispatch it from `_on_channel_message`
as `send(cm.conversation_id, reply, conversation_id=cm.message_id)`,
matching the already-fixed `ChannelAgent` path (#495/#459). Telegram's
`send()` is brought into line (destination = `channel`, with a
`reply_to_message_id` reply ref and a legacy `conversation_id`-only
fallback) so it keeps working unchanged.

The DiscordChannel `_gateway_loop` ChannelMessage shape locked by #495 is
untouched; `tests/agents/test_channel_agent.py` passes unchanged. The
stale `test_serve_channel_wiring.py` assertions (which encoded the old
buggy mapping from #94) are updated, and regression tests are added for
Discord (real channel id + correct message_reference), Telegram (chat id
+ reply ref + legacy fallback), and per-channel dispatch in
`_on_channel_message`.

Fixes #515
Fixes #516

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:10:06 -07:00
590addae4c test(engine): expect EngineConnectionError for vllm 404 (fix main CI) (#530)
#463 made the OpenAI-compatible engine wrap upstream HTTP errors (incl.
404) in EngineConnectionError with an actionable message, but
test_invalid_model_404 still asserted the raw httpx.HTTPStatusError, so it
broke on main once #463 landed (#463 was a stale fork PR with no CI, so it
wasn't caught pre-merge). Expect EngineConnectionError now, asserting the
httpx.HTTPStatusError is preserved as the chained cause. Whole tests/engine
suite is green again.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:09:37 -07:00
bc9fa6b3c8 fix(memory): surface clear error when openjarvis_rust missing instead of silent no-op (#527)
Memory tools degraded silently and misleadingly when the mandatory
`openjarvis_rust` extension was absent from the *serving* venv:

- `POST /v1/memory/store` returned HTTP 200 `{"status":"stored","note":
  "no backend available"}` and stored nothing (silent data loss).
- `POST /v1/memory/index` returned a generic "No memory backend available",
  and the desktop frontend discarded the server `detail` and threw a blanket
  "Failed to index path", blaming the path instead of the real cause.
- `GET /v1/memory/config` reported `backend_type: sqlite` even though no
  backend could be constructed.

Root cause: `SQLiteMemory.__init__` calls `get_rust_module()` (which raises
ImportError by design — the Rust ext is mandatory, no Python fallback), and
`_get_memory_backend` swallowed that ImportError and returned `None`,
conflating "native extension missing" (a hard install error) with "memory
intentionally disabled" (benign). A chunking floor also silently dropped whole
short documents, and the installer never verified the extension imported from
the serving venv before writing its success marker.

Fix (no fake Python fallback — the Rust ext stays mandatory by design):

- Add `MemoryBackendUnavailable` + `RUST_MISSING_HINT` in tools/storage/_stubs.
  `SQLiteMemory.__init__` translates the bridge ImportError into this clear,
  actionable error ("run `uv run maturin develop ...`").
- `_get_memory_backend` distinguishes the two cases: a missing native ext
  raises HTTP 503 with the actionable hint; a benign unconfigured backend
  still returns `None` (graceful path preserved for search/stats).
- `/store` now returns 503 instead of a 200 silent no-op.
- `/config` reports `available: false` + `detail` instead of falsely claiming
  a healthy `backend_type`.
- `/index` adds a `note` when `chunks_indexed == 0` so "indexed" never
  silently means "stored nothing".
- chunk_text no longer drops an entire short document below `min_chunk_size`
  (the floor only discards tiny *trailing* fragments now).
- Frontend `storeMemory`/`indexMemoryPath` surface the server `detail` instead
  of blanket strings; `MemoryConfig` gains optional `available`/`detail`.
  (Left the pre-existing `backend` vs `backend_type` mismatch untouched.)
- build-extension.sh verifies `import openjarvis_rust` succeeds in the serving
  venv before writing the `extension-built` marker.

Regression tests: tests/server/test_api_routes.py::TestMemoryRustMissing mocks
`get_rust_module` to raise ImportError and asserts /store (503, not 200 no-op),
/index (actionable detail, not "Failed to index path"), and /config
(available:false) all surface the clear error; tests/memory/test_chunking.py
asserts short-only docs are kept while tiny trailing fragments are still
filtered.

Fixes #502

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:25:10 -07:00
18d9897317 fix(version): restore version to 1.0.2 (undo erroneous revert to 0.1.1) (#525)
The package version was reverted from 1.0.2 to 0.1.1, which:
- makes `jarvis --version` / installed metadata report 0.1.1 (the root of
  #478 "version unchanged after update" and the version half of #520), and
- drives autotag.yml to compute `0.1.2.dev<count>` — BELOW the real 1.0.x
  line — so every push to main now auto-tags and publishes sub-1.0.2 dev
  releases to PyPI (latest stable is 1.0.2; dev line was 1.0.3.dev*).

Restore version to 1.0.2 (the last released stable). autotag resumes
`1.0.3.dev<count>`, back on the 1.0.x line, and reported versions are
correct again. Pipeline-compatible (pypi-publish.yml still seds the
tag version). Full hatch-vcs dynamic versioning is a separate follow-up.

Fixes #478. Refs #520.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:03:05 -07:00
Jon Saad-FalconandGitHub d8d4985eb5 Update README.md 2026-06-09 13:54:06 -07:00
4523715bff fix(ci): gate live HuggingFace Hub download tests behind hub marker (#507)
The two eval-dataset suites that download real corpora from the
HuggingFace Hub at runtime were running in the default CI lane. When the
Hub was unreachable or rate-limited they failed and reddened `main` even
though no code changed — confirmed by #506 (docs-only) failing on merge
while its own PR run passed an hour earlier. They also dominated CI
wall-time (~33 min of downloads + retry backoff on the failing run).

Add a `hub` pytest marker, apply it to both suites via module-level
`pytestmark`, and exclude it from the default CI lane
(`-m "not live and not cloud and not hub"`). The tests stay runnable on
demand with `pytest -m hub`.

The ADP provider swallows per-config download errors and returns 0
records on a network failure, so a Hub outage surfaced there as
`assert 1 <= 0` (not an exception) — it could not be made non-flaky by
exception handling alone, only by gating.

Coverage holds: removing these from CI drops total from 60.92% to
~60.73% (paranoid worst case 60.18%), still above the 60% gate.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:31:50 -07:00
Jon Saad-FalconandGitHub f7c948fe12 Merge pull request #506 from open-jarvis/update-discord-link
Update Discord invite link
2026-06-06 12:09:19 -07:00
Jon Saad-FalconandClaude Opus 4.8 30a014faf4 Update Discord invite link to discord.gg/CMVBmDQ5Fj
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:07:07 -07:00
68b27654a8 docs(showcase): add outcome-first gallery tier above Tutorials (#500)
Addresses feedback from our Discord admin curating #config-showcase: the
existing docs land non-technical users straight into Tutorials, which
are script-first and TOML-heavy ("standalone script you can run
immediately, a TOML recipe, a detailed walkthrough"). For a curious-
but-non-technical reader trying to decide whether OpenJarvis is worth
their weekend, that's the wrong first contact — they bounce before they
ever see what the framework can do for them.

This PR inserts a new Showcase tier *above* Tutorials in the docs
information architecture. Each entry is outcome-first: hook sentence,
hero screenshot, 2-3 short paragraphs of personal context, then a
"How I set this up →" link that lands on the relevant Tutorial /
User Guide. The Showcase is the funnel; Tutorials are the build steps.

Five inaugural entries — drafted to be paste-ready for #config-showcase:

- showcase/morning-brief.md          — Slack/email/GitHub overnight digest
- showcase/persistent-memory.md      — SOUL.md/MEMORY.md/USER.md story
- showcase/cost-savings.md           — the public leaderboard as motivation
- showcase/discord-companion.md      — DM Jarvis from anywhere
- showcase/coding-assistant.md       — code review on an airplane

Plus the contributor template and an assets directory:

- showcase/CONTRIBUTING.md           — format skeleton + editorial conventions
                                       (screenshot specs, what to redact, tone)
- assets/showcase/README.md          — asset directory conventions
- assets/showcase/*.png              — placeholder hero screenshots (1600x1000,
                                       6 KB each, dark gradient) so the gallery
                                       renders cleanly before community
                                       submissions populate real screenshots

Information-architecture changes:

- mkdocs.yml — insert "Showcase" tier between Getting Started and
  Tutorials. Funnel order is now: land → "what's possible?" → "build it."
- docs/index.md — new hero card directly under the tagline, pointing to
  the Showcase. The research-framework framing stays, but no longer
  occupies the first scroll-fold.

CSS:

- docs/stylesheets/extra.css — `.showcase-screenshot` class adds rounded
  corners + subtle border so hero images (placeholder or real) read as
  intentional rather than as broken-image artifacts.

Validation:

- `uv run mkdocs build` (CI mode) succeeds.
- `uv run mkdocs build --strict` produces zero showcase-specific
  warnings. The 18 remaining strict-mode warnings are all pre-existing
  on main (`desktop-auto-update.md`, `telemetry.md`, griffe parser
  warnings on existing source, mkdocs_autorefs cross-reference issues).

Explicit non-goals (deferred to follow-up PRs in the showcase-tier
roadmap):

- `jarvis showcase` CLI for personal recaps (PR #2)
- Showcase-aligned recipes in `src/openjarvis/recipes/data/` so
  "How I set this up →" links into 2-command installs (PR #2)
- Automated screenshot regeneration via Playwright on release tags (PR #3)
- Replacing placeholder PNGs with real screenshots — that happens
  organically as community contributors and team members submit their
  own setups (see CONTRIBUTING.md for the format)

Co-authored-by: krypticmouse <herumbshandilya123@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 17:42:23 -07:00
1827c1578b fix(leaderboard): filter quarantined methodology_version=0 rows (#501)
Pairs with the Supabase migration that added `methodology_version` to
`savings_entries` and demoted 43 corrupt rows (pre-fix telemetry from
March 2026) to version 0. Adds `methodology_version=gte.1` to the
public leaderboard fetch URL so the quarantined rows hide at the query
layer — fewer bytes over the wire than client-side outlier filtering,
and forward-compatible: new clients write `methodology_version >= 1`
and remain visible.

The leaderboard's existing client-side outlier thresholds stay in place
as a second line of defence in case any future v >= 1 row slips past
the bounds.

Smoke-tested live: anon-key fetch against
`mtbtgpwzrbostweaanpr.supabase.co` returns 291 rows (was 334), top
result is the largest-savings user with v=1. A separate `eq.0` probe
confirms quarantined rows are skipped only by the filter (no RLS
policy in place yet — flag for follow-up if you want hard isolation).

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 17:42:19 -07:00
28ef745384 fix(leaderboard): correct telemetry pipeline and outlier handling (#498)
* fix(leaderboard): correct telemetry pipeline and outlier handling

The public leaderboard at /leaderboard showed a clear bimodal Wh/token
distribution: most users at ~3-5 J/token, ~30% inflated by 1000-4000×.
A 5-agent investigation workflow + 4-agent verification pass traced the
inflation to a cluster of related bugs across telemetry, server, and
display layers. This PR fixes the in-repo half. Backfilling existing
Supabase data is a separate follow-up.

Bug 1 — Dual telemetry recording (`server/routes.py`)
=====================================================

`_handle_direct` wrapped the engine with `instrumented_generate`
unconditionally. When `app.state.engine` was already an
`InstrumentedEngine` (the common case when telemetry is wired in),
BOTH layers published `TELEMETRY_RECORD` — once from the inner
`InstrumentedEngine.generate`, once from the outer wrapper. Every
chat-completion request was counted twice in the leaderboard pipeline.

Fix: detect the InstrumentedEngine and unwrap to `._inner` before
passing to the wrapper so only one layer fires.

Bug 2 — KV-cache fallback over-counts multi-turn (`server/savings.py`)
=====================================================================

`compute_savings` falls back from `prompt_tokens_evaluated` to
`prompt_tokens` when the KV-cache-aware count is missing. But routes.py
aggregates by summing each turn's full prompt — which counts the system
prompt N times for an N-turn conversation. The fallback inflated FLOPs
and energy by N×.

Fix: use 0 (conservative under-count) when the evaluated count is
missing rather than falling back to the inflated `prompt_tokens` sum.

Bug 3 — TelemetryRecord lacked methodology versioning
=====================================================

There was no per-record version tag, so legacy (pre-fix) and current
records were silently aggregated together in the public leaderboard
even though they used different methodologies.

Fix: add `token_counting_version: Optional[int]` field to the
`TelemetryRecord` dataclass + a nullable column to the SQLite schema
(with idempotent migration); the constant moves from `server.savings`
to `core.types` to avoid the server→telemetry layering. New records
write the current version; pre-fix rows remain NULL. The aggregator
gains a `current_methodology_only=True` flag that filters NULL rows out
of leaderboard sums — local dashboards leave it off so historical
aggregates still render.

Bug 4 — Leaderboard JS displayed missing telemetry as legit zeros
=================================================================

Rows with significant token counts but `energy_wh_saved = 0` and
`flops_saved = 0` rendered as `0.00 Wh / 0 FLOPs` — visually identical
to a user who genuinely did almost nothing. The headline totals also
included these rows.

Fix: new `isMissingTelemetry()` detector renders missing-telemetry
energy/FLOPs cells as `—` (with a tooltip explaining why); a new
`.lb-missing` CSS class differentiates the placeholder visually.

Bug 5 — Outlier filter was too generous
=======================================

`MAX_ENERGY_WH_PER_TOKEN = 10` left a 10,000× margin that admitted
every Group B row even though those values are physically impossible
(would imply a space-heater per token). Same for the FLOPs cap at
`1e17`.

Fix: tighten to `0.5` Wh/token (still 500× over a typical consumer GPU)
and `1e15` FLOPs/token (still 10,000× over typical). This removes
existing pre-fix corrupt rows from the public view without touching
Supabase.

Tests
=====

New regression tests (all pass, ruff clean):

- `tests/server/test_routes.py::test_instrumented_engine_unwrapped_to_avoid_dual_telemetry`
  — pins the bug 1 fix; asserts exactly ONE TELEMETRY_RECORD event per
  request when the engine is already an InstrumentedEngine.
- `tests/server/test_savings.py` (NEW file, 3 tests) — pins the bug 2
  fix (FLOPs not inflated via fallback) and the cost-side invariant
  (dollar savings still use full prompt_tokens because cloud providers
  bill per input token even when local KV cache hit).
- `tests/telemetry/test_aggregator.py::TestMethodologyFilter` (3 tests)
  — default behaviour includes legacy rows (local dashboard parity);
  `current_methodology_only=True` excludes them; the summary surface
  honors the same filter.

Unrelated test note
===================

`tests/telemetry/test_energy_wiring.py::TestTelemetryStatsEnergy::test_export_includes_energy_fields`
and `::TestEndToEndPipeline::test_ask_to_export_with_energy` are flaky
on this branch but ALSO flaky on `main` (confirmed via stash + the
banner-only run). Root cause: `_version_check.py:132-135` prints the
"new version of OpenJarvis is available" banner to stdout outside the
throttle window, contaminating `json.loads(result.output)` in those two
tests. Reproducible with `OPENJARVIS_NO_UPDATE_CHECK=1` → all pass.
That's a separate bug (the banner shouldn't write to the same stream
as machine-readable output); not addressing it here to keep this PR
focused on the leaderboard pipeline.

What this PR explicitly does NOT do
====================================

- Backfill ~30% of Supabase rows that already shipped with inflated
  values from pre-fix clients. That requires Supabase write access and
  is the natural follow-up — see PR comments for proposed dry-run audit
  query and the additive `methodology_version` column migration.
- Distinguish which past submissions came from buggy vs correct clients
  retroactively (no `app_version` tag on existing submissions).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(evals): fix test_energy_scales_linearly under leaderboard fix

CI on #498 failed in the slow `test` job:

    tests/evals/test_use_case_benchmarks.py::TestSavings::
    test_energy_scales_linearly — ZeroDivisionError: float division by zero

Root cause: the test (and `test_energy_wh_matches_direct_formula`)
called `compute_savings(N, 0)` without `prompt_tokens_evaluated`. Under
the OLD buggy fallback, an unset evaluated count silently became N,
energy scaled linearly with N, and the test passed by accident. Under
this branch's conservative fix the fallback is 0, FLOPs collapse to 0,
and the `p10.energy_wh / p1.energy_wh` ratio divides 0 by 0.

The test's own docstring says "evaluated tokens (KV-cache model)" — it
was always meant to exercise the explicit-evaluated path, the API
call just didn't match. Pass `prompt_tokens_evaluated` explicitly so
the test now expresses the invariant it claims to.

Same one-line fix for `test_energy_wh_matches_direct_formula` plus an
explicit `flops > 0` sanity assertion — that test was passing trivially
with `0 == 0` after the conservative fallback fix, masking whether the
formula was actually being exercised.

No production code change in this commit. Verified locally: all 6
TestSavings tests pass; the 5 new regression tests added on this branch
still pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: krypticmouse <herumbshandilya123@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 16:59:24 -07:00
f72abadf68 fix(desktop): AppImage env-strip + attach to existing healthy server (#496)
Closes #455. @wishwanto on Linux Mint hit two distinct bugs in the desktop launcher that ship together because they share the same boot_backend code path.

Bug A — AppImage hang on "starting server":
When the desktop is shipped as an AppImage on Linux, the AppImage runtime sets LD_LIBRARY_PATH to its temp-extracted lib dir. Children we spawn (uv, ollama) inherit that env, then Python's numpy/cryptography extensions dlopen the AppImage's mismatched libstdc++/libssl versions and python dies silently. Stderr drainer sees immediate EOF → GUI hangs forever.

Fix: new helper prepare_subprocess_for_appimage strips LD_LIBRARY_PATH, LD_PRELOAD, APPIMAGE, APPIMAGE_UUID, APPDIR, ARGV0 when $APPIMAGE is set. Linux-only via #[cfg(target_os = "linux")] — true no-op on macOS/Windows. Called at all 3 spawn sites: ollama sidecar, uv sync, uv run jarvis serve.

Bug B — Desktop force-kills the user's already-running `jarvis serve`:
Old code (post #437) ran fuser -k 8000/tcp / taskkill /PID /F on ANY HTTP response from :8000/health — including 200 OK from a healthy user-launched serve.

Fix: replace the indiscriminate kill with a health-aware decision tree:
  * 2xx /health → confirm with a 500ms-apart second probe, then attach (set server/model/ollama ready, return without spawning)
  * 503 → user-facing error "wait for engine or stop it"
  * other 4xx/5xx → user-facing error with lsof -i :8000 (unix) / netstat (windows) hint
  * Err → fall through to normal spawn (unchanged)

Adversarial review caught and fixed 5 real issues pre-commit:
  - HIGH: parallel cargo-test env mutation race → APPIMAGE_ENV_LOCK Mutex
  - HIGH: 2xx attach left model_ready/ollama_ready false → set both true before return
  - MEDIUM: #[allow(unused_variables)] suppressed lint on Linux too → cfg_attr
  - MEDIUM: single-probe attach trusted a 2s snapshot → confirmatory second probe
  - MEDIUM: /bin/true would silently mislead on Windows → HARMLESS_BIN const per platform

2 new unit tests; CI green on all gates including rust.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 19:54:06 -07:00
945dabbce7 fix(channels): use conversation_id (not channel type) as Discord reply destination (#495)
Closes #459. Reported by @jasonftl with a precise smoking gun: incoming ChannelMessage has channel="discord" (TYPE label) and conversation_id=<numeric channel id>, but the reply code was using cm.channel as the destination — Discord saw /channels/discord/messages and 404'd silently, blackholing every reply.

The bug was two field-mappings in channel_agent.py:_process_message (the happy path + the exception path):

  self._channel.send(
      msg.channel,                          # WRONG — TYPE label
      reply,
      conversation_id=msg.conversation_id,  # WRONG — channel id used as msg-ref-id
  )

The existing DiscordChannel.send() contract — proved by the existing test_send_with_conversation_id test — is:
  - first positional `channel` = native destination ID (Discord channel id)
  - `conversation_id` kwarg = native message ID for reply threading

Fix: swap both fields to the correct ones from ChannelMessage:

  self._channel.send(
      msg.conversation_id,                  # Discord channel id
      reply,
      conversation_id=msg.message_id,       # message id for threading
  )

Plus a defensive guard in discord_channel.py: when channel is empty, refuse fast with a clear warning instead of POSTing to /channels//messages and silently 404'ing.

3 new tests, 38 affected pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 19:54:03 -07:00
7506bcc0e4 fix(mcp): send Authorization: Bearer; auto-load MCP tools in ask/serve (#494)
Closes #461. Reported and empirically validated by @swilliams76360.

Two bugs prevented authenticated MCP servers (e.g. Home Assistant) from working with OpenJarvis:

1. StreamableHTTPTransport never sent Authorization: Bearer <token> — constructor didn't accept a token kwarg and _build_headers() never set the header. Authenticated MCP servers always returned 401.

2. jarvis ask and jarvis serve never iterated config.tools.mcp.servers — only loaded tools from ToolRegistry. MCP tools were silently dropped on every CLI invocation.

The reporter's 3-file fix was correct; the workflow investigation surfaced a 4th file (agent_manager_routes.py:695, identical broken code) and an adversarial-review catch (MCP clients in _build_tools would be GC'd on function return, closing transports mid-request — fixed by stashing on agent._mcp_clients).

Edits:

- transport.py: token kwarg + Authorization header (skips on empty/None — avoids malformed "Bearer " that triggers confusing 400s).
- mcp/loader.py (NEW): shared load_mcp_tools_from_config helper returning (tools, clients). Caller MUST hold the clients reference.
- builder.py + agent_manager_routes.py: extract cfg.get("token"), forward to transport.
- cli/ask.py: _run_agent calls the loader, dedupes by spec.name (registry wins), stashes clients on agent._mcp_clients.
- cli/serve.py: same pattern in main-agent AND channel-agent paths; mcp_clients initialised before the accepts_tools branch so the post-instantiation reference is always valid.

22 new tests (transport + loader + discovery updates), 179 total cli/server/mcp tests pass on this branch.

Adversarial review interrogated 10 angles — slotted-class attr safety, MCPConfig duck-typing, config.tools.mcp AttributeError risk, dedup precedence, token leak via str(exc), logger scope in serve.py, _mcp_clients shadowing, _channel_mcp_clients lifetime, empty-token future-compat, lazy-import cost shift. Nine non-issues; the tenth (theoretical token leak via httpx exception str()) assessed as low actual risk because the token is a header value, not URL-embedded.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 18:27:00 -07:00
de3e86c544 fix(install): use requires-python range; bootstrap shims are Python-free (#476, #484) (#492)
Consolidates two reports that overlap in scope:

- #476 (@senki): install.sh hardcoded `--python 3.11` but
  pyproject.toml declares `requires-python = ">=3.10,<3.14"`. The
  installer should track the project's allowed range, not pin a
  conservative-three-years-ago version.
- #484 (@sanjayravit): install.sh crashed on hosts with no
  python3/python on PATH because `mark_done`, `beacon`, and
  `get_anon_id` all called $PY_CMD via inline Python heredocs.

This PR closes both gaps with five surgical edits to install.sh
(all behavior-preserving for the existing happy path; the bats
tests prove it):

1. get_anon_id — replace `python3 -c uuid` with POSIX
   `/dev/urandom + od` + bash substring expansion. Same UUID v4
   shape; works on Python-less hosts.

2. beacon — replace the 40-line Python heredoc with curl + a
   shell-built JSON payload. All inputs are from controlled
   sources (event ∈ fixed vocabulary, stage from stage_label(),
   numeric ids/codes from validated arithmetic, anon_id from a
   fresh UUID) so no general-purpose JSON escaping is needed.
   `|| true` is load-bearing — PostHog 5xx must never abort an
   install via the ERR trap.

3. mark_done — replace `python3 -c json.load+update+dump` with
   awk that regenerates the file from scratch. Idempotent: if
   the key is already marked, return early. Robust against
   prior format drift. wsl key is always rewritten last so a
   later FORCE_WSL=1 re-run correctly updates it.

4. parse_requires_python — new helper. Greps the project's
   requires-python field, handles inclusive (`<=3.13`) and
   exclusive (`<3.14`) upper bounds correctly, falls back to
   3.11 if pyproject can't be parsed (the previous hardcoded
   value — safe under the existing 3.10-3.13 range).

5. create_venv — call parse_requires_python instead of
   hardcoding 3.11. The existing uv-managed-Python fallback
   (from #444) still kicks in if the host doesn't have the
   target version installed.

Adversarial review caught two real bugs before commit:

- HIGH: parse_requires_python's exclusive-bound regex would
  also match the digits after `<=` (inclusive bound) and then
  incorrectly subtract 1 — producing 3.12 from `<=3.13`. Fixed
  by checking the inclusive form first.
- MEDIUM: the new "no Python on PATH" bats test silently skips
  symlinking `pgrep` on hosts where it isn't at /usr/bin or
  /bin (some minimal BusyBox configurations). Added an explicit
  "no matching process" fallback so start_ollama's check works.

New bats coverage:

- `install succeeds with no system Python on PATH (#484)` —
  builds a PATH that excludes python3/python and exercises the
  full install. Asserts state file is written and contains
  greppable step keys.
- `mark_done is idempotent — second mark of same key doesn't
  duplicate` — re-runs the install and verifies install_uv
  appears exactly once in install-state.json.
- `create_venv picks newest in requires-python range, not
  hardcoded 3.11 (#476)` — asserts uv was called with
  `--python 3.13` (the upper minor of `>=3.10,<3.14`).

The git stub now includes `requires-python = ">=3.10,<3.14"`
in its fake pyproject.toml so create_venv has something
realistic to parse.

@sanjayravit — your PR #484 motivated this consolidation;
closing that one as superseded with credit.

Co-authored-by: krypticmouse <herumbshandilya123@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 16:59:29 -07:00
b3cfa398b4 fix(config): honor system_prompt.prefix from config.toml (#482)
A [system_prompt] prefix set in config.toml was silently ignored: (1)
load_config()'s section allowlist dropped the [system_prompt],
[memory_files], [compression], and [skills] blocks entirely; (2)
SystemPromptConfig had no prefix field; (3) the builder never prepended
one.

Add the four blocks to the allowlist, add prefix: str = "" to
SystemPromptConfig, and prepend a "prefix" PromptSection at the front of
SystemPromptBuilder's frozen sections so it leads build() output and is
exposed via sections() (#457). Empty prefix emits no section — existing
configs are byte-for-byte unchanged.

Rebuilt on top of #457 (which refactored the builder to PromptSection
objects); the original #452 by @SoulSniper-V2 patched the pre-#457
method. Credit to @SoulSniper-V2. Adds the regression tests the original
PR lacked: config parse + prefix-prepended + empty-prefix-unchanged.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:14:42 -07:00
53fb42104e feat(rlm): expose real tool calls inside the REPL (#481)
* rlm: expose real tool calls inside the repl

* rlm: wrap long TypeError message in repl

* style(rlm): collapse over-wrapped TypeError to satisfy ruff format

The cherry-picked RLM tool-call work left a `raise TypeError(\n message\n)`
that `ruff format --check` rejects (the PR's own lint-fix commit broke
format). Collapse to `raise TypeError(message)`. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Eddie Richter <eddie.richter@amd.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:31:08 -07:00
f922811cba fix(tools): return labeled page content from web_search (#480)
web_search returned thin, unlabeled results (Tavily default search_depth,
**title**/url/content blob), so small models in the native_react loop
tended to echo URLs instead of synthesizing content (#390). Query Tavily
with search_depth="advanced" and format each result as a labeled block:

    ### {title}
    Source: {url}
    Summary: {content or snippet}

joined by `---` separators. DuckDuckGo fallback uses the same labeled
shape. Falls back to a result's `snippet` when `content` is absent.

Extracted from #448 (the web_search portion only; that PR also bundled
an unrelated install.sh rewrite, left out of scope here). Credit to
@sanjayravit for the original fix. Adds tests asserting the labeled
format, the snippet fallback, and search_depth="advanced"; updates the
max_results test for the new call signature.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:31:05 -07:00
58f7f717ae docs(test): use canonical github.io install URL in run-as-root case (#473)
The run-as-root install case doc still showed the retry command using
the community-operated openjarvis.ai domain, whose TLS is broken and
which the project does not control (#337). Point it at the canonical,
project-controlled GitHub Pages URL, matching README and the install
docs. Documentation only — the bats test it describes asserts exit
code + "root" in stderr and has no URL dependency.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:51:16 -07:00
4b4fd587b2 fix(frontend): send local API key as Bearer on /v1 + /api requests (#471)
When `jarvis serve` runs with an API key configured, AuthMiddleware 401s
every /v1 and /api request that lacks a Bearer token. The frontend never
sent one, so telemetry, managed-agents, savings, etc. all failed (#266).

- Add getApiKey() (reads settings.apiKey, with optional
  VITE_OPENJARVIS_API_KEY build-time override) and authHeaders() to
  api.ts, plus an apiFetch() wrapper that prepends getBase() and injects
  the Bearer header on every local-server call. Route all /v1 + /api
  fetches through it so none can omit auth.
- Add `apiKey` to the Settings model (store.ts) and a password field in
  Settings → Connection so users can enter it.

Keyless local servers are unaffected: with no key, no Authorization
header is sent (byte-for-byte unchanged). The Supabase savings path keeps
its own anon key — not conflated with the local key.

Bootstraps vitest (no prior frontend test runner) + a `test` script, and
adds api.auth.test.ts covering getApiKey/authHeaders. Verified: tsc
--noEmit clean, vitest 6/6, vite build succeeds.

Deferred (not in scope): WebSocket auth (browsers can't set WS headers)
and Tauri auto-injecting a generated key.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:25:14 -07:00
690b95e050 perf(serve): parallelize engine discovery + async version check (#470)
`jarvis serve` startup was slow on two counts addressed here:

1. discover_engines() probed each registered engine's health() serially,
   and every probe is a blocking network check with its own ~2s timeout —
   so N dead/slow localhost ports cost N*2s. Run the probes concurrently
   in a ThreadPoolExecutor; the existing healthy.sort() normalizes order,
   so the result is identical to the serial version. health() impls are
   read-only on per-instance HTTP clients with no shared mutable state.

2. The PyPI update check ran a blocking urlopen (up to 3s on a cache
   miss) inline before dispatch, delaying every command. Move it to a
   daemon thread — it's best-effort and never raises.

Together these remove ~10-30s from cold startup. Adds a regression test
asserting discovery probes overlap (concurrency), not just that output
is unchanged (covered by existing tests).

Note: the larger ~30-40s win — the duplicate SystemBuilder.build() in
serve.py — is NOT addressed here; it's not redundant (the second build
wires a JarvisSystem the inline path never constructs) and needs a
design pass. Deferred.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:17:38 -07:00
d1ad3316b6 build(rust): pin MSRV to 1.88 + add rust-toolchain.toml (#469)
The Rust workspace fails to build on stable 1.86/1.87 with cryptic E0658
errors deep in dependencies: rig-core uses let-chains and
openjarvis-skills uses `is_multiple_of`, both stabilized in 1.88 (#252).

Add a rust-toolchain.toml pinning channel 1.88 (rustup then auto-selects
a working toolchain instead of erroring mid-build) and declare
rust-version = "1.88" in [workspace.package] to self-document it.

Because the toolchain pin makes CI's `cargo clippy -D warnings` run under
1.88 — whose clippy enables `uninlined_format_args` — also apply the
mechanical `format!("{}", x)` -> `format!("{x}")` rewrites across the
workspace (via `clippy --fix`; string output is identical, no logic
change). Verified clippy + fmt + `cargo test --workspace` clean on BOTH
1.88 and current stable.

Verified locally: cargo +1.86 and +1.87 fail (E0658), +1.88 builds and
tests cleanly. Supporting true 1.86 is infeasible without downgrading
rig-core below the versions exposing the token-usage symbols we use —
deferred.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:17:33 -07:00
2b14315f3c fix(security): detect Rust at import time + SSRF Python fallback (#467)
* fix(security): detect Rust at import time + SSRF Python fallback (#225)

RUST_AVAILABLE was hardcoded to True, so the exported flag never
reflected reality and the pure-Python fallbacks it was meant to gate
were unreachable.

- _rust_bridge: compute RUST_AVAILABLE dynamically by probing the
  compiled extension once at import time.
- security.ssrf: check_ssrf now falls back to the existing
  _check_ssrf_python implementation when the Rust extension is not
  built, instead of raising ImportError. The SSRF guard is
  security-critical and must never be silently skipped or crash just
  because Rust was not compiled.
- tools.browser: drop the `except ImportError: pass` around the SSRF
  check, which previously disabled SSRF protection entirely on installs
  without the compiled backend (internal/metadata endpoints reachable).
- tests: cover the Python fallback path (metadata IP, private IP, and
  public URL) with RUST_AVAILABLE patched False.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(security): make test_no_hostname backend-agnostic

The cherry-picked #451 fix adds a pure-Python SSRF fallback, but
test_no_hostname asserted the Rust-specific message "Invalid URL". The
Python fallback returns "No hostname in URL" for the same input, so the
SSRF suite failed on exactly the uncompiled-install path #451 targets
(in CI the Rust extension is built, masking it).

Assert the security behavior (URL blocked, non-None reason) and accept
either backend's wording, so the suite passes on both the Rust and
Python paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(tools): update browser SSRF test for non-bypassable check

The #225 fix removes the `except ImportError: pass` that silently
disabled the SSRF check in browser navigation. The existing
test_execute_ssrf_module_missing asserted that very anti-pattern ("skip
check and proceed"), so it failed once the swallow was removed.

Replace it with tests that assert the SECURE behavior: the SSRF check
runs unconditionally and is honored (a private-IP URL is blocked even
when navigation would otherwise succeed), and a public URL still
navigates. check_ssrf's pure-Python fallback means the import never
fails anymore, so the old skip path no longer exists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Rahul <therahulll56@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:57:44 -07:00
b5926400e2 fix(engine): retry without temperature on unsupported_value 400 (#466)
Some OpenAI models (e.g. gpt-5, the default for a fresh cloud install)
reject a non-default temperature with HTTP 400 "Unsupported value:
'temperature' does not support 0.7 ... Only the default (1) value is
supported." — so the user's very first prompt fails (#426).

Detect that specific 400 (param=temperature + unsupported_value/"only the
default"/"does not support") and retry the create() once without
temperature, mirroring the tools-400 retry in the Ollama and
OpenAI-compat engines. Unrelated 400s are re-raised unchanged.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 10:26:17 -07:00
3398e0c10b fix(cli): escape exception text in agents-list error handler (#465)
`jarvis agents list` crashed with a secondary Rich MarkupError when the
underlying exception message contained markup metacharacters like
`[...]`: the error handler did `console.print(f"[red]Error: {exc}[/red]")`,
and Rich re-parsed the interpolated message as markup (#297).

Escape the dynamic message with rich.markup.escape and keep only the
static "Error:" label styled, so the original error surfaces cleanly
instead of a traceback. Adds a regression test that reproduces the
MarkupError via an exception message with brackets.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 10:26:11 -07:00
bd1ab115ec fix(engine): convert apple_fm_shim cumulative snapshots to deltas (#464)
Apple FM's stream_response yields cumulative text snapshots, but
OpenAI-compatible clients concatenate delta.content — so streamed
responses were duplicated/stuttered (#378). Diff each snapshot against
the last and emit only the incremental suffix; fall back to the full
snapshot if the model revises earlier text, and skip empty deltas.

Rebased on #377 (apple_fm_sdk migration): uses the options= streaming
API. Adds a stubbed-SDK regression test asserting deltas are
incremental, not cumulative.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 10:11:53 -07:00
a08282c3e9 fix(server): stream tool_calls instead of agent filler on tool requests (#460)
When a client streams (stream:true) with explicit `tools`, the server
routed to the agent stream bridge, which ignored request_body.tools, ran
the agent's own tool loop, and word-split filler content into fake token
deltas — dropping the caller's tool_calls. This is the streaming analog
of #414 (whose non-streaming fix was #454).

Now stream+tools bypasses the agent and streams the model's raw
function-calling decision via engine.stream_full(), emitting OpenAI-shape
tool_calls deltas and a tool_calls finish_reason. Adds tool_calls to
DeltaMessage and removes the now-dead _handle_agent_stream.

Verified end-to-end on Ollama (qwen3.5:4b) plus a unit regression test.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 16:09:36 -07:00
d13006263e fix(server): bypass agent on /v1/chat/completions when caller passes tools (#454)
Closes #414.

Root cause: routes.py:151 unconditionally routed non-streaming /v1/chat/completions through _handle_agent when an agent was registered. _handle_agent calls agent.run(input_text) which IGNORES request_body.tools entirely, runs the agent's own internal tool loop with its own (different) tool spec, and returns only result.content — never result.tool_calls. The "Understood. If you have another request..." filler is not hardcoded anywhere in OpenJarvis (the cloud_router.py:126 "Understood." is a different Gemini-only injection). It's the model's actual generic response when the agent re-prompts it without the user's intended tools.

Fix: one conditional. Skip _handle_agent when request_body.tools is present — the client is asking for raw OpenAI-compat function-calling, so route to _handle_direct which preserves tool_calls. Plus a forward-looking comment documenting this as an intentional trade-off so a future maintainer doesn't naively remove the guard.

Streaming path left intact (its asymmetry — "use agent_stream WHEN tools present" — is intentional per the existing comment at lines 143-145; reporter's repro is non-streaming).

Two regression tests:
- test_with_tools_bypasses_agent: mocks engine+agent, asserts tool_calls survives, agent.run is NOT called.
- test_without_tools_still_uses_agent: pins existing behavior for the no-tools path.

Reported by @gilbert-barajas — the side-by-side curl repro made the triage tractable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 14:12:19 -07:00
Jon Saad-FalconandGitHub 21f1becb4d feat(desktop): single default model + custom OpenAI-compatible endpoints (#453) 2026-05-30 19:34:51 -07:00
Jon Saad-FalconandGitHub d9af1eca45 fix(desktop): stop auto-pulling the entire Qwen3.5 model ladder (#446) 2026-05-30 19:31:22 -07:00
a901fcc12a fix(install-ps1): make install.ps1 actually zero-friction on a fresh Windows (#445)
PR B of the post-cluster install-hardening pair (PR A: install.sh #444 — merged). The audit found native Windows was the worst path at 2/10 zero-friction: the installer refused on every missing prereq (Python / git / Ollama), didn't pull a model, and gave the user no `jarvis` command after install. This closes those gaps.

Changes in deploy/windows/install.ps1:

1. Auto-install Python via winget when missing (was: hard refuse).
2. Auto-install git via the same Install-WithWinget helper.
3. Auto-install Ollama via the official OllamaSetup.exe (NSIS /S flag, $ProgressPreference SilentlyContinue for the 150 MB download).
4. Wait for Ollama daemon health before pulling the model (60s poll on `ollama list`).
5. Pull qwen3.5:2b foreground (~1.5 GB) — banner is honest if pull failed.
6. jarvis.cmd shim at %LOCALAPPDATA%\OpenJarvis\bin\ added to User PATH (deduped against expanded form so re-runs don't append). Shim uses %~dp0..\src to self-locate and uv from PATH so a future uv update can't break it.
7. Pre-check admin for the scheduled-task path; refuse fast in -Service branch if not elevated, default to skip-with-explanation in the interactive branch.
8. Final banner tells the truth: 'jarvis' if all good, 'jarvis doctor' if model missing, 'open a new PowerShell' if User PATH was just updated.

Adversarial review caught 5 real bugs before commit:
- CRITICAL: GetEnvironmentVariable returns REG_EXPAND_SZ raw → %LOCALAPPDATA% wasn't expanded → just-installed Python invisible. Wrap in ExpandEnvironmentVariables.
- HIGH: Ollama NSIS silent flag is /S not /silent (wrong flag opens GUI, hangs install).
- MEDIUM: PS 5.1 progress bar makes Invoke-WebRequest 30x slower.
- LOW: -Service branch missed isAdmin precheck.
- LOW: PATH dedup missed unexpanded %VAR% entries → duplicate every re-run.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 19:20:49 -07:00
0591a80448 fix(install): make install.sh actually zero-friction on a fresh laptop (#444)
PR A of the post-cluster install-hardening pair. A multi-agent audit (4 install paths × 2 agents each) showed the README's "copy this one-liner and chat" claim broke on every fresh laptop because of missing git, missing Python 3.11, a racing Ollama daemon, and silently-swallowed model-pull failures. This PR closes the macOS / Linux / WSL2 gaps.

Changes in scripts/install/install.sh:

1. Auto-install missing tools (was: hard refuse):
   - macOS: xcode-select --install + 10-min poll. Refuses fast under SSH (no display for the dialog).
   - Linux: detects apt-get / dnf / yum / pacman / zypper / apk. Pre-checks `sudo -n true` and refuses fast with actionable guidance if sudo would prompt (stdin is the curl pipe — any prompt silently hangs under `set -euo pipefail`). Uses `;` not `&&` between apt-get update and install. Skips sudo entirely if already root.

2. Auto-install Python 3.11 via uv when missing. Captures real errors to $STATE_DIR/venv-create.err so disk-full / permission-denied isn't hidden behind "downloading managed Python".

3. Replace `sleep 1` after `ollama serve` with a 60-second poll on `ollama list`. Caller guards with `|| true` so timeout surfaces as a warning in the final banner instead of aborting under `set -e`.

4. Track MODEL_PULL_OK + PATH_MODIFIED. The completion banner now tells the truth: if the model pull failed, point at `jarvis doctor` (not bare `jarvis` which would crash). If PATH was just written to ~/.bashrc / ~/.zshrc, print the exact `source <rc> && jarvis` so it works in the same shell.

Adversarial review caught 5 real bugs before commit:
- SSH/headless macOS xcode-select hang → pre-detect SSH session
- sudo no-TTY silent abort → `sudo -n true` precheck
- wait_for_ollama timeout tripping ERR trap → `|| true` at call sites
- swallowed venv stderr hiding diagnostics → capture to log + surface on fallback failure
- contradictory "PATH new + model missing" banner → conditional NEXT_CMD

All 26 install bats tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:06:15 -07:00
d8a8cd8df7 docs(readme): switch demo reel border from dark grey to light grey (#443)
Re-encode WebP with #cccccc (4px) replacing #3a3a3a.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 18:26:50 -07:00
c48784e304 docs(readme): scale demo reel to 75% width, add dark grey frame (#442)
- Resize embed: width="100%" → width="75%".
- Re-encode WebP with a 4px #3a3a3a border baked in (GitHub's README
  sanitizer doesn't reliably honor inline CSS borders on <img>, so the
  frame is part of the asset).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 18:24:02 -07:00
e29ed9986e docs(readme): replace stripped <video> with animated WebP demo reel (#441)
GitHub's README sanitizer strips <video> tags sourced from release
assets, so the previous embed never rendered. Swap for an animated
WebP (no audio anyway) at assets/openjarvis_demo_reel.webp — renders
inline on every Markdown viewer with auto-loop.

960px wide, 15fps, lossy q=60, 4.5MB.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 18:05:40 -07:00
a7b9e09305 docs(readme): tighten Install + Quick Start; add platform-guides hub (#440)
Tidies the README after the Windows cluster (#432-#438) landed. The Install section had grown three nested Windows sub-bullets, and Quick Start + Starter Configs both re-listed the same presets. -49 net lines, no content lost.

- `## Installation` is now a 3-row table (macOS·Linux·WSL2 / Native Windows / Desktop GUI), one one-liner each. Per-platform detail moves to the docs.
- `## Quick Start` absorbs Starter Configs into one preset table + one example + a row of per-preset deep-dive links.
- New "Platform-specific guides" hub at the top of `docs/getting-started/install.md` links to the existing `macos.md` / `linux.md` / `wsl2.md` / `windows-native.md` siblings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:46:58 -07:00
efa64beda8 docs(readme): embed demo reel video in hero section (#439)
* docs(readme): embed demo reel video in hero section

Adds a centered video block between the badges and the Documentation
links, framed by horizontal rules. URL is a placeholder pending the
user-attachments upload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(readme): point demo reel at readme-media release asset

Replaces the placeholder with a <video> tag sourced from the dedicated
readme-media prerelease.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 16:48:52 -07:00
a79cd6f5e9 feat(windows): Phase-1 native install + scheduled-task service (#438)
Closes Phase-1 of #298 (Native Windows Support RFC).

Reverses the long-standing "native Windows is not supported" stance. PowerShell installer at `deploy/windows/install.ps1` (published to https://open-jarvis.github.io/OpenJarvis/install.ps1) plus a `jarvis-service.ps1` scheduled-task helper that mirrors `deploy/systemd/openjarvis.service` and `deploy/launchd/com.openjarvis.plist`. Loopback default (127.0.0.1, no API key) — same as launchd.

One-liner install:
    irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex

Adversarial review caught and fixed two real bugs pre-commit:
1. `irm | iex` drops `param()` flags — added env-var fallbacks (OPENJARVIS_SKIP_SERVICE / OPENJARVIS_SERVICE / OPENJARVIS_FORCE).
2. Scheduled tasks don't inherit the registering session's env — the LAN-exposed `OPENJARVIS_API_KEY` path now persists the key to User scope so the task's logon environment can read it.

Supersedes #434 (the guidance-only "use WSL2" install.ps1).

Massive thanks to @SeCuReDmE-main-dev for the careful RFC #298 — the three-phase decomposition (install / service / shared-memory bridge) is exactly the right framing. This PR ships Phase-1 and Phase-2 of the RFC fused into one release; Phase-3 (shared memory bridge) remains future work.

Thanks also to @KadenBordeaux for raising #334 ("'bash' is not recognized as the name of a cmdlet"). That report is what made this whole Windows-support cluster a priority — without your bug report the unsupported stance would still be in the README. The friction you hit motivated #432 (numpy/python cap), #433 (CLI startup resilience), #436 (python discovery helpers), #437 (desktop launcher fix), and this PR.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:44:10 -07:00
b6a8280d68 fix(desktop): detect early child exit + drain stderr to avoid pipe stall (#437)
Closes #309.

Three root-cause fixes in `frontend/src-tauri/src/lib.rs`:

1. **Background stderr drainer.** `jarvis serve` was spawned with `stderr(Stdio::piped())` but its 4 KB Windows pipe buffer would fill from the startup log volume before the child could bind its HTTP port — hanging the process mid-startup. Now a tokio task drains stderr from the moment of spawn into a rolling 16 KB tail buffer; the pipe never fills.
2. **`try_wait()` early-exit detection.** The old `wait_for_url` was blind to child crashes — uv-not-on-PATH or extension-import failures would silently waste the full 10-minute timeout. The new `wait_for_jarvis_health` checks the child's exit status each iteration and surfaces the stderr tail with the exit code.
3. **HTTP 503 distinguished from connection-refused.** A 503 means the server bound but the inference engine failed to load (terminal); we now surface the body text immediately rather than polling for 10 minutes.

Adversarially reviewed before commit — caught and fixed a stderr-pipe back-pressure regression on the Ready path before push.

Huge thanks to @xoomarx for the careful #309 repro — without that exact symptom signature ("stuck on Starting api server" on Windows) the pipe-buffer deadlock would have been very hard to identify from the user-visible behavior alone.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:36:49 -07:00
caa3adbbce fix(windows): cross-platform python discovery + browser open helpers (#436)
Reimplements the useful parts of #385 cleanly.

Adds two small cross-platform helpers under `openjarvis.core.utils`:
- `get_python_executable()` — prefers `python3`, falls back to `python` for Windows / minimal distros that only ship the unversioned name.
- `open_browser(url)` — `webbrowser.open` by default; on Windows uses `cmd /c start "" <url>` to avoid console-host edge cases.

Swapped at every hardcoded `python3` / `webbrowser.open` site: `connectors/oauth.py`, `evals/scorers/livecodebench.py`, `scripts/oauth_all.py`, `scripts/install/install.sh` (adds `PY_CMD` detection block), `scripts/quickstart.sh` (adds `MINGW*|MSYS*|CYGWIN*) cmd /c start` case), and the two affected test files. Test files wrap `get_python_executable()` in `shlex.quote()` before interpolating into `shell=True` strings — Windows interpreter paths often contain spaces.

Deliberately different from #385: `openjarvis.core.__init__` does NOT re-export `DEFAULT_CONFIG_DIR` (would have raised ImportError because it's in `openjarvis.core.config`, not the package `__init__`; re-exporting would also force eager import of the heavy config module at every `import openjarvis.core`). `oauth.py` keeps `from openjarvis.core.config import DEFAULT_CONFIG_DIR` alongside the new `from openjarvis.core import open_browser`.

Original API surface and call-site sweep by @sanjayravit in #385 — huge thanks for the careful Windows-compatibility audit. This PR preserves your design while fixing the ImportError edge cases caught during review.

Co-Authored-By: sanjayravit <sanjayravit@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:28:45 -07:00
ad7c86495f fix(cli): don't let a broken numpy crash CLI/server startup on Windows (#433)
Addresses #404 (unable to launch / fully download) and contributes to #309 (stuck on starting api server) by removing the eager numpy import paths that fail hard when a Windows host has a partially-installed or cp314-incompatible numpy.

What changed:

- `src/openjarvis/connectors/embeddings.py` / `hybrid_search.py`: numpy imports are now lazy (inside the method that needs them) with a `TYPE_CHECKING` guard for annotations. Default-argument evaluation no longer touches numpy at module import.
- `src/openjarvis/cli/__init__.py`: the `deep_research_setup` command import is now guarded behind a `try/except Exception` so an OverflowError or ImportError during its module load doesn't crash the entire CLI.
- New regression test in `tests/cli/test_cli.py`: `test_importing_cli_does_not_import_numpy` spawns a subprocess and asserts `numpy` is not in `sys.modules` after `import openjarvis.cli`. Guards against future eager-numpy regressions on Windows.

Reported by @Tentacle39 in #404 and seen alongside @xoomarx's #309. Thanks to both — the Windows-only crash signature made this hard to diagnose without your repros.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:18:47 -07:00
f1b0df6b6b fix(packaging): cap requires-python to <3.14 for Windows numpy wheels (#432)
Fixes #350 (API server fails to start on Windows because numpy has no cp314 wheels).

Caps `requires-python` to `>=3.10,<3.14` in pyproject.toml so uv resolves a Python that has working numpy wheels on Windows. Extends the `test-windows` CI matrix to run on both Python 3.12 and 3.13 to keep the cap honest.

Reported by @RizaldyMongi in #350. Thanks for the careful repro — the Windows-only fallout was tricky to reproduce on Linux/macOS and the issue gave us the exact symptom signature to triage from.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:10:08 -07:00
c8970cc387 chore: restore green ruff lint on main (#435)
Auto-fixes 13 inherited ruff errors in agents/hybrid/skillorchestra/* and evals/scorers/swebench_harness.py (I001/F401/W291/W292/E703), strips trailing whitespace in evals/eval_orchestrator.py, wraps a long signature in speech/cartesia_tts.py, and extends per-file-ignores for `agents/hybrid/**` and `agents/research_loop.py` (research code with long prompt strings — same rationale as the existing evals relaxation).

Unblocks lint CI for the rest of the Windows-fix cluster (#432, #433, #436, #437, #438).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:03:53 -07:00
Jon Saad-FalconandGitHub 3aa24b2709 Merge pull request #429 from open-jarvis/feat/opencode-agent
feat(agents): add OpenCodeAgent — run opencode on a local engine
2026-05-28 20:38:46 -07:00
Jon Saad-FalconandGitHub 6a27094658 Update README.md 2026-05-28 10:30:12 -07:00
Jon Saad-FalconandGitHub 75d26d23ea Merge pull request #422 from open-jarvis/fix/persona-files-persistent-agents
fix(agents): persistent agents honor SOUL.md / MEMORY.md / USER.md persona files (#376)
2026-05-27 10:57:12 -07:00
Jon Saad-FalconandGitHub 00065cd960 Merge pull request #418 from open-jarvis/fix/managed-agent-streaming-parity
fix(server): managed-agent streaming parity — tool_calls replay, sampler params, tool DI (#382, #386, #395)
2026-05-27 10:16:17 -07:00
Jon Saad-FalconandGitHub f827a8165d Merge pull request #417 from open-jarvis/security/default-deploy-auth
security(deploy): require auth (or loopback) in default deployments (#221)
2026-05-25 19:07:17 -07:00
Jon Saad-FalconandGitHub 0d3cc6db51 Merge pull request #416 from open-jarvis/security/websocket-a2a-auth
security(server): authenticate WebSocket handshakes + A2A requests (#217)
2026-05-25 19:07:05 -07:00
Jon Saad-FalconandGitHub b0f133a42d Merge pull request #415 from open-jarvis/security/template-loader-rce
security(tools): fix RCE in template loader (eval + shell=True) (#216)
2026-05-25 19:06:54 -07:00
Jon Saad-FalconandGitHub 7e8db280e4 Merge pull request #413 from open-jarvis/ci/desktop-stable-updater-channel
ci(desktop): stable (desktop-latest) + edge (desktop-edge) updater channels
2026-05-25 16:41:08 -07:00
Jon Saad-FalconandGitHub cdae426d48 docs: fix broken desktop download links → stable desktop-v1.0.2 release (#412)
Repoints all desktop download links (README, docs/index.md, downloads.md, getting-started/installation.md) from the removed desktop-latest prerelease (with wrong 0.1.0 filenames) to the stable desktop-v1.0.2 release. All 5 URLs verified live (200). macOS .dmg now included (addresses #356).
2026-05-25 16:20:33 -07:00
Jon Saad-FalconandGitHub 56c9a59f8d release: v1.0.2 — fix broken PyPI wheel (#372) + pynvml/Windows/install fixes (#411)
Bumps 1.0.1 → 1.0.2 so the merged fixes (#372 wheel packaging, #389 pynvml, #373 Windows RAM, #331 desktop diagnostics, #337/#352 install URL) can ship to PyPI. See PR #411.
2026-05-25 10:51:56 -07:00
Jon Saad-FalconandGitHub b43f4d2291 refactor(desktop): extract testable uv-sync error helpers + unit tests (#331) (#406)
Extracts the #331 uv-sync error-formatting logic into pure functions with 7 unit tests, now run via cargo test in desktop.yml's validate job. Verified: all 7 pass on the real Tauri crate in CI.
2026-05-24 08:31:54 -07:00
Jon Saad-FalconandGitHub 0c34817ecd ci: add windows-latest job to empirically verify Windows code paths (#405)
Adds a windows-latest CI job that executes the real GlobalMemoryStatusEx RAM path (#373), the #293 stdout reconfigure test, and builds+imports the openjarvis_rust PyO3 extension on Windows. Verified green on a real Windows runner (4m1s, all steps success).
2026-05-24 08:08:49 -07:00
Jon Saad-FalconandGitHub a76319841a fix(install): serve install.sh from GitHub Pages as canonical URL (#402)
Resolves the openjarvis.ai SSL failure (#337, #352) by serving the installer from the project-controlled GitHub Pages site. Adds uv prerequisite docs for the Windows desktop path. See PR #402 for the full breakdown and the openjarvis.ai CNAME migration note.
2026-05-23 12:10:00 -07:00
Jon Saad-FalconandGitHub bced6cc274 fix(install): Windows discoverability + Git Bash early bail + concrete uv install command (#399)
Follow-up to PR #398. Three surface fixes for the Windows install confusion documented in two Discord support threads. See PR #399 for the full breakdown.
2026-05-23 08:54:10 -07:00
Jon Saad-FalconandGitHub 58f7b753bf fix: PyPI v1.0.2 cluster — wheel packaging + pynvml warning + install fallback + uv-sync diagnostics (#398)
Closes #372 #389 #337 #352 #331. Resolves #373 (already on main, awaiting release). See PR #398 for the full per-issue breakdown.

Credits: @gilbert-barajas (#372), @boeani05 (#389), @kumanday + @filactre (#337/#352), @Hris4oG + @NatanelBranski + @Robjes87 + @EmiDaDuck (#331).
2026-05-23 08:36:44 -07:00
Jon Saad-FalconandGitHub a6f18dff43 Consolidate contributor PRs #203 + #260 (#368)
Two clean contributor PRs landed with original authorship preserved. Two related PRs (#116, #119) excluded — both need rebase against significant main refactors (see PR #368 for details). Credits: @bootcrowns (#203), @samy19980109 (#260).
2026-05-20 18:56:20 -07:00
Jon Saad-FalconandGitHub 75cb70526e Consolidate contributor PRs #340 + #341 + #301 + #347 + #202 (#367)
Consolidates five contributor PRs into one bundle to avoid overlapping issues (notably #340/#341 both touching mcp/transport.py + agent_cmd.py + executor.py). Original authorship preserved on every commit. See PR #367 for full per-author breakdown.

Credits: @Dilligaf371 (#340, #341), @eddierichter-amd (#301), @kriptoburak (#347), @bootcrowns (#202).
2026-05-20 13:37:46 -07:00
Jon Saad-FalconandGitHub c8f1b3c54a Consolidate top-priority contributor PRs (#235 + #339 + #293 + #294 + #295) (#362)
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).
2026-05-20 13:11:34 -07:00
Jon Saad-FalconandGitHub 4a7817509b docs(index): align homepage with canonical surfaces (#361) 2026-05-19 18:52:30 -07:00
Jon Saad-FalconandGitHub 2b0e6e7130 Update README.md 2026-05-19 18:18:26 -07:00
289fcb00e2 fix(ci): desktop — translate PEP 440 .devN to SemVer for Tauri (#360)
Tauri's build script enforces strict SemVer
(`MAJOR.MINOR.PATCH[-pre][+build]`) on `tauri.conf.json > version`, but
the autotag scheme introduced in #358 emits PEP 440 dev releases like
`1.0.2.dev661` — PEP 440 separates dev with `.`, SemVer requires `-`.
First post-#358 desktop run failed with:

    tauri.conf.json > version must be a semver string

PyPI requires PEP 440; Tauri requires SemVer. They genuinely don't
agree on `.devN`. Translate just for the Tauri bundle:

  1.0.2.dev661  -> 1.0.2-dev.661   (valid SemVer prerelease)
  1.0.2          -> 1.0.2          (passthrough)
  1.0.0-rc.1     -> 1.0.0-rc.1     (passthrough)

Tag, git history, PyPI wheel, and the updater's `latest.json` all keep
the PEP 440 form. Only the embedded bundle version uses SemVer, and
the comparison the updater does is bundle-vs-latest.json (both SemVer
now) so the upgrade path stays consistent.

Failing run for reference: 26118619500

Co-authored-by: krypticmouse <herumbshandilya123@gmail.com>
2026-05-19 12:22:24 -07:00
4668ea0b7f fix(ci): publish-to-pypi — match Vite's actual outDir (#359)
The frontend bundling step in pypi-publish.yml assumed Vite produced
`frontend/dist/`, but vite.config.ts is configured with
`outDir: '../src/openjarvis/server/static'` and `emptyOutDir: true` —
Vite writes straight to the package's static dir and clears stale
assets itself. The `rm -rf dist; cp -r dist/.` ceremony was dead code
that would have deleted the build output had the assertion not
short-circuited it first.

First post-#358 autotag run failed at this step with
`frontend/dist/index.html missing or empty after build` (build was
fine; the assertion checked the wrong path).

Fix: drop the rm/cp dance and assert the actual output location.

Co-authored-by: krypticmouse <herumbshandilya123@gmail.com>
2026-05-19 12:12:04 -07:00
Jon Saad-FalconandGitHub fea8d3e872 release: v1.0.1 (#357) 2026-05-18 20:19:37 -07:00
Jon Saad-FalconandGitHub 5b847cd081 Update README.md 2026-05-18 18:41:48 -07:00
Jon Saad-FalconandGitHub a5029e297f Update README.md 2026-05-18 18:40:54 -07:00
Jon Saad-FalconandGitHub e97088f199 release: v1.0.0 (#349) 2026-05-16 13:47:23 -07:00
Jon Saad-FalconandGitHub 9540e85dfb ci: fix env-dependent test failures (gated datasets, server extra, router prefix) (#348) 2026-05-15 21:31:13 -07:00
Jon Saad-FalconandGitHub eacf34e500 spec_search: drop CLI stubs, rename, and ship paper-aligned tutorial + configs (#346) 2026-05-15 14:17:23 -07:00
Jon Saad-FalconandGitHub 44815e8544 feat(evals): add TerminalBench V2.1 benchmark (#345) 2026-05-14 20:23:14 -07:00
Jon Saad-FalconandGitHub 57151327b3 feat: LLM-guided spec search building blocks (split-aware sampling, external corpora, agent-trace adapter) (#332) 2026-05-08 20:18:40 -07:00
Jon Saad-FalconandGitHub b8245136db fix(security): block IPv4-mapped IPv6 addresses in SSRF check (#327) 2026-05-07 14:55:52 -07:00
Jon Saad-FalconandGitHub ab46c89660 Delete CLAUDE.md 2026-05-07 12:14:45 -07:00
Jon Saad-FalconandGitHub f41cf420be feat(mining): Pearl mining integration
Consolidates NVIDIA vLLM, Apple Silicon, CPU Pearl mining support, CLI/docs, and live H100 validation.
2026-05-05 19:11:15 -07:00
Jon Saad-Falcon f16bf734cf Merge branch 'main' of https://github.com/open-jarvis/OpenJarvis 2026-04-28 20:26:44 -07:00
Jon Saad-FalconandClaude Opus 4.7 3f2f46e406 ci: stop auto-running Claude PR review on every pull request
Removes the pull_request trigger so the workflow only runs on explicit
@claude mentions or manual workflow_dispatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:23:24 -07:00
Jon Saad-FalconandGitHub 484d0f090b Delete results (#280) 2026-04-26 05:43:06 -07:00
Jon Saad-FalconandGitHub f5695845b7 feat(distillation): M1→M2→M3 spec-level distillation pipeline + hill-climb optimizer (#273) 2026-04-20 19:18:10 -07:00
Jon Saad-FalconandGitHub 2d868163cc Update README.md (#239) 2026-04-12 08:21:47 -07:00