@v587d/capital-generation 2.8.3 → 2.9.1

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 (169) hide show
  1. package/CHANGELOG.md +81 -1
  2. package/README.md +40 -19
  3. package/chart-ui/README.md +5 -4
  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 +13 -8
  25. package/lib/chart/artifact-ref.js +27 -10
  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 +43 -189
  34. package/lib/chart/html.d.ts +2 -5
  35. package/lib/chart/html.js +8 -15
  36. package/lib/chart/present-tool.d.ts +28 -0
  37. package/lib/chart/present-tool.js +128 -0
  38. package/lib/chart/runtime.d.ts +5 -18
  39. package/lib/chart/runtime.js +14 -34
  40. package/lib/chart/series.d.ts +10 -17
  41. package/lib/chart/series.js +22 -47
  42. package/lib/chart/source-token.d.ts +5 -13
  43. package/lib/chart/source-token.js +18 -17
  44. package/lib/chart/spec.d.ts +6 -12
  45. package/lib/chart/spec.js +7 -31
  46. package/lib/chart/tool.d.ts +7 -29
  47. package/lib/chart/tool.js +15 -25
  48. package/lib/chart-render-entry.d.ts +10 -0
  49. package/lib/chart-render-entry.js +14 -0
  50. package/lib/chart-source-entry.d.ts +13 -0
  51. package/lib/chart-source-entry.js +20 -0
  52. package/lib/collect-entry.d.ts +9 -0
  53. package/lib/collect-entry.js +10 -0
  54. package/lib/collector-runtime.d.ts +22 -0
  55. package/lib/collector-runtime.js +97 -0
  56. package/lib/data-collector/args.d.ts +6 -24
  57. package/lib/data-collector/args.js +6 -24
  58. package/lib/data-collector/dataset-tools.d.ts +7 -1
  59. package/lib/data-collector/dataset-tools.js +27 -56
  60. package/lib/data-collector/describe.d.ts +8 -33
  61. package/lib/data-collector/describe.js +17 -70
  62. package/lib/data-collector/hub.d.ts +27 -85
  63. package/lib/data-collector/hub.js +19 -85
  64. package/lib/data-collector/query-time.d.ts +7 -26
  65. package/lib/data-collector/query-time.js +8 -27
  66. package/lib/data-collector/store.d.ts +41 -117
  67. package/lib/data-collector/store.js +32 -111
  68. package/lib/data-collector/time-axis.d.ts +16 -76
  69. package/lib/data-collector/time-axis.js +25 -111
  70. package/lib/data-collector/tools.d.ts +6 -6
  71. package/lib/data-collector/tools.js +34 -43
  72. package/lib/dataset-read-entry.d.ts +9 -0
  73. package/lib/dataset-read-entry.js +7 -0
  74. package/lib/index.d.ts +7 -117
  75. package/lib/index.js +43 -422
  76. package/lib/net/eastmoney-client.d.ts +13 -65
  77. package/lib/net/eastmoney-client.js +12 -53
  78. package/lib/net/throttle.d.ts +2 -13
  79. package/lib/net/throttle.js +3 -14
  80. package/lib/ocr/client.d.ts +12 -21
  81. package/lib/ocr/client.js +23 -40
  82. package/lib/ocr/markdown.d.ts +20 -26
  83. package/lib/ocr/markdown.js +23 -36
  84. package/lib/ocr/store.d.ts +14 -47
  85. package/lib/ocr/store.js +15 -45
  86. package/lib/ocr-entry.d.ts +9 -0
  87. package/lib/ocr-entry.js +10 -0
  88. package/lib/policy-entry.d.ts +17 -0
  89. package/lib/policy-entry.js +22 -0
  90. package/lib/present-entry.d.ts +9 -0
  91. package/lib/present-entry.js +13 -0
  92. package/lib/preset-claim.d.ts +22 -0
  93. package/lib/preset-claim.js +24 -0
  94. package/lib/preset-ids.d.ts +38 -0
  95. package/lib/preset-ids.js +46 -0
  96. package/lib/retriever-config.d.ts +37 -0
  97. package/lib/retriever-config.js +36 -0
  98. package/lib/retriever-runtime.d.ts +31 -0
  99. package/lib/retriever-runtime.js +82 -0
  100. package/lib/root-extras-entry.d.ts +17 -0
  101. package/lib/root-extras-entry.js +25 -0
  102. package/lib/runtime-entry.d.ts +15 -0
  103. package/lib/runtime-entry.js +5 -0
  104. package/lib/runtime.d.ts +21 -0
  105. package/lib/runtime.js +33 -0
  106. package/lib/search-entry.d.ts +9 -0
  107. package/lib/search-entry.js +9 -0
  108. package/lib/sources/eastmoney-http.js +20 -68
  109. package/lib/sources/ftshare-rest.d.ts +6 -34
  110. package/lib/sources/ftshare-rest.js +84 -292
  111. package/lib/sources/fuyao-core.d.ts +8 -18
  112. package/lib/sources/fuyao-core.js +10 -21
  113. package/lib/sources/fuyao-rest.d.ts +2 -6
  114. package/lib/sources/fuyao-rest.js +23 -102
  115. package/lib/sources/security-code.d.ts +6 -12
  116. package/lib/sources/security-code.js +6 -12
  117. package/lib/sources/security-identity.d.ts +1 -4
  118. package/lib/sources/security-identity.js +3 -8
  119. package/lib/sources/tencent-http.js +14 -28
  120. package/lib/sources/tencent-public-core.d.ts +8 -60
  121. package/lib/sources/tencent-public-core.js +12 -77
  122. package/lib/sources/wind-mcp.d.ts +0 -5
  123. package/lib/sources/wind-mcp.js +23 -78
  124. package/lib/sources-entry.d.ts +9 -0
  125. package/lib/sources-entry.js +9 -0
  126. package/lib/time/tools.d.ts +8 -31
  127. package/lib/time/tools.js +30 -66
  128. package/lib/time-entry.d.ts +9 -0
  129. package/lib/time-entry.js +7 -0
  130. package/lib/tool-exec.d.ts +45 -11
  131. package/lib/tool-exec.js +59 -6
  132. package/lib/user-customization.d.ts +14 -0
  133. package/lib/user-customization.js +47 -0
  134. package/lib/watchlist/tools.d.ts +3 -13
  135. package/lib/watchlist/tools.js +10 -30
  136. package/lib/watchlist-entry.d.ts +9 -0
  137. package/lib/watchlist-entry.js +6 -0
  138. package/lib/web-retriever/engines.d.ts +6 -11
  139. package/lib/web-retriever/engines.js +9 -14
  140. package/lib/web-retriever/html-markdown.d.ts +14 -34
  141. package/lib/web-retriever/html-markdown.js +39 -75
  142. package/lib/web-retriever/local-fetch.d.ts +22 -58
  143. package/lib/web-retriever/local-fetch.js +33 -77
  144. package/lib/web-retriever/retriever.d.ts +6 -18
  145. package/lib/web-retriever/retriever.js +7 -7
  146. package/lib/web-retriever/sources.d.ts +37 -124
  147. package/lib/web-retriever/sources.js +64 -196
  148. package/lib/web-retriever/tools.d.ts +43 -27
  149. package/lib/web-retriever/tools.js +99 -137
  150. package/lib/web-retriever/wind-client.d.ts +14 -31
  151. package/lib/web-retriever/wind-client.js +16 -32
  152. package/locale/zh.json +1 -1
  153. package/package.json +72 -7
  154. package/preset/capital-generation/README.md +87 -53
  155. package/preset/capital-generation/agent.patch.yml +532 -337
  156. package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +6 -4
  157. package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
  158. package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
  159. package/preset/capital-generation/skills/capital-equity-research/SKILL.md +4 -2
  160. package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
  161. package/preset/capital-generation/skills/capital-orchestration/SKILL.md +30 -33
  162. package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
  163. package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +14 -16
  164. package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
  165. package/watchlist/client.js +15 -148
  166. package/watchlist/client.src.cjs +148 -662
  167. package/watchlist/holding-rules.js +11 -38
  168. package/watchlist/index.js +127 -397
  169. package/watchlist/package.json +2 -2
@@ -0,0 +1,128 @@
1
+ import { MAX_CHARTS_PER_TURN } from './events.js';
2
+ import { callerSession } from '../tool-exec.js';
3
+ const jsonObject = (properties = {}, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
4
+ const parameters = jsonObject({
5
+ chart_refs: {
6
+ type: 'array',
7
+ items: { type: 'string' },
8
+ description: 'data_junior 回传的全部 chart_ref,一次传完(最多 8 个,不猜写)。与 task_id 至少给一个。',
9
+ },
10
+ task_id: {
11
+ type: 'string',
12
+ description: '本任务 canonical task_id:省略 chart_refs 时按它登记本会话该任务的全部图表(上下文压缩后记不清 refs 时用)。',
13
+ },
14
+ });
15
+ const receiptSchema = jsonObject({
16
+ presented: {
17
+ type: 'array',
18
+ items: jsonObject({
19
+ chart_ref: { type: 'string' },
20
+ title: { type: 'string' },
21
+ }, ['chart_ref', 'title']),
22
+ },
23
+ failed: {
24
+ type: 'array',
25
+ items: jsonObject({
26
+ chart_ref: { type: 'string' },
27
+ reason: { type: 'string' },
28
+ }, ['chart_ref', 'reason']),
29
+ },
30
+ }, ['presented', 'failed']);
31
+ function normalizeRefs(value) {
32
+ if (value === undefined)
33
+ return [];
34
+ if (!Array.isArray(value))
35
+ throw new Error('present_charts 的 chart_refs 必须是字符串数组');
36
+ const refs = [];
37
+ for (const item of value) {
38
+ if (typeof item !== 'string' || item.trim().length === 0)
39
+ throw new Error('present_charts 的 chart_refs 里不能有空值');
40
+ const ref = item.trim();
41
+ if (!refs.includes(ref))
42
+ refs.push(ref);
43
+ }
44
+ if (refs.length > MAX_CHARTS_PER_TURN) {
45
+ throw new Error(`present_charts 一次最多登记 ${MAX_CHARTS_PER_TURN} 张图(收到 ${refs.length} 个 chart_ref)`);
46
+ }
47
+ return refs;
48
+ }
49
+ function normalizeTaskId(value) {
50
+ if (value === undefined)
51
+ return undefined;
52
+ if (typeof value !== 'string' || value.trim().length === 0)
53
+ throw new Error('present_charts 的 task_id 必须是非空字符串');
54
+ return value.trim();
55
+ }
56
+ /** Present refs first; otherwise present the whole task so compaction cannot strand the charts. */
57
+ function selectArtifacts(input, session, refs, taskId) {
58
+ const failed = [];
59
+ if (refs.length > 0) {
60
+ const selected = [];
61
+ for (const chartRef of refs) {
62
+ try {
63
+ selected.push(input.artifacts.resolve({ session, chart_ref: chartRef, task_id: taskId }));
64
+ }
65
+ catch (error) {
66
+ failed.push({ chart_ref: chartRef, reason: error instanceof Error ? error.message : String(error) });
67
+ }
68
+ }
69
+ // A stale ref set must not strand charts the task_id can still find (compaction can mix both).
70
+ if (selected.length > 0 || taskId === undefined)
71
+ return { selected, failed };
72
+ }
73
+ if (taskId === undefined)
74
+ throw new Error('present_charts 需要 chart_refs 或 task_id:两者都不给就无法确定要登记哪几张图');
75
+ const all = input.artifacts.list({ session, task_id: taskId });
76
+ if (all.length === 0)
77
+ failed.push({ chart_ref: '(task)', reason: `本会话该 task_id 没有可登记的图表:${taskId}` });
78
+ const selected = all.slice(0, MAX_CHARTS_PER_TURN);
79
+ if (all.length > MAX_CHARTS_PER_TURN) {
80
+ failed.push({ chart_ref: '(task)', reason: `该 task_id 有 ${all.length} 张图,本轮最多登记 ${MAX_CHARTS_PER_TURN} 张;其余未展示` });
81
+ }
82
+ return { selected, failed };
83
+ }
84
+ export function registerPresentChartsTool(ctx, input) {
85
+ const tools = ctx.get('tools');
86
+ if (!tools)
87
+ return;
88
+ const definition = {
89
+ name: 'present_charts',
90
+ description: '把本会话已生成的图表登记为**最终答复的交付物**(官方 deliverables/presented):用户会在本轮答复末尾的「本轮文件改动 / 交付」行看到卡片,点开即渲染自包含 chart.html。'
91
+ + '**只由主 Agent 在写最终答复前调用一次**:参数是 data_junior 回传的 chart_refs(一次全部传入,最多 8 个);refs 已丢失时改传 task_id 登记该任务的全部图表;调用后立刻写最终答复。'
92
+ + '不要在过程轮 / 等待行前调用,不要分批重复调用;不要传文件路径或 HTML(路径由宿主解析)。'
93
+ + '不调用本工具,图表不会出现在对话里。',
94
+ parameters,
95
+ async execute(args, exec) {
96
+ const session = callerSession(exec);
97
+ const refs = normalizeRefs(args.chart_refs);
98
+ const taskId = normalizeTaskId(args.task_id);
99
+ if (refs.length === 0 && taskId === undefined) {
100
+ throw new Error('present_charts 需要 chart_refs 或 task_id:两者都不给就无法确定要登记哪几张图');
101
+ }
102
+ const publisher = input.chartEvents();
103
+ if (publisher === undefined) {
104
+ throw new Error('图表交付通道不可用(sessions / sessionProjections 解析不到):图表已落盘,可在 workspace 打开 chart.html');
105
+ }
106
+ const { selected, failed } = selectArtifacts(input, session, refs, taskId);
107
+ const receipt = { presented: [], failed };
108
+ for (const artifact of selected) {
109
+ const accepted = publisher.publish({
110
+ ownerSessionId: session.id,
111
+ chart_id: artifact.chart_id,
112
+ title: artifact.title,
113
+ html_path: artifact.html_path,
114
+ });
115
+ if (accepted)
116
+ receipt.presented.push({ chart_ref: artifact.chart_ref, title: artifact.title });
117
+ else
118
+ receipt.failed.push({ chart_ref: artifact.chart_ref, reason: `本轮登记已达 ${MAX_CHARTS_PER_TURN} 张上限或回合已关闭,未写入交付行` });
119
+ }
120
+ return receipt;
121
+ },
122
+ output: {
123
+ schema: receiptSchema,
124
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
125
+ },
126
+ };
127
+ ctx.effect(() => tools.register(definition), 'capital-generation.tool(present_charts)');
128
+ }
@@ -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";
@@ -1,21 +1,8 @@
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
7
  export const CHART_RUNTIME_SOURCE = `
21
8
  (function () {
@@ -31,8 +18,7 @@ export const CHART_RUNTIME_SOURCE = `
31
18
  '--dsw-alias-state-success-primary': '#2f9e44'
32
19
  }
33
20
 
34
- // 主题 token 不一定是十六进制(也可能是 rgb/rgba 或自定义 CSS 色值)。只有确认是
35
- // 十六进制时才追加 alpha;其余格式转换成 rgba,转换不了就退回原色,绝不拼出非法 CSS。
21
+ // Hex colors receive alpha; other formats convert to rgba or stay unchanged, never emit invalid CSS.
36
22
  function withAlpha(color, alpha) {
37
23
  var text = String(color || '').trim()
38
24
  var hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(text)
@@ -52,7 +38,7 @@ export const CHART_RUNTIME_SOURCE = `
52
38
  return text
53
39
  }
54
40
 
55
- // 多序列配色:第一条用主题品牌色,其余用固定色板(保证同一条序列在不同图里颜色一致)。
41
+ // First series uses brand color; later series use a stable palette so one symbol keeps its color.
56
42
  var PALETTE = ['#f59e0b', '#10b981', '#8b5cf6', '#0ea5e9', '#ec4899']
57
43
 
58
44
  function readToken(name) {
@@ -60,7 +46,7 @@ export const CHART_RUNTIME_SOURCE = `
60
46
  var value = getComputedStyle(document.documentElement).getPropertyValue(name)
61
47
  if (value && value.trim().length > 0) return value.trim()
62
48
  } catch (error) {
63
- /* 非浏览器环境或取不到样式时退回默认色 */
49
+ /* Non-browser or unavailable styles fall back to defaults. */
64
50
  }
65
51
  return FALLBACK[name]
66
52
  }
@@ -91,18 +77,14 @@ export const CHART_RUNTIME_SOURCE = `
91
77
  var key = String(time)
92
78
  return Object.prototype.hasOwnProperty.call(labels, key) ? labels[key] : key
93
79
  }
94
- // 轴的刻度文案归 timeScale.tickMarkFormatter 管,十字线/提示归 localization.timeFormatter;
95
- // 分类轴两边都要换掉,否则标尺上还是 2000 年那种合成日期。
80
+ // Replace both tick and crosshair formatters for category axes or synthetic dates leak through.
96
81
  chart.applyOptions({
97
82
  localization: { timeFormatter: format },
98
83
  timeScale: { tickMarkFormatter: function (time) { return format(time) } }
99
84
  })
100
85
  }
101
86
 
102
- /**
103
- * 挂标记点。v5 把 markers 从 series API 移到了 createSeriesMarkers(series, markers)
104
- * 返回的插件对象上;这里同时兼容两种形状,上游再动一次也不会静默丢掉标记。
105
- */
87
+ /** Support both v5 plugin markers and the older series API so markers never disappear silently. */
106
88
  function attachMarkers(anchor, markers, LWC) {
107
89
  if (typeof LWC.createSeriesMarkers === 'function') {
108
90
  LWC.createSeriesMarkers(anchor, markers)
@@ -156,7 +138,7 @@ export const CHART_RUNTIME_SOURCE = `
156
138
  function renderCapitalChart(container, payload, library) {
157
139
  if (!container) throw new Error('renderCapitalChart: container is required')
158
140
  if (!payload || typeof payload !== 'object') throw new Error('renderCapitalChart: payload is required')
159
- // 图表库由调用方注入,绝不回退到页面全局:回退会让"另一个插件的同库版本"悄悄生效。
141
+ // Use the injected library; never fall back to a global copy owned by another plugin.
160
142
  var LWC = library
161
143
  if (!LWC || typeof LWC.createChart !== 'function') {
162
144
  showError(container, '图表库未加载(调用方未提供 LightweightCharts)。')
@@ -171,7 +153,7 @@ export const CHART_RUNTIME_SOURCE = `
171
153
 
172
154
  var chart = LWC.createChart(container, {
173
155
  autoSize: true,
174
- // attributionLogo 保持开启:这满足 Apache-2.0 归属要求(见 vendor/lightweight-charts/VERSION.json)。
156
+ // attributionLogo stays enabled for Apache-2.0 attribution.
175
157
  layout: { background: { color: 'transparent' }, textColor: colors.text, fontFamily: 'inherit', attributionLogo: true },
176
158
  grid: { vertLines: { color: colors.grid }, horzLines: { color: colors.grid } },
177
159
  rightPriceScale: { borderColor: colors.grid, entireTextOnly: true },
@@ -185,8 +167,7 @@ export const CHART_RUNTIME_SOURCE = `
185
167
  var anchor = null
186
168
 
187
169
  if (payload.ohlc && Array.isArray(payload.ohlc.data) && payload.ohlc.data.length > 0) {
188
- // kind=bar 用 BarSeries(OHLC 柱),其余 OHLC 图形用 CandlestickSeries。
189
- // 两者的数据契约相同(都继承 OhlcData),区别只在渲染样式。
170
+ // kind=bar uses BarSeries; other OHLC kinds use CandlestickSeries with the same data contract.
190
171
  var isOhlcBar = payload.kind === 'bar'
191
172
  var candles = chart.addSeries(isOhlcBar ? LWC.BarSeries : LWC.CandlestickSeries, {
192
173
  upColor: colors.up,
@@ -214,8 +195,7 @@ export const CHART_RUNTIME_SOURCE = `
214
195
  options.lineWidth = 2
215
196
  series = chart.addSeries(LWC.AreaSeries, options)
216
197
  } else if (item.type === 'column') {
217
- // 单值柱:HistogramSeries 吃 SingleValueData({time, value})。
218
- // 注意不能用 BarSeries —— 它要 OhlcData,塞单值只会画出空图。
198
+ // Single-value histograms need HistogramSeries; BarSeries would render an empty chart.
219
199
  options.color = color
220
200
  options.base = 0
221
201
  series = chart.addSeries(LWC.HistogramSeries, options)
@@ -254,7 +234,7 @@ export const CHART_RUNTIME_SOURCE = `
254
234
 
255
235
  appendLegend(container, payload, colors)
256
236
 
257
- // 主图与成交量副图的高度比 3:1;只有一个 pane 时 panes()[1] 不存在。
237
+ // Main/volume panes use a 3:1 ratio; a single pane has no second pane object.
258
238
  var panes = typeof chart.panes === 'function' ? chart.panes() : []
259
239
  if (panes.length > 1) {
260
240
  if (typeof panes[0].setStretchFactor === 'function') panes[0].setStretchFactor(3)
@@ -1,7 +1,7 @@
1
1
  import type { ChartKind, ChartSeriesType, MarkerPosition, NormalizedChartSpec } from './spec.js';
2
- /** 单张图保留的最大点数(下采样后)。轻量图表在这个量级仍然流畅,且载荷只有几十 KB。 */
2
+ /** Post-downsample point cap; keeps the payload tens of KB and rendering fluid. */
3
3
  export declare const MAX_CHART_POINTS = 2000;
4
- /** 字段候选清单最多回报多少个(错误路径本身也不能变成上下文炸弹)。 */
4
+ /** Error-path hints are capped too; an error must not become a context bomb. */
5
5
  export declare const MAX_FIELD_HINTS = 15;
6
6
  export interface ChartSeriesPayload {
7
7
  id: string;
@@ -43,7 +43,7 @@ export interface ChartPayload {
43
43
  text: string;
44
44
  position: MarkerPosition;
45
45
  }>;
46
- /** 分类轴专用:合成时间 → 原始标签,运行时用它做刻度与十字线文案。 */
46
+ /** Category axis: synthetic time → original label for ticks and crosshair. */
47
47
  labels?: Record<string, string>;
48
48
  meta: {
49
49
  points: number;
@@ -60,31 +60,24 @@ export interface ChartPayload {
60
60
  };
61
61
  }
62
62
  /**
63
- * 从解析好的 JSON 里取出行数组。
64
- *
65
- * 真实数据源的同花顺返回常常是 envelope(行数组在 `item` / `data` / `stock_items` …),
66
- * 所以这里不能只认顶层数组;但也不能乱猜——猜错会让用户看到一张空图或错图。
67
- * 策略:顶层数组直接用;否则在候选键里找;候选都没有时列出**全部**数组型顶层键让模型
68
- * 自己指定路径,而不是静默取第一个。
63
+ * Sources often wrap rows in an envelope (item/data/stock_items); prefer known keys, and list
64
+ * candidates instead of silently guessing when several arrays match.
69
65
  */
70
66
  export declare function extractRows(value: unknown): {
71
67
  rows: unknown[];
72
68
  shape: string;
73
69
  };
74
70
  interface ParsedTime {
75
- /** 交给图表库的值:'YYYY-MM-DD' 字符串(业务日)或 UTC 秒数。 */
71
+ /** Value handed to the library: 'YYYY-MM-DD' business day, or UTC seconds. */
76
72
  display: string | number;
77
- /** 用于排序/过滤/对齐的比较键(UTC 秒)。 */
73
+ /** Comparison key (UTC seconds) for sort/filter/alignment. */
78
74
  seconds: number;
79
- /** 该值是否来自"纯日期"字符串(决定整张图是否用业务日显示)。 */
75
+ /** True when the value came from a date-only string; decides business-day rendering. */
80
76
  dateOnly: boolean;
81
77
  }
82
78
  /**
83
- * 时间值解析。规则必须**确定**(同一份数据在任何机器上得到同一张图):
84
- * - 纯日期 `YYYY-MM-DD` → 业务日字符串,交给图表库按业务日显示(不做时区换算,避免差一天);
85
- * - `YYYY-MM-DD HH:mm[:ss]` 无时区 → 按 UTC 解释(显式规则,不依赖宿主本地时区);
86
- * - 数字/纯数字字符串 → epoch;`>= 1e11` 视为毫秒,否则视为秒;
87
- * - 其余字符串 → `Date.parse`;失败即视为该行没有时间。
79
+ * Deterministic across machines: date-only stays a business-day string (no TZ shift), naive
80
+ * datetime is UTC, numbers >= 1e11 are epoch ms, unparseable means the row has no time.
88
81
  */
89
82
  export declare function parseTimeValue(value: unknown): ParsedTime | undefined;
90
83
  export interface BuildChartPayloadInput {
@@ -1,34 +1,28 @@
1
1
  /**
2
- * 把原始 rows 变成**有界、对齐、可渲染**的图表载荷。
3
- *
4
- * 边界(这是整个方案的核心不变量):本模块的**输入**是原始 rows,**输出**是可视化载荷。
5
- * 输出会写进 workspace 产物并交给浏览器,永不进入模型上下文;工具只回一条小回执。
6
- * 因此这里可以放心读全量行,但必须保证输出有界(点数上限 + 字段数上限 + 标记数上限)。
2
+ * Turn raw rows into a bounded, aligned, renderable payload.
3
+ * Output goes to a workspace artifact and the browser, never into model context; input may be
4
+ * read in full, but the payload must stay bounded (points, series, fields, markers).
7
5
  */
8
6
  import { largestTriangleThreeBuckets } from './downsample.js';
9
7
  import { ChartError } from './errors.js';
10
8
  import { MAX_CHART_MARKERS, MAX_CHART_SERIES, OHLC_KINDS } from './spec.js';
11
- /** 单张图保留的最大点数(下采样后)。轻量图表在这个量级仍然流畅,且载荷只有几十 KB。 */
9
+ /** Post-downsample point cap; keeps the payload tens of KB and rendering fluid. */
12
10
  export const MAX_CHART_POINTS = 2_000;
13
- /** 字段候选清单最多回报多少个(错误路径本身也不能变成上下文炸弹)。 */
11
+ /** Error-path hints are capped too; an error must not become a context bomb. */
14
12
  export const MAX_FIELD_HINTS = 15;
15
- /** 判定"x 是时间列"的采样解析成功率阈值。 */
13
+ /** Share of x values that must parse as time to keep a time axis. */
16
14
  const TIME_AXIS_THRESHOLD = 0.8;
17
- /** 分类轴的合成时间基准(UTC 秒):1970 太容易被误认为真实时间,取 2000-01-01。 */
15
+ /** Synthetic category-axis base (2000-01-01); 1970 reads as real data. */
18
16
  const INDEX_EPOCH_SECONDS = Date.UTC(2000, 0, 1) / 1000;
19
17
  const SECONDS_PER_DAY = 86_400;
20
- /** epoch 毫秒与秒的分界:>= 1e11 视为毫秒(1e11 秒 ≈ 公元 5138 年)。 */
18
+ /** Values >= 1e11 are epoch ms (1e11 seconds is year 5138). */
21
19
  const MILLISECONDS_THRESHOLD = 1e11;
22
20
  function isRecord(value) {
23
21
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
24
22
  }
25
23
  /**
26
- * 从解析好的 JSON 里取出行数组。
27
- *
28
- * 真实数据源的同花顺返回常常是 envelope(行数组在 `item` / `data` / `stock_items` …),
29
- * 所以这里不能只认顶层数组;但也不能乱猜——猜错会让用户看到一张空图或错图。
30
- * 策略:顶层数组直接用;否则在候选键里找;候选都没有时列出**全部**数组型顶层键让模型
31
- * 自己指定路径,而不是静默取第一个。
24
+ * Sources often wrap rows in an envelope (item/data/stock_items); prefer known keys, and list
25
+ * candidates instead of silently guessing when several arrays match.
32
26
  */
33
27
  export function extractRows(value) {
34
28
  if (Array.isArray(value))
@@ -50,7 +44,6 @@ export function extractRows(value) {
50
44
  }
51
45
  throw new ChartError('chart_no_rows', 'the JSON object contains no row array', { object_keys: Object.keys(value).slice(0, MAX_FIELD_HINTS) });
52
46
  }
53
- /** 字段候选清单:按出现顺序去重,最多 MAX_FIELD_HINTS 个。 */
54
47
  function collectFields(records) {
55
48
  const seen = [];
56
49
  const set = new Set();
@@ -66,12 +59,11 @@ function collectFields(records) {
66
59
  }
67
60
  return seen;
68
61
  }
62
+ /** Hints live in the message so the model can self-correct even if details are dropped. */
69
63
  function assertFields(fields, available) {
70
64
  const missing = [...new Set(fields.filter((field) => field !== undefined))].filter((field) => !available.includes(field));
71
65
  if (missing.length === 0)
72
66
  return;
73
- // 候选清单写进 message:模型看到的错误文本就是自愈所需的全部信息,
74
- // 不依赖调用方是否把 details 透传出去。
75
67
  const hints = available.slice(0, MAX_FIELD_HINTS);
76
68
  const suffix = hints.length > 0 ? `; available fields: ${hints.join(', ')}${available.length > hints.length ? ` (共 ${available.length} 个,其余已省略)` : ''}` : '';
77
69
  throw new ChartError('chart_field_not_found', `field not found in the data: ${missing.join(', ')}${suffix}`, {
@@ -80,7 +72,7 @@ function assertFields(fields, available) {
80
72
  available_total: available.length,
81
73
  });
82
74
  }
83
- /** 数值读取:数字直接用,纯数字字符串兼容(与 Dataset schema 的 allow_numeric_string 同源)。 */
75
+ /** Numbers pass through; pure numeric strings are accepted (same tolerance as Dataset allow_numeric_string). */
84
76
  function toNumber(value) {
85
77
  if (typeof value === 'number')
86
78
  return Number.isFinite(value) ? value : undefined;
@@ -98,11 +90,8 @@ function toNumber(value) {
98
90
  const DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
99
91
  const DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?$/;
100
92
  /**
101
- * 时间值解析。规则必须**确定**(同一份数据在任何机器上得到同一张图):
102
- * - 纯日期 `YYYY-MM-DD` → 业务日字符串,交给图表库按业务日显示(不做时区换算,避免差一天);
103
- * - `YYYY-MM-DD HH:mm[:ss]` 无时区 → 按 UTC 解释(显式规则,不依赖宿主本地时区);
104
- * - 数字/纯数字字符串 → epoch;`>= 1e11` 视为毫秒,否则视为秒;
105
- * - 其余字符串 → `Date.parse`;失败即视为该行没有时间。
93
+ * Deterministic across machines: date-only stays a business-day string (no TZ shift), naive
94
+ * datetime is UTC, numbers >= 1e11 are epoch ms, unparseable means the row has no time.
106
95
  */
107
96
  export function parseTimeValue(value) {
108
97
  if (typeof value === 'number') {
@@ -157,23 +146,17 @@ export function buildChartPayload(input) {
157
146
  assertFields([spec.ohlc.open, spec.ohlc.high, spec.ohlc.low, spec.ohlc.close], available);
158
147
  if (spec.volume)
159
148
  assertFields([spec.volume.field], available);
160
- // ── 1. 时间轴判定 ─────────────────────────────────────────────────────────
161
- // 显式给了 x 但值不像时间(分类数据)时,自动降级为分类轴并用 x 的取值做刻度标签,
162
- // 而不是报错让模型改口——"用 x 画柱状图"是最自然的表达。
149
+ // Explicit x that rarely parses as time degrades to a category axis instead of erroring.
163
150
  const axisField = spec.timeField;
164
151
  let axis = axisField === undefined ? 'index' : 'time';
165
- // OHLC 图形(candlestick / bar)不允许降级:图表库的 OhlcData 需要真实有序时间轴,
166
- // 拿分类轴硬画只会得到一张错位的图。
152
+ // OHLC kinds cannot degrade: the library needs a real ordered time axis, else the result is misaligned.
167
153
  if (axisField !== undefined && !OHLC_KINDS.includes(spec.kind)) {
168
- // 后续预处理本来就会逐行解析时间;这里也统计全量,避免前 100 行的脏数据把整条
169
- // 后面的真实时间序列误判成分类轴。
170
154
  const parsed = records.filter((record) => parseTimeValue(record[axisField]) !== undefined).length;
171
155
  if (parsed / records.length < TIME_AXIS_THRESHOLD) {
172
156
  axis = 'index';
173
157
  warnings.push(`x field "${axisField}" was treated as a category axis: only ${parsed}/${records.length} values look like time`);
174
158
  }
175
159
  }
176
- // ── 2. 逐行预处理 ────────────────────────────────────────────────────────
177
160
  const prepared = [];
178
161
  let skippedRows = 0;
179
162
  records.forEach((record, sourceIndex) => {
@@ -197,14 +180,12 @@ export function buildChartPayload(input) {
197
180
  if (skippedRows > 0)
198
181
  warnings.push(`${skippedRows} row(s) skipped: the time field is missing or unparseable`);
199
182
  if (prepared.length === 0) {
200
- // OHLC 图形必须给可解析的时间列:它不是"降级"问题,而是这张图根本画不出来。
201
- // 错误必须直接把模型引到能用的 kind 上,而不是让它反复试同一个 spec。
183
+ // OHLC has no fallback; point the model at column/line instead of retrying the same spec.
202
184
  if (OHLC_KINDS.includes(spec.kind)) {
203
185
  throw new ChartError('chart_no_rows', `${spec.kind} 需要可解析的时间列,但字段 "${axisField ?? '(未提供 x)'}" 的值都解析不出日期或时间戳;分类数据请改用 kind=column(单值柱)或 line`);
204
186
  }
205
187
  throw new ChartError('chart_no_rows', 'no row has a usable time value');
206
188
  }
207
- // ── 3. 排序 + 去重 + 区间过滤 ──────────────────────────────────────────────
208
189
  const ordered = [...prepared].sort((left, right) => left.time.seconds - right.time.seconds);
209
190
  if (ordered.some((row, index) => row !== prepared[index]))
210
191
  warnings.push('rows were reordered ascending by time (the chart library requires an ordered axis)');
@@ -233,19 +214,17 @@ export function buildChartPayload(input) {
233
214
  if (filtered.length === 0)
234
215
  throw new ChartError('chart_no_rows', 'spec.range excludes every row');
235
216
  }
236
- // ── 4. 下采样(一次求下标,所有子序列共用)────────────────────────────────
217
+ // One LTTB index set is shared by every subplot so they stay time-aligned.
237
218
  const originalPoints = filtered.length;
238
219
  const primaryValues = primarySeriesValues(filtered, spec);
239
220
  const keep = originalPoints > MAX_CHART_POINTS ? largestTriangleThreeBuckets(primaryValues, MAX_CHART_POINTS) : filtered.map((_row, index) => index);
240
221
  const kept = keep.map((index) => filtered[index]).filter((row) => row !== undefined);
241
222
  const downsampled = kept.length < originalPoints;
242
- // ── 5. 时间显示口径:全是纯日期才用业务日字符串 ────────────────────────────
243
223
  const useBusinessDay = axis === 'time' && kept.every((row) => row.time.dateOnly);
244
224
  if (axis === 'time' && !useBusinessDay && kept.some((row) => row.time.dateOnly)) {
245
225
  warnings.push('mixed date and datetime values; the axis is rendered as timestamps');
246
226
  }
247
227
  const displayTime = (row) => (useBusinessDay ? String(row.time.display) : row.time.seconds);
248
- // ── 6. 子序列 ────────────────────────────────────────────────────────────
249
228
  const series = [];
250
229
  for (const item of spec.series.slice(0, MAX_CHART_SERIES)) {
251
230
  const data = [];
@@ -268,8 +247,7 @@ export function buildChartPayload(input) {
268
247
  if (spec.ohlc) {
269
248
  const data = [];
270
249
  let dropped = 0;
271
- // OHLC 自洽性检查:脏数据确实存在,所以**不拦截**(拦截会让用户看不到任何图),
272
- // 但必须如实披露,否则读者会把一根 high<low 的错柱子当成真行情。
250
+ // Inconsistent candles exist in real data: disclose them, never block — blocking hides the chart.
273
251
  let inconsistent = 0;
274
252
  const inconsistentExamples = [];
275
253
  for (const row of kept) {
@@ -308,7 +286,7 @@ export function buildChartPayload(input) {
308
286
  dropped += 1;
309
287
  continue;
310
288
  }
311
- // A 股习惯:涨红跌绿。方向由同一行的 OHLC 推得,颜色映射留给运行时(跟随主题)。
289
+ // A-share convention: up = red, down = green; the actual colors come from the runtime theme.
312
290
  let direction;
313
291
  if (spec.ohlc) {
314
292
  const open = toNumber(row.raw[spec.ohlc.open]);
@@ -324,12 +302,10 @@ export function buildChartPayload(input) {
324
302
  warnings.push(`volume: ${dropped} point(s) dropped (non-numeric or missing)`);
325
303
  volume = { label: '成交量', data };
326
304
  }
327
- // ── 7. 标记点:吸附到保留下来的时间点 ──────────────────────────────────────
328
305
  const markers = [];
329
306
  const keptSeconds = kept.map((row) => row.time.seconds);
330
307
  for (const marker of spec.markers.slice(0, MAX_CHART_MARKERS)) {
331
- // 两种定位都先化成"目标秒数",再吸附到**保留**的时间点上:下采样把某个点丢掉时,
332
- // 标记不该跟着消失(那正是用户最在意的那一天)。
308
+ // Resolve both forms to seconds, then snap to a kept point so downsampling cannot drop a marker.
333
309
  let targetSeconds;
334
310
  if (marker.index !== undefined) {
335
311
  const atIndex = filtered[marker.index];
@@ -383,7 +359,7 @@ export function buildChartPayload(input) {
383
359
  },
384
360
  };
385
361
  }
386
- /** 主序列取值(用于 LTTB);K 线用收盘价,其余用第一个数值字段。 */
362
+ /** LTTB source: close for OHLC, else the first numeric field. Field gaps carry the last value forward. */
387
363
  function primarySeriesValues(rows, spec) {
388
364
  const field = spec.ohlc ? spec.ohlc.close : spec.series[0]?.field ?? spec.volume?.field;
389
365
  const values = [];
@@ -395,7 +371,6 @@ function primarySeriesValues(rows, spec) {
395
371
  }
396
372
  return values;
397
373
  }
398
- /** 把标记点吸附到最近的**保留**时间点,避免下采样后标记消失。 */
399
374
  function nearestRow(rows, keptSeconds, target) {
400
375
  if (rows.length === 0)
401
376
  return undefined;
@@ -1,10 +1,7 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  import { type SessionLike, WorkspaceDatasetStore } from '../data-collector/store.js';
3
- /**
4
- * The registry is host-only and never serializes this handle into Agent output.
5
- * Keeping the live Session object is required because DSH sandbox policies use
6
- * session.snapshotEvents() when resolving workspace permissions.
7
- */
3
+ /** Host-only; never serialized into agent output. Keep the live Session: sandbox policies call
4
+ * session.snapshotEvents() to resolve workspace permissions. */
8
5
  export interface ChartSourceTokenRef {
9
6
  chart_source_ref: string;
10
7
  dataset_id: string;
@@ -21,13 +18,8 @@ export interface ChartSourceTokenStoreOptions {
21
18
  ttlMs?: number;
22
19
  newToken?: () => string;
23
20
  }
24
- /**
25
- * Short-lived capability tokens for nested visualization children.
26
- *
27
- * The token captures the direct data-agent session rather than asking a nested
28
- * child to re-prove the Dataset's one-hop session scope. Possession of the
29
- * opaque token is the capability; it is never converted into a filesystem path.
30
- */
21
+ /** Short-lived capability tokens for nested visualization children. Possession is the capability;
22
+ * the token never becomes a filesystem path or raw rows. */
31
23
  export declare class ChartSourceTokenStore {
32
24
  private readonly tokens;
33
25
  private readonly now;
@@ -51,7 +43,7 @@ export declare class ChartSourceTokenStore {
51
43
  revoke(chartSourceRef: string): boolean;
52
44
  size(): number;
53
45
  }
54
- /** Register the delegated-child-only token issuer. */
46
+ /** Preset-based authorization; see authorizedChartSession. */
55
47
  export declare function registerChartSourceTool(ctx: Context, options: {
56
48
  store: WorkspaceDatasetStore;
57
49
  tokens: ChartSourceTokenStore;