@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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ChartError } from './errors.js';
|
|
2
2
|
import { DatasetStoreError, } from '../data-collector/store.js';
|
|
3
|
+
// Authorization has one implementation (src/tool-exec.ts); the error shape stays chart-specific.
|
|
4
|
+
import { authorizeToolSession } from '../tool-exec.js';
|
|
5
|
+
import { CHART_SOURCE_PRESET_IDS } from '../preset-ids.js';
|
|
3
6
|
const CHART_SOURCE_TOKEN_PATTERN = /^cs_[A-Za-z0-9_-]{1,128}$/;
|
|
4
7
|
const DEFAULT_CHART_SOURCE_TTL_MS = 15 * 60 * 1000;
|
|
5
8
|
const jsonObject = (properties = {}, required = []) => ({
|
|
@@ -25,10 +28,14 @@ function sessionOf(exec) {
|
|
|
25
28
|
}
|
|
26
29
|
return session;
|
|
27
30
|
}
|
|
28
|
-
|
|
31
|
+
/** Shared preset-based authorizer; chart_source_scope_mismatch shape stays here (test-pinned). */
|
|
32
|
+
function authorizedChartSession(ctx, exec) {
|
|
29
33
|
const session = sessionOf(exec);
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
const decision = authorizeToolSession(ctx, exec, 'prepare_chart_source', CHART_SOURCE_PRESET_IDS);
|
|
35
|
+
if (!decision.allowed) {
|
|
36
|
+
throw new ChartError('chart_source_scope_mismatch', decision.via === 'preset'
|
|
37
|
+
? `prepare_chart_source 不对人设 ${decision.presetId} 开放;签发出图读能力只属于数据角色`
|
|
38
|
+
: 'prepare_chart_source is restricted to delegated data agents');
|
|
32
39
|
}
|
|
33
40
|
return session;
|
|
34
41
|
}
|
|
@@ -55,13 +62,8 @@ function readToken(value) {
|
|
|
55
62
|
}
|
|
56
63
|
return value;
|
|
57
64
|
}
|
|
58
|
-
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* The token captures the direct data-agent session rather than asking a nested
|
|
62
|
-
* child to re-prove the Dataset's one-hop session scope. Possession of the
|
|
63
|
-
* opaque token is the capability; it is never converted into a filesystem path.
|
|
64
|
-
*/
|
|
65
|
+
/** Short-lived capability tokens for nested visualization children. Possession is the capability;
|
|
66
|
+
* the token never becomes a filesystem path or raw rows. */
|
|
65
67
|
export class ChartSourceTokenStore {
|
|
66
68
|
tokens = new Map();
|
|
67
69
|
now;
|
|
@@ -86,10 +88,8 @@ export class ChartSourceTokenStore {
|
|
|
86
88
|
const taskId = readTaskId(input.task_id, true);
|
|
87
89
|
const now = this.now();
|
|
88
90
|
this.purgeExpired(now);
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
// 重签,跟着就是 7.9K 字符的思考回合——skill 里的"不要重签"警告拦不住,所以把幂等沉到
|
|
92
|
-
// 工具层。token 是读能力而非一次性凭证(resolve 不消费),复用没有语义损失。
|
|
91
|
+
// Idempotent within TTL for the same session+task+dataset: re-issuing triggers huge thinking
|
|
92
|
+
// turns, and resolve() does not consume the token, so reuse has no semantic cost.
|
|
93
93
|
for (const record of this.tokens.values()) {
|
|
94
94
|
if (record.dataset_id === datasetId && record.task_id === taskId && record.sourceSession.id === input.session.id) {
|
|
95
95
|
return {
|
|
@@ -110,6 +110,7 @@ export class ChartSourceTokenStore {
|
|
|
110
110
|
}
|
|
111
111
|
if (this.tokens.has(token))
|
|
112
112
|
throw new ChartError('chart_source_invalid', 'generated chart_source_ref already exists');
|
|
113
|
+
// Never outlive the dataset's retention.
|
|
113
114
|
const expiresAt = Math.min(inspection.retention_until, now + this.ttlMs);
|
|
114
115
|
const record = {
|
|
115
116
|
chart_source_ref: token,
|
|
@@ -166,14 +167,14 @@ const outputSchema = jsonObject({
|
|
|
166
167
|
task_id: { type: 'string' },
|
|
167
168
|
expires_at: { type: 'integer' },
|
|
168
169
|
}, ['chart_source_ref', 'dataset_id', 'task_id', 'expires_at']);
|
|
169
|
-
/**
|
|
170
|
+
/** Preset-based authorization; see authorizedChartSession. */
|
|
170
171
|
export function registerChartSourceTool(ctx, options) {
|
|
171
172
|
const tools = ctx.get('tools');
|
|
172
173
|
if (!tools)
|
|
173
174
|
return;
|
|
174
175
|
const definition = {
|
|
175
176
|
name: 'prepare_chart_source',
|
|
176
|
-
description: '
|
|
177
|
+
description: '为**当前数据角色会话**已授权的 Dataset 签发短期 chart_source_ref,供 visualization_specialist 通过受控 render_chart 读取(授权按人设判定,被委派与独立开启的数据模式都能签发)。' +
|
|
177
178
|
'只返回 opaque token 和小型元数据,不返回原始数据、文件路径或数据行;必须提供 dataset_id,可选 task_id。' +
|
|
178
179
|
'同一 task_id + dataset_id 重复调用幂等返回同一个 chart_source_ref(TTL 内),改了视图方案直接复用即可。',
|
|
179
180
|
parameters: jsonObject({
|
|
@@ -183,7 +184,7 @@ export function registerChartSourceTool(ctx, options) {
|
|
|
183
184
|
output: { schema: outputSchema, render },
|
|
184
185
|
async execute(args, exec) {
|
|
185
186
|
try {
|
|
186
|
-
const session =
|
|
187
|
+
const session = authorizedChartSession(ctx, exec);
|
|
187
188
|
return await options.tokens.issue({
|
|
188
189
|
store: options.store,
|
|
189
190
|
session,
|
package/lib/chart/spec.d.ts
CHANGED
|
@@ -15,14 +15,14 @@ export interface ChartOhlcSpec {
|
|
|
15
15
|
export interface ChartMarkerSpec {
|
|
16
16
|
text: string;
|
|
17
17
|
position: MarkerPosition;
|
|
18
|
-
/**
|
|
18
|
+
/** Time value matching x; mutually exclusive with index. */
|
|
19
19
|
time?: string | number;
|
|
20
|
-
/**
|
|
20
|
+
/** 0-based row index into the filtered series. */
|
|
21
21
|
index?: number;
|
|
22
22
|
}
|
|
23
23
|
export interface NormalizedChartSpec {
|
|
24
24
|
kind: ChartKind;
|
|
25
|
-
/** time =
|
|
25
|
+
/** time = real time column; index = row order (category axis, labels mapped at render time). */
|
|
26
26
|
axis: 'time' | 'index';
|
|
27
27
|
timeField?: string;
|
|
28
28
|
series: ChartSeriesSpec[];
|
|
@@ -38,14 +38,8 @@ export interface NormalizedChartSpec {
|
|
|
38
38
|
title?: string;
|
|
39
39
|
}
|
|
40
40
|
export declare const CHART_KINDS: readonly ChartKind[];
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* 依据是图表库的数据契约本身:`CandlestickData` 与 `BarData` 都继承 `OhlcData`,
|
|
45
|
-
* 而 `LineData` / `HistogramData` 继承 `SingleValueData`。把单值序列塞进 BarSeries
|
|
46
|
-
* 不会报错,只会画出一张**空图** —— 所以这个约束必须在 spec 层硬拦,
|
|
47
|
-
* 不能指望"模型会写对"(这是实测出来的坑,不是推演)。
|
|
48
|
-
*/
|
|
41
|
+
/** OHLC kinds need four fields (library OhlcData vs SingleValueData); a single-value series in
|
|
42
|
+
* BarSeries silently draws an empty chart, so reject it at the spec layer. */
|
|
49
43
|
export declare const OHLC_KINDS: readonly ChartKind[];
|
|
50
44
|
export declare const CHART_SERIES_TYPES: readonly ChartSeriesType[];
|
|
51
45
|
export declare const MARKER_POSITIONS: readonly MarkerPosition[];
|
|
@@ -53,6 +47,6 @@ export declare const MAX_CHART_SERIES = 6;
|
|
|
53
47
|
export declare const MAX_CHART_MARKERS = 50;
|
|
54
48
|
export declare const MAX_FIELD_NAME = 128;
|
|
55
49
|
export declare const MAX_CHART_TITLE = 200;
|
|
56
|
-
/**
|
|
50
|
+
/** Shared model-facing kind description so error text and tool docs cannot drift. */
|
|
57
51
|
export declare function describeKinds(): string;
|
|
58
52
|
export declare function normalizeChartSpec(raw: unknown): NormalizedChartSpec;
|
package/lib/chart/spec.js
CHANGED
|
@@ -1,24 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* 设计要点(见内部设计文档 docs/design/chart-visualization.md §3.3):
|
|
5
|
-
* - spec 用**自由对象**表达,协议细节写在 skill 里按需加载,常驻的只有工具 schema——
|
|
6
|
-
* 这是"可视化不污染上下文"的关键:模型不需要在每轮都看一遍图表 DSL。
|
|
7
|
-
* - 但自由对象不等于放任:这里做一次严格归一化,错误必须**可自愈**(点名字段 + 给候选)。
|
|
8
|
-
* - 与 query_dataset 同源的宽容度:模型经常把对象/数组序列化成 JSON 字符串再传
|
|
9
|
-
* (实测教训见 src/data-collector/dataset-tools.ts 的注释),这里在**工具边界**
|
|
10
|
-
* 做一次宽容解析,内部保持严格。
|
|
11
|
-
*/
|
|
1
|
+
/** Normalize/validate the chart spec at the tool boundary. Errors must be self-correcting
|
|
2
|
+
* (name the field, list candidates); JSON-string forms are tolerated here, strict inside. */
|
|
12
3
|
import { ChartError } from './errors.js';
|
|
13
4
|
export const CHART_KINDS = ['line', 'area', 'column', 'candlestick', 'bar'];
|
|
14
|
-
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 依据是图表库的数据契约本身:`CandlestickData` 与 `BarData` 都继承 `OhlcData`,
|
|
18
|
-
* 而 `LineData` / `HistogramData` 继承 `SingleValueData`。把单值序列塞进 BarSeries
|
|
19
|
-
* 不会报错,只会画出一张**空图** —— 所以这个约束必须在 spec 层硬拦,
|
|
20
|
-
* 不能指望"模型会写对"(这是实测出来的坑,不是推演)。
|
|
21
|
-
*/
|
|
5
|
+
/** OHLC kinds need four fields (library OhlcData vs SingleValueData); a single-value series in
|
|
6
|
+
* BarSeries silently draws an empty chart, so reject it at the spec layer. */
|
|
22
7
|
export const OHLC_KINDS = ['candlestick', 'bar'];
|
|
23
8
|
export const CHART_SERIES_TYPES = ['line', 'area', 'column'];
|
|
24
9
|
export const MARKER_POSITIONS = ['aboveBar', 'belowBar', 'inBar'];
|
|
@@ -26,14 +11,7 @@ export const MAX_CHART_SERIES = 6;
|
|
|
26
11
|
export const MAX_CHART_MARKERS = 50;
|
|
27
12
|
export const MAX_FIELD_NAME = 128;
|
|
28
13
|
export const MAX_CHART_TITLE = 200;
|
|
29
|
-
/**
|
|
30
|
-
* 容忍的说法 → canonical 名。
|
|
31
|
-
*
|
|
32
|
-
* `histogram` 是图表库的类名,但它画的是**着色柱**、不是统计分箱直方图;直接把它当
|
|
33
|
-
* canonical 名会让模型以为"可以画分布"。canonical 用 `column`,同时接受旧说法以免硬失败。
|
|
34
|
-
* 同理 `bar` 在中文语境里常被当成"柱状图",而它其实是 OHLC 柱:单值柱状图会得到一条
|
|
35
|
-
* 明确指向 `column` 的错误,而不是一张空图。
|
|
36
|
-
*/
|
|
14
|
+
/** `histogram` maps to `column` (a colored bar, not a statistical bin); `bar` is an OHLC bar. */
|
|
37
15
|
const KIND_ALIASES = {
|
|
38
16
|
histogram: 'column',
|
|
39
17
|
candles: 'candlestick',
|
|
@@ -42,14 +20,14 @@ const KIND_ALIASES = {
|
|
|
42
20
|
candlestick_bar: 'bar',
|
|
43
21
|
};
|
|
44
22
|
const SERIES_TYPE_ALIASES = { histogram: 'column' };
|
|
45
|
-
/**
|
|
23
|
+
/** Shared model-facing kind description so error text and tool docs cannot drift. */
|
|
46
24
|
export function describeKinds() {
|
|
47
25
|
return `${CHART_KINDS.join(' / ')}(candlestick 与 bar 需要 ohlc 四字段与真实时间列;单值柱状图用 column,本库没有统计分箱直方图)`;
|
|
48
26
|
}
|
|
49
27
|
function isRecord(value) {
|
|
50
28
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
51
29
|
}
|
|
52
|
-
/**
|
|
30
|
+
/** Tolerate JSON-string spec fragments at the tool boundary. */
|
|
53
31
|
function coerceObject(value) {
|
|
54
32
|
if (isRecord(value))
|
|
55
33
|
return value;
|
|
@@ -66,7 +44,6 @@ function coerceObject(value) {
|
|
|
66
44
|
return undefined;
|
|
67
45
|
}
|
|
68
46
|
}
|
|
69
|
-
/** 宽容解析对象数组:真数组、JSON 数组字符串、单个对象(或其 JSON 字符串)都接受。 */
|
|
70
47
|
function coerceObjectList(value) {
|
|
71
48
|
if (Array.isArray(value))
|
|
72
49
|
return value;
|
|
@@ -106,7 +83,6 @@ function readOptionalFieldName(value, where) {
|
|
|
106
83
|
return undefined;
|
|
107
84
|
return readFieldName(value, where);
|
|
108
85
|
}
|
|
109
|
-
/** 取第一个出现的别名键;用于容忍模型用 x / time / time_field 等等价说法。 */
|
|
110
86
|
function pickAlias(source, names) {
|
|
111
87
|
for (const name of names)
|
|
112
88
|
if (source[name] !== undefined)
|
package/lib/chart/tool.d.ts
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* 上下文经济学(这是本工具存在的理由):
|
|
5
|
-
* - 常驻成本 = 一条工具 schema;协议细节在 skill `capital-chart-protocol` 里按需加载;
|
|
6
|
-
* - 每次调用成本 = spec(几十到一百多 token)+ 回执(几十 token);
|
|
7
|
-
* - 序列数据 **0 token**:由宿主写进 workspace 产物,浏览器走旁路取。
|
|
8
|
-
* 对照:让模型把 250 点日线写进参数 ≈ 5–8k token,还会被工具结果剪枝器截断。
|
|
9
|
-
*
|
|
10
|
-
* 权限边界(docs/dev/data-roles.md §1.1「数据调度纪律」的显式豁免):本工具是**呈现**工具,不是数据工具。
|
|
11
|
-
* 它读 Dataset 只在宿主进程内进行,回执不含任何 rows —— 因此不越过"数据内容不进主 Agent
|
|
12
|
-
* 上下文"这条真正的边界。需要数值结论仍走 data_junior 的 profile / query。
|
|
13
|
-
*/
|
|
1
|
+
/** render_chart: render data into workspace artifacts and return only a small receipt.
|
|
2
|
+
* Series costs zero model tokens and rows stay host-side; numeric conclusions still go through
|
|
3
|
+
* data_junior. */
|
|
14
4
|
import type { Context } from '@deepseek-ai/cordis';
|
|
15
5
|
import { WorkspaceDatasetStore } from '../data-collector/store.js';
|
|
16
6
|
import { type ChartSourceTokenStore } from './source-token.js';
|
|
@@ -22,6 +12,7 @@ type AgentExecutionLike = {
|
|
|
22
12
|
header?: {
|
|
23
13
|
cwd?: string;
|
|
24
14
|
parentSession?: string;
|
|
15
|
+
origin?: 'subagent';
|
|
25
16
|
};
|
|
26
17
|
};
|
|
27
18
|
};
|
|
@@ -30,7 +21,7 @@ type ToolExecLike = {
|
|
|
30
21
|
signal: AbortSignal;
|
|
31
22
|
};
|
|
32
23
|
export declare const CHART_ARTIFACT_DIR = "capital-analysis/charts";
|
|
33
|
-
/**
|
|
24
|
+
/** Receipt is small metadata only; series never appears here. */
|
|
34
25
|
export interface ChartReceipt {
|
|
35
26
|
chart_ref: string;
|
|
36
27
|
chart_id: string;
|
|
@@ -62,22 +53,9 @@ export interface RenderChartInput {
|
|
|
62
53
|
store: WorkspaceDatasetStore;
|
|
63
54
|
sourceTokens?: ChartSourceTokenStore;
|
|
64
55
|
artifacts?: ChartArtifactRegistry;
|
|
65
|
-
/**
|
|
66
|
-
* 惰性解析 host 平面图表服务(`@v587d/capital-charts` 提供)。
|
|
67
|
-
*
|
|
68
|
-
* 每次画图时解析,而不是在 `apply` 时解析一次:host 平面行与本 preset 行的挂载先后
|
|
69
|
-
* 不由我们决定,一次性解析会把"服务稍后才可用"永久固化成 `chart_url=null`。
|
|
70
|
-
* 服务始终缺席(未安装/无 Web 载体/坏掉)时返回 undefined,整条链路照常降级:产物照样
|
|
71
|
-
* 落盘、`html_path` 照样可 present,只是 `chart_url` 为 null。
|
|
72
|
-
*/
|
|
56
|
+
/** Resolve per render, not at apply: host rows may mount later; absence degrades to chart_url=null. */
|
|
73
57
|
charts?: () => ChartPublisher | undefined;
|
|
74
|
-
/**
|
|
75
|
-
* 惰性解析"图表 → 本轮交付"发布器(官方 `deliverables/presented`)。
|
|
76
|
-
*
|
|
77
|
-
* 与 `charts` 同理每次出图解析一次:宿主平面的 `sessions` / `sessionProjections`
|
|
78
|
-
* 不可用时返回 undefined,整条链路照常降级——图仍在 workspace 里,回执照样给 html_path,
|
|
79
|
-
* 只是本轮交付行不会登记这张图。
|
|
80
|
-
*/
|
|
58
|
+
/** Same lazy rule: an absent publisher skips only the deliverable row. */
|
|
81
59
|
chartEvents?: () => ChartEventPublisher | undefined;
|
|
82
60
|
now?: () => number;
|
|
83
61
|
}
|
package/lib/chart/tool.js
CHANGED
|
@@ -56,10 +56,7 @@ function normalizeStoreError(error) {
|
|
|
56
56
|
return undefined;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
/**
|
|
60
|
-
* 工具框架对 throw 的错误只保留 message;把 ChartError 的 details 在这里展开成
|
|
61
|
-
* JSON 信封,模型才能拿到 array_keys / available 等一次修正所需的信息。
|
|
62
|
-
*/
|
|
59
|
+
/** Errors keep only message; expand details into JSON so the model can self-correct. */
|
|
63
60
|
function toolVisibleError(error) {
|
|
64
61
|
const normalized = error instanceof ChartError ? error : normalizeStoreError(error);
|
|
65
62
|
if (!normalized)
|
|
@@ -115,7 +112,7 @@ export async function renderChart(input, args, exec) {
|
|
|
115
112
|
if (sourceCount !== 1) {
|
|
116
113
|
throw new ChartError('chart_source_invalid', 'provide exactly one data source: dataset_id, path, or chart_source_ref');
|
|
117
114
|
}
|
|
118
|
-
if (session.header?.
|
|
115
|
+
if (session.header?.origin === 'subagent' && chartSourceRef === undefined) {
|
|
119
116
|
throw new ChartError('chart_source_scope_mismatch', 'delegated visualization callers must use chart_source_ref');
|
|
120
117
|
}
|
|
121
118
|
const tokenSource = chartSourceRef === undefined ? undefined : chartSourceTokenForRender(input.sourceTokens, {
|
|
@@ -126,13 +123,10 @@ export async function renderChart(input, args, exec) {
|
|
|
126
123
|
const spec = normalizeChartSpec(args.spec);
|
|
127
124
|
if (typeof args.title === 'string' && args.title.trim().length > 0)
|
|
128
125
|
spec.title = args.title.trim().slice(0, 200);
|
|
129
|
-
//
|
|
126
|
+
// The token carries the direct data-agent session; the nested child never sees raw rows.
|
|
130
127
|
let rows;
|
|
131
128
|
let meta;
|
|
132
129
|
if (datasetId !== undefined) {
|
|
133
|
-
// A chart_source_ref carries the direct data-agent session that already has
|
|
134
|
-
// access to the parent Dataset scope. The nested visualization child is
|
|
135
|
-
// only the caller of render_chart; it never receives raw rows.
|
|
136
130
|
const readSession = tokenSource?.sourceSession ?? session;
|
|
137
131
|
const { ref, rows: datasetRows } = await input.store.readPresentationRows({ session: readSession, dataset_id: datasetId, signal: exec.signal });
|
|
138
132
|
rows = datasetRows;
|
|
@@ -144,7 +138,6 @@ export async function renderChart(input, args, exec) {
|
|
|
144
138
|
rows = extracted.rows;
|
|
145
139
|
meta = { source_kind: 'path' };
|
|
146
140
|
}
|
|
147
|
-
// ── 有界化 ───────────────────────────────────────────────────────────────
|
|
148
141
|
const chartId = newChartId();
|
|
149
142
|
const payload = buildChartPayload({ chart_id: chartId, spec, rows, meta });
|
|
150
143
|
const library = loadVendoredChartLibrary();
|
|
@@ -162,8 +155,7 @@ export async function renderChart(input, args, exec) {
|
|
|
162
155
|
signal: exec.signal,
|
|
163
156
|
});
|
|
164
157
|
const pathOf = (name) => written.find((file) => file.name === name)?.path ?? `${dir}/${name}`;
|
|
165
|
-
//
|
|
166
|
-
// 登记失败(服务缺失/实现异常)不阻断出图——图已经落盘了,降级成"只有文件路径"。
|
|
158
|
+
// Register the absolute series.json path with the host route; failure degrades to file paths only.
|
|
167
159
|
let chartUrl = null;
|
|
168
160
|
const seriesFile = written.find((file) => file.name === 'series.json');
|
|
169
161
|
if (input.charts && seriesFile !== undefined) {
|
|
@@ -185,20 +177,19 @@ export async function renderChart(input, args, exec) {
|
|
|
185
177
|
captured_at: meta.captured_at ?? null,
|
|
186
178
|
kind: payload.kind,
|
|
187
179
|
axis: payload.axis,
|
|
180
|
+
title: payload.title,
|
|
188
181
|
chart_url: chartUrl,
|
|
189
182
|
spec_path: pathOf('spec.json'),
|
|
190
183
|
series_path: pathOf('series.json'),
|
|
191
184
|
html_path: pathOf('chart.html'),
|
|
192
185
|
created_at: generatedAt,
|
|
193
186
|
});
|
|
194
|
-
//
|
|
195
|
-
// a stable receipt; production apply() always injects the registry.
|
|
187
|
+
// Unit callers without the registry still get a stable receipt.
|
|
196
188
|
const chartRef = artifact?.chart_ref ?? `chart_${chartId.slice(3)}`;
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
// 事件不进模型消息历史。呈现通道可选:写不进去不影响图表产物与回执。
|
|
189
|
+
// Monolith carriers still register this turn's deliverable here (first-party
|
|
190
|
+
// deliverables/presented only — never a custom event type; closed vocabulary breaks cold load).
|
|
191
|
+
// The auto-mode specialist row passes no chartEvents: the root Agent presents chart_refs in its
|
|
192
|
+
// final turn instead, so the card cannot land in a folded process turn.
|
|
202
193
|
let deliverableTrace;
|
|
203
194
|
try {
|
|
204
195
|
const publisher = input.chartEvents?.();
|
|
@@ -219,10 +210,7 @@ export async function renderChart(input, args, exec) {
|
|
|
219
210
|
catch (error) {
|
|
220
211
|
deliverableTrace = `交付登记抛错(已吞掉,不影响出图):${error instanceof Error ? error.message : String(error)}`;
|
|
221
212
|
}
|
|
222
|
-
//
|
|
223
|
-
// 而宿主终端不总能拿到(cordis logger 只进内存),所以留一条可从磁盘定位的出口;
|
|
224
|
-
// 但它属于调试产物,不该出现在对外版本的 workspace 里。
|
|
225
|
-
// 需要时用 `CAPITAL_CHART_TRACE=1` 启动宿主,图表产物目录会多出 deliverable-trace.txt。
|
|
213
|
+
// Trace file only under CAPITAL_CHART_TRACE=1; a debugging aid, not a release artifact.
|
|
226
214
|
if (chartTraceEnabled()) {
|
|
227
215
|
try {
|
|
228
216
|
await input.store.writeWorkspaceFiles({
|
|
@@ -233,7 +221,7 @@ export async function renderChart(input, args, exec) {
|
|
|
233
221
|
});
|
|
234
222
|
}
|
|
235
223
|
catch {
|
|
236
|
-
//
|
|
224
|
+
// Trace write failures never affect chart output.
|
|
237
225
|
}
|
|
238
226
|
}
|
|
239
227
|
return {
|
|
@@ -271,7 +259,9 @@ export function registerChartTool(ctx, options) {
|
|
|
271
259
|
`最多保留 ${MAX_CHART_POINTS} 个点(超出由宿主下采样并如实标注)。` +
|
|
272
260
|
'字段语义、可用键与错误码见 skill capital-chart-protocol —— 首次画图前先加载它。' +
|
|
273
261
|
'是否出图由 data_junior 的可视化协议决定;本工具只负责安全生成图表产物和小型回执。' +
|
|
274
|
-
|
|
262
|
+
(typeof options.chartEvents === 'function'
|
|
263
|
+
? '出图成功后宿主会把这张图登记为当前对话的**本轮交付物**(官方 deliverables/presented),用户可从收尾的交付行点开自包含图表;调用方**不要**在回传或正文里罗列图表文件、路径或 HTML。'
|
|
264
|
+
: '交付登记由主 Agent 在最终答复前调用 present_charts 完成:你只把 chart_ref 回传给 data_junior 汇总,**不要**调用 present_charts、不要在回传或正文里罗列图表文件、路径或 HTML。') +
|
|
275
265
|
'图表是呈现不是分析:需要数值结论(首末值、涨跌幅、分位数等)仍走 data_junior 的 profile / query,不要用图去推断数字。',
|
|
276
266
|
parameters,
|
|
277
267
|
output: { schema: receiptSchema, render },
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tools/chart-render` row: render_chart only. Row-level expression of "only the
|
|
3
|
+
* visualization specialist holds the chart pipeline"; shares token/artifact state with
|
|
4
|
+
* `tools/chart-source`. No chartEvents here: delivery is the root Agent's final-turn
|
|
5
|
+
* `present_charts` step, so the card never lands in a folded process turn.
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
export declare const name = "capital-chart-render-tools";
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
export declare function apply(ctx: Context): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { registerChartTool } from './chart/tool.js';
|
|
2
|
+
import { capitalDatasetStore } from './runtime.js';
|
|
3
|
+
import { capitalSharedRuntimeOf } from './chart-source-entry.js';
|
|
4
|
+
export const name = 'capital-chart-render-tools';
|
|
5
|
+
export const inject = ['tools'];
|
|
6
|
+
export function apply(ctx) {
|
|
7
|
+
const shared = capitalSharedRuntimeOf(ctx);
|
|
8
|
+
registerChartTool(ctx, {
|
|
9
|
+
store: capitalDatasetStore(ctx),
|
|
10
|
+
sourceTokens: shared.sourceTokens,
|
|
11
|
+
artifacts: shared.artifacts,
|
|
12
|
+
charts: () => ctx.get('capitalCharts'),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tools/chart-source` row: prepare_chart_source. The token store must be the same
|
|
3
|
+
* instance used by `tools/chart-render`; separate stores make `render_chart` always
|
|
4
|
+
* report `chart_source_expired` without throwing. Prefer the host-plane runtime, fall
|
|
5
|
+
* back to the root-keyed module cache.
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
import { type CapitalShared } from './runtime.js';
|
|
9
|
+
export declare const name = "capital-chart-source-tools";
|
|
10
|
+
export declare const inject: string[];
|
|
11
|
+
export declare function apply(ctx: Context): void;
|
|
12
|
+
/** Host row first, root-keyed module cache as fallback (monolith carriers). */
|
|
13
|
+
export declare function capitalSharedRuntimeOf(ctx: Context): CapitalShared;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { registerChartSourceTool } from './chart/source-token.js';
|
|
2
|
+
import { capitalDatasetStore, capitalSharedRuntime } from './runtime.js';
|
|
3
|
+
export const name = 'capital-chart-source-tools';
|
|
4
|
+
export const inject = ['tools'];
|
|
5
|
+
export function apply(ctx) {
|
|
6
|
+
const shared = capitalSharedRuntimeOf(ctx);
|
|
7
|
+
registerChartSourceTool(ctx, { store: capitalDatasetStore(ctx), tokens: shared.sourceTokens });
|
|
8
|
+
}
|
|
9
|
+
/** Host row first, root-keyed module cache as fallback (monolith carriers). */
|
|
10
|
+
export function capitalSharedRuntimeOf(ctx) {
|
|
11
|
+
try {
|
|
12
|
+
const hostShared = ctx.get('capitalChartRuntime');
|
|
13
|
+
if (hostShared !== undefined)
|
|
14
|
+
return hostShared;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Monolith carrier without the host row.
|
|
18
|
+
}
|
|
19
|
+
return capitalSharedRuntime(ctx);
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tools/collect` row: request_data / list_capabilities / describe_capability /
|
|
3
|
+
* dc_status backed by hub + 128 sources. The preset isolates hub/store to this row;
|
|
4
|
+
* time tools live in `tools/time`.
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
export declare const name = "capital-collect-tools";
|
|
8
|
+
export declare const inject: string[];
|
|
9
|
+
export declare function apply(ctx: Context): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createCollectorRuntime } from './collector-runtime.js';
|
|
2
|
+
import { registerDataCollectorTools } from './data-collector/tools.js';
|
|
3
|
+
import { readCollectorConfig } from './capital-settings.js';
|
|
4
|
+
export const name = 'capital-collect-tools';
|
|
5
|
+
export const inject = ['tools'];
|
|
6
|
+
export function apply(ctx) {
|
|
7
|
+
const config = readCollectorConfig(ctx);
|
|
8
|
+
const { hub, diagnostics } = createCollectorRuntime(ctx, config);
|
|
9
|
+
registerDataCollectorTools(ctx, hub, diagnostics);
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared data_collector assembly (store + hub + 128 sources). Both `tools/collect` and
|
|
3
|
+
* the monolith need it; duplicating would drift capability registration, credential
|
|
4
|
+
* probes and time tools. Collector domain only — no dataset/chart/watchlist tools.
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import { provideDataCollectorHub } from './data-collector/hub.js';
|
|
8
|
+
import { type DataCollectorDiagnostics } from './data-collector/tools.js';
|
|
9
|
+
import { capitalDatasetStore } from './runtime.js';
|
|
10
|
+
export interface CollectorConfig {
|
|
11
|
+
fuyaoCredentialRef?: string;
|
|
12
|
+
ftshareCredentialRef?: string;
|
|
13
|
+
/** Flat projection of `retriever.windDocs.credentialRef`. */
|
|
14
|
+
windCredentialRef?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CollectorRuntime {
|
|
17
|
+
store: ReturnType<typeof capitalDatasetStore>;
|
|
18
|
+
hub: ReturnType<typeof provideDataCollectorHub>;
|
|
19
|
+
/** Injected into `dc_status` by the caller that owns the `tools/collect` row. */
|
|
20
|
+
diagnostics: DataCollectorDiagnostics;
|
|
21
|
+
}
|
|
22
|
+
export declare function createCollectorRuntime(ctx: Context, config?: CollectorConfig): CollectorRuntime;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { provideDataCollectorHub } from './data-collector/hub.js';
|
|
2
|
+
import { capitalDatasetStore } from './runtime.js';
|
|
3
|
+
import { rootPolicyProbes } from './agents/root-tool-policy.js';
|
|
4
|
+
import { rolePolicyProbes } from './agents/role-tool-policy.js';
|
|
5
|
+
import { createTencentSources } from './sources/tencent-http.js';
|
|
6
|
+
import { createEastmoneySources } from './sources/eastmoney-http.js';
|
|
7
|
+
import { createFuyaoRestSources, resolveFuyaoApiKey } from './sources/fuyao-rest.js';
|
|
8
|
+
import { createFtshareSources, resolveFtshareApiKey } from './sources/ftshare-rest.js';
|
|
9
|
+
import { createWindSources } from './sources/wind-mcp.js';
|
|
10
|
+
import { createWindCredentialResolver } from './retriever-runtime.js';
|
|
11
|
+
function diagnosticsFor(ctx, config) {
|
|
12
|
+
const fuyaoCredentialRef = config.fuyaoCredentialRef || 'FUYAO_API_KEY';
|
|
13
|
+
return {
|
|
14
|
+
probeApiKey: async () => {
|
|
15
|
+
try {
|
|
16
|
+
const credentials = ctx.get('credentials');
|
|
17
|
+
if (credentials?.resolve) {
|
|
18
|
+
const resolved = await credentials.resolve(fuyaoCredentialRef);
|
|
19
|
+
if (resolved?.value)
|
|
20
|
+
return { present: true, source: resolved.source ?? `credentials(${fuyaoCredentialRef})` };
|
|
21
|
+
}
|
|
22
|
+
const processLike = globalThis.process;
|
|
23
|
+
if (processLike?.env?.[fuyaoCredentialRef])
|
|
24
|
+
return { present: true, source: `env(${fuyaoCredentialRef})` };
|
|
25
|
+
return { present: false, source: null };
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return { present: false, source: null, error: error instanceof Error ? error.message : String(error) };
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
// dc_status readout for root/role convergence; desktop does not persist host logs.
|
|
32
|
+
// Keep the real session shape: opened root and delegated child converge differently.
|
|
33
|
+
rootPolicy: () => [
|
|
34
|
+
...rootPolicyProbes().map((probe) => ({ ...probe, allowed: [] })),
|
|
35
|
+
...rolePolicyProbes().map((probe) => ({
|
|
36
|
+
agentId: probe.agentId,
|
|
37
|
+
root: probe.root,
|
|
38
|
+
presetId: probe.presetId,
|
|
39
|
+
outcome: probe.outcome,
|
|
40
|
+
// Roles have no allow-list (family rows define the surface); report sibling
|
|
41
|
+
// denials and call-level guard rejections.
|
|
42
|
+
allowed: [],
|
|
43
|
+
denied: probe.denied,
|
|
44
|
+
failed: probe.failed,
|
|
45
|
+
})),
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Registers all 128 sources; registration is structural, credentials resolve per call. */
|
|
50
|
+
function registerSources(ctx, hub, config) {
|
|
51
|
+
ctx.effect(() => {
|
|
52
|
+
const sources = createTencentSources();
|
|
53
|
+
const disposers = sources.map((dataSource) => hub.registerSource(dataSource));
|
|
54
|
+
ctx.logger.info(`capital-generation: 已注册 ${disposers.length} 个腾讯数据源(tencent_*)`);
|
|
55
|
+
return () => { for (const dispose of disposers)
|
|
56
|
+
dispose(); };
|
|
57
|
+
}, 'capital-generation.tencent-sources()');
|
|
58
|
+
ctx.effect(() => {
|
|
59
|
+
const sources = createEastmoneySources();
|
|
60
|
+
const disposers = sources.map((dataSource) => hub.registerSource(dataSource));
|
|
61
|
+
ctx.logger.info(`capital-generation: 已注册 ${disposers.length} 个东方财富数据源(eastmoney_*)`);
|
|
62
|
+
return () => { for (const dispose of disposers)
|
|
63
|
+
dispose(); };
|
|
64
|
+
}, 'capital-generation.eastmoney-sources()');
|
|
65
|
+
ctx.effect(() => {
|
|
66
|
+
const sources = createFuyaoRestSources(() => resolveFuyaoApiKey(ctx, config.fuyaoCredentialRef || 'FUYAO_API_KEY'));
|
|
67
|
+
const disposers = sources.map((dataSource) => hub.registerSource(dataSource));
|
|
68
|
+
ctx.logger.info(`capital-generation: 已注册 ${disposers.length} 个同花顺数据源(key 每次调用现取)`);
|
|
69
|
+
return () => { for (const dispose of disposers)
|
|
70
|
+
dispose(); };
|
|
71
|
+
}, 'capital-generation.fuyao-sources()');
|
|
72
|
+
ctx.effect(() => {
|
|
73
|
+
const sources = createFtshareSources(() => resolveFtshareApiKey(ctx, config.ftshareCredentialRef || 'FTSHARE_API_KEY'));
|
|
74
|
+
const disposers = sources.map((dataSource) => hub.registerSource(dataSource));
|
|
75
|
+
ctx.logger.info(`capital-generation: 已注册 ${disposers.length} 个 FTShare 数据源(ftshare_*,key 每次调用现取)`);
|
|
76
|
+
return () => { for (const dispose of disposers)
|
|
77
|
+
dispose(); };
|
|
78
|
+
}, 'capital-generation.ftshare-sources()');
|
|
79
|
+
const wind = createWindCredentialResolver(ctx, config.windCredentialRef);
|
|
80
|
+
ctx.effect(() => {
|
|
81
|
+
const sources = createWindSources(wind.resolve);
|
|
82
|
+
const disposers = sources.map((dataSource) => hub.registerSource(dataSource));
|
|
83
|
+
ctx.logger.info(`capital-generation: 已注册 ${disposers.length} 个 Wind 数据源(wind_*,与 wind_docs 共用 ${wind.credentialRef})`);
|
|
84
|
+
return () => { for (const dispose of disposers)
|
|
85
|
+
dispose(); };
|
|
86
|
+
}, 'capital-generation.wind-sources()');
|
|
87
|
+
}
|
|
88
|
+
export function createCollectorRuntime(ctx, config = {}) {
|
|
89
|
+
const store = capitalDatasetStore(ctx);
|
|
90
|
+
// The collector preset isolates these services; keep them in the realm to avoid
|
|
91
|
+
// process-wide name collisions across presets.
|
|
92
|
+
ctx.provide('datasetStore', store);
|
|
93
|
+
const hub = provideDataCollectorHub(ctx, { store });
|
|
94
|
+
registerSources(ctx, hub, config);
|
|
95
|
+
// Builds runtime only; model tools belong to the `tools/collect` and `tools/time` rows.
|
|
96
|
+
return { store, hub, diagnostics: diagnosticsFor(ctx, config) };
|
|
97
|
+
}
|
|
@@ -1,38 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dataset
|
|
3
|
-
*
|
|
4
|
-
* 模型经常把数组/对象参数序列化成 JSON 字符串再传(实测:`"select": "[\"date_ms\"]"`、
|
|
5
|
-
* `"group_by": "[]"`、`"aggregates": "[{...}]"`、`"limit": "1"`),于是参数其实是对的,
|
|
6
|
-
* 却报 `query_spec_invalid: select must contain 1-32 column names`,模型只能反复试错。
|
|
7
|
-
*
|
|
8
|
-
* 这里在**工具边界**做一次宽容解析,查询引擎本身保持严格:能解析成正确类型就放行,
|
|
9
|
-
* 解析不了就原样交给校验器报错(错误信息仍指向真实问题)。
|
|
10
|
-
*
|
|
11
|
-
* 本模块由 `query_dataset`(dataset-tools.ts)与 `describe_dataset`(describe.ts)共用,
|
|
12
|
-
* 保证两个工具对同一种"字符串化的 JSON"给出**完全一致**的宽容度——两份实现漂移会让模型
|
|
13
|
-
* 在换工具后重新踩同一个坑。
|
|
2
|
+
* Lenient parameter parsing at the Dataset tool boundary: models often send stringified JSON.
|
|
3
|
+
* Shared by query_dataset and describe_dataset so both accept exactly the same shapes.
|
|
14
4
|
*/
|
|
15
5
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
16
6
|
export declare function parseJsonContainer(value: unknown): unknown;
|
|
17
|
-
/**
|
|
7
|
+
/** Accept a real array, a JSON array string, or a single column name. */
|
|
18
8
|
export declare function coerceNameList(value: unknown): unknown;
|
|
19
|
-
/** 对象数组:接受真数组、JSON 数组字符串,或单个对象(对象本身或其 JSON 字符串)。 */
|
|
20
9
|
export declare function coerceObjectList(value: unknown): unknown;
|
|
21
10
|
export declare function coerceLimit(value: unknown): unknown;
|
|
22
11
|
export declare const QUERY_NAME_LIST_FIELDS: readonly ["select", "group_by"];
|
|
23
12
|
export declare const QUERY_OBJECT_LIST_FIELDS: readonly ["filters", "aggregates", "order_by"];
|
|
24
|
-
/**
|
|
13
|
+
/** Coerce QuerySpec array/object fields and limit to engine types; leave other fields untouched. */
|
|
25
14
|
export declare function coerceQueryShape(input: Record<string, unknown>): Record<string, unknown>;
|
|
26
|
-
/** dataset_id
|
|
15
|
+
/** Reject an invalid dataset_id at the tool boundary; never pass it to the store. */
|
|
27
16
|
export declare function readDatasetId(value: unknown): string;
|
|
28
|
-
/** task_id 是可选元数据:空值等同于未提供,只做长度上限校验。 */
|
|
29
17
|
export declare function readOptionalTaskId(value: unknown): string | undefined;
|
|
30
|
-
/** 可选的字符串参数(time_column / primary_key):非字符串一律视为未提供。 */
|
|
31
18
|
export declare function readOptionalString(value: unknown): string | undefined;
|
|
32
|
-
/**
|
|
33
|
-
* 列名过滤参数(describe_dataset 的 `columns_of_interest`)。
|
|
34
|
-
*
|
|
35
|
-
* 接受真数组 / JSON 数组字符串 / 单个列名字符串;解析结果不是数组就抛错——静默忽略一个
|
|
36
|
-
* 写错的过滤条件会让模型以为"结果已经收窄",而实际返回的是全量(或反过来),所以这里响亮失败。
|
|
37
|
-
*/
|
|
19
|
+
/** columns_of_interest: accept array/JSON/single name; a wrong shape fails loudly rather than silently widening results. */
|
|
38
20
|
export declare function readOptionalColumnList(value: unknown): string[] | undefined;
|