Refactor Chat to ACP-Native Subpackage with Deep Agents Provider#10
Open
deeleeramone wants to merge 69 commits intomainfrom
Open
Refactor Chat to ACP-Native Subpackage with Deep Agents Provider#10deeleeramone wants to merge 69 commits intomainfrom
deeleeramone wants to merge 69 commits intomainfrom
Conversation
…SQLite state backend Restructures the chat system from 3 monolithic files into a `pywry/chat/` subpackage implementing the Agent Client Protocol (ACP). All provider adapters now conform to ACP's session lifecycle (initialize → new_session → prompt → cancel) and yield typed SessionUpdate notifications. Chat subpackage: - models.py: ACP content blocks, ACPToolCall, ChatMessage, ChatThread - session.py: SessionMode, SessionConfigOption, PlanEntry, PermissionRequest, Capabilities - updates.py: SessionUpdate discriminated union (11 types) - artifacts.py: 8 artifact types including TradingViewArtifact - manager.py: ChatManager accepting providers or legacy handlers - providers/: OpenAI, Anthropic, Magentic, Callback, Stdio, DeepAgent - permissions.py: RBAC permission mappings for ACP operations - html.py: build_chat_html() DeepAgent provider: - Wraps LangChain Deep Agents CompiledGraph - Maps LangGraph astream_events to ACP SessionUpdate types - Auto-configures checkpointer and memory store from PyWry state backend - Built-in system prompt onboarding the agent to the chat UI - Tool kind mapping for Deep Agents built-in tools - write_todos → PlanUpdate translation SQLite state backend: - Complete implementation of all 5 state ABCs (Widget, Session, Chat, EventBus, ConnectionRouter) - Encrypted at rest via SQLCipher with keyring-based key management - Full audit trail: tool calls, artifacts, token usage, resources, skills - Auto-created admin session with seeded role permissions - Same schema as Redis — fully interchangeable JS extraction: - 8 JS files extracted from scripts.py to frontend/src/ - WS bridge extracted from inline.py to frontend/src/ws-bridge.js - scripts.py rewritten to load from files Frontend: - TradingView artifact renderer in chat-handlers.js - ACP event handlers: permission-request, plan-update, mode-update, config-update, commands-update - PyWryChatWidget expanded with chat ESM and trait-based asset loading Documentation: - Rewrote chat guide and providers page with full ACP coverage - Created PyTauri, Anywidget, and IFrame+WebSocket transport protocol pages - Rewrote AG Grid and Plotly integration pages - Rewrote Why PyWry page reflecting current capabilities - Moved multi-widget to concepts, tauri-plugins under pytauri - Purged all print() statements from example code across all docs - Removed all section-banner comments and forbidden comment words Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Local settings file should not be in the repository. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- callback.py: remove redundant GenerationCancelledError import (used in _iter_result) - deepagent.py: add logger, log exceptions instead of bare pass, add on_chat_model_start and on_chain_start event handlers (StatusUpdate was unused) - base.py: add return statements to audit trail no-op methods (B027) - sqlite.py: restructure keyring try/except with else block (TRY300), log keyring fallback instead of bare pass (S110), add D102 per-file-ignore - test files: remove unused imports (F401), fix async sleep (ASYNC251), inline lambda (PLW0108), remove unused variable assignments (F841) - ruff.toml: add sqlite.py D102 ignore for ABC-documented methods Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- deepagent.py: fix E402 import order, extract _handle_tool_end to reduce prompt() complexity (C901/PLR0912) - ruff.toml: add S108 and PERF401 to test ignores - test_deepagent_provider.py: add test for on_chat_model_start → StatusUpdate (was imported but unused) - test_state_sqlite.py: remove unused time import Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SqliteWidgetStore: implement all 10 abstract methods from WidgetStore ABC (register, get, get_html, get_token, exists, delete, list_active, update_html, update_token, count) - SqliteEventBus/SqliteConnectionRouter: alias to Memory implementations since SQLite is single-process (no custom pub/sub or routing needed) - DeepagentProvider: guard all langgraph imports with try/except so tests pass without langgraph installed, return None from _create_store and _create_checkpointer when unavailable - Tests: use correct ABC method names, skip langgraph-dependent test when not installed, remove cache_clear call on non-cached function, pass auto_store=False alongside auto_checkpointer=False in all tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- models.py: remove unused type:ignore comments - providers/__init__.py: annotate return from getattr to satisfy no-any-return - providers/stdio.py: annotate session_id as str - providers/deepagent.py: add type:ignore for optional dep imports, use alias names in ToolCallUpdate constructors for mypy pydantic plugin, type _map_todo_status return as Literal - state/sqlite.py: type:ignore pysqlcipher3 import, annotate conn and cost variables - state/_factory.py: use MemoryEventBus/MemoryConnectionRouter directly for SQLite backend (no db_path needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous type:ignore removal merged two statements onto one line, causing a SyntaxError that broke every test. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…it trail - chat-providers.md: add DeepagentProvider mkdocstrings entry - state.md: add ChatStore ABC, SqliteWidgetStore, SqliteSessionStore, SqliteChatStore mkdocstrings entries - reference/index.md: update state description to include SQLite - state-and-auth.md: add SQLite section with audit trail details, encryption, auto-admin setup, config table entry - deploy-mode.md: update backend value to include "sqlite" - configuration.md: document sqlite_path setting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- manager.py: renamed _process_legacy_item to _process_handler_item, removed "legacy" from docstrings - test_chat_protocol.py: renamed TestLegacyHandlerWithNewUpdates - auth/deploy_routes.py: removed "legacy alias" comment - cli.py: removed "backwards compatibility" comment - inline.py, templates.py: removed "legacy template" from JS comments - tvchart/__init__.py: removed "backward compatibility" from docstring - tvchart/udf.py: removed "legacy" from docstring - mcp/skills/__init__.py: removed "backward compatibility" from docstring - test_mcp_unit.py: removed "legacy" from comment - test_plotly_theme_merge.py: renamed legacy -> single in test names, comments, and JS template strings - test_plotly_theme_merge_e2e.py: renamed storedLegacy -> storedSingle - test_tvchart.py: renamed test_symbol_info_legacy_alias, removed "backward compat" from docstrings Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Check for iterators (__next__) after coroutines and strings, not before. Using __iter__ matched strings; __next__ correctly identifies generators and iterators without matching strings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Python 3.10's asyncio.iscoroutine() returns True for some generator objects. Use inspect.isgenerator() and inspect.isasyncgen() to detect generators first, then inspect.iscoroutine() for strict coroutine checking. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Provider factory: explicit class name mapping instead of capitalize() which produced wrong names (OpenaiProvider vs OpenAIProvider) 2. PermissionRequestUpdate: add alias on request_id field so model_dump(by_alias=True) produces requestId for frontend 3. StdioProvider: map _request_id to request_id instead of stripping it, preserving correlation ID for permission flow 4. system-events.js: initialize window.pywry if missing before registering handlers, prevents undefined errors if loaded before bridge.js 5. chat-handlers.js: only prepend / to command names if not already present, preventing //foo double-slash 6. events/chat.md: replace stale InputRequiredResponse example with correct PermissionRequestUpdate usage 7. test_chat.py: add OpenAI provider factory test to cover class name resolution 8. widget.py: move asset listener registration inside render() function via __TOOLBAR_HANDLERS__ replacement where model is in scope, instead of module-level IIFE where it isn't Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Key changes across the stack:
- NEW `examples/pywry_demo_deepagent_nvidia.py`: LangChain Deep Agent
driving the TradingView chart through the in-process FastMCP server;
model discovery via NVIDIA NIM, graceful uvicorn shutdown, proactor
close-race filtering.
- `chat/providers/deepagent.py`:
- `InlineToolCallMiddleware` rewrites leaked
``functions.<name>:<idx>{...}`` markup from the model's text stream
into structured `tool_calls`, appending to any existing structured
calls so mixed-format responses fire end-to-end.
- `PlanContinuationMiddleware` re-enters the graph on exit when
`state.todos` still has pending entries — no nudge counting, no
auto-completion, pure state read.
- `_ToolCallTextFilter` stateful stripper removes both inline markup
and `<|...|>` chat-template special tokens from the streamed text,
handling chunk-boundary splits.
- TVChart frontend:
- Event-based data-settled signal (no state polling) tracks
applyDefault, chart-type re-apply, and indicator re-add so Python
tools see the chart fully stable before the next call.
- `whenMainSeriesReady` callback replaces the poll that chained
chart-type-change off series attachment.
- Case-insensitive time-range values ("1D" == "1d") and preserved
range cleared on new zoom so applyDefault can't clobber the user's
explicit zoom.
- Theme-aware CSS vars for hollow-body, hidden, price-line, and
settings-dialog defaults — no hardcoded hex in JS.
- MCP handlers: `_emit_zoom_and_confirm` waits on tvchart:data-settled
rather than polling state; `_minimal_confirm_state` strips rawData
from tool returns so the agent can't fabricate prices.
- Three SKILL.md files (tvchart, chat_agent, events) added under
`pywry/mcp/skills/` for agents operating inside the demo.
- Docs updated for the new MCP tools and skills surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ``PYWRY_TVCHART_CREATE`` now calls ``_tvEnsureDrawingLayer`` so every live chart has a drawing overlay canvas from the start, rather than lazy-creating it on first tool selection. The visibility / lock / state-export paths depend on the overlay existing. - ``tvchart:request-state``, ``tvchart:tool-visibility``, and ``tvchart:tool-lock`` handlers resolve chart id via a shared ``_resolveCid(data)`` that falls back through ``data.chartId`` → ``bridge._chartId`` → the first entry on ``__PYWRY_TVCHARTS__``. Single-widget native-window mode doesn't set ``bridge._chartId``, so the old ``_cid``-only code no-op'd against a live chart. - ``tests/test_tvchart_e2e.py`` drawing tests (20-24) rewritten to drive the REAL drawing pipeline: select the tool with ``_tvSetDrawTool`` then dispatch a synthetic ``click`` MouseEvent on the overlay canvas. The click handler does the work — computes price/time via ``_tvFromPixel``, pushes the drawing, calls ``_tvRenderDrawings``, creates the native ``priceLine``. The prior ``ds.drawings.push`` shortcut bypassed rendering, so nothing ever actually landed on the canvas. Test 20 also verifies the canvas has non-empty alpha pixels afterwards. - ``tests/test_chat_protocol.py`` updated to the current chat-manager event split: ``chat:tool-call`` for pending / in_progress (creates the collapsible tool card); ``chat:tool-result`` for completed / failed (fills the result body). Same ``toolCallId`` links the pair. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Kept the property-accessor pattern on ``bridge._chartId`` (from
main) so native-mode ``PYWRY_TVCHART_CREATE`` propagates the
chart id into the closure.
- Kept the ``_resolveCid(data)`` registry-fallback helper (from
branch) so handlers that operate on the live chart still resolve
when the accessor hasn't been set yet.
- Kept the backwards-compatible ``_handler`` test helper (accepts
both ``window.pywry.on('<event>', ...)`` and
``bridge.on('<event>', ...)``).
- Kept the ``_tvApplyAbsoluteDateRange`` assertion in the
time-range-selection test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
``pysqlcipher3`` (used by the encrypted SQLite state backend, pulled in via the ``features`` / ``dev`` / ``sqlite`` / ``all`` groups) builds a C extension that ``#include``s ``sqlcipher/sqlite3.h``. The previous workflows didn't install the SQLCipher headers on any runner, so every ``pip install .[dev]`` step failed with ``fatal error: sqlcipher/sqlite3.h: No such file or directory``. - Linux (``test-pywry.yml`` + ``publish-pywry.yml``): ``libsqlcipher- dev`` added to the existing ``apt-get install`` lines in the Lint, Test, Wheel-build verification, and Publish-verification jobs. - macOS: ``brew install sqlcipher`` + ``LDFLAGS`` / ``CPPFLAGS`` env vars so Homebrew's ``/opt/homebrew`` (or ``/usr/local``) include and lib directories are on the compile / link paths. - Windows: ``vcpkg install sqlcipher:<triplet>`` covering both ``x64-windows`` and ``arm64-windows``, with ``SQLCIPHER_PATH`` / ``INCLUDE`` / ``LIB`` / ``PATH`` pointed at the vcpkg install root so the MSVC build picks up the headers and .lib files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
``pysqlcipher3`` 1.2.0 hasn't seen a release since 2021 and its C extension fails to compile against Python 3.13+: it calls ``PyObject_AsCharBuffer`` (removed in 3.10) and ``_PyLong_AsInt`` (removed in 3.13). That breaks every CI runner on Python 3.13 / 3.14 and every ``pip install`` of the encrypted-SQLite opt-in. Switching to ``sqlcipher3-binary>=0.5.4``: - Actively maintained drop-in fork (same ``dbapi2`` API). - Ships prebuilt manylinux / macOS / Windows wheels on PyPI, so no ``libsqlcipher-dev`` / ``brew install sqlcipher`` / ``vcpkg install sqlcipher`` setup is needed in CI. The system- dep installs added in c0e0f4a are reverted. ``pywry/state/sqlite.py`` tries ``sqlcipher3`` first and falls back to ``pysqlcipher3`` for users pinning the legacy package. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Asserts are stripped under `python -O`, so any runtime invariant that
was "enforced" with `assert` would silently evaporate in an optimised
install. This commit takes the invariants seriously:
- ruff.toml: remove S101 from the global ignore list so asserts in
non-test code fail lint. S101 stays per-file-ignored under
tests/ (with a leading block comment + an inline reason on every
rule explaining why the check doesn't apply under tests/), and
the remaining global ignores now carry short inline comments so
future readers can evaluate whether each one still pays rent.
- pywry/chat/providers/stdio.py: the four `assert proc.stdin is
not None` / `assert self._process{,.stdout} is not None` spots
now raise RuntimeError with a specific message. Creating the
subprocess with PIPE means these are practically unreachable,
but when they DO trip we want a diagnostic exception, not a bare
AssertionError or (worse) a silent skip under -O.
- pywry/mcp/handlers.py: every `assert widget is not None` after
`_get_widget_or_error()` is replaced with an explicit
`if error is not None or widget is None: return error or {...}`
guard. This matches the existing pattern in
`_get_widget_or_error` itself and makes the invariant visible at
the call site.
ruff check pywry/ tests/ is clean and all touched unit tests still
pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontend load-order robustness:
- event-bridge.js (#20): install a minimal `window.pywry` + `_trigger`
shim so the Tauri `pywry:event` listener can fire before `bridge.js`
has finished loading.
- hot-reload.js (#21): guard against missing `window.pywry` and WRAP
any existing `pywry.refresh` rather than clobbering it (so a later
bridge.js / custom app can still add pre-refresh hooks). Also
gates the "initialised" console.log behind `PYWRY_DEBUG`.
- theme-manager.js (#30): gate the remaining unconditional
console.log calls behind `window.PYWRY_DEBUG`, matching bridge.js.
stdio provider reliability:
- #24–#26: `_read_loop` now wraps its body in try/except/finally that
fails every pending JSON-RPC request when the subprocess exits,
stdout closes, or the reader is cancelled — callers awaiting
`_send_request` used to hang forever. Extracted
`_fail_pending_requests` and `_dispatch_rpc_message` helpers to
keep complexity under the PLR0912 budget.
- #22–#23: `prompt()` now always settles `prompt_future` in a
`finally` block (cancel if still running, then await and log any
exception), so agent errors surface to the caller and
`_pending` can't leak on cancellation. Extracted
`_serialize_content_blocks`, `_update_type_map`, and
`_settle_prompt_future` helpers to keep `prompt()` itself simple.
Pydantic / docs clarity:
- chat/updates.py (#27): add a comment explaining why
`Field(discriminator="session_update")` still routes raw
`{"sessionUpdate": ...}` JSON correctly — every variant has
`populate_by_name=True` + `alias="sessionUpdate"`, so the union
dispatches on both key styles. Verified locally via TypeAdapter.
CI:
- test-pywry.yml (#29): explicitly guard the `lint` job's
"Build SQLCipher from source (Linux)" step with
`if: runner.os == 'Linux'`. The job is already pinned to
ubuntu-24.04, but the guard makes intent explicit and stops the
step running if anyone later adds matrix support to the lint job.
Verified false positives (no code change):
- #28: `pywry:download-csv` IS implemented, in plotly-widget.js.
- #29 "duplicate build step": the second SQLCipher build is in a
different job (`test`), not a duplicate.
Full `ruff check pywry/ tests/` and `mypy pywry/` are clean; chat
unit + state unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Loaded via importlib.import_module(), so mypy never sees them as direct imports and the overrides are reported as unused. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port of the upstream TradingView lightweight-charts volume-profile plugin example. Ships both variants: Fixed Range anchors the histogram to a bar-index range, Visible Range follows the viewport and recomputes on every pan/zoom (debounced via rAF). - 09-indicators.js: _tvComputeVolumeProfile buckets OHLCV volume across price levels, _tvMakeVolumeProfilePrimitive renders the histogram via the ISeriesPrimitive API, _tvRefreshVisibleVolumeProfiles recomputes visible-range instances on viewport change. - 04-series.js: _tvAddIndicator branch for both VP variants; creates the primitive and attaches it to the main price series. - 09-indicators.js: _tvRemoveIndicator detaches the primitive alongside the usual series cleanup. - 05-lifecycle.js: subscribeVisibleLogicalRangeChange now also calls the VP refresh hook (rAF-debounced). - 05-lifecycle.js + 06-storage.js: layout export/restore round-trips mode / bucketCount / fromIndex / toIndex. - 10-events.js: tvchart:add-indicator handler forwards fromIndex / toIndex to _tvAddIndicator. - tvchart/mixin.py: new add_volume_profile() convenience method with mode="fixed"|"visible", bucket_count, from_index/to_index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Volume Profile: replace upstream time-anchored port with proper VPVR/VPFR — horizontal rows pinned to the pane edge (right by default), bar length proportional to net volume at each price bucket, split into up-volume / down-volume by bar direction, POC line, and 70%-volume Value Area colouring. New indicators: - Moving Averages: HMA, VWMA, Ichimoku Cloud - Volatility: Keltner Channels, Historical Volatility - Trend: Parabolic SAR - Momentum: MACD, Stochastic, Williams %R, CCI, ADX, Aroon - Volume: Accumulation/Distribution Multi-line indicators (MACD, Stochastic, Aroon, ADX) share a subplot pane and a group id so they remove together. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 09-indicators.js: fix bitmap-pixel-ratio bug (bars now render); cap bar width at 15% of pane (down from 25); add Volume mode (Up/Down / Total / Delta); add Developing POC + Developing VA step-line plots; recompute on viewport change with the new opts shape (was passing bucketCount as opts, silently ignoring layout toggles); fix _tvApplyIndicatorSettings VP branch to use the new rowsLayout / rowSize / volumeMode schema; surface settings-dialog click errors via try/catch + console.error. - tvchart.css: VP defaults now use a blue / violet palette with low opacity so the histogram doesn't camouflage the green/red candles and price action stays readable through it; new --pywry-tvchart-vp-* CSS variables override the old ones (and a small indicator multi-plot palette --pywry-tvchart-ind-* powers MACD / Stoch / Aroon / ADX / KC / Ichimoku / SAR defaults). - 04-series.js: the VP add branch + the new indicator branches now pull every default colour from the CSS palette (themed) instead of hard-coded literals; VP carries rowsLayout / rowSize / volumeMode through to the slot + indicator state. - 10-events.js: forward the new VP / indicator opts (rowsLayout, rowSize, volumeMode, developingPOC/VA, dev colours, fast/slow/ signal/dPeriod/step/maxStep/tenkan/kijun/senkouB/annualization) from the Python ``tvchart:add-indicator`` event into the JS indicator def. - mixin.py: extend add_volume_profile() with placement / width / value-area / show-poc / show-value-area / colour overrides. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- _tvSetIndicatorVisibility now toggles the VP primitive's hidden flag and triggers a redraw — the eye icon actually hides VP. - VP renderer skips drawing when getHidden() is true. - Legend row now shows "VPVR Number Of Rows 24 Up/Down 70" (matches TradingView), reads from rowsLayout / rowSize / volumeMode / valueAreaPct. - Legend dot uses the up-volume swatch when the indicator has no line colour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ReferenceError when clicking the gear — _tvAppendOverlay(chartId, …) read an undeclared identifier. Other indicators happened to never hit this path because the dialog throws AFTER the body is rendered in some flows; VPVR exposes it consistently. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Every Inputs / Style row in the VP settings dialog now funnels through _tvApplyVPDraftLive() so changes paint instantly: colours swap, width / placement reflow, rows-layout / row-size / value-area / developing-poc/va recompute the bucket profile and redraw without waiting for the OK button. - Legend row now carries a value span showing up / down / total volume formatted as 1.23M / 4.56K, refreshed on every recompute (visible-range pan / zoom, settings change, layout restore). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
03-theme.js: set explicit textColor on rightPriceScale + leftPriceScale when building chart options. layout.textColor doesn't always cascade to price-scale labels, so labels rendered with the dark-theme grey on the white light-mode background. 05-lifecycle.js: on theme switch, clear the stored Text-Color / Lines-Color overrides in _chartPrefs so the new palette wins; pass explicit textColor to both price scales alongside the layout update. 07-drawing.js: _emitDrawingAdded now calls _tvRevertToCursor so the left-toolbar drawing button drops its "active" state the moment the drawing finishes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files grew to 3.9k–6.2k lines each; break them up into modular subfolders loaded recursively via `rglob`, alphabetical order preserved so dependencies still resolve. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove-Volume only flipped a legend dataset flag — series and pane stayed on the chart. Now it calls chart.removeSeries, deletes the pane, and reindexes higher panes. Restore-Volume rebuilds the histogram from the stored raw bars. Also fix the volume series to use the pane's standard 'right' scale so its axis labels render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_tvRecomputeIndicatorSeries was missing branches for VWAP, MACD, Stochastic, Aroon, ADX, CCI, Williams %R, A/D, Historical Volatility, Keltner Channels, Ichimoku, Parabolic SAR, and Volume Profile — so when the datafeed replaced initial bars with real data, these indicators stayed frozen at their initial snapshot. Most visibly: VWAP stuck at 9.99 (the placeholder bars' h=l=c value) on a $270 stock because it was computed once, never again. Add recompute branches for every new indicator, matching each group's actual series-key prefix (_macd_line_, _adx_plus_, _ichi_tenkan_, etc.) and reapplying per-bar colors for the MACD histogram. moving-average-ex now also recomputes HMA/VWMA, not just SMA/EMA/WMA. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a real contract suite under TestTVChartIndicatorCatalog / TestTVChartVolumeProfile / TestTVChartLegendVolumeRemoval / TestTVChartThemeVariables: every catalog entry has a compute fn, add branch, and recompute branch; VP returns the expected fields; volume Remove actually removes; every --pywry-tvchart-vp-* and --pywry-tvchart-ind-* var is defined for both themes (86 new tests, 267 total). Docs: new Indicators reference page listing TradingView-accurate parameter names for every indicator, CSS reference updated with VP and indicator palette sections, events reference updated with the full name list for tvchart:add-indicator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ywhere
The SMA/EMA/WMA catalog entries were removed in the MA unification; the
recompute path still had a dead baseName branch for them, and the e2e /
unit tests + docs still called add_builtin_indicator("SMA", ...).
Update tests and docstrings to add indicators as Moving Average with
method="SMA" | "EMA" | "WMA" | "HMA" | "VWMA", matching how the
indicator panel UI actually lets users add them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dialog previously only pushed edits on Ok — the user couldn't see their colour / period / line-width change until they dismissed the modal. Wire every row helper through a shared _livePreview() that calls _tvApplyIndicatorSettings on each edit, coalesced to rAF so a number-spinner drag paints once per frame. Also make the "Values in status line" and "Inputs in status line" checkboxes actually drive the legend rebuild — when off, drop the numeric-parameter suffix from the indicator label and skip appending the per-series value spans. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symbol changes used to wipe indicators and compare overlays; an SMA(20) on AAPL is still an SMA(20) on MSFT — the user shouldn't need to re-add it after switching tickers. The restore loop also only forwarded five config fields so MACD lengths, Ichimoku lines, VP range + layout, status-line toggles, and every other tunable got reset to defaults on interval change. Carry every info.* field through as indicatorDef._*, clean up stale _activeIndicators / _volumeProfilePrimitives entries before destroy so the re-add doesn't duplicate rows in the legend, and skip extra group members during re-add since _tvAddIndicator materialises them itself. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ttach Datafeed mode was firing the ready callback as soon as the series was constructed — BEFORE the async getBars + setData step populated _seriesRawData. Every consumer that needs the bars (indicator re-add after interval/symbol change in particular) saw an empty bar set and silently produced lines with stale or zeroed values. Static-bar mode was already firing at the right spot; move the datafeed fire to match so the ready semantics are consistent: "series has data", not "series exists". Switch the post-destroy indicator re-add from setTimeout(100) to whenMainSeriesReady and force a recompute pass after all indicators are attached, so every saved indicator redraws against the current bars across both interval and symbol changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs when flipping ETH → RTH: 1. Session filter iterated seriesMap and setData'd every series in it — including indicator series. Indicators have no _seriesRawData entry so they silently fell through, but the filter shouldn't be touching them at all. Skip any sid with an _activeIndicators entry up front. 2. After filtering the main bars to the RTH subset, indicators weren't recomputed. An SMA(9) stayed at the ETH-computed value even though the chart now shows only RTH bars — a 9-bar window on the RTH chart is very different from a 9-bar ETH window with an overnight gap. Teach _tvSeriesRawData to return _seriesDisplayData when the user flipped to RTH, then drive _tvRecomputeIndicatorsForChart and _tvRefreshVisibleVolumeProfiles from _tvApplySessionFilter so every indicator and VPVR lines up with the filtered bar set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The RTH toggle shrinks the main bar array (often by 2-3x for US equities), and LWC's getVisibleLogicalRange keeps pointing at the ETH indices until the time scale is refit. _tvRefreshVisibleVolumeProfiles was pulling that stale range and the VP compute clamped its lo/hi to the last single bar — explaining the "877K up, 0 down" VPVR readout on an otherwise empty-looking chart. Reorder _tvApplySessionFilter so fitContent runs first (re-seating the visible range on the filtered set), then indicator recompute + VP refresh run against the fresh range. Also harden the VP refresh itself: when the visible range sits entirely outside the new bar array, fall back to [0, N-1] instead of clamping to one bar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three regressions surfaced when flipping session to RTH: 1. _tvFilterBarsBySession couldn't parse TradingView-style strings like "0930-1600:23456" (weekday suffix) or pipe-separated multi-sessions — split on both `,` and `|`, strip anything after `:` before the HHMM-HHMM match. 2. Scrollback's onVisibleLogicalRangeChange handler called series.setData(merged) directly, bypassing the active session filter — so every chunk of older bars loaded via the left-edge scrollback wiped the RTH filter for the merged view, leaving an empty-looking chart because _seriesDisplayData went stale. Hand off to _tvApplySessionFilter when RTH is active so the merged bars get re-filtered, indicators recompute, and VP refreshes atomically. 3. The security-info session schedule never rendered the overnight leg because nothing derived it from Yahoo metadata and the legend-side window builder only looked at pre/regular/post. Add a sixth `overnight_str` field to `_build_session_string`, ship it as `session_overnight` in the symbolInfo dict, wire it through the datafeed normalizer, and teach `_sessionWindows` to split the wrap-around "2000-0400" into an evening leg (Sun-Thu) and a next-morning early leg (Mon-Fri). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ounce range emit Three fixes for the scroll-past-data overload + lingering RTH leaks: 1. Streaming bars during after-hours kept painting onto an RTH chart because subscribeBars only checked an optional bar.market_hours field most providers don't ship. Drive the gate off _tvIsBarInCurrentSession (uses the symbol's actual session string) and keep market_hours as a fallback. 2. Scrollback was infinite-looping after RTH was on: the post-merge re-filter via _tvApplySessionFilter always called fitContent at the end, which snapped range.from back to 0, retriggered the scrollback handler, fetched another batch, filtered again, fitContent again. The chain hogged the JS event loop hard enough that toolbar clicks (search/compare) never reached their handlers. Add an opts.skipFitContent flag and pass it from the scrollback path; the user-facing session toggle still fits. 3. Scroll-past-data overload: each visible-range tick fired straight to Python via bridge.emit (60+/sec when scrolling). Coalesce via rAF and skip emit when the range hasn't moved by ≥ 0.5 logical bars. Also harden the scrollback callback to mark exhausted on ANY non-OK response (error, status:no_data, noData:true, empty bars, or bars that don't actually advance past the current oldest), and add an extra guard against rapidly dragging the scroll wheel past where data could possibly exist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dark theme dim was #787b86 — TradingView's default but too washed out in PyWry's dark background. The labels in the legend (Volume, VPVR header, OHLC O/C tags, time stamp) and pane legends became hard to read against the deep-black background. Lift each text tier one notch: text → #e6e9f0, muted → #c4c9d4, dim → #a3a8b5 in dark; mirror with deeper-than-default values in light. Bump main legend font from 12px → 13px and indicator legend font from 11px → 13px so the per-bar OHLC, the symbol-time line, and indicator names actually read at a glance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LWC's series.update throws "Cannot update oldest data" if the incoming bar's time is before the last bar already in the series. That happens easily after a session-filter swap (the series was just setData'd with filtered bars whose newest is e.g. yesterday's RTH close, then a late tick arrives for an earlier extended-hours bar) and the unhandled throw bubbled out of pywry.dispatch into the console as an "Error in event handler". Skip the tick when its time is older than _seriesRawData[sid]'s last entry, and wrap series.update / volumeMap.update in try/catch so any remaining mismatch (format, business-day vs unix) drops the tick instead of breaking the stream subscription. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Volume Profile indicator keeps info.color = null because its colours live on opts.upColor / downColor / vaUpColor / vaDownColor / pocColor. But the settings dialog seeded draft.color with the fallback "#e6b32c" (a gold that matched the default POC shade), and the "Apply per-plot styles" pass wrote draft.color back onto info.color for every style-sid — including VP. The next legend rebuild saw info.color = gold, skipped the VP-specific blue-up fallback, and rendered the whole "VPVR Number Of Rows 24 Up/Down 70 ..." row in POC-looking gold. Guard both sides: - 11-apply-settings skips the per-plot colour write when type is VP. - 09-legend-rebuild forces the VP name to the default legend text colour (and the dot to the up-volume swatch) regardless of any info.color that might have leaked through. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cross destroy _tvBuildChartOptions previously set only ~60% of what LWC's ChartOptionsImpl exposes — gaps like kineticScroll, trackingMode, addDefaultPane, full timeScale tuning (rightOffset, barSpacing, fixLeftEdge, lockVisibleTimeRangeOnResize, etc.), and full price-scale tuning (mode, invertScale, alignLabels, entireTextOnly, minimumWidth, visible) meant any payload.chartOptions override for those silently fell through to LWC's library default instead of PyWry's. Add defaults for every property on the interface so payloads can override anything and the chart still boots with sensible PyWry behaviour. Also persist _chartPrefs / _sessionMode / _selectedTimezone through the interval/symbol destroy-recreate path so user-tuned scale mode, bar spacing, navigation toggles, and session filter don't reset on every interval switch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR restructures the chat system from 3 monolithic files into a
pywry/chat/subpackage implementing the Agent Client Protocol (ACP). All provider adapters now conform to ACP's session lifecycle (initialize → new_session → prompt → cancel) and yield typed SessionUpdate notifications.Chat subpackage:
DeepAgent provider:
SQLite state backend:
JS extraction:
Frontend:
Documentation: