feat(core): ChartController 暴露 Agent 工具宿主注册(registerToolHost)[supersedes #272] - #274
Open
EliteOtaku wants to merge 117 commits into
Open
EliteOtaku wants to merge 117 commits into
EliteOtaku wants to merge 117 commits into
Conversation
Add './dist/data/provider/sources/*.js' subpath (types + import) to package exports so hosts can deep-import builtin market-data source registrations. Without it, hosts bootstrapping the provider registry outside the aggregation UI get an empty registry and no routable providers.
The bare web-component entry gets its side-effect source registrations tree-shaken away (package sideEffects:false), leaving hosts with an empty provider registry. New entry force-imports the five builtin market-data sources (baostock/finshare/gotdx/mock/tradingview) and re-exports the element so the side-effect chain survives bundling. It also declares static capabilities for host-wired sources: the shell's SourceRouter filters providers by source.capabilities, which the stock flow only populates via probe() (aggregation-UI-only path), so headless bootstrap was deadlocked with no routable provider even after registration. Static declarations unblock routing on the first bars request (idempotent: only fills undefined capabilities).
Add '4h' to KLinePeriod union, KLINE_PERIODS set, market data policy initial days (90), agent tool whitelist, semantic DataConfig period union + validator max range (180 days), and the level dropdown entry. Hosts mapping broker resolutions (e.g. H4=240) currently have no in-library period to land on.
applyToolSession resets the active tool via setDrawingToolId('cursor'),
which also clears the current selection. Emitting onDrawingCreated
before that reset meant a host selecting the new drawing in the
callback (TV-style floating style toolbar) had its selection wiped
immediately. Swap the order: reset the tool first, then notify.
Selected-drawing floating toolbar gains a template dropdown (filtered by the current drawing kind) plus a 'save as template' dialog: - KLineChart accepts an optional drawingTemplateStore prop; a localStorage-backed default store is used when not injected, so the mechanism stays host-agnostic (hosts can route to their own backend) - apply merges the template style into the selected drawings (may add keys updateBatch would reject via its field-intersection guard) - save captures the primary selected drawing's style under a name Depends on the selection-timing fix so the toolbar actually appears right after finishing a drawing.
Add packages/nexus-shell: a generic, business-decoupled TradingView-style host shell (React + Vite, private workspace package, never published): - top bar (period presets incl. 4h, theme toggle), left drawing toolbar, indicator panel, template panel skeleton, chart stage mounting the engine via the react adapter - theme tokens as CSS variables (light/dark), all colors centralized in tokens.css per upstream convention - UI strings centralized in labels.ts; template storage behind a DrawingTemplateStore port so hosts can inject their own backend Engine wiring (datafeed, tool selection, indicator registry, template store default impl) is intentionally left as documented next milestones: this scaffold ships chrome + ports only, no product coupling.
Add a fork section (via a docs fragment so generated READMEs stay in sync with pnpm docs:generate) describing the fork's purpose: a generic TV-style host shell plus engine enhancements offered back upstream in small batches. Add NOTICE with upstream attribution; LICENSE is retained unchanged from upstream.
- ambient module stub for the web component entry (same approach as packages/react) instead of following into .vue resolution - drop vite.config.ts from tsconfig include (repo convention excludes config files from type-check) - type DEFAULT_INDICATORS as ReadonlyArray to avoid literal-type trap
The exports wildcard for data provider sources has no single source file to map, so createCoreSourceAliases aborted every vitest/vite config that consumes it (ai-runtime tests failed at startup). Wildcard entries are runtime deep-import patterns for built output; source-alias contexts do not need them.
The vue package source (pulled in via the web component alias) imports ~icons/tabler/* virtual modules; without the Icons plugin the chart mount fails to resolve them and the dynamic import of the web component entry throws.
- ChartStage hosts core createChartController directly: the react WC adapter exposes no controller channel and KLineChart.vue embeds its own toolbar chrome, so the shell now owns all chrome itself (G-10) - left toolbar: grouped flyouts with last-used memory, favorites, magnet tri-state (shell-side OHLC snapping), stay-in-drawing-mode, measure and eraser pseudo-tools, zoom buttons - selection style flybar: color/width/linetype/fillOpacity with mixed-state handling, lock toggle, template dropdown, delete - drawing templates on nexus.* localStorage: save/apply/rename/delete plus auto-apply to newly created drawings (toggleable) - pointer bridge: Shift 45-degree angle lock, Shift-click multi-select normalization (engine only supports Ctrl), Ctrl drag-copy via restore-in-place strategy, Esc/Delete keyboard flow - deterministic seeded mock data source covering symbols and periods; dev server pinned to port 5273 - E2E acceptance probe scripts/probe-drawing.mjs (39 assertions, all green) and docs/action-checklist.md tracking TV-parity actions
…batch 2) - SymbolPicker: dropdown with keyword filter over the mock catalog, Enter-to-select and recents persisted on nexus.shell.recent-symbols - TopBar rebuilt on shell context: grouped period select (minutes / hours / day-week-month) re-feeds mock data on change; theme toggle - probe-topbar.mjs acceptance probe (12 assertions) including canvas legend rendering verification; drawing probe regression 39/39
Add magnetSnapper pure module (weak: 8px to high/low, strong: 15px to OHLC, X snaps to bar center) mirroring the verified nexus-shell ChartPointerBridge.applyMagnet semantics. resolveDrawingPointer gains an optional magnet config applied before screenToAnchor so cursor hit, marquee and label paths stay untouched. DrawingInteractionController owns the session-level magnet mode (off/weak/strong, Ctrl/Meta temporarily upgrades to strong including off) and passes it only along the drawing anchor and preview paths.
A locked drawing was still clickable, marquee-selectable and dragged along with the selection group. Exclude locked drawings from the hit candidates fed to HitTester, from marquee commit candidates, and from the connected drag group so a locked drawing can no longer be selected, boxed or moved by pointer interaction.
Shift+click in cursor mode now toggles drawing selection and preserves the selection when clicking blank space, matching Ctrl semantics. The shell previously normalized Shift into a synthetic Ctrl event; this makes the engine accept both modifiers natively.
Host UIs previously had to duplicate the anchor-count table to render step hints. Export getAnchorCountForTool plus the SINGLE/DOUBLE/TRIPLE anchor tool lists from the drawing module and re-export them through the controllers facade.
Hosts implementing eraser or object-tree hover needed the same filtered hit candidates as cursor clicks but had no access to HitTester. Extract the candidate filtering shared with findDrawingHit and expose hitTestAt(x, y) returning the drawing under container-local coordinates, honoring pane offset and the locked exclusion.
Channel kinds render an area primitive whose fill falls back to stroke when unset, so fill is a kind-inherent capability rather than an explicitly-stored style key. getBatchStyleKeys now includes 'fill' when every target is a channel kind, letting hosts batch-edit the fill color without changing the default style (unset fill keeps following stroke, so no visual regression). Mixed channel+line selections keep the intersection guard rejecting fill.
vue-tsc resolves @363045841yyt/klinechart-agent-runtime (and its /contracts/ui subpath) through package exports, which point at a dist that is never built in dev clones; the vite dev/build path already aliases the same specifier to agent-runtime source. Mirror that mapping in tsconfig.app.json paths so the whole features/agent type chain resolves (337 type errors drop to 56 pre-existing test-file debts outside the agent chain; use-agent-workspace implicit-any errors were downstream of the broken imports and disappear with them).
Document magnet tier semantics (radii, candidate order, Ctrl override, why X snaps to bar center), the resolveDrawingPointer opt-in contract, why magnet mode lives on the controller instead of StateKernel, and the locked-drawing interaction semantics plus the channel fill key decision.
…xports, hitTestAt, channel fill, vue type chain)
Shift 角度锁(宿主壳层实现)会先改写指针坐标,引擎磁吸若再吸附 会造成双重改写;此前壳侧以分支互斥规避,迁移到引擎 setMagnetMode 前必须先在 resolveMagnetOptions 恢复该互斥语义。Shift 优先级高于 Ctrl/Meta 的 strong 升级,单锚点工具按 Shift 亦不吸附。 补 interaction.magnet.test.ts 用例(Shift 不吸附、Shift+Ctrl 抑制 升级),设计文档修饰键条目同步更新。
引擎自 fb5392d 起原生提供 OHLC 磁吸(档位/半径/X 吸附/Ctrl 升级 与壳侧基准逐点一致),壳侧删除重复实现:applyMagnet、其按下/移动 两处调用分支与 MAGNET_RADIUS_* 常量。偏好读取保留为 BridgeStateAccessors.getMagnet,新增桥方法 syncMagnet 由壳在桥挂载 与偏好变化时调用 dic.setMagnetMode 同步档位;持久化键不变。
…i-selects natively 引擎光标模式自 fb5392d 起原生支持 Shift 多选(ctrlKey || shiftKey 同语义:toggle 且空白不清空),壳侧删除 cursor 模式的 shiftKey→ctrlKey 事件归一化分支;clonePointerEvent 的 shiftKey 覆写字段随之失去调用方,一并移除。
橡皮擦从"点选→读选中→删"三步组合改为 hitTestAt 公开命中查询 (与点选同口径:locked 排除、pane/工作区过滤)直接删除;不再借用 光标点选语义,消除误开拖拽会话与空白点击清空选中的副作用。
磁吸/Shift 多选/橡皮擦已切换引擎原生实现(G-01/G-06/G-03), locked 强制、锚点数表导出、通道 fill 键、vue 类型链均随 fb5392d 关闭;缺口表同步标注,仅 G-02(测量)/G-05(FVG)/G-10(WC 通道) 保持登记。
…tMagnetMode wiring, native shift-select, hitTestAt eraser)
TV 官方 Magnet Mode 文档定义 Ctrl/Command 为磁吸临时取反:off 时 临时开启、开启时临时关闭。此前引擎沿壳侧旧基准"一律强制 strong", 后半段与官方相反。临时开启强度取 strong(TV 对磁吸开的描述即吸附 OHLC 四值)。测试改写为取反三态用例,设计文档修饰键条目同步。
getTimestampAtLogicalIndex 越界分支走 projectTradingTimestamp(session 由 Chart 注入主品种画像,数据<2 根不外推);RenderContext 增加可选 getTimestampAtLogicalIndex 回调,时间轴十字签对越界索引回退外推时间。
可见区间不含任何真实 bar 时(range.start >= data.length),Pane.updateRange 早退保留最近一次有效 priceRange 与基准价,消除拖入未来区时价格轴跳变到 {100,0} 兜底;空数据冷启动仍走原兜底。
…ading calendar collectFutureTimeBoundaries 纯函数按外推时间 key 变化检测未来槽位边界(经 RenderContext.getTimestampAtLogicalIndex 复用 Task 5 外推 SSOT,渲染器不二次推导 session/周期);时间轴未来刻度以 text.tertiary 降级渲染,纵向网格同帧合并。
…olation MT5 连接器 symbol-catalog 规格约定 sessionId=MT5(对应前端注册的会话),但内建注册表只有 CN/HK/KR/US,resolveSymbolMarketSession 对 MT5 品种 throw 使 futureSession 恒 null,未来区时间外推/十字签/网格在 MT5 链路整体失效。注册 MT5 -> FOREX_MARKET_SESSION(24/5 主场景),crypto 等全周品种的周末预测由索引制轴自愈兜底。
# Conflicts: # packages/core/src/engine/controller/__tests__/interaction.future.test.ts # packages/core/src/engine/data/__tests__/contentGeometry.parity.test.ts # packages/core/src/engine/data/chartDataManager.ts # packages/core/src/engine/market/cryptoMarketSession.ts # packages/core/src/engine/market/forexMarketSession.ts # packages/core/src/engine/market/marketSessionRegistry.ts # packages/core/src/engine/render/chartRenderer.ts # packages/core/src/engine/renderers/__tests__/gridLines.mode.test.ts # packages/core/src/engine/renderers/__tests__/helpers/futureAxisTestKit.ts # packages/core/src/engine/renderers/__tests__/timeAxis.crosshairFuture.test.ts # packages/core/src/engine/renderers/__tests__/timeAxis.future.test.ts # packages/core/src/engine/renderers/timeAxis.ts # packages/core/src/engine/utils/__tests__/chartZoomController.future.test.ts # packages/core/src/engine/viewport/__tests__/visibleRange.clamp.test.ts # packages/core/src/foundation/plugin/types.ts # packages/core/src/foundation/utils/sessionTimeLabels.ts # packages/vue/src/components/KLineChart.vue
…/getRenderer) 宿主业务 overlay(指标扩展/业务标记层)此前只能经 dev-only 的 window.__chart 自挂实例触达 useRenderer;vue/agent 层 ChartController 无注册入口。 - ChartController 新增 useRenderer/removeRenderer/getRenderer,透传核心 Chart 实例(幂等语义一致:按 plugin.name 保留首注册实例) - 根导出补充 RendererPlugin/RendererPluginWithHost 类型 - 新增 createChartController.renderers.test.ts 3 用例(注册/幂等/移除)
预览工作台启动(controller-ready)时加载宿主声明的渲染器插件模块: - 声明通道:localStorage['kcq_external_renderers'](JSON URL 数组)或 ?externalRenderers=url1,url2 - 模块契约:default/renderers/renderer 导出 RendererPlugin 或其数组 - 相对路径归一为绝对 URL 后动态 import(dev 管线只放行外部协议的动态 import,相对路径会被重写 ?import 进模块图致 public 资产 404——实测坑) - 单模块失败仅 console 告警,插件间与工作台互相隔离 - demo:preview/public/external-demo-renderer.js(主图中部虚线参考线)
useRenderer/removeRenderer/getRenderer 之外补 scheduleDraw(level?):外部渲染器 插件经轮询/WS 取到新数据后需要显式触发重绘(引擎仅在交互/数据变更时自绘)。 委托核心 Chart.scheduleDraw,缺省 UpdateLevel.All;测试补 1 用例(注册前后 调用均不抛)。
插件可经 host.controller 读取品种(controller.symbols 信号)与触发重绘 (controller.scheduleDraw),与 core API 面配套。
…20260929 # Conflicts: # packages/vue/src/components/DrawingStyleToolbar.vue # packages/vue/src/components/KLineChart.vue # packages/vue/src/composables/chart/useDrawingTemplates.ts # packages/vue/src/web-component-with-sources.ts # scripts/core-source-aliases.mjs
数据根数随周期变化,切换后保持 scrollLeft 会让视口漂进未来区 (上游 363045841#261 右缘空白放开后 scroll 不再钳制到数据区),极端时整个 可视区都是未来网格、图元创建全部落空。rAF 双帧等待数据注入后的 视口布局,再 scrollToRight 对齐最新——TV 同款切周期看最新行为。
preview dev server 增加三个本地代理: - /api/option_levels、/api/options_gamma → 127.0.0.1:8888(cloudtrade 后端期权插件试点数据源,同机 FastAPI) - /api/v1 → 127.0.0.1:8090(KCQ market-data 聚合服务,MT5 XAUUSD 等 品种 K 线),preview 直连连接器取真实行情
…gger scope note)
…ain docs) - AGENTS.md 追加 Agent skills 段(issue tracker / triage labels / domain docs) - docs/agents/issue-tracker.md:GitHub Issues 为唯一 triage 通道(gh CLI 约定, PRs 不作为请求面,wayfinder 地图/子票/阻塞依赖操作) - docs/agents/triage-labels.md:五角色同名标签词表 - docs/agents/domain.md:单上下文消费规则(根 CONTEXT.md + docs/adr/, 惰性创建,术语表词汇约束,ADR 冲突显式标记)
…egisterToolHost) Tool 装饰器协议允许任意宿主把领域方法暴露为 Agent 工具,但 toolHosts 硬编码(仅内置对比品种宿主),外部宿主的 @tool 方法无法被归属解析。 - ChartController 新增 registerToolHost/unregisterToolHost(幂等去重); ChartAgentControllerDependencies 加可选 extraToolHosts() - toolHosts getter 合并内置+动态宿主;browser tool registry 的 owns() 解析消费方零改动 - 根导出补通用工具协议:Tool 装饰器 + ChartToolConfig/ ChartToolExecutionContext/ChartToolSafety/RegisteredChartTool 类型 - 新增 toolHosts 测试 3 用例;core 251 文件 2676 用例全绿
@tool 装饰器依赖跨 bundle 不可达(外部插件与 core 不共享模块实例), bridge 工具目录又只在构造时静态快照——外部宿主的 Agent 工具无法进入目录。 - core:chartToolRegistry 增 registerChartTool/unregisterChartTool(编程式 写入唯一真源,冲突语义与装饰器一致);根导出同步 - vue:BrowserAgentBridgeOptions 增 extraChartTools?: () => RegisteredChartTool[] (惰性求值一次),BrowserToolRegistry.registerTools 合并内置+外部源 - 与 363045841#272(registerToolHost 方法归属解析)组成外部 Agent 工具完整链: 宿主持有 @tool 类实例 → registerToolHost 注册归属 → bridge extraChartTools 声明工具目录
插件模块可带 chartTools: RegisteredChartTool[] 命名导出;loader 收集进 externalChartTools 引用(bridge extraChartTools getter 先构造后填充)。
extraChartTools 惰性 getter 在 BrowserToolRegistry 构造时被同步求值一次, 晚于 bridge 加载的外部插件工具永远进不了目录(用户实测:模型自述找不到 期权数据、自行转 web search)。 - BrowserToolRegistry 增公开 registerChartTools(tools):catalog 后补注册, check() 判重跳过同名 - BrowserAgentBridge 增公开 registerExternalChartTools 透传 - preview loader 收集到插件 chartTools 后立即调用后补注册
…sts 的 input unknown 类型错
EliteOtaku
added a commit
to EliteOtaku/KCQ-NexusAI
that referenced
this pull request
Sep 30, 2026
… indicator-registration,对应 PR 363045841#266/363045841#274/363045841#273)
This branch has not been deployed
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.
动机
Tool装饰器协议(foundation/agent/chartToolRegistry)允许任何持有宿主对象的代码把领域方法暴露为 Agent 工具,但ChartAgentController.toolHosts目前是硬编码数组(仅内置对比品种宿主)——外部宿主(插件、嵌入方)无法把自己的@Tool宿主注册进 Agent 的方法归属解析,@Tool协议因此对第三方宿主不可用。方案(纯增量 API)
ChartController新增两个方法:ChartAgentControllerDependencies增加可选extraToolHosts?: () => ReadonlyArray<object>;toolHostsgetter 返回[comparisonCommands, ...extraToolHosts()],既有消费方(browser tool registry 的chartToolTarget按owns()解析)零改动;Tool装饰器与ChartToolConfig/ChartToolExecutionContext/ChartToolSafety/RegisteredChartTool类型——外部宿主此前无法从公开 API 获取装饰器。典型用法(外部宿主/插件):
验证
createChartController.toolHosts.test.ts3 用例:注册幂等(重复注册仅一份)/ unregister 移除 /@Tool方法经owns解析宿主并执行(TypeBox 校验走通);兼容性
纯增量:不改既有接口行为;未注册宿主时
toolHosts行为与之前完全一致。补充(第二段:工具目录动态源)
@Tool装饰器依赖模块级注册表,外部宿主 bundle 与 core 不共享模块实例时不可达;工具目录又只在 bridge 构造时静态快照。本 PR 同时补齐:chartToolRegistry增registerChartTool(tool)/unregisterChartTool(name)(编程式写入唯一真源,冲突语义与装饰器一致);BrowserAgentBridgeOptions增extraChartTools?: () => ReadonlyArray<RegisteredChartTool>,BrowserToolRegistry.registerTools合并内置与外部源。组合用法(外部宿主/插件):