- POST /sessions/{id}/title/auto — summarize the first exchange into a short
title in the user's language; never clobbers a manual rename
- Frontend fires it after the first completed exchange and refreshes the sidebar
- reasoning_delta now carries a bounded 600-char rolling tail on the existing
throttled emit; ActivityLine whispers the live thought (serif italic, 3-line
clamp) while the attempt is active
One durable activity object per turn rendered by a single ActivityLine
(verb + newest step + 1s tabular timer; userOpen ?? auto disclosure with
900ms dwell; cancel/timeout/failure leave terminal rows with Continue and
re-attach; scattered spinners and the pulse bar removed). Escape-to-stop
and full + menu keyboard contract; follow-up placeholder after runs;
hidden-tab completion title marker. Agent.tsx decomposed 2353→1686 via
memoized Composer (byte-faithful IME guard + render-isolation test),
GoalPanel, LiveRuntimePanel. ja/ko/ar translated to full 929-key parity.
Assistant narrative paragraphs read in a CJK-safe serif at 15.5px; tables,
metrics, and code stay sans/mono. Remaining page-level hardcoded strings
localized.
Backend (surgical, additive): call_id on tool_call/result/progress/heartbeat
(loop.py, swarm worker); tool_trail serialized onto assistant messages at
attempt completion and exposed through session history. Frontend: exact
call_id matching with FIFO replay fallback, trail rebuild on reload via the
same helper as the live path, 84 localized tool labels, MetricsCard plain-
language glosses, focus-trap dialogs, overlay connection banner with
terminal reload state, cross-tab prefs sync, idle echarts prefetch, mandate
zh glossing (交易授权), swarm vocabulary unification, '/' product-first
routing with /about.
React Router 8 requires Node >=22.22.0, so `npm install` now fails for anyone
still on Node 20 with an error that does not name the cause. Declare it in
package.json engines, and say it on the Quick Start line that tells people to
run it, in all five READMEs.
React Router 7.12.0 through 8.2.x carry a high-severity RSC-mode CSRF bypass
(GHSA-qwww-vcr4-c8h2). We were on 7.18.1, inside that range. The advisory is not
reachable here — the app is a client-side SPA on createBrowserRouter with no
@react-router/server and no RSC entry point, and the frontend ships as static
Vite output — but 7.18.1 is the final 7.x release, no backport exists, and 8.3.0
is the only patched version, so staying put means staying on an EOL line with a
permanent open alert.
react-router-dom has no 8.x at all; the DOM exports moved into react-router
itself. So the dependency is swapped and every import rewritten. All ten APIs in
use here are unchanged in 8.3.0, and none of the data-layer surface that changed
most between majors is used.
react-router 8 requires Node >=22.22.0, which is the same wall that had jsdom 30
and @testing-library/jest-dom 7 sitting in the Dependabot ignore list. Node 20
reached EOL on 2026-04-30, so the toolchain moves to Node 22 across CI, the wiki
workflow, the Dockerfile and docker-compose. That unblocks all three at once:
jsdom and jest-dom are taken here too, and their ignores are removed rather than
left carrying a rationale that no longer holds.
Verified on the frontend suite: tsc -b and vite build clean, 310 tests across 35
files pass before and after the jsdom/jest-dom bumps.
ccxt does not use ranges — it exact-pins its entire transitive closure. All 23
of those packages that appear in our lock sit at precisely ccxt's pin, with no
mismatches, so none can move until ccxt itself releases, and ccxt 4.5.68 is
already the latest. Any Dependabot PR touching one is unmergeable by
construction: the pip group PR proposed five of them and died with
ResolutionImpossible on certifi against the ccxt line in the lock.
Re-running the resolver confirms this is the ceiling rather than a stale lock —
pip-compile --upgrade under python:3.11-slim, matching CI, reproduces every one
of those versions, so there is no lock change to make. pydantic-core is in the
same position via pydantic's own == pin, and was the sixth package in that PR.
Ignore the set. ccxt itself is deliberately not ignored: it is the package whose
bump legitimately carries all of them. This suppresses only the automated PRs —
Dependabot alerts come from the dependency graph and are unaffected by this
file, so a CVE in cryptography, urllib3, requests or certifi still surfaces in
the Security tab, where the remedy is a ccxt bump.
close.pct_change() silently assumes price[t-1] > 0. That holds for equities but
not for instruments that can print zero or negative prices, such as European
day-ahead power. As the divisor approaches zero an ordinary absolute move reads
as an enormous percentage move, so the compounded benchmark explodes — a
reported +39,560% where the honest figure was -42.7%. At exactly zero the next
bar is inf, which fillna(0.0) does not neutralise because it fills NaN, and
(1 + r).prod() then collapses to nan.
Adopt an explicit contract. A return is defined only when the previous price is
finite and strictly positive; otherwise it is 0.0, never inf or nan. Buy-and-
hold total return becomes the price relative P_end / P_start - 1 rather than the
compounded product, and is omitted entirely when the entry price is not
positive, because no honest percentage exists. Undefined bars are logged rather
than silently swallowed: a wrong return is a wrong reported number, not a crash.
bar_returns keeps the exact `close / prev - 1` expression pct_change uses, so
results for strictly positive series are bit-identical and ordinary equity and
crypto runs are unchanged.
The loader still rejects an exact-zero close, but the reason has narrowed to the
one that survives: sizing is target_notional / abs(price), undefined at zero.
Closes#872
The research-goal tools are registered unconditionally and resolve their
session from the host runtime, so an entry point that calls _run_agent without
an id makes every goal call fail validation with "session_id is required" while
the run still reports SUCCESS. Reported for `vibe-trading run`; six of the seven
_run_agent call sites had the same omission, including cmd_session_chat, which
already received the session id as a parameter and simply never forwarded it.
Add _ensure_session_id, which builds a Session (whose id is populated at
construction) and persists it best effort, so a store or index failure still
yields a usable id rather than the empty string that caused the bug.
cmd_continue reuses one session across continuations so goals accumulate: a
session-backed run_id already is a session id, since sessions and their traces
share SESSIONS_DIR, so it is used directly; a plain run gets a derived id.
Re-registering an existing id is idempotent. The interactive REPL had the same
latent hole whenever _new_session returned None, and now falls back to an
unpersisted id.
Also align the MCP contract. All four MCP goal tools declared session_id
required while the in-process registry injects it and leaves it optional — the
opposite contract, which asked the model to invent an internal identifier it
has no way to know. session_id is now optional there too, defaulting to one
stable id per server process.
Closes#885
CMD has no `cp`, so `cp agent/.env.example agent/.env` fails there; add the
`copy` form and note that PowerShell's `cp` alias already works. Also add the
CMD activation line and the execution-policy hint that blocks Activate.ps1.
Reported by @brady-cec in discussion #29.
jsdom 30.x requires Node ^22.22.2 || ^24.15.0 || >=26 while CI and Docker are
on Node 20. On Node 20 the incompatibility surfaces indirectly as vitest
failing to start its pool workers rather than as a clean engine error, so #877
reads like a flaky frontend failure. Same wall as @testing-library/jest-dom 7.
Dependabot could not land either half of a mutually-blocking pair: the
minor/patch group wanted ccxt 4.5.68 (which pins setuptools==83.0.0) while
keeping setuptools at 82.0.1, and the separate setuptools 83.0.0 PR conflicted
with the ccxt 4.5.67 already in the lock. Neither can go green alone because
setuptools is a major bump and is excluded from the minor/patch group by
design, so it cannot ride along.
Regenerate the lock so both move together. Verified with pip-compile under
Python 3.11 on Linux (matching CI) and checked with CI's own gate,
pip install --dry-run --require-hashes. Note the lock is platform-sensitive:
regenerating on macOS drops jeepney/secretstorage and swaps py-mini-racer for
mini-racer, so it must be produced on Linux.
Also ignore websockets, capped at <16 by langgraph-sdk 0.4.2, and move all
three ecosystems to monthly with 2 concurrent PRs. Security updates are a
separate channel and are unaffected.
caio: aiofile 3.11.1 (latest) requires caio~=0.9.0, so bumping caio to 0.10.x
makes pip unresolvable and the grouped PR fails CI. Ignore only caio, not the
whole minor/patch group.
@vitejs/plugin-react 6.x peers vite@^8 (we are on 6.x) and
@testing-library/jest-dom 7.x requires Node >=22 (CI/Docker use 20). Both are
migrations, not dependency bumps.
loader_cache_enabled() returned the raw config value, so a MagicMock config
read truthy and switched the opt-in cache on. loader_cache_root() then passed
that MagicMock to Path(), which resolves via __fspath__ to a *relative* path
and put real market-data parquet under agent/MagicMock/ inside the repo.
Validate both at the boundary: require a genuine True to enable the cache, and
honor the root override only when it is a real non-blank str.
Resolves the import-list conflict in agent/tests/test_trade_journal.py
introduced when #862 landed on main: keep both _to_float (this branch)
and load_dataframe (#862).
Add task-scoped cancellation, reject broad Python process-name kills, expose timeout context, and cover the lifecycle on Windows 11 and Windows x64 with Python 3.14.
Signed-off-by: Haozhe Wu <haozhe_wu@connect.hku.hk>
ja/ar were missing the longbridge Data Sources table row and its place in
the US/HK fallback chains (a pre-0.1.12 drift; en/ko/zh already had them).
Now all 5 locales agree on the 23-source table + chains. (The en-only
'Using Longbridge explicitly' config subsection remains en-only.)
Add the v0.1.12 cycle cohort (28 contributors) and demote the v0.1.11 list
into a collapsible block, in en/ja/ko/zh/ar. en/ja/ko keep English
descriptions; zh/ar translated.
Three fixes for the native zai provider on glm-5.1:
- get_llm_credentials now falls back to the provider catalog's
default_base_url when no *_BASE_URL is set, so CLI/manual-.env users
reach the canonical endpoint (Z.ai /api/coding/paas/v4) instead of
silently defaulting to api.openai.com. Web Settings already did this;
the backend credential path did not. Benefits all 22 providers.
- stream_chat falls back to a non-streaming invoke when a provider
streams zero chunks ('No generation chunks were returned'), instead of
raising ProviderStreamError. Scoped to the empty-stream case; genuine
provider errors still raise.
- ProviderStreamError appends a base-URL hint when the endpoint returns
an HTML error page (a common misconfiguration signal).
Fixes#758.
Rebased onto main and deduped — reuse the existing _restore_original_codes helper instead of the PR's parallel _map_result_to_requested; apply the same missing-symbol completion to both _fetch_auto and the explicit-source path. Fixes#681.
LLM answers with \(...\)/\[...\] or $$ formulas rendered as plain text --
the markdown pipeline had no math plugin. Normalize LLM delimiters to $$
outside code segments (streaming-safe for unclosed fences), parse with
remark-math (single-$ disabled so dollar amounts like "$150 to $120"
never become formulas), render with rehype-katex.
The options engine close branch computed cash/PnL from the full matched lot and
removed the whole position, ignoring the leg's requested qty — any partial close
wiped the entire lot with full PnL. Now an explicit leg qty closes only that many
contracts (clamped to the open size), cash/PnL scale to the closed fraction, and
the remainder stays open as a reduced-qty position; a close leg with no qty still
closes the whole lot (legacy). Closes#577.
Self-fix on merge: moved langchain-anthropic from core dependencies to an
[anthropic] optional extra (like [deepseek]) — _build_anthropic lazy-imports it
and raises an actionable install hint, so the base install stays lean. Resolved
llm.py conflicts against #563's centralized get_llm_credentials + the anthropic
native adapter-mode branch. Closes#694.
Reconciled the connector renderers with #735 (Longbridge schema-tolerant balances):
_print_connector_account now handles balances (broker_sdk) → remote-MCP nested
(Robinhood) → IBKR summary; positions use both remote-MCP shape and _first_present aliasing.
The vectorized _align did in-place np.nan_to_num/clip on arrays taken from
pandas via .values/astype(copy=False), which are read-only views under pandas
copy-on-write, raising 'ValueError: assignment destination is read-only' on
current main. Force a writable copy for the per-symbol signal array and use the
copy-returning nan_to_num for the position matrix. Vectorization stays numerically
equivalent to the scalar path (assert_frame_equal tests pass).
get_market_data could not resolve tickers like GC=F or EURUSD=X: detect_source
matched no pattern and fell to the 'tushare' default, routing them to
China-market loaders that cannot serve them; and the yahoo direct loader's
_is_supported guard rejected any non-.US/.HK/.NS/.BO symbol even though Yahoo's
public chart endpoint serves =F/=X verbatim.
Add =F/=X source patterns (route to yahoo) and accept =F/=X in the yahoo loader.
Closes#718
The shared 'connector positions'/'connector account' CLI renderers assumed the
IBKR result shape (position/avg_cost/sec_type/summary). broker_sdk connectors
such as Longbridge emit quantity/cost_price/market/balances, so every
non-matching key rendered as an empty cell.
Read key aliases (position|quantity, avg_cost|cost_price, sec_type|market) via a
None-aware _first_present helper (keeps a real zero qty), and render a balances
table when a connector returns 'balances' instead of IBKR 'summary' rows.
Closes#735
#729 made _apply_filter raise on inverted/degenerate date ranges (fail-fast
over silently returning an empty frame). But analyze_trade_journal called it
unwrapped, so the ValueError escaped as a raw traceback instead of the
{status: error} envelope the rest of the function uses; and the pre-existing
test_apply_filter_preserves_full_date_and_mixed_precision still asserted the
old silent-empty behavior. Wrap the call to return the error envelope and
update the stale assertion to expect the raise.
The annualized example used 0.01 with a '0.01%' comment, which reads as
1% (→1095% annualized), not the intended 0.01% (→10.95%). Standardize the
whole skill on the per-8h decimal convention OKX/Binance actually return
(0.0001 = 0.01%): fix the annualized example, funding_regime and
oi_funding_matrix thresholds, the cross-exchange dict, and the carry-trade
example; add an explicit units-convention note. Human-readable % tables
kept as-is.
The AST validator scanned os.<attr> but not getattr(os, "system")(...). Reject
getattr/setattr/delattr whose target is os or a forbidden module — keyed off the
target, not the attr string, so getattr(os, "sys"+"tem") is caught too.
Legit getattr(obj, name, default) on user objects (as the bundled harmonic
example uses) is unaffected.
IM /pairing ran with no authorization, so any allow-listed chat participant could
take over pairing across channels (+ Signal group members bypassed the sender
check, + code/sender_id leak). Add a config-driven operators allowlist (global +
per-channel, fail-closed when empty); gate every /pairing on operator identity in
runtime; scope store list/approve/revoke by channel; require per-sender auth for
control-plane slash-commands in Signal groups (ordinary /new /reset unchanged).
README documents the operator requirement.
Untrusted web/doc/search content entered the LLM context with no special-token
filtering, letting it forge system/assistant role boundaries. Add
neutralize_special_tokens() (hooked into with_security_warnings, applied to the 3
untrusted ingestion tools) that ZWSP-defangs the ChatML <|...|> family, the
DeepSeek fullwidth-bar <|...|> family (the shipped default model), Llama
[INST]/<<SYS>>/<s>, and Gemma role tokens. Text stays visually identical.
sse / streamable-http bound a TCP port with no Host/Origin validation (fastmcp
3.2.4 ships none), so a DNS-rebinding browser page could reach every MCP tool.
Wrap the network ASGI app (mcp.http_app) with TrustedHostMiddleware + an Origin
allow-list (VIBE_TRADING_MCP_ALLOWED_HOSTS, default loopback) and serve via
uvicorn. stdio (default) untouched.
_validate_api_auth / require_event_stream_auth trusted the loopback peer BEFORE
checking API_AUTH_KEY, so a same-host reverse-proxy/sidecar reached key-protected
routes with no token. Invert to key-first (mirrors require_settings_write_auth):
when a key is set every peer incl. loopback must present a valid bearer/ticket;
only the no-key dev mode keeps loopback trust. No-key default UX unchanged; the
bundled frontend already sends the bearer/ticket when a key is stored. Updates
3 tests that codified the loopback-bypass behavior.
MCP stdio previously force-enabled bash/background_run with no opt-out and the
module-level default was fail-open. Default to fail-closed and gate every
transport behind explicit opt-in (--enable-shell-tools flag or
VIBE_TRADING_ENABLE_SHELL_TOOLS). Local CLI is unaffected (still opts in
explicitly). Also shrinks the network-reachable surface of the bash
OS-command-injection sink (GHSA-m768). Adds regression tests + doc alignment.