Two improvements to the CLI interactive mode:
1. Live status bar: displays provider/model, session duration (real-time
tick), last run elapsed time, and cumulative tool call stats. Uses
prompt_toolkit bottom_toolbar with 1s refresh for live updates; falls
back to static Rich print when prompt_toolkit is unavailable.
2. Arrow key navigation: replaces Rich Prompt.ask() with prompt_toolkit
PromptSession, enabling up/down history browsing and left/right cursor
editing. Falls back to Prompt.ask() when prompt_toolkit is not installed
or stdin is not a tty.
Co-authored-by: RuifengFu <frf2000@tju.edu.cn>
Adds the ashare-pre-st-filter skill, Sina regulatory penalty scraper, and regression tests. Follow-up hardening will refine penalty-record relevance filtering.
Validate the backtest validation CLI run_dir argument and add regression coverage for missing, blank, malformed, missing-path, and non-directory inputs.
Move 29 preset YAMLs from agent/config/swarm/ to agent/src/swarm/presets/
so they ship as package-data; loader resolves via
Path(__file__).parent / "presets". Identical behavior under editable
installs and built wheels — fixes "No presets available" reported in #55
after pip install / uv tool install vibe-trading-ai 0.1.5.
Root cause: preset YAMLs were declared via [tool.setuptools.data-files],
which lands them under <install-prefix>/.data/data/config/swarm/ rather
than at the <site-packages>/config/swarm/ path the loader expected.
Pinned by a 6-test packaging regression suite. Bump 0.1.5 -> 0.1.6.
SKILL.md and 5 READMEs synced to reality: 72 skills, 22 MCP tools
(adds 5 shadow-account / journal tool names), 6 data sources
(adds Futu). Adds 2026-04-28 v0.1.6 release news rollup.
Closes#50, closes#54.
akshare_loader._fetch_one previously dispatched every unrecognized symbol to
stock_zh_a_hist. Two consequences:
- Exchange-listed ETFs (518880.SH, 159915.SZ, ...) match the .SH/.SZ tail and
are misrouted to the A-share endpoint, which can't price them (#50).
- Forex pairs (EURUSD, GBPUSD, ...) match no predicate and fall through to
the same default branch (#54).
Add two predicates + endpoints:
- _is_etf_listed: 6-digit code with prefix 15/16/50/51/52/56/58 on .SH/.SZ
-> fund_etf_hist_sina(symbol='sh518880')
- _is_forex: looks up akshare.forex.cons.symbol_market_map (190 pairs)
-> forex_hist_em(symbol='EURUSD'), with .FX suffix stripped
ETF check runs before _is_a_share so 518880.SH takes the ETF branch.
registry.resolve_loader / get_loader_cls_with_fallback used to construct the
loader before checking is_available(). Tushare's __init__ calls ts.pro_api(token)
and raises 'api init error' on missing token, which broke the fallback chain
(secondary issue from #50). Construction is now wrapped in try/except so init
failures are treated as unavailable and the chain keeps walking.
Tests:
- 21 new unit tests in test_akshare_loader.py (predicates + routing via
monkeypatched sys.modules['akshare']); no network, no API keys.
- 2 new tests in test_registry.py covering init-error fallback.
- Real-data E2E (manual): loader.fetch returned 242 rows for 518880.SH and
262 rows for EURUSD over 2024; full runner.main produced metrics for both.
Credit:
- @LaierX for issue #50 root-cause + reference patch
- @yuzawaya for issue #54 root-cause + reference patch
Pin PR #53 behavior with 4 cases:
- under-limit upload writes to UPLOADS_DIR
- exactly-at-limit upload succeeds
- over-limit returns 413 and removes the partial file
- blocked extension returns 400 with no file written
Tests shrink MAX_UPLOAD_SIZE to 4 KB via monkeypatch so the streaming
chunk loop and cleanup path run without allocating 50 MB.
Also gitignore AGENTS.md (local mirror of CLAUDE.md for non-Claude
agent tools, identical content, no need to track twice).
Adds benchmark.py with yfinance-backed ticker resolution (SPY, CSI 300, etc.), injects external benchmark via config['benchmark'] in engines/base.py, and renders a benchmark panel (ticker / benchmark return / excess return / information ratio) in cli.py.
Closes#48
Wrap each route in React.lazy() + Suspense so Home/Agent/RunDetail/Compare
download only on first visit. Most importantly, echarts (630KB) now loads
async when a chart-using route is visited instead of eagerly on first
paint. Home and Agent no longer pay the chart import cost.
Measurements (vite build):
before: index 688KB + vendor-charts 631KB, both eager (~1.3MB blocking)
after: index 262KB + vendor-react 101KB + CSS 51KB, ~414KB blocking.
vendor-charts (630KB) is async, loaded on /runs/:id or /compare.
Previously `pip install vibe-trading-ai --no-binary` missed
`agent/.env.example` and `agent/tests/`, so users who built from
sdist had no template env and no test suite to run. MANIFEST.in also
includes 4 localized READMEs and Docker configs.
Local debug scripts prefixed with `_` in agent/tests/ are explicitly
excluded so untracked files in the working tree never leak to the sdist.
- safe_path() now actually raises ValueError on escape / UNC paths
(existing callers already expected this but never got it).
- New safe_user_path() validates user-supplied broker paths against a
HOME+CWD envelope, blocking /etc/passwd-style LLM hijack attempts.
- Wire into analyze_trade_journal + 3 shadow account tools.
- 14 new tests in test_path_safety.py cover traversal, UNC, absolute
escape, and tilde expansion on both helpers.
Implements FutuLoader using the existing DataLoaderProtocol so users can pull HK and China A-share OHLCV data via Futu OpenAPI instead of yfinance (which frequently rate-limits on HK equity requests).
- agent/backtest/loaders/futu.py: FutuLoader class with symbol mapping (700.HK → HK.00700, 000001.SZ → SZ.000001), interval mapping (1D/1H/4H → KLType), is_available() env-var check, and fetch() that normalises request_history_kline() output to the standard OHLCV schema
- agent/tests/test_futu_loader.py: fully mocked tests — symbol mapping, interval mapping, OHLCV normalisation, is_available() with missing env vars, NoAvailableSourceError on connection failure
- agent/backtest/loaders/registry.py: register futu loader
- agent/.env.example: add FUTU_HOST and FUTU_PORT with comments
Closes#40
Hardens against LLMs passing run_dir="." or "" after context compression, which previously resolved against the agent process CWD and exposed repo files (incl. .env) to read_file / write_file and silently mis-routed bash subprocess cwd.
Two follow-ups to b769580 reported by @codewings:
1. "Blocked duplicate call: run_swarm" — loop.py dedups by tool name only,
and SwarmTool defaulted to repeatable=False. A single successful
run_swarm per session blocked every subsequent call (retry with a
different preset, different market, etc). Mark SwarmTool.repeatable=True.
2. threading._shutdown noise on Ctrl+C — `with ThreadPoolExecutor` calls
shutdown(wait=True) on exit, which blocks indefinitely while kimi
workers are mid-stream. Switch to manual executor + try/finally with
shutdown(wait=False, cancel_futures=True); catch KeyboardInterrupt at
layer boundary to set cancel_event. CLI now returns immediately instead
of deadlocking on exit.
LangChain's ChatOpenAI strips custom fields across all three serialization
boundaries, but prior commits (73b179a + f866e5f) only patched the two
inbound paths. Assistant messages in context therefore lose reasoning_content
on every ReAct continuation — Moonshot's strict kimi snapshots
(reported as "kimi-k2.5") reject those turns.
Add _get_request_payload override to ChatOpenAIWithReasoning:
- Copy reasoning_content from additional_kwargs back into the outbound
messages dict so it echoes across turns.
- Normalize content=None → "" for assistant messages (LangChain emits
null when content='' and tool_calls are present, which kimi-k2.5
rejects).
- Default missing reasoning_content to "" so strict providers accept
every assistant turn.
Inbound/outbound symmetry for reasoning_content now covers all three
LangChain serialization boundaries. Verified with real moonshot 2-turn
roundtrip and DeepSeek regression; 22/22 unit tests pass.
Non-streaming invoke against moonshot is unreliable — kimi-k2 either stalls the
TCP connection (APIConnectionError after 3 min) or exceeds the 120s default
timeout, causing swarm workers to retry-loop forever. Swap worker.py to the
same stream_chat path the main Agent already uses — all other providers are
unaffected. As a bonus the dashboard now receives live worker_text deltas
during kimi's thinking window instead of silent minutes.
Also harden runtime.py: as_completed() now enforces a layer-level deadline
(timeout_seconds * (max_retries+1) + 60s) so any future stuck past the in-loop
check is cancelled cleanly instead of freezing the DAG.
Verified e2e with kimi-k2-0905-preview + equity_research_team:
- 724 streaming deltas across 3 parallel workers in 8 min
- 32 tool calls, 0 Request-timed-out retries
- first dashboard update within 10s (was 10+ min)
build_llm() now returns ChatOpenAIWithReasoning (introduced in 73b179a)
instead of ChatOpenAI. The minimax temperature clamping tests were still
patching ChatOpenAI directly, so the fake was never hit and the real
subclass was instantiated — causing KeyError: 'temperature' on assert.
PR #41 fixed the protocol layer (LLMResponse / format_assistant_tool_calls)
but langchain-openai 0.3.x's _convert_dict_to_message silently drops
unknown fields — reasoning_content never reached our _parse_response,
so the fix was a no-op end-to-end.
Add ChatOpenAIWithReasoning: subclass that overrides _create_chat_result
to re-read the raw API response and restore reasoning_content into
additional_kwargs. PR #41's 3-location fallback in _parse_response then
picks it up without changes.
Benefits all OpenAI-compat thinking providers: Moonshot K2.5, DeepSeek
reasoner, Qwen thinking (Qwen-QwQ, Qwen3-thinking).
Reported by @codewings on issue #39 after PR #41 shipped.