@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.
- package/CHANGELOG.md +81 -1
- package/README.md +40 -19
- package/chart-ui/README.md +5 -4
- package/chart-ui/client.src.cjs +7 -16
- package/chart-ui/index.js +12 -33
- package/chart-ui/package.json +2 -2
- package/config/client.js +18 -39
- package/config/client.src.cjs +79 -217
- package/config/index.js +22 -72
- package/config/package.json +2 -2
- package/cordis.patch.yml +46 -51
- package/lib/agents/bash-guard.d.ts +9 -55
- package/lib/agents/bash-guard.js +27 -67
- package/lib/agents/preset-roster.d.ts +22 -0
- package/lib/agents/preset-roster.js +87 -0
- package/lib/agents/role-tool-policy.d.ts +46 -0
- package/lib/agents/role-tool-policy.js +205 -0
- package/lib/agents/root-tool-policy.d.ts +28 -124
- package/lib/agents/root-tool-policy.js +164 -237
- package/lib/agents/selected-skills-provider.d.ts +2 -6
- package/lib/agents/selected-skills-provider.js +27 -84
- package/lib/capital-settings.d.ts +27 -0
- package/lib/capital-settings.js +23 -0
- package/lib/chart/artifact-ref.d.ts +13 -8
- package/lib/chart/artifact-ref.js +27 -10
- package/lib/chart/downsample.d.ts +2 -5
- package/lib/chart/downsample.js +3 -8
- package/lib/chart/errors.d.ts +3 -7
- package/lib/chart/errors.js +1 -2
- package/lib/chart/event-context.d.ts +8 -0
- package/lib/chart/event-context.js +39 -0
- package/lib/chart/events.d.ts +21 -78
- package/lib/chart/events.js +43 -189
- package/lib/chart/html.d.ts +2 -5
- package/lib/chart/html.js +8 -15
- package/lib/chart/present-tool.d.ts +28 -0
- package/lib/chart/present-tool.js +128 -0
- package/lib/chart/runtime.d.ts +5 -18
- package/lib/chart/runtime.js +14 -34
- package/lib/chart/series.d.ts +10 -17
- package/lib/chart/series.js +22 -47
- package/lib/chart/source-token.d.ts +5 -13
- package/lib/chart/source-token.js +18 -17
- package/lib/chart/spec.d.ts +6 -12
- package/lib/chart/spec.js +7 -31
- package/lib/chart/tool.d.ts +7 -29
- package/lib/chart/tool.js +15 -25
- package/lib/chart-render-entry.d.ts +10 -0
- package/lib/chart-render-entry.js +14 -0
- package/lib/chart-source-entry.d.ts +13 -0
- package/lib/chart-source-entry.js +20 -0
- package/lib/collect-entry.d.ts +9 -0
- package/lib/collect-entry.js +10 -0
- package/lib/collector-runtime.d.ts +22 -0
- package/lib/collector-runtime.js +97 -0
- package/lib/data-collector/args.d.ts +6 -24
- package/lib/data-collector/args.js +6 -24
- package/lib/data-collector/dataset-tools.d.ts +7 -1
- package/lib/data-collector/dataset-tools.js +27 -56
- package/lib/data-collector/describe.d.ts +8 -33
- package/lib/data-collector/describe.js +17 -70
- package/lib/data-collector/hub.d.ts +27 -85
- package/lib/data-collector/hub.js +19 -85
- package/lib/data-collector/query-time.d.ts +7 -26
- package/lib/data-collector/query-time.js +8 -27
- package/lib/data-collector/store.d.ts +41 -117
- package/lib/data-collector/store.js +32 -111
- package/lib/data-collector/time-axis.d.ts +16 -76
- package/lib/data-collector/time-axis.js +25 -111
- package/lib/data-collector/tools.d.ts +6 -6
- package/lib/data-collector/tools.js +34 -43
- package/lib/dataset-read-entry.d.ts +9 -0
- package/lib/dataset-read-entry.js +7 -0
- package/lib/index.d.ts +7 -117
- package/lib/index.js +43 -422
- package/lib/net/eastmoney-client.d.ts +13 -65
- package/lib/net/eastmoney-client.js +12 -53
- package/lib/net/throttle.d.ts +2 -13
- package/lib/net/throttle.js +3 -14
- package/lib/ocr/client.d.ts +12 -21
- package/lib/ocr/client.js +23 -40
- package/lib/ocr/markdown.d.ts +20 -26
- package/lib/ocr/markdown.js +23 -36
- package/lib/ocr/store.d.ts +14 -47
- package/lib/ocr/store.js +15 -45
- package/lib/ocr-entry.d.ts +9 -0
- package/lib/ocr-entry.js +10 -0
- package/lib/policy-entry.d.ts +17 -0
- package/lib/policy-entry.js +22 -0
- package/lib/present-entry.d.ts +9 -0
- package/lib/present-entry.js +13 -0
- package/lib/preset-claim.d.ts +22 -0
- package/lib/preset-claim.js +24 -0
- package/lib/preset-ids.d.ts +38 -0
- package/lib/preset-ids.js +46 -0
- package/lib/retriever-config.d.ts +37 -0
- package/lib/retriever-config.js +36 -0
- package/lib/retriever-runtime.d.ts +31 -0
- package/lib/retriever-runtime.js +82 -0
- package/lib/root-extras-entry.d.ts +17 -0
- package/lib/root-extras-entry.js +25 -0
- package/lib/runtime-entry.d.ts +15 -0
- package/lib/runtime-entry.js +5 -0
- package/lib/runtime.d.ts +21 -0
- package/lib/runtime.js +33 -0
- package/lib/search-entry.d.ts +9 -0
- package/lib/search-entry.js +9 -0
- package/lib/sources/eastmoney-http.js +20 -68
- package/lib/sources/ftshare-rest.d.ts +6 -34
- package/lib/sources/ftshare-rest.js +84 -292
- package/lib/sources/fuyao-core.d.ts +8 -18
- package/lib/sources/fuyao-core.js +10 -21
- package/lib/sources/fuyao-rest.d.ts +2 -6
- package/lib/sources/fuyao-rest.js +23 -102
- package/lib/sources/security-code.d.ts +6 -12
- package/lib/sources/security-code.js +6 -12
- package/lib/sources/security-identity.d.ts +1 -4
- package/lib/sources/security-identity.js +3 -8
- package/lib/sources/tencent-http.js +14 -28
- package/lib/sources/tencent-public-core.d.ts +8 -60
- package/lib/sources/tencent-public-core.js +12 -77
- package/lib/sources/wind-mcp.d.ts +0 -5
- package/lib/sources/wind-mcp.js +23 -78
- package/lib/sources-entry.d.ts +9 -0
- package/lib/sources-entry.js +9 -0
- package/lib/time/tools.d.ts +8 -31
- package/lib/time/tools.js +30 -66
- package/lib/time-entry.d.ts +9 -0
- package/lib/time-entry.js +7 -0
- package/lib/tool-exec.d.ts +45 -11
- package/lib/tool-exec.js +59 -6
- package/lib/user-customization.d.ts +14 -0
- package/lib/user-customization.js +47 -0
- package/lib/watchlist/tools.d.ts +3 -13
- package/lib/watchlist/tools.js +10 -30
- package/lib/watchlist-entry.d.ts +9 -0
- package/lib/watchlist-entry.js +6 -0
- package/lib/web-retriever/engines.d.ts +6 -11
- package/lib/web-retriever/engines.js +9 -14
- package/lib/web-retriever/html-markdown.d.ts +14 -34
- package/lib/web-retriever/html-markdown.js +39 -75
- package/lib/web-retriever/local-fetch.d.ts +22 -58
- package/lib/web-retriever/local-fetch.js +33 -77
- package/lib/web-retriever/retriever.d.ts +6 -18
- package/lib/web-retriever/retriever.js +7 -7
- package/lib/web-retriever/sources.d.ts +37 -124
- package/lib/web-retriever/sources.js +64 -196
- package/lib/web-retriever/tools.d.ts +43 -27
- package/lib/web-retriever/tools.js +99 -137
- package/lib/web-retriever/wind-client.d.ts +14 -31
- package/lib/web-retriever/wind-client.js +16 -32
- package/locale/zh.json +1 -1
- package/package.json +72 -7
- package/preset/capital-generation/README.md +87 -53
- package/preset/capital-generation/agent.patch.yml +532 -337
- package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +6 -4
- package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
- package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +4 -2
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +30 -33
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +14 -16
- package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
- package/watchlist/client.js +15 -148
- package/watchlist/client.src.cjs +148 -662
- package/watchlist/holding-rules.js +11 -38
- package/watchlist/index.js +127 -397
- 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
|
+
}
|
package/lib/chart/runtime.d.ts
CHANGED
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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";
|
package/lib/chart/runtime.js
CHANGED
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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)
|
package/lib/chart/series.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChartKind, ChartSeriesType, MarkerPosition, NormalizedChartSpec } from './spec.js';
|
|
2
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
71
|
+
/** Value handed to the library: 'YYYY-MM-DD' business day, or UTC seconds. */
|
|
76
72
|
display: string | number;
|
|
77
|
-
/**
|
|
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
|
-
*
|
|
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 {
|
package/lib/chart/series.js
CHANGED
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
13
|
+
/** Share of x values that must parse as time to keep a time axis. */
|
|
16
14
|
const TIME_AXIS_THRESHOLD = 0.8;
|
|
17
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
46
|
+
/** Preset-based authorization; see authorizedChartSession. */
|
|
55
47
|
export declare function registerChartSourceTool(ctx: Context, options: {
|
|
56
48
|
store: WorkspaceDatasetStore;
|
|
57
49
|
tokens: ChartSourceTokenStore;
|