Commit Graph
158 Commits
Author SHA1 Message Date
Haozhe Wu 7b27f6e3e2 Add static Vibe-Trading wiki
Add a static Cloudflare Pages wiki under wiki/, including the landing page, docs app, redirects/headers, maintainer README, and wiki validation workflow.
2026-05-14 19:31:31 +08:00
Teerapat Vatpitak 02a16a6422 fix(memory): extend tokenizer + slug regex to Thai/Arabic/Hebrew/Cyrillic (#104)
The previous CJK tokenizer ranges (#87, #95) only matched ``一-鿿``
and ``㐀-䶿``, so memory entries with Thai, Arabic, Hebrew, or
Cyrillic titles:

- Tokenized to the empty set, making recall always miss (e.g.
  ``find_relevant("ถัวเฉลี่ย")`` returned nothing even when the body
  contained the word).
- Had their slug characters stripped to ``_``, so two distinct Thai
  titles of equal length silently overwrote each other on disk.

The new ``_NON_LATIN_SCRIPT_RANGES`` constant covers CJK + Thai +
Arabic + Hebrew + Cyrillic and is reused by:

- ``_TOKEN_RE`` — single alternation pattern, one ``re.findall`` per
  ``_tokenize`` call (one text scan instead of two; precompiled at
  module level so it doesn't go through ``re.compile`` cache lookup on
  each invocation).
- ``_SLUG_DISALLOWED_RE`` — negation pattern used by ``add()``.

Arabic and Hebrew are deliberately narrowed to the basic letter blocks
(U+0620-U+064A, U+05D0-U+05EA) to keep bidi-control codepoints like
U+061C ARABIC LETTER MARK and combining marks out of on-disk slugs,
where they would render as invisible-but-distinct filenames.

Tests cover tokenization for each script, slug preservation
(parametrized across the four new scripts), and a Thai collision-
distinction regression.

Out of scope: ``agent/src/session/search.py`` has the same CJK-only
range in its FTS sanitizer; worth a follow-up PR to consume the same
constant.
2026-05-14 18:46:40 +08:00
Haozhe Wu 6b6683a026 docs: sync localized READMEs and feature assets 2026-05-14 02:48:18 +08:00
Haozhe Wu 45a9f525e4 docs: refresh README positioning and feature art 2026-05-14 02:21:48 +08:00
Haozhe Wu 75ecbce1fc docs: update README news for memory CLI 2026-05-14 00:46:09 +08:00
Haozhe Wu 4604d4c67a Merge pull request #102 from Teerapat-Vatpitak/feat/memory-introspection-cli
feat(cli): add memory introspection commands
2026-05-14 00:22:14 +08:00
Teerapat-Vatpitak 16102825b4 fix(memory): coerce frontmatter values to str in _scan_entries
The frontmatter parser returns lists for ``[a, b]`` syntax and bools
for ``true``/``false``, but MemoryEntry annotates ``title``,
``description``, and ``memory_type`` as ``str``. The mismatch was latent
until the new memory CLI tried to render those fields through
``rich.markup.escape``, which raised ``TypeError: expected string or
bytes-like object, got 'list'`` for any description that looked like a
YAML list (e.g. ``description: [red]inject[/red]``).

Add a small ``_coerce_str`` helper and route the three frontmatter
fields through it in ``_scan_entries`` so the dataclass type contract is
truthful at the storage boundary. Other consumers (recall scoring,
``remember_tool``) also benefit transparently.

Tests:

- Unit coverage of ``_coerce_str`` for str/None/list/bool inputs
- Storage-layer regression: a markdown file with a bracketed description
  parses to a string-typed description
- CLI regression: ``cmd_memory_list`` no longer crashes on entries with
  a bracketed description
2026-05-13 16:38:55 +07:00
Teerapat-Vatpitak 2d80b6248c style(cli): revert unintended formatter spillover on unrelated functions
The previous two commits inadvertently captured whole-file output from a
local pre-tool-edit formatter, reformatting ~30 functions
(`_print_status_bar`, `_format_tool_call_args`, `_build_benchmark_table`,
`_print_result`, `_build_welcome_panel`, ...) that have nothing to do
with the memory introspection feature.

Restore those functions to upstream `main` while keeping only the
intentional changes for this PR:

- `from datetime import datetime` hoisted to top imports (so the new
  CLI commands can use it without a per-call local import)
- `from rich.markup import escape as rich_escape` for safely rendering
  user-supplied memory titles/descriptions
- New `memory` subparser block in `_build_parser`
- New `cmd_memory_list / show / search / forget` plus the local
  `_MEMORY_TYPES` constant and `_MEMORY_TYPE_STYLES` dict
- Dispatch for `args.command == "memory"` in `main()`
- Drop the now-redundant local `from datetime import datetime` inside
  `cmd_trace`

Net diff for agent/cli.py shrinks from +296/-53 to +170/-1.
2026-05-13 16:27:40 +07:00
Teerapat-Vatpitak 0e0a584d85 perf(cli): avoid loading agent runtime on cli startup
Hoisting PersistentMemory to top-level cli.py made every CLI invocation
import `src.agent.frontmatter`, which transitively triggers
`src/agent/__init__.py` and eagerly loads AgentLoop, SkillsLoader (74
skills), and ToolRegistry. `vibe-trading --version` measured 7.6s
because of this chain.

- Move `from src.memory.persistent import PersistentMemory` back into
  each `cmd_memory_*` so only memory commands pay the import cost
- Mirror MEMORY_TYPES as a local `_MEMORY_TYPES` tuple inside cli.py so
  argparse `choices` and `_MEMORY_TYPE_STYLES` no longer require the
  module-level import. The module-load assertion still guards against
  drift between the local tuple and the style dict

`vibe-trading --version` drops from 7.6s to 0.66s (11.5x). Memory
commands keep the same latency since they need the module anyway.
2026-05-13 15:02:24 +07:00
Teerapat-Vatpitak 32a96fb5b2 feat(cli): add memory introspection commands
Add `vibe-trading memory list/show/search/forget` so users can inspect
and manage persistent memory without opening the file store directly.
Two recent memory bugs (#87 underscore tokenization, #95 CJK slug
collisions) were hard to spot without observability into what is
actually saved and recalled.

Also expand the PersistentMemory public surface to keep CLI lookup
logic out of storage internals:

- MEMORY_TYPES tuple as the single source of truth for type names,
  shared by argparse choices and the CLI style table
- list_entries() / find() / remove_entry() back the new commands;
  find() resolves by title first, then by on-disk filename stem so
  users can paste either form from the index
- remove_entry(entry) skips the re-scan that remove(title) does

Existing remove(title) is unchanged; remember_tool stays green.
2026-05-13 13:59:04 +07:00
Haozhe Wu b41141fa8f docs: update May 13 README news 2026-05-13 01:40:03 +08:00
Haozhe Wu 0678fd195a Merge commit 'refs/pr/93' 2026-05-13 01:15:28 +08:00
Haozhe Wu fb7efe76f6 Merge commit 'refs/pr/84' 2026-05-13 01:15:28 +08:00
Haozhe Wu 9e70dca97c fix(swarm): thread grounding data safely 2026-05-13 01:13:44 +08:00
Haozhe Wu e7d72be574 fix(swarm): redact worker event previews 2026-05-13 01:13:44 +08:00
Haozhe Wu 3981eb53f8 feat: add trust layer run cards 2026-05-12 18:44:19 +08:00
Peng QianandClaude Opus 4.6 ca26b0660a fix: resolve report.md into worker summary and sanitize event payloads
Address PR review feedback (HKUDS#84):

1. Read report.md back into WorkerResult.summary:
   - Add `_resolve_summary()` that checks for report.md in artifact dir
   - Applied at all 5 exit points (timeout, token_limit, llm_failure,
     normal_completion, iteration_limit)
   - Ensures API/UI shows the full report, not just the brief closing text

2. Sanitize event logging:
   - Truncate tool arguments to 200 chars per value
   - Rename "result" to "result_preview" and reduce from 5000 to 500 chars
   - Avoids persisting/streaming large or sensitive tool payloads

3. Add regression tests for report → summary handoff:
   - Test report.md content is returned when present
   - Test fallback behavior when report.md missing or empty
   - Test error handling for unreadable paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 19:15:12 +08:00
Peng QianandClaude Opus 4.6 5d90f3317f fix: add swarm runs to allowed paths and increase all preset timeouts
- Add `agent_root / ".swarm" / "runs"` to `_default_run_roots()` in
  path_utils.py. Without this, all `write_file` / `edit_file` / `read_file`
  calls from swarm workers fail with "run_dir is outside allowed run roots".
- Increase `timeout_seconds` from 600 to 1800 for all 29 swarm presets.
  The aggregator agent in commodity_research_team timed out at 615s, and
  complex analysis tasks routinely exceed 600s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 19:15:12 +08:00
Peng QianandClaude Opus 4.6 37e287c6c7 fix: persist swarm worker reports and improve summary extraction
Swarm workers frequently produced no usable output because:
- The Execution Rules prompt asked agents to write summaries "directly in
  your response," but the worker only captured `last_assistant_content`
  (often a mid-process message like "let me fix the script").
- On timeout/iteration limit, all in-memory messages were discarded.
- Event logging recorded only tool names and elapsed time, not I/O.

Changes to worker.py:
- Force agents to write final reports via `write_file report.md` (Phase 3),
  decoupling report persistence from the in-memory message lifecycle.
- Add `_best_summary()` to extract the longest assistant message (>100
  chars) instead of relying on the last fragment.
- Add `_persist_messages()` to save full conversation to messages.json on
  timeout/iteration-limit for post-mortem debugging.
- Include tool arguments in tool_call events and result content (up to 5000
  chars) in tool_result events for better observability.

Changes to equity_research_team.yaml:
- Increase timeout_seconds from 600 to 900 for all four agents, as the
  aggregator previously timed out at 615s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 19:15:12 +08:00
Haozhe Wu 3daa8dfae8 docs: update May 11 README news 2026-05-11 12:15:42 +08:00
ykj@hku 7320ac8d01 Improve CLI run UI and preflight startup check
Refresh the CLI run experience and add a startup preflight check for common environment issues.
2026-05-11 12:09:32 +08:00
Teerapat Vatpitak 96dbdb8050 feat(swarm): use real provider tokens for SwarmRun totals
Prefer provider-reported token usage for swarm runs while keeping the existing estimate fallback.
2026-05-11 12:09:21 +08:00
d 🔹 ac955c5692 fix(memory): preserve CJK chars in slug to avoid silent filename collisions
Preserve CJK characters in persistent memory slug generation and add regression coverage.
2026-05-11 12:09:11 +08:00
Teerapat Vatpitak 5731868a20 fix(swarm): route grounding fetch through _detect_market(code)
Empirical end-to-end verification on a real run revealed the dispatch
in fetch_grounding_data was wrong:

    loader_cls = resolve_loader(code)   # resolve_loader takes a market key
    loader     = loader_cls()           # resolve_loader already returns
                                        # a ready instance, not a class

So a real call with code="NVDA.US" produced
"No available data source for market 'NVDA.US'. Tried: []." — the
fallback chain table is keyed by market type ("us_equity",
"hk_equity", "crypto", ...), not by raw symbol shape.

The unit tests passed only because they monkey-patched resolve_loader
to a `lambda code: lambda: _StubLoader(...)` that happened to satisfy
both the wrong call site and the wrong return-type expectation. A
useful reminder that mock-shaped tests can hide real interface bugs.

Fix
---
- Import the same `_detect_market` runner.py uses for source routing
  and feed its output to `resolve_loader`. The private import is a
  reasonable pragmatic choice — there is no public equivalent today,
  and replicating the regex table in two places would invite drift.
- Drop the `loader_cls()` instantiation step; `resolve_loader` already
  returns a ready loader instance.

Test
----
Updated `test_fetch_returns_normalized_bars` to assert the call is
routed through `"us_equity"` (captured via a stub `resolve_loader`).
This pins the contract: a regression that passes the raw code as the
market key, or rewrites the helper so dispatch goes elsewhere, will
fire here instead of waiting for someone to actually run a swarm.

Verified with the local pipeline:
    NVDA.US latest close = $215.20 on 2026-05-08  (7 bars, yfinance)
i.e. the same number this PR was supposed to make available to the
worker prompt, instead of the empty block the original commit
silently produced.

`pytest agent/tests/test_swarm_grounding.py -v` -> 12/12 pass.
Full sweep at the same baseline (877 passed, 3 pre-existing Windows-
specific failures untouched, separately addressed in #88).
2026-05-10 20:19:29 +07:00
Teerapat Vatpitak b1d363b33c feat(swarm): pre-fetch market data and ground worker prompts in real prices
Symptom
-------
Workers in fundamental_research_team and similar presets routinely quote
prices from their model's training cutoff. A run targeting NVDA.US in
2026-05 produced a research report anchored on $145.89 while the actual
yfinance close that day was $215.20. Subsequent recommendations
("Hold/Accumulate, target $148-160") were therefore wrong by 30%+ before
any analyst even saw them. issue #42-class "weird outputs" land here too
when a worker silently substitutes a remembered number for a missing
data call.

Why prompt rules alone don't fix it
-----------------------------------
worker.py:158-171 already prints generic "use tools when needed"
guidance. LLMs nevertheless prefer the cheap path of recalling a number
over the expensive path of issuing a tool call — especially under the
20-tool-call hard limit imposed two paragraphs later. Adding a stronger
"never quote prices" rule helps but does not eliminate the failure mode.

Fix
---
Make the data unavoidable. SwarmRuntime.start_run now:

  1. Scans every value in user_vars for suffixed symbols
     (NVDA.US / 700.HK / 600519.SH / BTC-USDT / 000001.SZ / 430090.BJ).
  2. Pre-fetches the last 30 days of OHLCV via the existing loader
     registry (resolve_loader handles per-market routing). Failures
     are non-fatal — the run still proceeds, just without grounding.
  3. Pins the result on a new SwarmRun.grounding_data field and
     persists it via the existing run.json path.

SwarmRuntime._execute_run renders the data into a markdown "Ground
Truth" block once per run and threads it through
_run_worker_with_retries -> run_worker -> build_worker_prompt, which
splices it ahead of Execution Rules. The block contains the recent
window range, last close, the last 5 daily closes, and an explicit
instruction to prefer these prices over training-data prices and to
call get_market_data for any range outside the window.

Backward compatibility
----------------------
* SwarmRun.grounding_data defaults to None; existing run.json files
  parse unchanged (Pydantic deserialises the missing key to the
  default). The legacy regression case is covered by
  test_swarm_run_metadata.py once that lands; this PR's tests cover the
  new module and prompt plumbing in isolation.
* build_worker_prompt and run_worker each gain a grounding_block= kw
  with default "" so callers outside the swarm runtime keep working.
* Empty grounding (no symbols detected, or every fetch failed) skips
  the entire section — no empty heading rendered.

What's not in scope
-------------------
* Bare tickers without a suffix (just "NVDA") are intentionally not
  matched. The false-positive cost on common English words is too high
  and every shipped preset already supplies suffixed symbols.
* The block is a one-shot snapshot at run start. Long-running swarms
  may see stale data after many minutes — still strictly better than
  training-data prices from a year ago. A refresh hook can land later.

Tests
-----
agent/tests/test_swarm_grounding.py covers symbol extraction (all four
loader-supported suffix shapes, ordering, non-string values, word
boundaries), fetch behaviour with a stubbed loader (normalised bars,
empty-frame skip, empty-input no-op), markdown rendering (range / last
close / explicit instruction text), and end-to-end prompt plumbing
(grounding block lands before Execution Rules; absent block omits the
section). All 12 pass; full pytest sweep against main shows the same
pre-existing 3 failures (test_backtest_runner_security x2,
test_loop_helpers normalize) as main and 875 passed (= baseline 863 +
the new 12 grounding tests).
2026-05-10 20:19:29 +07:00
Haozhe Wu fbe37e0f63 docs: update May 10 README news 2026-05-10 20:36:15 +08:00
Haozhe Wu 1e0a7a0dd8 Merge pull request #92 from Teerapat-Vatpitak/feat/swarm-record-provider-model
feat(swarm): record provider/model on SwarmRun for cost audit + debug
2026-05-10 20:24:34 +08:00
Haozhe Wu bb977f2614 Merge pull request #91 from Teerapat-Vatpitak/fix/runner-validate-run-dir
fix(backtest): validate run_dir in CLI entry against allowed run roots
2026-05-10 20:24:24 +08:00
Haozhe Wu 209334d609 Merge pull request #90 from Teerapat-Vatpitak/fix/api-server-bare-except
fix(api_server): replace bare except: with specific exception types
2026-05-10 20:24:17 +08:00
Haozhe Wu f55ef1ffb9 Merge pull request #88 from Teerapat-Vatpitak/fix/windows-test-compat
test: make path-handling tests pass on Windows runners
2026-05-10 20:24:10 +08:00
Teerapat Vatpitak ba7cd19564 feat(swarm): record provider/model on SwarmRun for cost audit + debug
run.json currently has no field describing which LLM provider/model the
swarm was actually launched against — SwarmAgentSpec.model_name carries
per-agent overrides but defaults to None ("use the global config"), and
that resolved config is never captured anywhere. Cost auditing,
side-by-side provider experiments, and post-hoc bug triage all need this
information; today it has to be reconstructed from .env timestamps.

This adds two optional fields to SwarmRun:

- provider: lowercase form of LANGCHAIN_PROVIDER, the same env var
  src/providers/llm.py:136,195 reads when constructing ChatLLM.
- model: LANGCHAIN_MODEL_NAME (raw, since model strings are
  case-sensitive — "gpt-4o" vs "openai/gpt-5").

Both default to None and are populated in SwarmRuntime.start_run before
the run is persisted (runtime.py:start_run), so every new run.json from
this point forward records the active provider/model. Per-agent
overrides remain visible on SwarmAgentSpec.model_name; this field is the
run-level default.

Backward compat: existing run.json files lack the keys entirely. Pydantic
deserializes missing optional fields to their default (None), so
SwarmStore.get_run / list_runs continue to work on legacy files. A
parametrized regression test covers this in
agent/tests/test_swarm_run_metadata.py along with construction,
serialization round-trip, and acceptance of multiple provider names.
2026-05-10 15:49:32 +07:00
Teerapat Vatpitak 80e177228f fix(backtest): validate run_dir in CLI entry against allowed run roots
backtest.runner.main() previously accepted any Path it was handed and
went straight to reading config.json + importing code/signal_engine.py
from that location. The MCP backtest tool already routes through
safe_run_dir (src/tools/backtest_tool.py:23), but
"python -m backtest.runner <path>" did not — meaning a contributor or
agent that can drop config.json + code/signal_engine.py anywhere on
disk could have it loaded and executed by the CLI path.

The AST scrubber at runner.py:217-239 blocks executable top-level
statements, but any function or method body still runs on instantiation
or method call (BaseEngine.run_backtest -> SignalEngine().__init__ ->
.generate(...)), so the AST guard is not a substitute for a path
boundary.

Reuse the existing safe_run_dir() (src/tools/path_utils.py:190) at the
top of main() so the CLI honours the same VIBE_TRADING_ALLOWED_RUN_ROOTS
whitelist as the MCP path. Errors are surfaced through the existing
{"error": "..."} JSON-on-stdout + sys.exit(1) convention this entry
point already uses.

Existing test_path_safety.py exercises safe_run_dir directly and still
passes; behaviour for run dirs under the default roots
(agent/runs, $cwd/runs, ~/.vibe-trading/shadow_runs) and any path listed
in VIBE_TRADING_ALLOWED_RUN_ROOTS is unchanged.
2026-05-10 15:49:31 +07:00
Teerapat Vatpitak 51d0188cc5 fix(api_server): replace bare except: with specific exception types
Three bare except: clauses in api_server.py silently caught BaseException
(KeyboardInterrupt, SystemExit, GeneratorExit) in addition to ordinary
errors. That makes the API server unresponsive to Ctrl+C inside these
blocks and hides real bugs (NameError, AttributeError) as silent passes.

The blocks themselves are best-effort metadata extraction from per-run
artifact files, where falling through with the default values is the
right behavior — but only for the specific I/O / parse failures these
blocks can actually produce. The new clauses spell those out:

- lines 1015, 1022 (req.json / planner_output.json prompt extraction):
    except (json.JSONDecodeError, OSError)
- line 1042 (metrics.csv numeric parse):
    except (OSError, ValueError)

Behavior is unchanged for the failures the blocks are designed to swallow;
KeyboardInterrupt, SystemExit, and programming errors now propagate as
they should.
2026-05-10 15:49:31 +07:00
Teerapat Vatpitak 5eb8390921 test: make path-handling tests pass on Windows runners
Three tests fail when the suite is run on Windows even though the
underlying behaviour is correct on both platforms — the failures are
test-side platform assumptions, not application bugs. Linux CI is
unaffected; this is purely a quality-of-life fix for contributors who
run pytest on Windows.

1. test_backtest_runner_security.py — rce path interpolation
   ----------------------------------------------------------
   Both ``test_signal_engine_rejects_top_level_execution`` and
   ``test_signal_engine_rejects_class_level_execution`` write a
   ``signal_engine.py`` whose source contains the pytest tmp_path
   formatted into an ``os.system('touch ...')`` call. On Windows,
   tmp_path looks like ``C:\Users\teera\AppData\Local\Temp\...``;
   interpolated into a single-quoted Python string literal, the leading
   ``\U`` is interpreted as a (truncated) unicode escape and ``ast.parse``
   raises ``SyntaxError`` before the security scrubber under test ever
   runs. Switching to ``Path.as_posix()`` produces forward-slash paths
   that survive Python source escaping intact while pointing at the same
   filesystem location on every OS.

2. test_loop_helpers.py — absolute-path preservation
   --------------------------------------------------
   ``test_preserves_absolute_run_dir`` passed the literal
   ``/var/tmp/custom_run`` and expected it back unchanged.
   ``_normalize_tool_run_dir`` calls ``Path.is_absolute()``, which on
   Windows returns False for a bare drive-less path — so the function
   correctly treats the input as relative and joins it with
   ``memory_run_dir``. The test was wrong, not the function: it asserted
   POSIX semantics on a cross-platform helper. Resolving the input
   through ``os.path.abspath`` before handing it to the function
   produces the platform's actual absolute form (``/var/tmp/custom_run``
   on POSIX, ``C:\var\tmp\custom_run`` on Windows) and the assertion now
   holds on both.

Verified locally: the targeted three now pass; the full pytest sweep
goes from "3 failed, 863 passed" on main to "866 passed, 0 failed".
No production code is touched.
2026-05-10 15:47:09 +07:00
Hitesh PandClaude Opus 4.7 09dd59e9aa fix(memory): treat underscores as word boundaries in _tokenize (#87)
Memory titles saved through RememberTool are slugified to snake_case
(e.g. "mcp_wiring_test"), so the previous regex `[a-zA-Z0-9_]{3,}`
collapsed them into a single token. A natural-language recall query
like "mcp wiring" produces tokens {"mcp", "wiring"} and never
intersects {"mcp_wiring_test"} — recall returned 0 results despite
the memory existing on disk.

Drop `_` from the character class so underscores act as token
boundaries. Verbatim queries still work because the same string
splits into the same tokens on both sides of the comparison.

Adds a regression test for the snake_case case.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 15:43:20 +08:00
Teerapat Vatpitak 5eef5f3bd3 test(mcp_server): add smoke test for tools/call deadlock regression (#86)
Spawns vibe-trading-mcp via subprocess and walks through the JSON-RPC
happy path: initialize + tools/list + tools/call analyze_options. The
critical assertion is that tools/call does not hang — without the
registry pre-warm in mcp_server.main() (PR #85), the lazy registry build
runs inside FastMCP's worker thread and deadlocks on the first call.

The test also verifies (a) the tool catalogue has at least 20 entries
including analyze_options/get_market_data/list_skills, and (b) the
analyze_options result has a positive price and a delta in [0, 1] —
covering tool-registration regressions and Black-Scholes formula breaks
in addition to the deadlock.

analyze_options is chosen over network-dependent tools (get_market_data,
web_search) so the test stays deterministic and runs offline in CI.

Verified locally with differential validation:
- with pre-warm: PASS in 3.91s
- pre-warm commented out: FAIL with 15s timeout on tools/call
- pre-warm restored: PASS in 3.81s
2026-05-10 15:35:06 +08:00
Haozhe Wu 9fe6698d17 docs: update May 9 README news 2026-05-09 22:00:58 +08:00
Haozhe Wu 79efe1d6a8 Merge pull request #85 from Teerapat-Vatpitak/fix/mcp-server-tools-call-deadlock
fix(mcp_server): pre-warm registry in main thread to avoid tools/call deadlock
2026-05-09 15:13:48 +08:00
Teerapat Vatpitak dd594080c3 fix(mcp_server): pre-warm registry in main thread to avoid tools/call deadlock
_get_registry() lazy-imports src.tools.build_registry on first call. Every
caller is a FastMCP tool handler, so the first tools/call triggers the lazy
init inside FastMCP's asyncio worker thread, where the import chain (notably
src.tools.shell.*) deadlocks. initialize and tools/list succeed because
they don't touch the registry.

Pre-warming _get_registry() in main() — after _include_shell_tools is set
and before mcp.run() — performs the import on the main thread and lets
worker threads only ever read the cached singleton. _get_registry() is
idempotent (if _registry is None guard), so the 14 existing call sites are
unaffected.

Verified deterministically: removing the line reintroduces the hang within
15s; adding it back fixes tools/call within 0.01s. End-to-end through
Claude Code's MCP client returns in <2s.
2026-05-09 12:36:43 +07:00
Haozhe Wu 5f411ddaf7 Merge pull request #80 from SJoon99/fix/validate-path-params
fix(api): validate run_id and session_id path parameters
2026-05-09 09:52:11 +08:00
Haozhe Wu b9a82655bd fix(api): reject newline path parameters 2026-05-09 09:51:50 +08:00
Haozhe Wu bd722334d3 Merge pull request #82 from voidborne-d/fix/frontend-proxy-vite-api-url
fix(frontend): honor VITE_API_URL env for vite dev proxy target
2026-05-08 22:30:24 +08:00
voidborne-d 977fddfbff fix(frontend): honor VITE_API_URL env for vite dev proxy target
vite.config.ts hardcoded http://localhost:8899 across 14 proxy entries,
ignoring the VITE_API_URL env that docker-compose.yml passes to the
frontend service (http://vibe-trading:8899). Inside the frontend
container, localhost resolves to the frontend's own network namespace,
so all proxied requests (sessions, runs, skills, etc.) ECONNREFUSED.

Read VITE_API_URL via loadEnv with the original localhost:8899 default,
and DRY the per-route target into a single PROXY_PATHS list driven by
Object.fromEntries.

Closes #78
2026-05-08 21:21:59 +08:00
SJoon99 e2535cc7c3 fix(api): validate run_id and session_id path parameters
Endpoints under /runs/{run_id}, /swarm/runs/{run_id} and /sessions/{session_id}
constructed filesystem paths as `RUNS_DIR / run_id / ...` without validating
the path-parameter value. An authenticated caller could submit values like
`..` or `foo/../bar` and reach paths outside the intended directory.

The same file already validates `{shadow_id}` with `_SHADOW_ID_RE`. This
change applies the same hardening to `run_id` and `session_id` via a shared
`_validate_path_param()` helper that enforces a safe character class
(`^[A-Za-z0-9_-]{1,128}$`). The class accepts every ID format produced by the
codebase today (regular run_id from `state.create_run_dir`, swarm
`swarm-{ts}-{short_uuid}`, session_id from `uuid.uuid4().hex[:12]`) and
rejects path separators, dots, NUL, whitespace, and oversize input.

13 endpoints are guarded:
  - GET /runs/{run_id}/code, /pine, /
  - GET|DELETE|PATCH /sessions/{session_id}
  - POST /sessions/{session_id}/messages, /cancel
  - GET /sessions/{session_id}/messages, /events
  - GET /swarm/runs/{run_id}, /events
  - POST /swarm/runs/{run_id}/cancel

Tests added in `test_security_auth_api.py`:
  - parametrized validator unit tests (5 valid IDs / 9 invalid inputs)
  - per-endpoint integration tests using FastAPI TestClient
2026-05-08 07:50:36 +00:00
Haozhe Wu 52b624d3c8 docs: update news for Tushare statement filters 2026-05-08 10:53:35 +08:00
Haozhe Wu 4115960a42 Merge pull request #77 from HKUDS/fix/fundamental-fields-hard-fail
fix: fail fast on Tushare fundamental enrichment errors
2026-05-08 10:41:11 +08:00
Haozhe Wu 4fd82a0952 fix: fail fast on Tushare fundamental enrichment errors 2026-05-08 10:38:06 +08:00
Haozhe Wu 4ae673b6d1 Merge pull request #76 from mrbob-git/codex/vibe-trading-issue-62-prefilter
feat: wire Tushare fundamentals into filters
2026-05-08 10:33:56 +08:00
MrBob 94d0589638 feat: wire tushare fundamentals into filters 2026-05-07 15:16:07 -03:00
Haozhe Wu 0f4e3d7110 docs: sync May 7 translated news 2026-05-07 13:40:17 +08:00