@v587d/capital-generation 2.8.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +41 -20
  3. package/chart-ui/README.md +1 -1
  4. package/chart-ui/client.src.cjs +7 -16
  5. package/chart-ui/index.js +12 -33
  6. package/chart-ui/package.json +2 -2
  7. package/config/client.js +18 -39
  8. package/config/client.src.cjs +79 -217
  9. package/config/index.js +22 -72
  10. package/config/package.json +2 -2
  11. package/cordis.patch.yml +46 -51
  12. package/lib/agents/bash-guard.d.ts +9 -55
  13. package/lib/agents/bash-guard.js +27 -67
  14. package/lib/agents/preset-roster.d.ts +22 -0
  15. package/lib/agents/preset-roster.js +87 -0
  16. package/lib/agents/role-tool-policy.d.ts +46 -0
  17. package/lib/agents/role-tool-policy.js +205 -0
  18. package/lib/agents/root-tool-policy.d.ts +28 -124
  19. package/lib/agents/root-tool-policy.js +164 -237
  20. package/lib/agents/selected-skills-provider.d.ts +2 -6
  21. package/lib/agents/selected-skills-provider.js +27 -84
  22. package/lib/capital-settings.d.ts +27 -0
  23. package/lib/capital-settings.js +23 -0
  24. package/lib/chart/artifact-ref.d.ts +3 -8
  25. package/lib/chart/artifact-ref.js +3 -8
  26. package/lib/chart/downsample.d.ts +2 -5
  27. package/lib/chart/downsample.js +3 -8
  28. package/lib/chart/errors.d.ts +3 -7
  29. package/lib/chart/errors.js +1 -2
  30. package/lib/chart/event-context.d.ts +8 -0
  31. package/lib/chart/event-context.js +39 -0
  32. package/lib/chart/events.d.ts +21 -78
  33. package/lib/chart/events.js +40 -189
  34. package/lib/chart/html.d.ts +2 -5
  35. package/lib/chart/html.js +8 -15
  36. package/lib/chart/runtime.d.ts +5 -18
  37. package/lib/chart/runtime.js +14 -34
  38. package/lib/chart/series.d.ts +10 -17
  39. package/lib/chart/series.js +22 -47
  40. package/lib/chart/source-token.d.ts +5 -13
  41. package/lib/chart/source-token.js +18 -17
  42. package/lib/chart/spec.d.ts +6 -12
  43. package/lib/chart/spec.js +7 -31
  44. package/lib/chart/tool.d.ts +6 -29
  45. package/lib/chart/tool.js +9 -23
  46. package/lib/chart-render-entry.d.ts +9 -0
  47. package/lib/chart-render-entry.js +17 -0
  48. package/lib/chart-source-entry.d.ts +13 -0
  49. package/lib/chart-source-entry.js +20 -0
  50. package/lib/collect-entry.d.ts +9 -0
  51. package/lib/collect-entry.js +10 -0
  52. package/lib/collector-runtime.d.ts +22 -0
  53. package/lib/collector-runtime.js +97 -0
  54. package/lib/data-collector/args.d.ts +6 -24
  55. package/lib/data-collector/args.js +6 -24
  56. package/lib/data-collector/dataset-tools.d.ts +7 -1
  57. package/lib/data-collector/dataset-tools.js +27 -56
  58. package/lib/data-collector/describe.d.ts +8 -33
  59. package/lib/data-collector/describe.js +17 -70
  60. package/lib/data-collector/hub.d.ts +27 -85
  61. package/lib/data-collector/hub.js +19 -85
  62. package/lib/data-collector/query-time.d.ts +7 -26
  63. package/lib/data-collector/query-time.js +8 -27
  64. package/lib/data-collector/store.d.ts +40 -117
  65. package/lib/data-collector/store.js +32 -111
  66. package/lib/data-collector/time-axis.d.ts +16 -76
  67. package/lib/data-collector/time-axis.js +25 -111
  68. package/lib/data-collector/tools.d.ts +6 -6
  69. package/lib/data-collector/tools.js +34 -43
  70. package/lib/dataset-read-entry.d.ts +9 -0
  71. package/lib/dataset-read-entry.js +7 -0
  72. package/lib/index.d.ts +7 -117
  73. package/lib/index.js +43 -422
  74. package/lib/net/eastmoney-client.d.ts +13 -65
  75. package/lib/net/eastmoney-client.js +12 -53
  76. package/lib/net/throttle.d.ts +2 -13
  77. package/lib/net/throttle.js +3 -14
  78. package/lib/ocr/client.d.ts +12 -21
  79. package/lib/ocr/client.js +23 -40
  80. package/lib/ocr/markdown.d.ts +20 -26
  81. package/lib/ocr/markdown.js +23 -36
  82. package/lib/ocr/store.d.ts +14 -47
  83. package/lib/ocr/store.js +15 -45
  84. package/lib/ocr-entry.d.ts +9 -0
  85. package/lib/ocr-entry.js +10 -0
  86. package/lib/policy-entry.d.ts +17 -0
  87. package/lib/policy-entry.js +22 -0
  88. package/lib/preset-claim.d.ts +22 -0
  89. package/lib/preset-claim.js +24 -0
  90. package/lib/preset-ids.d.ts +38 -0
  91. package/lib/preset-ids.js +46 -0
  92. package/lib/retriever-config.d.ts +37 -0
  93. package/lib/retriever-config.js +36 -0
  94. package/lib/retriever-runtime.d.ts +31 -0
  95. package/lib/retriever-runtime.js +82 -0
  96. package/lib/root-extras-entry.d.ts +17 -0
  97. package/lib/root-extras-entry.js +25 -0
  98. package/lib/runtime-entry.d.ts +15 -0
  99. package/lib/runtime-entry.js +5 -0
  100. package/lib/runtime.d.ts +21 -0
  101. package/lib/runtime.js +33 -0
  102. package/lib/search-entry.d.ts +9 -0
  103. package/lib/search-entry.js +9 -0
  104. package/lib/sources/eastmoney-http.js +20 -68
  105. package/lib/sources/ftshare-rest.d.ts +6 -31
  106. package/lib/sources/ftshare-rest.js +1194 -225
  107. package/lib/sources/fuyao-core.d.ts +8 -18
  108. package/lib/sources/fuyao-core.js +10 -21
  109. package/lib/sources/fuyao-rest.d.ts +2 -6
  110. package/lib/sources/fuyao-rest.js +23 -102
  111. package/lib/sources/security-code.d.ts +6 -12
  112. package/lib/sources/security-code.js +6 -12
  113. package/lib/sources/security-identity.d.ts +1 -4
  114. package/lib/sources/security-identity.js +3 -8
  115. package/lib/sources/tencent-http.js +14 -28
  116. package/lib/sources/tencent-public-core.d.ts +8 -60
  117. package/lib/sources/tencent-public-core.js +12 -77
  118. package/lib/sources/wind-mcp.d.ts +0 -5
  119. package/lib/sources/wind-mcp.js +23 -78
  120. package/lib/sources-entry.d.ts +9 -0
  121. package/lib/sources-entry.js +9 -0
  122. package/lib/time/tools.d.ts +8 -31
  123. package/lib/time/tools.js +39 -64
  124. package/lib/time-entry.d.ts +9 -0
  125. package/lib/time-entry.js +7 -0
  126. package/lib/tool-exec.d.ts +45 -11
  127. package/lib/tool-exec.js +59 -6
  128. package/lib/user-customization.d.ts +14 -0
  129. package/lib/user-customization.js +47 -0
  130. package/lib/watchlist/tools.d.ts +3 -13
  131. package/lib/watchlist/tools.js +10 -30
  132. package/lib/watchlist-entry.d.ts +9 -0
  133. package/lib/watchlist-entry.js +6 -0
  134. package/lib/web-retriever/engines.d.ts +6 -11
  135. package/lib/web-retriever/engines.js +9 -14
  136. package/lib/web-retriever/html-markdown.d.ts +14 -34
  137. package/lib/web-retriever/html-markdown.js +39 -75
  138. package/lib/web-retriever/local-fetch.d.ts +22 -58
  139. package/lib/web-retriever/local-fetch.js +33 -77
  140. package/lib/web-retriever/retriever.d.ts +6 -18
  141. package/lib/web-retriever/retriever.js +7 -7
  142. package/lib/web-retriever/sources.d.ts +37 -124
  143. package/lib/web-retriever/sources.js +64 -196
  144. package/lib/web-retriever/tools.d.ts +43 -27
  145. package/lib/web-retriever/tools.js +99 -137
  146. package/lib/web-retriever/wind-client.d.ts +14 -31
  147. package/lib/web-retriever/wind-client.js +16 -32
  148. package/locale/zh.json +1 -1
  149. package/package.json +67 -7
  150. package/preset/capital-generation/README.md +87 -53
  151. package/preset/capital-generation/agent.patch.yml +526 -337
  152. package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
  153. package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
  154. package/preset/capital-generation/skills/capital-equity-research/SKILL.md +1 -1
  155. package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
  156. package/preset/capital-generation/skills/capital-orchestration/SKILL.md +15 -19
  157. package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
  158. package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +9 -12
  159. package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
  160. package/watchlist/client.js +15 -148
  161. package/watchlist/client.src.cjs +148 -662
  162. package/watchlist/holding-rules.js +11 -38
  163. package/watchlist/index.js +127 -397
  164. package/watchlist/package.json +2 -2
@@ -1,77 +1,23 @@
1
1
  /**
2
- * 图表 → 对话流的宿主侧事件通道(**first-party 口径**)。
3
- *
4
- * 问题:图表由 `visualization_specialist`(主 Agent 的**孙** Agent)生成,它的 tool call
5
- * 落在自己的会话里;用户在客户端看的是主会话,子会话的事件不会流过来。
6
- *
7
- * 做法:`render_chart` 成功后,宿主往**用户正在看的那条会话**追加官方
8
- * `deliverables/presented` 事件(`present` 工具用的同一条通道),把
9
- * `capital-analysis/charts/<chart_id>/chart.html` 登记为本轮交付物。用户在收尾的
10
- * 「本轮文件改动 / 交付」行点开该文件,右侧即用官方 document preview 的
11
- * script-enabled iframe 渲染自包含图表。
12
- *
13
- * ⚠️ 为什么**绝不能**再发明自己的事件类型(2026-09-18 事故,实测根因):
14
- * 会话日志的事件词汇表是**闭集**——`@deepseek-ai/dsh-session` 的
15
- * `KNOWN_SESSION_EVENT_TYPES` 由 harness 仓库自己的 `SessionEventMap` 生成,
16
- * 注释明写 "downstream (out-of-repo) plugin events are outside this list by
17
- * construction"。读取侧 `validateStoredEvents()` 是 fail-closed 的:
18
- *
19
- * `KNOWN_SESSION_EVENT_TYPES.has(event.type) || event.ignorable === true`
20
- *
21
- * 否则抛 `SessionFormatUnsupportedError` 并拒绝解释**整份日志**。而 `Session.append`
22
- * 的 options 只转发 `sourceEventSeqs` / `surfaceOp`——**调用方无法设置 `ignorable`**
23
- * (实测 0.1.5-rc.2 与 0.1.6-alpha.2 都没有该选项)。于是自定义事件是"写时静默通过、
24
- * 冷加载时整份会话打不开":本仓曾发 `capital/chart-rendered`,导致 6 份会话在重启后
25
- * 全部 `failed to observe session`。只有 first-party 事件能持久化。
26
- *
27
- * ⚠️ 回合归属(实测教训 2026-09-17):交付行是**按回合**渲染的,只会出现在它所属回合的
28
- * 收尾位置。而主 Agent 在等子 Agent 时会**先结束自己的回合**,于是出图往往发生在两个回合
29
- * 之间——此时 `turnBoundary.lastTurn` 指向那个**已经结束**的回合,照抄它会把交付登记到
30
- * 上一轮。因此:
31
- * - 有回合打开(`openTurnStartSeq` 是数字)→ 立即写入该回合;
32
- * - 没有回合打开 → 先寄存,等 owner 的**下一个 `turn/start`** 再写入(那正是会消费这份
33
- * 子 Agent 结果、写下最终答复的回合)。
34
- * 寄存的冲刷必须延到微任务:`session/event` 观察者在 `Session.append` 的 appending 窗口内同步执行,
35
- * 在那里再次 append 会被 "session append cannot reenter" 拒绝。
36
- *
37
- * 为什么这不"污染 transcripts":
38
- * - `deliverables/presented` 不在 surface 事件表里,`deriveEventMessage()` 返回 null,
39
- * 因此它**永不进入** `deriveMessages()`(模型看到的消息历史);
40
- * - payload 只有 `turn` / `callId`(`capital-chart:<chart_id>` 句柄)/ `files[]`
41
- * (工作区相对 `html_path` + 标题),没有 rows、series、HTML、绝对路径。
42
- *
43
- * 容错:`sessions` / `sessionProjections` 任一不可用、或 append 抛错,都只告警并跳过——
44
- * 出图本身绝不因此失败(文件已落盘、回执照样给 html_path)。
2
+ * Chart → conversation deliverable channel (first-party events only).
3
+ * Event types are a closed set: anything outside KNOWN_SESSION_EVENT_TYPES without `ignorable`
4
+ * makes the whole log unreadable on cold load, and append() cannot set it.
5
+ * Only `deliverables/presented` is safe; attribution and the owner-keyed queue are the invariants.
45
6
  */
46
7
  import { chartSessionScopeId } from './artifact-ref.js';
47
- /**
48
- * 登记本轮交付物用的官方事件名。
49
- *
50
- * 必须留在 `KNOWN_SESSION_EVENT_TYPES` 内——它是 `dsh-tool-present` 追加的同一类型,
51
- * 因此在**任何** harness 版本上都能被读回。改这里等于改会话日志契约,
52
- * `test/chart-turn-events.test.mjs` 有一条断言把它钉在 first-party 词汇表上。
53
- */
8
+ /** Must stay in `KNOWN_SESSION_EVENT_TYPES`; test/chart-turn-events.test.mjs pins it. */
54
9
  export const CHART_DELIVERABLE_EVENT = 'deliverables/presented';
55
- /** `callId` 前缀:交付事件里携带的图表句柄(官方契约只要求非空字符串)。 */
10
+ /** `callId` only needs to be a non-empty string; carries the chart handle for log tracing. */
56
11
  export const CHART_CALL_ID_PREFIX = 'capital-chart:';
57
- /** 同一 (session, turn) 的事件上限:防模型循环,超出只告警。 */
12
+ /** Per (session, turn) cap: stops model loops; overflow warns only. */
58
13
  export const MAX_CHARTS_PER_TURN = 8;
59
- /** 向上找根会话的最大跳数(main → data_junior → specialist 只有一跳,留余量)。 */
14
+ /** Root lookup cap (main → data_junior → specialist is one hop, plus headroom). */
60
15
  export const MAX_OWNER_HOPS = 4;
61
- /**
62
- * 最近一次 publish 的逐步轨迹(模块级:发布器每次出图都新建,轨迹要跨实例可读)。
63
- */
64
16
  let lastPublishTrace = '';
65
- /** 只读:最近一次 publish 轨迹。 */
66
17
  export function chartEventPublishTrace() {
67
18
  return lastPublishTrace;
68
19
  }
69
- /**
70
- * 交付通道的**诊断开关**:`CAPITAL_CHART_TRACE=1` 时才输出进度日志与落盘 trace。
71
- *
72
- * 默认关闭——对外发布不该往用户终端刷进度、也不该往 workspace 写调试文件。
73
- * 排查"图表/交付行不出现"时打开它(见 `deliverable-trace.txt` 与 docs/dev/chart-delivery-events.md §6.3 的说明)。
74
- */
20
+ /** Diagnostic switch; off by default to keep terminals and workspaces clean. */
75
21
  export function chartTraceEnabled() {
76
22
  try {
77
23
  const env = globalThis.process?.env;
@@ -91,10 +37,7 @@ function parentSessionOf(session) {
91
37
  const parent = session.header?.parentSession;
92
38
  return typeof parent === 'string' && parent.length > 0 ? parent : undefined;
93
39
  }
94
- /**
95
- * 从 owner scope 出发向上走到根会话:用户看的是根会话的对话流,卡片必须挂在它上面。
96
- * 找不到(会话已回收)时返回 undefined,调用方静默跳过。
97
- */
40
+ /** Walk up to the root session — the user watches the root conversation. Missing session → undefined. */
98
41
  export function resolveOwnerSession(sessions, ownerSessionId, maxHops = MAX_OWNER_HOPS) {
99
42
  let current;
100
43
  try {
@@ -123,18 +66,8 @@ export function resolveOwnerSession(sessions, ownerSessionId, maxHops = MAX_OWNE
123
66
  }
124
67
  return current;
125
68
  }
126
- /**
127
- * 官方 `deliverables/presented` 载荷。
128
- *
129
- * 形状与 `dsh-tool-present` 逐字段对齐(那边是 `{ turn, callId, files }`,
130
- * 客户端 `isPresentedData()` 要求 `turn` 是 >=1 的安全整数、`callId` 非空字符串、
131
- * `files` 是数组)。`files[].path` 必须是**工作区相对**路径:官方打开动作会拿它去
132
- * `workspaceFiles.stat({ sessionId, workspaceRoot: session.cwd })` 解析,绝对路径过不了
133
- * 「Presented file has no verified Host path」这一关。
134
- *
135
- * `callId` 只是官方契约里的一个非空字符串句柄,没有配对约束(`present` 那边用来关联
136
- * tool/result),这里借它携带 `chart_id`,便于在日志里把一条交付回溯到具体图表。
137
- */
69
+ /** Matches `dsh-tool-present` ({ turn, callId, files }); files[].path must be workspace-relative
70
+ * because the official open action resolves it against session.cwd. */
138
71
  export function buildChartEventPayload(input, turn) {
139
72
  return {
140
73
  turn,
@@ -147,58 +80,17 @@ export function buildChartEventPayload(input, turn) {
147
80
  ],
148
81
  };
149
82
  }
150
- /**
151
- * 待登记队列(**进程级**,不能放进发布器实例)。
152
- *
153
- * ⚠️ 2026-09-20 真机事故的根因:`createChartEventPublisher()` 是在**每次 `render_chart`
154
- * 时**惰性调用的(见 `src/index.ts` 的 `chartEvents: () => createChartEventPublisher(...)`),
155
- * 所以每个实例都拿到一份**自己的** `pending`。于是:
156
- * 1. 出图时回合是关的 → 图被寄存进**实例 A** 的 pending;
157
- * 2. 稍后 owner 那一轮关闭 → 冲刷回调跑在**实例 B**(甚至没有实例)上;
158
- * 3. B 的 pending 是空的 → 什么都没写 → 图**永久丢失**,且静默无报错。
159
- * 旧代码之所以看不出这个问题,是因为"回合打开时立即 append"这条路径占多数,
160
- * 只有"回合之间出图"(本项目的主 Agent 等子 Agent 时必然发生)才走寄存。
161
- *
162
- * 因此 pending 必须与进程内的服务实例同生命周期:模块级,并在注册冲刷监听时才清理。
163
- */
83
+ /** Process-level, not per publisher (which is rebuilt per render): keyed by owner (root) session
84
+ * id — the id flush events carry — and cleared only by flushing or an explicit reset. */
164
85
  const pending = new Map();
165
- /**
166
- * 进程内待登记条目的总量上限(安全阀)。
167
- *
168
- * 正常流程下条目会在 owner 那一轮关闭时被冲刷清空;但如果冲刷时机始终没接上
169
- * (上游事件名消失、监听注册失败且 turn/start 也没来),模块级 Map 会一直攒着。
170
- * 这是纯内存元数据(只有 chart_id / 标题 / 相对路径),上限只为防无界增长,
171
- * 与 `MAX_CHARTS_PER_TURN` 的"防模型循环"是两件事。
172
- */
86
+ /** Safety valve if flushing never fires; metadata only, bounded against unbounded growth. */
173
87
  export const MAX_PENDING_CHARTS = 64;
174
- /**
175
- * 只给测试用:清空待登记队列。
176
- *
177
- * `pending` 是模块级的(必须跨发布器实例存活,见下方说明),因此进程内所有测试
178
- * 共享它。测试之间要隔离,就在用例开始时调用一次。
179
- */
88
+ /** Test-only: clear the shared queue between cases. */
180
89
  export function resetChartEventPending() {
181
90
  pending.clear();
182
91
  }
183
- /**
184
- * 惰性构造发布器:`sessions` / `sessionProjections` 缺一不可(都是宿主平面服务,
185
- * 在 preset scope 里正常可解析;缺失表示宿主组合不完整,此时静默降级)。
186
- */
187
- /**
188
- * 进程级单例发布器。
189
- *
190
- * ⚠️ 2026-09-20 真机事故的**真根因**:`src/index.ts` 的 `chartEvents: () => createChartEventPublisher(...)`
191
- * 是**每次 `render_chart` 都调用一次**的。若每次都新建发布器:
192
- * 1. 实例 A 出图时把图寄存进自己的队列,并注册了**绑定在 A 上**的冲刷监听;
193
- * 2. 下一次出图再建实例 B(又注册一套监听);
194
- * 3. 冲刷回调里 `flush` 闭包引用的是某个实例的队列,而实例不断增多,
195
- * 寄存与冲刷落在不同实例上 —— 于是"专家确实出图,但主会话一条交付事件都没有"。
196
- *
197
- * 所以发布器必须**进程内唯一**:队列、监听、perTurn 计数都只有一份。
198
- * 缓存键用 `ctx.root ?? ctx`(cordis 的 root context 是稳定对象;`index.ts` 传进来的
199
- * 是个每次新建的 spread 字面量,不能用它做身份判断)。构造失败不缓存——宿主服务
200
- * 可能稍后才就绪,下次出图要能重新解析(保留原有的惰性语义)。
201
- */
92
+ /** Process-wide singleton (queue, listeners, per-turn counts) keyed by `ctx.root ?? ctx`;
93
+ * never cache a failed build so later-mounting host services can be picked up. */
202
94
  let cachedPublisher;
203
95
  export function createChartEventPublisher(ctx) {
204
96
  const ref = ctx.root ?? ctx;
@@ -209,7 +101,7 @@ export function createChartEventPublisher(ctx) {
209
101
  cachedPublisher = { ref, publisher };
210
102
  return publisher;
211
103
  }
212
- /** 只给测试用:丢弃单例缓存(配合 resetChartEventPending 做用例隔离)。 */
104
+ /** Test-only: drop the singleton cache. */
213
105
  export function resetChartEventPublisher() {
214
106
  cachedPublisher = undefined;
215
107
  }
@@ -222,23 +114,15 @@ function buildChartEventPublisher(ctx) {
222
114
  return undefined;
223
115
  const perTurn = new Map();
224
116
  let warned = false;
225
- /**
226
- * 输出一条诊断。
227
- *
228
- * ⚠️ 实测(2026-09-20):cordis 的 `ctx.logger` **只挂了一个内存 exporter**
229
- * (`cordis/lib/index.js` 的 `LoggerService` 构造器),全仓没有任何地方注册 console
230
- * exporter —— 也就是说 `ctx.logger.warn` 只进内存缓冲,**不会出现在 dsh 终端里**。
231
- * 这正是"卡片不出现却零报错"、两轮排查都只能靠猜的原因。
232
- * 因此这里**同时**写 `process.stderr`(宿主 stderr 一定能在终端看到);
233
- * logger 照发,保留结构化疗日志,将来有人接 exporter 就能收到。
234
- */
117
+ /** `ctx.logger` only buffers in memory (no console exporter), so also write stderr; otherwise
118
+ * delivery failures are invisible. */
235
119
  const emit = (message) => {
236
120
  const line = `capital-generation: ${message}`;
237
121
  try {
238
122
  ctx.logger?.warn(line);
239
123
  }
240
124
  catch {
241
- // logger 不可用不影响诊断输出。
125
+ // Logger unavailable; diagnostics still go to stderr below.
242
126
  }
243
127
  try {
244
128
  const stderr = globalThis.process?.stderr;
@@ -246,7 +130,7 @@ function buildChartEventPublisher(ctx) {
246
130
  stderr.write(`${line}\n`);
247
131
  }
248
132
  catch {
249
- // 没有 process(非 Node 载体)时静默跳过。
133
+ // No process (non-Node host).
250
134
  }
251
135
  };
252
136
  const warn = (message) => {
@@ -255,16 +139,7 @@ function buildChartEventPublisher(ctx) {
255
139
  warned = true;
256
140
  emit(message);
257
141
  };
258
- /**
259
- * 进度日志:**只在 `CAPITAL_CHART_TRACE=1` 时输出**。
260
- *
261
- * 寄存/登记这两处状态迁移是排查"交付行不出现"的关键线索,但对**普通用户是噪音**
262
- * (每张图两条)。所以默认静默、按需打开:
263
- *
264
- * CAPITAL_CHART_TRACE=1 dsh web
265
- *
266
- * 失败用的 `warn` 不受此开关影响——真出问题必须让人看见。
267
- */
142
+ /** Progress log only under CAPITAL_CHART_TRACE=1; warnings bypass the switch. */
268
143
  const note = (message) => {
269
144
  if (!chartTraceEnabled())
270
145
  return;
@@ -278,7 +153,6 @@ function buildChartEventPublisher(ctx) {
278
153
  return undefined;
279
154
  }
280
155
  };
281
- /** 真正写一条事件;返回是否写入。 */
282
156
  const append = (owner, turn, input) => {
283
157
  const key = `${owner.id}:${turn}`;
284
158
  const used = perTurn.get(key) ?? 0;
@@ -294,16 +168,10 @@ function buildChartEventPublisher(ctx) {
294
168
  return false;
295
169
  }
296
170
  perTurn.set(key, used + 1);
297
- // 与"已寄存"成对的成功日志:排查交付行不出现时,一眼能看出是没写还是没渲染。
298
171
  note(`图表已登记为本轮交付(owner=${owner.id},turn=${turn},${input.chart_id})`);
299
172
  return true;
300
173
  };
301
- /**
302
- * 把寄存的图表写进某一轮。
303
- *
304
- * 冲刷时机决定交付行的落点:首选 `agent/turn-stopping`(该轮即将关闭),
305
- * 只有在拿不到这个钩子时才退回 `turn/start`。
306
- */
174
+ /** Flush queued charts into a turn; the preferred hook is turn-stopping. */
307
175
  const flush = (ownerSessionId, turn) => {
308
176
  const queued = pending.get(ownerSessionId);
309
177
  if (queued === undefined || queued.length === 0)
@@ -317,14 +185,9 @@ function buildChartEventPublisher(ctx) {
317
185
  for (const item of queued)
318
186
  append(owner, turn, item);
319
187
  };
320
- /**
321
- * 首选时机是否可用。
322
- *
323
- * 关键:`turn-stopping` 缺席时**必须**退回 `turn/start`,否则寄存的图会永远不写(静默丢失)。
324
- * 安装成功才置 true;effect 卸载时复位,避免失效后仍以为首选可用。
325
- */
188
+ /** Only a real registration may flip this; otherwise turn/start fallback stays live. */
326
189
  let turnStoppingActive = false;
327
- /** 不能在任何 appending 窗口内重入 append,统一延到微任务。 */
190
+ /** Never append inside the appending window; defer to a microtask. */
328
191
  const flushSoon = (ownerSessionId, turn) => {
329
192
  queueMicrotask(() => {
330
193
  try {
@@ -343,8 +206,7 @@ function buildChartEventPublisher(ctx) {
343
206
  const event = args[1];
344
207
  if (event?.type !== 'turn/start')
345
208
  return;
346
- // 首选钩子在场时,`turn/start` 不得抢跑:那一轮往往还是过程轮(2026-09-20 真机教训),
347
- // 交付行会被落到空轮里。只有拿不到 `agent/turn-stopping` 时才用它兜底。
209
+ // turn/start must not race ahead while turn-stopping is active; it is only the fallback.
348
210
  if (turnStoppingActive)
349
211
  return;
350
212
  const sessionId = typeof session?.id === 'string' ? session.id : undefined;
@@ -356,7 +218,7 @@ function buildChartEventPublisher(ctx) {
356
218
  flushSoon(sessionId, turn);
357
219
  }
358
220
  catch {
359
- // 事件观察者永不抛出。
221
+ // Observers never throw.
360
222
  }
361
223
  });
362
224
  if (typeof ctx.effect === 'function') {
@@ -379,21 +241,18 @@ function buildChartEventPublisher(ctx) {
379
241
  flushSoon(sessionId, turn);
380
242
  }
381
243
  catch {
382
- // 事件观察者永不抛出。
244
+ // Observers must not throw.
383
245
  }
384
246
  });
385
- // ⚠️ 只有**真的拿到 disposer** 才算首选可用(2026-09-20 真机事故):
386
- // 早先这里无条件置 true,于是 `ctx.on('agent/turn-stopping')` 注册失败
387
- // (适配器 catch 后返回 undefined)时,闸门仍被打开、`turn/start` 兜底被永久关掉,
388
- // 图就**永远滞留 pending**——表现是交付卡片从头到尾不出现,且零报错。
389
- // 注册失败必须让 `turnStoppingActive` 保持 false,由 `turn/start` 接上。
247
+ // Only a real disposer means the preferred hook is active; failed registration must leave
248
+ // the turn/start fallback on, or queued charts stay pending forever.
390
249
  if (typeof disposeTurnStopping === 'function')
391
250
  turnStoppingActive = true;
392
251
  else
393
252
  warn('交付行的首选冲刷时机不可用(agent/turn-stopping 注册失败),已退回 turn/start 兜底;落点可能偏早一轮');
394
253
  if (typeof ctx.effect === 'function') {
395
254
  ctx.effect(() => () => {
396
- // 卸载即视为"首选不可用",让 `turn/start` 兜底重新接上,避免图静默丢失。
255
+ // Teardown re-enables the fallback so queued charts are not lost.
397
256
  turnStoppingActive = false;
398
257
  if (typeof disposeTurnStopping === 'function')
399
258
  disposeTurnStopping();
@@ -414,20 +273,15 @@ function buildChartEventPublisher(ctx) {
414
273
  const openSeq = boundary?.openTurnStartSeq;
415
274
  const lastTurn = boundary?.lastTurn;
416
275
  if (typeof openSeq !== 'number') {
417
- // 回合之间出图(主 Agent 等子 Agent 时会先关轮):寄存,等 owner 那一轮
418
- // **即将关闭**(`agent/turn-stopping`)时再写——那才是消费这份图、写下答复的回合。
419
- //
420
- // ⚠️ 寄存队列必须按 **owner**(根会话)的 id 归档,不能按 `input.ownerSessionId`:
421
- // 后者是发起出图的 scope(visualization_specialist,孙会话),而冲刷时机
422
- // (`turn/start` / `agent/turn-stopping`)拿到的都是 owner 的 id。
423
- // 两边键不一致时 `flush()` 永远查不到,图会**永久滞留在 pending**、静默不登记
424
- // (2026-09-20 实测:3 个 specialist 会话 render_chart=2 而 deliverables=0)。
276
+ // Between turns (main agent closes its turn while waiting on children): queue until
277
+ // the owner's turn closes. Key by owner.id — flush events carry that id; a caller-scope
278
+ // key would never match and strands charts forever.
425
279
  const queued = pending.get(owner.id) ?? [];
426
280
  if (queued.length >= MAX_CHARTS_PER_TURN) {
427
281
  warn(`图表对话流事件跳过:${owner.id} 已寄存 ${MAX_CHARTS_PER_TURN} 张,等不到新回合`);
428
282
  return false;
429
283
  }
430
- // 安全阀:正常不该触发(冲刷会清空);触发说明冲刷链路坏了,宁可丢图也要有界。
284
+ // Unreachable in normal flow; bound memory if flushing is broken.
431
285
  let total = 0;
432
286
  for (const list of pending.values())
433
287
  total += list.length;
@@ -437,14 +291,11 @@ function buildChartEventPublisher(ctx) {
437
291
  }
438
292
  queued.push(input);
439
293
  pending.set(owner.id, queued);
440
- // 出图时回合是关的 ⇒ 走寄存。这条日志是**必要的可观测性**:它之后若没有对应的
441
- // "已登记" 日志,就说明冲刷时机没接上(2026-09-20 的静默丢失正是死在这里,
442
- // 两轮排查都因为没有日志而只能猜)。只在进程内报一次,不刷屏。
443
294
  note(`图表已寄存,待 owner 回合关闭时登记(owner=${owner.id},lastTurn=${String(lastTurn)},寄存 ${queued.length} 张)`);
444
295
  lastPublishTrace = `${trace.join(' ')} 回合未打开(openTurnStartSeq=${String(openSeq)} lastTurn=${String(lastTurn)}) → 已寄存 ${queued.length} 张,等 turn-stopping/turn-start 冲刷`;
445
296
  return true;
446
297
  }
447
- // turn 0 / 非法值表示该会话还没真正开过回合。
298
+ // turn 0 / invalid means the session never really opened a turn.
448
299
  if (typeof lastTurn !== 'number' || !Number.isSafeInteger(lastTurn) || lastTurn < 1) {
449
300
  lastPublishTrace = `${trace.join(' ')} 回合号非法(lastTurn=${String(lastTurn)}) → 未写入`;
450
301
  warn('图表对话流事件跳过:owner 会话没有打开中的 turn');
@@ -454,7 +305,7 @@ function buildChartEventPublisher(ctx) {
454
305
  lastPublishTrace = `${trace.join(' ')} 回合打开(turn=${lastTurn}) → ${written ? '已直接登记' : '写入失败'}`;
455
306
  return written;
456
307
  },
457
- /** 诊断/测试用:当前寄存未冲刷的图表数。 */
308
+ /** Diagnostics/tests: queued charts not yet flushed. */
458
309
  pendingCount(ownerSessionId) {
459
310
  if (ownerSessionId !== undefined)
460
311
  return pending.get(ownerSessionId)?.length ?? 0;
@@ -465,7 +316,7 @@ function buildChartEventPublisher(ctx) {
465
316
  },
466
317
  };
467
318
  }
468
- /** 出图时的 owner scope:与 chart_ref 同源(specialist 出图时正好是主会话)。 */
319
+ /** Owner scope at render time; same source as chart_ref (specialist renders land on the main session). */
469
320
  export function chartEventOwnerSessionId(session, sourceSession) {
470
321
  return chartSessionScopeId(sourceSession ?? session);
471
322
  }
@@ -5,16 +5,13 @@ export interface VendoredChartLibrary {
5
5
  license: string;
6
6
  }
7
7
  /**
8
- * 读取随包发布的 lightweight-charts standalone 构建。
9
- *
10
- * 路径相对 `import.meta.url`:编译后是 `lib/chart/vendor/...`(由 scripts/copy-assets.mjs
11
- * 从 `vendor/` 复制过去)。缺失时给出可操作错误,而不是让 HTML 里静默少一段脚本。
8
+ * Path is relative to import.meta.url (assets copied into lib by scripts/copy-assets.mjs);
9
+ * a missing library is a hard, actionable error rather than silently missing script bytes.
12
10
  */
13
11
  export declare function loadVendoredChartLibrary(): VendoredChartLibrary;
14
12
  export interface BuildStandaloneHtmlInput {
15
13
  payload: ChartPayload;
16
14
  library: VendoredChartLibrary;
17
- /** 生成时刻(epoch ms),仅用于页脚署名,不参与渲染。 */
18
15
  generatedAt: number;
19
16
  }
20
17
  export declare function buildStandaloneHtml(input: BuildStandaloneHtmlInput): string;
package/lib/chart/html.js CHANGED
@@ -1,12 +1,7 @@
1
1
  /**
2
- * 自包含 HTML 图表的生成器。
3
- *
4
- * 这是整个方案的**降级底线**(内部设计文档 docs/design/chart-visualization.md §5.4 的 L2):
5
- * 它只依赖"用户有一个浏览器",不依赖 DSH 的客户端插件系统、不依赖网络、
6
- * 不依赖任何上游扩展点。即使 Slot / bundle / 路由全部被上游改动打坏,
7
- * 用户依然能拿到一张可交互的图。
8
- *
9
- * 因此它必须:内联 vendored 库 + 内联数据 + 内联运行时,**零外部请求**。
2
+ * Standalone HTML: the L2 fallback that needs only a browser. Vendored library, data and
3
+ * runtime are inlined (zero external requests) so a broken client plugin, route or network
4
+ * cannot take charts down.
10
5
  */
11
6
  import { ChartError } from './errors.js';
12
7
  import { CHART_RUNTIME_SOURCE } from './runtime.js';
@@ -20,10 +15,8 @@ function nodeFs() {
20
15
  }
21
16
  let cachedLibrary;
22
17
  /**
23
- * 读取随包发布的 lightweight-charts standalone 构建。
24
- *
25
- * 路径相对 `import.meta.url`:编译后是 `lib/chart/vendor/...`(由 scripts/copy-assets.mjs
26
- * 从 `vendor/` 复制过去)。缺失时给出可操作错误,而不是让 HTML 里静默少一段脚本。
18
+ * Path is relative to import.meta.url (assets copied into lib by scripts/copy-assets.mjs);
19
+ * a missing library is a hard, actionable error rather than silently missing script bytes.
27
20
  */
28
21
  export function loadVendoredChartLibrary() {
29
22
  if (cachedLibrary)
@@ -41,15 +34,15 @@ export function loadVendoredChartLibrary() {
41
34
  throw new ChartError('chart_runtime_unavailable', `vendored lightweight-charts is missing (${error instanceof Error ? error.message : String(error)}); run npm run vendor:charts && npm run build`);
42
35
  }
43
36
  }
44
- /** 内联脚本的安全转义:`</script` 会提前结束 script 元素。 */
37
+ /** `</script` would close the inline script element early. */
45
38
  function escapeScript(source) {
46
39
  return source.replace(/<\/script/gi, '<\\/script');
47
40
  }
48
- /** 内联 JSON 的安全转义:`<` 一律写成 `\u003c`(JSON 里合法,且彻底杜绝标签闭合)。 */
41
+ /** `<` becomes `\u003c` (valid JSON) so data can never close a tag. */
49
42
  function escapeJson(value) {
50
43
  return JSON.stringify(value).replace(/</g, '\\u003c');
51
44
  }
52
- /** 数据截至时间:按本仓数据口径用 Asia/Shanghai 显示,同时保留 UTC。 */
45
+ /** Data timestamps display in Asia/Shanghai per data convention; UTC is the fallback. */
53
46
  function formatCapturedAt(epochMs) {
54
47
  try {
55
48
  const parts = new Intl.DateTimeFormat('en-CA', {
@@ -1,20 +1,7 @@
1
1
  /**
2
- * 浏览器侧渲染运行时 —— 以**源码字符串**形式存在,而不是一个模块。
3
- *
4
- * 为什么是字符串:同一段渲染代码有两个宿主,二者都不能 pre-built 共用同一份模块——
5
- * 1. **自包含 HTML 产物**(本步就有):内联进 `<script>`,离线双击可开;
6
- * 2. **对话流内嵌视图**(Step 3):DSH 客户端 bundle 里再包一次。
7
- * 写成字符串可以保证两边**逐字节同源**,不会出现"文件里对、页面上错"。
8
- * 代价是这里没有类型检查,所以 test/chart-runtime.test.mjs 会用 `new Function`
9
- * 做一次语法与 API 形状校验,把笔误挡在测试里而不是浏览器里。
10
- *
11
- * 约束:这段源码里**不能**出现反引号或 `${`(它本身在 TS 模板字符串里)。
12
- * 依赖:DOM,以及**由调用方传入**的图表库对象(自包含 HTML 传 UMD 全局,Step 3 客户端传
13
- * ESM import)。刻意不读、也不回退到 `globalThis.LightweightCharts`:那条全局正是与其它
14
- * 插件带的同库不同版本互相覆盖的来源(见内部设计文档 docs/design/chart-visualization.md §9 风险表)。
15
- * 形态:IIFE **返回** `{ render, version }`,由各宿主自行捕获(HTML 用 `var __capitalChart =`,
16
- * 客户端 bundle 存为模块局部),**不往任何全局挂载**。
17
- * 主题:只读 CSS 变量 `--dsw-*`(见 docs/reference/dsh-surface-ledger.md L7),
18
- * 不调 DSH 的 theme 服务;自包含 HTML 自己定义同一批变量,因此两边渲染完全一致。
2
+ * Browser render runtime, kept as source text: the same bytes are inlined into standalone HTML
3
+ * and re-packaged in the client bundle. Must contain no backtick or `${`, and must never fall
4
+ * back to `globalThis.LightweightCharts` — another plugin's copy could silently win. The IIFE
5
+ * returns `{ render, version }`; nothing is attached to globals.
19
6
  */
20
- export declare const CHART_RUNTIME_SOURCE = "\n(function () {\n 'use strict'\n\n var FALLBACK = {\n '--dsw-alias-bg-base': '#ffffff',\n '--dsw-alias-border-l1': 'rgba(0, 0, 0, 0.10)',\n '--dsw-alias-label-primary': '#1f2328',\n '--dsw-alias-label-secondary': '#6b7280',\n '--dsw-alias-brand-primary': '#2563eb',\n '--dsw-alias-state-error-primary': '#d94a3d',\n '--dsw-alias-state-success-primary': '#2f9e44'\n }\n\n // \u4E3B\u9898 token \u4E0D\u4E00\u5B9A\u662F\u5341\u516D\u8FDB\u5236\uFF08\u4E5F\u53EF\u80FD\u662F rgb/rgba \u6216\u81EA\u5B9A\u4E49 CSS \u8272\u503C\uFF09\u3002\u53EA\u6709\u786E\u8BA4\u662F\n // \u5341\u516D\u8FDB\u5236\u65F6\u624D\u8FFD\u52A0 alpha\uFF1B\u5176\u4F59\u683C\u5F0F\u8F6C\u6362\u6210 rgba\uFF0C\u8F6C\u6362\u4E0D\u4E86\u5C31\u9000\u56DE\u539F\u8272\uFF0C\u7EDD\u4E0D\u62FC\u51FA\u975E\u6CD5 CSS\u3002\n function withAlpha(color, alpha) {\n var text = String(color || '').trim()\n var hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(text)\n if (hex) {\n var expanded = hex[1].length === 3\n ? '#' + hex[1].split('').map(function (part) { return part + part }).join('')\n : text\n return expanded + Math.round(alpha * 255).toString(16).padStart(2, '0')\n }\n var rgb = /^rgba?\\(\\s*([^)]*)\\)$/i.exec(text)\n if (rgb) {\n var parts = rgb[1].split(/[,\\s/]+/).filter(Boolean)\n if (parts.length >= 3 && parts.slice(0, 3).every(function (part) { return /^[-+]?\\d*\\.?\\d+%?$/.test(part) })) {\n return 'rgba(' + parts.slice(0, 3).join(', ') + ', ' + alpha + ')'\n }\n }\n return text\n }\n\n // \u591A\u5E8F\u5217\u914D\u8272\uFF1A\u7B2C\u4E00\u6761\u7528\u4E3B\u9898\u54C1\u724C\u8272\uFF0C\u5176\u4F59\u7528\u56FA\u5B9A\u8272\u677F\uFF08\u4FDD\u8BC1\u540C\u4E00\u6761\u5E8F\u5217\u5728\u4E0D\u540C\u56FE\u91CC\u989C\u8272\u4E00\u81F4\uFF09\u3002\n var PALETTE = ['#f59e0b', '#10b981', '#8b5cf6', '#0ea5e9', '#ec4899']\n\n function readToken(name) {\n try {\n var value = getComputedStyle(document.documentElement).getPropertyValue(name)\n if (value && value.trim().length > 0) return value.trim()\n } catch (error) {\n /* \u975E\u6D4F\u89C8\u5668\u73AF\u5883\u6216\u53D6\u4E0D\u5230\u6837\u5F0F\u65F6\u9000\u56DE\u9ED8\u8BA4\u8272 */\n }\n return FALLBACK[name]\n }\n\n function resolveColors() {\n return {\n bg: readToken('--dsw-alias-bg-base'),\n grid: readToken('--dsw-alias-border-l1'),\n text: readToken('--dsw-alias-label-secondary'),\n strong: readToken('--dsw-alias-label-primary'),\n brand: readToken('--dsw-alias-brand-primary'),\n up: readToken('--dsw-alias-state-error-primary'),\n down: readToken('--dsw-alias-state-success-primary')\n }\n }\n\n function showError(container, message) {\n container.textContent = ''\n var box = document.createElement('div')\n box.setAttribute('data-chart-error', '1')\n box.style.cssText = 'padding:12px;border:0.5px solid ' + readToken('--dsw-alias-border-l1') + ';border-radius:8px;color:' + readToken('--dsw-alias-label-secondary') + ';font-size:13px;line-height:1.6'\n box.textContent = message\n container.appendChild(box)\n }\n\n function applyIndexLabels(chart, labels) {\n function format(time) {\n var key = String(time)\n return Object.prototype.hasOwnProperty.call(labels, key) ? labels[key] : key\n }\n // \u8F74\u7684\u523B\u5EA6\u6587\u6848\u5F52 timeScale.tickMarkFormatter \u7BA1\uFF0C\u5341\u5B57\u7EBF/\u63D0\u793A\u5F52 localization.timeFormatter\uFF1B\n // \u5206\u7C7B\u8F74\u4E24\u8FB9\u90FD\u8981\u6362\u6389\uFF0C\u5426\u5219\u6807\u5C3A\u4E0A\u8FD8\u662F 2000 \u5E74\u90A3\u79CD\u5408\u6210\u65E5\u671F\u3002\n chart.applyOptions({\n localization: { timeFormatter: format },\n timeScale: { tickMarkFormatter: function (time) { return format(time) } }\n })\n }\n\n /**\n * \u6302\u6807\u8BB0\u70B9\u3002v5 \u628A markers \u4ECE series API \u79FB\u5230\u4E86 createSeriesMarkers(series, markers)\n * \u8FD4\u56DE\u7684\u63D2\u4EF6\u5BF9\u8C61\u4E0A\uFF1B\u8FD9\u91CC\u540C\u65F6\u517C\u5BB9\u4E24\u79CD\u5F62\u72B6\uFF0C\u4E0A\u6E38\u518D\u52A8\u4E00\u6B21\u4E5F\u4E0D\u4F1A\u9759\u9ED8\u4E22\u6389\u6807\u8BB0\u3002\n */\n function attachMarkers(anchor, markers, LWC) {\n if (typeof LWC.createSeriesMarkers === 'function') {\n LWC.createSeriesMarkers(anchor, markers)\n return true\n }\n if (anchor && typeof anchor.setMarkers === 'function') {\n anchor.setMarkers(markers)\n return true\n }\n return false\n }\n\n function volumePointColor(point, colors) {\n if (point.direction === 'up') return colors.up\n if (point.direction === 'down') return colors.down\n return colors.brand\n }\n\n function seriesColor(index, colors) {\n return index === 0 ? colors.brand : PALETTE[(index - 1) % PALETTE.length]\n }\n\n function appendLegend(container, payload, colors) {\n var items = []\n if (payload.ohlc && payload.ohlc.label) items.push({ label: payload.ohlc.label, color: colors.up })\n if (Array.isArray(payload.series)) {\n payload.series.forEach(function (item, index) {\n if (item && item.label) items.push({ label: item.label, color: seriesColor(index, colors) })\n })\n }\n if (payload.volume && payload.volume.label) items.push({ label: payload.volume.label, color: colors.brand })\n if (items.length === 0) return\n\n var legend = document.createElement('div')\n legend.setAttribute('data-chart-legend', '1')\n legend.style.cssText = 'position:absolute;z-index:2;top:8px;left:8px;right:8px;display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center;pointer-events:none;color:' + colors.text + ';font-size:11px;line-height:16px'\n items.forEach(function (item) {\n var entry = document.createElement('span')\n entry.style.cssText = 'display:inline-flex;align-items:center;gap:4px;padding:1px 5px;border-radius:4px;background:' + withAlpha(colors.bg, 0.8)\n var swatch = document.createElement('span')\n swatch.style.cssText = 'width:7px;height:7px;border-radius:50%;background:' + item.color + ';flex:none'\n var label = document.createElement('span')\n label.textContent = item.label\n entry.appendChild(swatch)\n entry.appendChild(label)\n legend.appendChild(entry)\n })\n container.appendChild(legend)\n }\n\n function renderCapitalChart(container, payload, library) {\n if (!container) throw new Error('renderCapitalChart: container is required')\n if (!payload || typeof payload !== 'object') throw new Error('renderCapitalChart: payload is required')\n // \u56FE\u8868\u5E93\u7531\u8C03\u7528\u65B9\u6CE8\u5165\uFF0C\u7EDD\u4E0D\u56DE\u9000\u5230\u9875\u9762\u5168\u5C40\uFF1A\u56DE\u9000\u4F1A\u8BA9\"\u53E6\u4E00\u4E2A\u63D2\u4EF6\u7684\u540C\u5E93\u7248\u672C\"\u6084\u6084\u751F\u6548\u3002\n var LWC = library\n if (!LWC || typeof LWC.createChart !== 'function') {\n showError(container, '\u56FE\u8868\u5E93\u672A\u52A0\u8F7D\uFF08\u8C03\u7528\u65B9\u672A\u63D0\u4F9B LightweightCharts\uFF09\u3002')\n return null\n }\n\n var colors = resolveColors()\n container.textContent = ''\n if (container.style) container.style.position = 'relative'\n container.setAttribute('data-chart-id', String(payload.chart_id || ''))\n container.setAttribute('data-chart-kind', String(payload.kind || ''))\n\n var chart = LWC.createChart(container, {\n autoSize: true,\n // attributionLogo \u4FDD\u6301\u5F00\u542F\uFF1A\u8FD9\u6EE1\u8DB3 Apache-2.0 \u5F52\u5C5E\u8981\u6C42\uFF08\u89C1 vendor/lightweight-charts/VERSION.json\uFF09\u3002\n layout: { background: { color: 'transparent' }, textColor: colors.text, fontFamily: 'inherit', attributionLogo: true },\n grid: { vertLines: { color: colors.grid }, horzLines: { color: colors.grid } },\n rightPriceScale: { borderColor: colors.grid, entireTextOnly: true },\n timeScale: { borderColor: colors.grid, rightOffset: 2, barSpacing: 6, minBarSpacing: 0.5 },\n crosshair: { mode: LWC.CrosshairMode ? LWC.CrosshairMode.Normal : 0 },\n localization: { locale: 'zh-CN' }\n })\n\n if (payload.axis === 'index' && payload.labels) applyIndexLabels(chart, payload.labels)\n\n var anchor = null\n\n if (payload.ohlc && Array.isArray(payload.ohlc.data) && payload.ohlc.data.length > 0) {\n // kind=bar \u7528 BarSeries\uFF08OHLC \u67F1\uFF09\uFF0C\u5176\u4F59 OHLC \u56FE\u5F62\u7528 CandlestickSeries\u3002\n // \u4E24\u8005\u7684\u6570\u636E\u5951\u7EA6\u76F8\u540C\uFF08\u90FD\u7EE7\u627F OhlcData\uFF09\uFF0C\u533A\u522B\u53EA\u5728\u6E32\u67D3\u6837\u5F0F\u3002\n var isOhlcBar = payload.kind === 'bar'\n var candles = chart.addSeries(isOhlcBar ? LWC.BarSeries : LWC.CandlestickSeries, {\n upColor: colors.up,\n downColor: colors.down,\n borderUpColor: colors.up,\n borderDownColor: colors.down,\n wickUpColor: colors.up,\n wickDownColor: colors.down\n })\n candles.setData(payload.ohlc.data)\n anchor = candles\n }\n\n if (Array.isArray(payload.series)) {\n for (var index = 0; index < payload.series.length; index += 1) {\n var item = payload.series[index]\n if (!item || !Array.isArray(item.data) || item.data.length === 0) continue\n var color = seriesColor(index, colors)\n var options = { title: item.label || '', priceLineVisible: false, lastValueVisible: true }\n var series = null\n if (item.type === 'area') {\n options.lineColor = color\n options.topColor = withAlpha(color, 0.2)\n options.bottomColor = withAlpha(color, 0.02)\n options.lineWidth = 2\n series = chart.addSeries(LWC.AreaSeries, options)\n } else if (item.type === 'column') {\n // \u5355\u503C\u67F1\uFF1AHistogramSeries \u5403 SingleValueData\uFF08{time, value}\uFF09\u3002\n // \u6CE8\u610F\u4E0D\u80FD\u7528 BarSeries \u2014\u2014 \u5B83\u8981 OhlcData\uFF0C\u585E\u5355\u503C\u53EA\u4F1A\u753B\u51FA\u7A7A\u56FE\u3002\n options.color = color\n options.base = 0\n series = chart.addSeries(LWC.HistogramSeries, options)\n } else {\n options.color = color\n options.lineWidth = 2\n series = chart.addSeries(LWC.LineSeries, options)\n }\n series.setData(item.data)\n if (anchor === null) anchor = series\n }\n }\n\n if (payload.volume && Array.isArray(payload.volume.data) && payload.volume.data.length > 0) {\n var volume = chart.addSeries(LWC.HistogramSeries, {\n title: payload.volume.label || '',\n priceFormat: { type: 'volume' },\n priceLineVisible: false,\n lastValueVisible: false\n }, 1)\n volume.setData(payload.volume.data.map(function (point) {\n return { time: point.time, value: point.value, color: volumePointColor(point, colors) }\n }))\n }\n\n if (anchor === null) {\n showError(container, '\u8FD9\u5F20\u56FE\u6CA1\u6709\u53EF\u6E32\u67D3\u7684\u5E8F\u5217\uFF08payload \u91CC\u65E2\u6CA1\u6709 ohlc \u4E5F\u6CA1\u6709 series\uFF09\u3002')\n return chart\n }\n\n if (Array.isArray(payload.markers) && payload.markers.length > 0) {\n attachMarkers(anchor, payload.markers.map(function (marker) {\n return { time: marker.time, position: marker.position, text: marker.text, color: colors.strong, shape: 'circle' }\n }), LWC)\n }\n\n appendLegend(container, payload, colors)\n\n // \u4E3B\u56FE\u4E0E\u6210\u4EA4\u91CF\u526F\u56FE\u7684\u9AD8\u5EA6\u6BD4 3:1\uFF1B\u53EA\u6709\u4E00\u4E2A pane \u65F6 panes()[1] \u4E0D\u5B58\u5728\u3002\n var panes = typeof chart.panes === 'function' ? chart.panes() : []\n if (panes.length > 1) {\n if (typeof panes[0].setStretchFactor === 'function') panes[0].setStretchFactor(3)\n if (typeof panes[1].setStretchFactor === 'function') panes[1].setStretchFactor(1)\n }\n\n chart.timeScale().fitContent()\n return chart\n }\n\n return { render: renderCapitalChart, version: 1 }\n})()\n";
7
+ export declare const CHART_RUNTIME_SOURCE = "\n(function () {\n 'use strict'\n\n var FALLBACK = {\n '--dsw-alias-bg-base': '#ffffff',\n '--dsw-alias-border-l1': 'rgba(0, 0, 0, 0.10)',\n '--dsw-alias-label-primary': '#1f2328',\n '--dsw-alias-label-secondary': '#6b7280',\n '--dsw-alias-brand-primary': '#2563eb',\n '--dsw-alias-state-error-primary': '#d94a3d',\n '--dsw-alias-state-success-primary': '#2f9e44'\n }\n\n // Hex colors receive alpha; other formats convert to rgba or stay unchanged, never emit invalid CSS.\n function withAlpha(color, alpha) {\n var text = String(color || '').trim()\n var hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(text)\n if (hex) {\n var expanded = hex[1].length === 3\n ? '#' + hex[1].split('').map(function (part) { return part + part }).join('')\n : text\n return expanded + Math.round(alpha * 255).toString(16).padStart(2, '0')\n }\n var rgb = /^rgba?\\(\\s*([^)]*)\\)$/i.exec(text)\n if (rgb) {\n var parts = rgb[1].split(/[,\\s/]+/).filter(Boolean)\n if (parts.length >= 3 && parts.slice(0, 3).every(function (part) { return /^[-+]?\\d*\\.?\\d+%?$/.test(part) })) {\n return 'rgba(' + parts.slice(0, 3).join(', ') + ', ' + alpha + ')'\n }\n }\n return text\n }\n\n // First series uses brand color; later series use a stable palette so one symbol keeps its color.\n var PALETTE = ['#f59e0b', '#10b981', '#8b5cf6', '#0ea5e9', '#ec4899']\n\n function readToken(name) {\n try {\n var value = getComputedStyle(document.documentElement).getPropertyValue(name)\n if (value && value.trim().length > 0) return value.trim()\n } catch (error) {\n /* Non-browser or unavailable styles fall back to defaults. */\n }\n return FALLBACK[name]\n }\n\n function resolveColors() {\n return {\n bg: readToken('--dsw-alias-bg-base'),\n grid: readToken('--dsw-alias-border-l1'),\n text: readToken('--dsw-alias-label-secondary'),\n strong: readToken('--dsw-alias-label-primary'),\n brand: readToken('--dsw-alias-brand-primary'),\n up: readToken('--dsw-alias-state-error-primary'),\n down: readToken('--dsw-alias-state-success-primary')\n }\n }\n\n function showError(container, message) {\n container.textContent = ''\n var box = document.createElement('div')\n box.setAttribute('data-chart-error', '1')\n box.style.cssText = 'padding:12px;border:0.5px solid ' + readToken('--dsw-alias-border-l1') + ';border-radius:8px;color:' + readToken('--dsw-alias-label-secondary') + ';font-size:13px;line-height:1.6'\n box.textContent = message\n container.appendChild(box)\n }\n\n function applyIndexLabels(chart, labels) {\n function format(time) {\n var key = String(time)\n return Object.prototype.hasOwnProperty.call(labels, key) ? labels[key] : key\n }\n // Replace both tick and crosshair formatters for category axes or synthetic dates leak through.\n chart.applyOptions({\n localization: { timeFormatter: format },\n timeScale: { tickMarkFormatter: function (time) { return format(time) } }\n })\n }\n\n /** Support both v5 plugin markers and the older series API so markers never disappear silently. */\n function attachMarkers(anchor, markers, LWC) {\n if (typeof LWC.createSeriesMarkers === 'function') {\n LWC.createSeriesMarkers(anchor, markers)\n return true\n }\n if (anchor && typeof anchor.setMarkers === 'function') {\n anchor.setMarkers(markers)\n return true\n }\n return false\n }\n\n function volumePointColor(point, colors) {\n if (point.direction === 'up') return colors.up\n if (point.direction === 'down') return colors.down\n return colors.brand\n }\n\n function seriesColor(index, colors) {\n return index === 0 ? colors.brand : PALETTE[(index - 1) % PALETTE.length]\n }\n\n function appendLegend(container, payload, colors) {\n var items = []\n if (payload.ohlc && payload.ohlc.label) items.push({ label: payload.ohlc.label, color: colors.up })\n if (Array.isArray(payload.series)) {\n payload.series.forEach(function (item, index) {\n if (item && item.label) items.push({ label: item.label, color: seriesColor(index, colors) })\n })\n }\n if (payload.volume && payload.volume.label) items.push({ label: payload.volume.label, color: colors.brand })\n if (items.length === 0) return\n\n var legend = document.createElement('div')\n legend.setAttribute('data-chart-legend', '1')\n legend.style.cssText = 'position:absolute;z-index:2;top:8px;left:8px;right:8px;display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center;pointer-events:none;color:' + colors.text + ';font-size:11px;line-height:16px'\n items.forEach(function (item) {\n var entry = document.createElement('span')\n entry.style.cssText = 'display:inline-flex;align-items:center;gap:4px;padding:1px 5px;border-radius:4px;background:' + withAlpha(colors.bg, 0.8)\n var swatch = document.createElement('span')\n swatch.style.cssText = 'width:7px;height:7px;border-radius:50%;background:' + item.color + ';flex:none'\n var label = document.createElement('span')\n label.textContent = item.label\n entry.appendChild(swatch)\n entry.appendChild(label)\n legend.appendChild(entry)\n })\n container.appendChild(legend)\n }\n\n function renderCapitalChart(container, payload, library) {\n if (!container) throw new Error('renderCapitalChart: container is required')\n if (!payload || typeof payload !== 'object') throw new Error('renderCapitalChart: payload is required')\n // Use the injected library; never fall back to a global copy owned by another plugin.\n var LWC = library\n if (!LWC || typeof LWC.createChart !== 'function') {\n showError(container, '\u56FE\u8868\u5E93\u672A\u52A0\u8F7D\uFF08\u8C03\u7528\u65B9\u672A\u63D0\u4F9B LightweightCharts\uFF09\u3002')\n return null\n }\n\n var colors = resolveColors()\n container.textContent = ''\n if (container.style) container.style.position = 'relative'\n container.setAttribute('data-chart-id', String(payload.chart_id || ''))\n container.setAttribute('data-chart-kind', String(payload.kind || ''))\n\n var chart = LWC.createChart(container, {\n autoSize: true,\n // attributionLogo stays enabled for Apache-2.0 attribution.\n layout: { background: { color: 'transparent' }, textColor: colors.text, fontFamily: 'inherit', attributionLogo: true },\n grid: { vertLines: { color: colors.grid }, horzLines: { color: colors.grid } },\n rightPriceScale: { borderColor: colors.grid, entireTextOnly: true },\n timeScale: { borderColor: colors.grid, rightOffset: 2, barSpacing: 6, minBarSpacing: 0.5 },\n crosshair: { mode: LWC.CrosshairMode ? LWC.CrosshairMode.Normal : 0 },\n localization: { locale: 'zh-CN' }\n })\n\n if (payload.axis === 'index' && payload.labels) applyIndexLabels(chart, payload.labels)\n\n var anchor = null\n\n if (payload.ohlc && Array.isArray(payload.ohlc.data) && payload.ohlc.data.length > 0) {\n // kind=bar uses BarSeries; other OHLC kinds use CandlestickSeries with the same data contract.\n var isOhlcBar = payload.kind === 'bar'\n var candles = chart.addSeries(isOhlcBar ? LWC.BarSeries : LWC.CandlestickSeries, {\n upColor: colors.up,\n downColor: colors.down,\n borderUpColor: colors.up,\n borderDownColor: colors.down,\n wickUpColor: colors.up,\n wickDownColor: colors.down\n })\n candles.setData(payload.ohlc.data)\n anchor = candles\n }\n\n if (Array.isArray(payload.series)) {\n for (var index = 0; index < payload.series.length; index += 1) {\n var item = payload.series[index]\n if (!item || !Array.isArray(item.data) || item.data.length === 0) continue\n var color = seriesColor(index, colors)\n var options = { title: item.label || '', priceLineVisible: false, lastValueVisible: true }\n var series = null\n if (item.type === 'area') {\n options.lineColor = color\n options.topColor = withAlpha(color, 0.2)\n options.bottomColor = withAlpha(color, 0.02)\n options.lineWidth = 2\n series = chart.addSeries(LWC.AreaSeries, options)\n } else if (item.type === 'column') {\n // Single-value histograms need HistogramSeries; BarSeries would render an empty chart.\n options.color = color\n options.base = 0\n series = chart.addSeries(LWC.HistogramSeries, options)\n } else {\n options.color = color\n options.lineWidth = 2\n series = chart.addSeries(LWC.LineSeries, options)\n }\n series.setData(item.data)\n if (anchor === null) anchor = series\n }\n }\n\n if (payload.volume && Array.isArray(payload.volume.data) && payload.volume.data.length > 0) {\n var volume = chart.addSeries(LWC.HistogramSeries, {\n title: payload.volume.label || '',\n priceFormat: { type: 'volume' },\n priceLineVisible: false,\n lastValueVisible: false\n }, 1)\n volume.setData(payload.volume.data.map(function (point) {\n return { time: point.time, value: point.value, color: volumePointColor(point, colors) }\n }))\n }\n\n if (anchor === null) {\n showError(container, '\u8FD9\u5F20\u56FE\u6CA1\u6709\u53EF\u6E32\u67D3\u7684\u5E8F\u5217\uFF08payload \u91CC\u65E2\u6CA1\u6709 ohlc \u4E5F\u6CA1\u6709 series\uFF09\u3002')\n return chart\n }\n\n if (Array.isArray(payload.markers) && payload.markers.length > 0) {\n attachMarkers(anchor, payload.markers.map(function (marker) {\n return { time: marker.time, position: marker.position, text: marker.text, color: colors.strong, shape: 'circle' }\n }), LWC)\n }\n\n appendLegend(container, payload, colors)\n\n // Main/volume panes use a 3:1 ratio; a single pane has no second pane object.\n var panes = typeof chart.panes === 'function' ? chart.panes() : []\n if (panes.length > 1) {\n if (typeof panes[0].setStretchFactor === 'function') panes[0].setStretchFactor(3)\n if (typeof panes[1].setStretchFactor === 'function') panes[1].setStretchFactor(1)\n }\n\n chart.timeScale().fitContent()\n return chart\n }\n\n return { render: renderCapitalChart, version: 1 }\n})()\n";