Skip to content

feat(core): ChartController 暴露 Agent 工具宿主注册(registerToolHost)[supersedes #272] - #274

Open
EliteOtaku wants to merge 117 commits into
363045841:mainfrom
EliteOtaku:pr/agent-tool-host-registration
Open

EliteOtaku wants to merge 117 commits into
363045841:mainfrom
EliteOtaku:pr/agent-tool-host-registration

Conversation

@EliteOtaku

Copy link
Copy Markdown
Contributor

依赖:基于 #266(外部渲染器加载点/ChartController 渲染器 API)。建议在 #266 合并后评审;分支含少量 preview 与 controller 相邻行改动。

动机

Tool 装饰器协议(foundation/agent/chartToolRegistry)允许任何持有宿主对象的代码把领域方法暴露为 Agent 工具,但 ChartAgentController.toolHosts 目前是硬编码数组(仅内置对比品种宿主)——外部宿主(插件、嵌入方)无法把自己的 @Tool 宿主注册进 Agent 的方法归属解析,@Tool 协议因此对第三方宿主不可用。

方案(纯增量 API)

ChartController 新增两个方法:

/** 注册 Agent 工具宿主:宿主的 @Tool 标注方法会成为 Agent 可调用工具,
 *  调用时按方法函数身份在已注册宿主中解析执行目标。幂等。 */
registerToolHost(host: object): void
/** 移除先前注册的 Agent 工具宿主。 */
unregisterToolHost(host: object): void
  • ChartAgentControllerDependencies 增加可选 extraToolHosts?: () => ReadonlyArray<object>;
  • toolHosts getter 返回 [comparisonCommands, ...extraToolHosts()],既有消费方(browser tool registry 的 chartToolTarget 按 owns() 解析)零改动;
  • 根导出补充通用工具协议:Tool 装饰器与 ChartToolConfig / ChartToolExecutionContext / ChartToolSafety / RegisteredChartTool 类型——外部宿主此前无法从公开 API 获取装饰器。

典型用法(外部宿主/插件):

class MyToolHost {
  @Tool({ name: 'my_query', label: '...', description: '...', parameters: MySchema, safety: 'read-only' })
  myQuery(input: Static<typeof MySchema>) { /* 领域查询 */ }
}
controller.registerToolHost(new MyToolHost())

验证

  • 新增 createChartController.toolHosts.test.ts 3 用例:注册幂等(重复注册仅一份)/ unregister 移除 / @Tool 方法经 owns 解析宿主并执行(TypeBox 校验走通);
  • core 全量:251 文件 / 2676 用例全绿;tsc build 绿。

兼容性

纯增量:不改既有接口行为;未注册宿主时 toolHosts 行为与之前完全一致。

补充(第二段:工具目录动态源)

@Tool 装饰器依赖模块级注册表,外部宿主 bundle 与 core 不共享模块实例时不可达;工具目录又只在 bridge 构造时静态快照。本 PR 同时补齐:

  • core:chartToolRegistry 增 registerChartTool(tool) / unregisterChartTool(name)(编程式写入唯一真源,冲突语义与装饰器一致);
  • vue:BrowserAgentBridgeOptions 增 extraChartTools?: () => ReadonlyArray<RegisteredChartTool>,BrowserToolRegistry.registerTools 合并内置与外部源。

组合用法(外部宿主/插件):

const host = new MyToolHost()                    // @Tool 标注方法
controller.registerToolHost(host)                // 方法归属解析(toolHosts.owns)
bridge = new BrowserAgentBridge({                // 宿主装配 bridge 时声明工具目录源
  getChartAgent,
  extraChartTools: () => getRegisteredChartTools().filter(t => t.config.name.startsWith('my_')),
})

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 直连连接器取真实行情
…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 后立即调用后补注册

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant