@v587d/capital-generation 2.8.3 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -1
- package/README.md +40 -19
- package/chart-ui/README.md +1 -1
- 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 +3 -8
- package/lib/chart/artifact-ref.js +3 -8
- 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 +40 -189
- package/lib/chart/html.d.ts +2 -5
- package/lib/chart/html.js +8 -15
- 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 +6 -29
- package/lib/chart/tool.js +9 -23
- package/lib/chart-render-entry.d.ts +9 -0
- package/lib/chart-render-entry.js +17 -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 +40 -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/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 +67 -7
- package/preset/capital-generation/README.md +87 -53
- package/preset/capital-generation/agent.patch.yml +526 -337
- 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 +1 -1
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +15 -19
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +9 -12
- 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,35 +1,19 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import type { DataTimeContract } from '../time/tools.js';
|
|
3
3
|
import type { DatasetRef, SaveDatasetInput, SessionLike } from './store.js';
|
|
4
|
-
/**
|
|
5
|
-
* 规范化内部 data_key 片段:斜杠/冒号等路径分隔符一律映射为点,其余非常规
|
|
6
|
-
* 字符折叠为点、合并连续点、去掉首尾点。data_key 只是宿主内部路由/审计身份,
|
|
7
|
-
* 不出现在模型可见协议、人设或工具输出中。
|
|
8
|
-
*/
|
|
4
|
+
/** Normalize an internal data_key token; data_key is host-only and never model-visible. */
|
|
9
5
|
export declare function normalizeKeyToken(value: string): string;
|
|
10
|
-
/**
|
|
11
|
-
* 生成内部 data_key:provider.kind.resource(例如
|
|
12
|
-
* buildDataKey('fuyao', 'api', '/api/a-share/prices/snapshot')
|
|
13
|
-
* => 'fuyao.api.api.a-share.prices.snapshot')。
|
|
14
|
-
*/
|
|
15
6
|
export declare function buildDataKey(provider: string, kind: string, resource: string): string;
|
|
16
|
-
/**
|
|
17
|
-
* 数据请求:capability 是模型可见的短能力名(全局唯一),session 由工具层以
|
|
18
|
-
* exec.agent.session 注入,不接受模型传参。内部 data_key 不出现在请求协议里。
|
|
19
|
-
*/
|
|
7
|
+
/** capability is the model-visible unique key; session is injected by the tool layer, not supplied by the model. */
|
|
20
8
|
export interface DataRequest {
|
|
21
9
|
capability: string;
|
|
22
10
|
params: Record<string, unknown>;
|
|
23
|
-
/** force_refresh
|
|
11
|
+
/** force_refresh bypasses manifest reuse and creates a new immutable Dataset. */
|
|
24
12
|
force_refresh?: boolean;
|
|
25
|
-
/** 本次用户任务关联 id(可复用);只用于追溯,不要求模型理解。 */
|
|
26
13
|
task_id?: string;
|
|
27
14
|
session: SessionLike;
|
|
28
15
|
}
|
|
29
|
-
/**
|
|
30
|
-
* 数据源契约:capability 为唯一注册键与模型可见名;name/source/data_key
|
|
31
|
-
* 均为宿主内部字段(日志、路由、审计),不得进入工具 schema、输出或人设。
|
|
32
|
-
*/
|
|
16
|
+
/** capability is the unique registration key; name/source/data_key stay host-internal (logs, routing, audit). */
|
|
33
17
|
export interface SchemaDescriptor {
|
|
34
18
|
capability: string;
|
|
35
19
|
name: string;
|
|
@@ -37,34 +21,29 @@ export interface SchemaDescriptor {
|
|
|
37
21
|
data_key: string;
|
|
38
22
|
source_label?: string;
|
|
39
23
|
paginated?: boolean;
|
|
40
|
-
/**
|
|
24
|
+
/** Longest reusable Dataset age; defaults to the retention window. Host-only. */
|
|
41
25
|
cacheMaxAgeMs?: number;
|
|
42
|
-
/**
|
|
43
|
-
* 行数组位置的显式声明。龙虎榜这类响应的行数组不在 `item` 下(`stock_items`),
|
|
44
|
-
* 只靠形状推断会把整份数据判成「不可读的文档」。谁产出数据谁声明行在哪里,
|
|
45
|
-
* 存储层不再猜。
|
|
46
|
-
*/
|
|
26
|
+
/** Where the row array lives; without it, envelopes such as `stock_items` look unreadable. */
|
|
47
27
|
rowShape?: RowShapeHint;
|
|
48
|
-
/**
|
|
28
|
+
/** One-line catalog summary (~50 chars max). */
|
|
49
29
|
summary?: string;
|
|
50
|
-
/**
|
|
30
|
+
/** Full detail for describe_capability only (units, null semantics, time/pagination rules). */
|
|
51
31
|
description?: string;
|
|
52
32
|
input_schema: object;
|
|
53
|
-
/**
|
|
33
|
+
/** Machine-readable time-parameter contract shared with the time tools. */
|
|
54
34
|
time_contract?: DataTimeContract;
|
|
55
35
|
output_schema?: object;
|
|
56
36
|
}
|
|
57
|
-
/**
|
|
37
|
+
/** Catalog entry: just enough to pick a capability. */
|
|
58
38
|
export interface CapabilitySummary {
|
|
59
39
|
capability: string;
|
|
60
40
|
summary: string;
|
|
61
41
|
paginated: boolean;
|
|
62
42
|
}
|
|
63
|
-
/**
|
|
43
|
+
/** Full contract for one capability; must be read before calling. */
|
|
64
44
|
export interface CapabilityDetail extends CapabilitySummary {
|
|
65
45
|
description: string;
|
|
66
46
|
input_schema: object;
|
|
67
|
-
/** `output_schema` 的模型可见投影,见 renderOutputFields;声明不出字段时为 null。 */
|
|
68
47
|
output_fields: string | null;
|
|
69
48
|
}
|
|
70
49
|
export interface DataSource {
|
|
@@ -73,22 +52,16 @@ export interface DataSource {
|
|
|
73
52
|
data: unknown;
|
|
74
53
|
schema?: object | null;
|
|
75
54
|
}>;
|
|
76
|
-
/** Optional source
|
|
55
|
+
/** Optional source guard; false rejects the result before persistence. */
|
|
77
56
|
validateOutput?: (data: unknown) => boolean;
|
|
78
57
|
/**
|
|
79
|
-
* Optional
|
|
80
|
-
*
|
|
81
|
-
* case/whitespace/list order/default values share one Dataset instead of
|
|
82
|
-
* writing several. It must be idempotent (execute calls it again) and may
|
|
83
|
-
* throw to reject invalid parameters before anything is queued. Sources that
|
|
84
|
-
* omit it keep their previous behaviour exactly.
|
|
58
|
+
* Optional canonicalization; the Hub calls it before merge key and params_digest, so equivalent
|
|
59
|
+
* requests share one Dataset. Must be idempotent (execute calls it again) and may throw.
|
|
85
60
|
*/
|
|
86
61
|
normalizeParams?: (params: Record<string, unknown>) => Record<string, unknown>;
|
|
87
62
|
}
|
|
88
|
-
/** Dataset 落盘器(Hub 的唯一成功出口)。 */
|
|
89
63
|
export interface DatasetStoreLike {
|
|
90
64
|
save(input: SaveDatasetInput): Promise<DatasetRef>;
|
|
91
|
-
/** Optional for compatibility with lightweight test stores; production store implements manifest lookup. */
|
|
92
65
|
findLatest?(input: {
|
|
93
66
|
session: SessionLike;
|
|
94
67
|
capability: string;
|
|
@@ -96,35 +69,29 @@ export interface DatasetStoreLike {
|
|
|
96
69
|
signal?: AbortSignal;
|
|
97
70
|
}): Promise<DatasetRef | undefined>;
|
|
98
71
|
}
|
|
99
|
-
/** 数据源声明的行数组位置;缺省按 `item` 推断。 */
|
|
100
72
|
export interface RowShapeHint {
|
|
101
|
-
/**
|
|
73
|
+
/** Top-level key holding rows (default `item`). */
|
|
102
74
|
rowKey?: string;
|
|
103
|
-
/**
|
|
75
|
+
/** Multiple row paths; `[]` expands and merges them into one row set. */
|
|
104
76
|
rowKeys?: string[];
|
|
105
|
-
/**
|
|
77
|
+
/** The top level is itself the row array. */
|
|
106
78
|
rootArray?: boolean;
|
|
107
79
|
}
|
|
108
80
|
export interface DataCollectorHubOptions {
|
|
109
|
-
/**
|
|
81
|
+
/** Required Dataset store; request fails without it. */
|
|
110
82
|
store: DatasetStoreLike;
|
|
111
|
-
/**
|
|
83
|
+
/** Queue length (default 50). */
|
|
112
84
|
maxQueueLength?: number;
|
|
113
|
-
/**
|
|
85
|
+
/** Transport timeout only (30s default), not data lifetime. */
|
|
114
86
|
requestTimeoutMs?: number;
|
|
115
|
-
/** 时钟注入(测试用);默认 Date.now。 */
|
|
116
87
|
now?: () => number;
|
|
117
88
|
}
|
|
118
89
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* 明确不做:长期 raw data 内存缓存、按 data_key 的查询、source_preference 路由。
|
|
123
|
-
* 已验证 DatasetRef 的复用由 workspace manifest 提供,内存只保留 in-flight 队列。
|
|
90
|
+
* FIFO executor: success is persisted immediately as an immutable Dataset and only a DatasetRef is
|
|
91
|
+
* returned. In-flight queue is the only in-memory raw data; reuse comes from workspace manifests.
|
|
124
92
|
*/
|
|
125
93
|
export declare class DataCollectorHub {
|
|
126
94
|
private readonly queue;
|
|
127
|
-
/** 正在执行的队列项(执行期间不在 queue 中,但仍参与合并与容量判断)。 */
|
|
128
95
|
private active;
|
|
129
96
|
private readonly sources;
|
|
130
97
|
private readonly cacheLookups;
|
|
@@ -136,46 +103,21 @@ export declare class DataCollectorHub {
|
|
|
136
103
|
constructor(options: DataCollectorHubOptions);
|
|
137
104
|
registerSource(source: DataSource): () => void;
|
|
138
105
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* in-flight 请求仍共享一次执行,成功后立即由宿主 store 原子落盘。
|
|
142
|
-
*
|
|
143
|
-
* 参数规范化(source 可选)发生在这里:merge key 与 digest 都用规范化后的参数,
|
|
144
|
-
* 因此「大小写/空白/重复项不同、语义相同」的请求命中同一个 Dataset。未知
|
|
145
|
-
* capability 不做处理,仍由执行阶段抛出统一的 no data source 错误。
|
|
106
|
+
* Enqueue and wait: reuse a valid manifest unless force_refresh; identical in-flight requests share
|
|
107
|
+
* one execution. Params are normalized before merge key and digest.
|
|
146
108
|
*/
|
|
147
109
|
request(request: DataRequest, options?: {
|
|
148
110
|
signal?: AbortSignal;
|
|
149
111
|
}): Promise<DatasetRef>;
|
|
150
|
-
/** 应用 source 的可选参数规范化;未声明或 capability 未注册时原样返回。 */
|
|
151
112
|
private normalizeRequest;
|
|
152
|
-
/** 挂一个等待者并返回其结算承诺;signal 触发时摘除等待者并拒绝(不影响共享执行)。 */
|
|
153
113
|
private awaitSettlement;
|
|
154
|
-
/** 结算一个等待者:无论成败都摘除监听,防止重复结算。 */
|
|
155
114
|
private settleWaiter;
|
|
156
|
-
/**
|
|
157
|
-
* 能力目录(模型可见的最外层发现入口):只含 capability、一行摘要与是否分页。
|
|
158
|
-
*
|
|
159
|
-
* 目录刻意不带 input_schema / output_schema —— 全量 schema 一度达 23KB,
|
|
160
|
-
* 会被工具结果剪枝器截掉中间部分,导致排在中间的能力在发现阶段不可见。
|
|
161
|
-
* 目录必须一次性全部返回(体积预算由测试守住),详情走 describeCapability。
|
|
162
|
-
* 不含任何内部路由字段(name/source/data_key/source_label)。
|
|
163
|
-
*/
|
|
115
|
+
/** Catalog: capability, one-line summary, paginated only. Full schemas would be pruned; details come from describeCapability. */
|
|
164
116
|
listCapabilities(): CapabilitySummary[];
|
|
165
|
-
/** 当前已注册的能力名(用于未知名字的引导与诊断)。 */
|
|
166
117
|
capabilityNames(): string[];
|
|
167
|
-
/**
|
|
168
|
-
* 能力目录的模型可见形态:一行一条 `capability|summary|paginated`(1=分页、0=不分页)。
|
|
169
|
-
* 同等信息量下 JSON 数组要 5530 字符(69 条实测,其中 58% 是键名与标点),行编码 2435、
|
|
170
|
-
* 均摊从 80 降到 35 字符——目录余量从 +7 条变成 +105 条。信息量没变、依然一次性全量返回,
|
|
171
|
-
* §1.1 那条"不分页、不裁剪"的硬约束原样成立。结构化形态仍由 listCapabilities 提供(测试与内部消费)。
|
|
172
|
-
*/
|
|
118
|
+
/** Model-visible catalog: one `capability|summary|paginated` line each; JSON costs about twice the tokens. */
|
|
173
119
|
capabilityDirectory(): string;
|
|
174
|
-
/**
|
|
175
|
-
* 单个能力的完整契约;名字未注册时返回 undefined,由工具层分类成可引导的错误。
|
|
176
|
-
* 返回值只含模型可见字段,是新建的普通对象,不回传内部 live data。
|
|
177
|
-
* `output_fields` 是 `output_schema` 的投影:详情体积按列数增长会被宽表打穿,见 renderOutputFields。
|
|
178
|
-
*/
|
|
120
|
+
/** Single capability contract; undefined for unknown names (the tool layer classifies it). */
|
|
179
121
|
describeCapability(capability: string): CapabilityDetail | undefined;
|
|
180
122
|
private processNext;
|
|
181
123
|
}
|
|
@@ -1,23 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 规范化内部 data_key 片段:斜杠/冒号等路径分隔符一律映射为点,其余非常规
|
|
3
|
-
* 字符折叠为点、合并连续点、去掉首尾点。data_key 只是宿主内部路由/审计身份,
|
|
4
|
-
* 不出现在模型可见协议、人设或工具输出中。
|
|
5
|
-
*/
|
|
1
|
+
/** Normalize an internal data_key token; data_key is host-only and never model-visible. */
|
|
6
2
|
export function normalizeKeyToken(value) {
|
|
7
3
|
return value.replace(/[\\/:]+/g, '.')
|
|
8
4
|
.replace(/[^A-Za-z0-9._-]+/g, '.')
|
|
9
5
|
.replace(/\.{2,}/g, '.')
|
|
10
6
|
.replace(/^\.+|\.+$/g, '');
|
|
11
7
|
}
|
|
12
|
-
/**
|
|
13
|
-
* 生成内部 data_key:provider.kind.resource(例如
|
|
14
|
-
* buildDataKey('fuyao', 'api', '/api/a-share/prices/snapshot')
|
|
15
|
-
* => 'fuyao.api.api.a-share.prices.snapshot')。
|
|
16
|
-
*/
|
|
17
8
|
export function buildDataKey(provider, kind, resource) {
|
|
18
9
|
return [provider, kind, resource].map(normalizeKeyToken).filter(Boolean).join('.');
|
|
19
10
|
}
|
|
20
|
-
/** 目录项摘要的兜底:描述缺失或没有 summary 时取首句并截断。 */
|
|
21
11
|
function summarize(source) {
|
|
22
12
|
if (typeof source.summary === 'string' && source.summary.trim().length > 0)
|
|
23
13
|
return source.summary.trim();
|
|
@@ -27,10 +17,7 @@ function summarize(source) {
|
|
|
27
17
|
const firstSentence = description.split(/[。;;.]/)[0]?.trim() ?? description;
|
|
28
18
|
return firstSentence.length > 80 ? `${firstSentence.slice(0, 79)}…` : firstSentence;
|
|
29
19
|
}
|
|
30
|
-
/**
|
|
31
|
-
* 目录字段的转义:`|` 与换行是行编码的分隔符,撞上去的后果是**一条能力被读成两条**
|
|
32
|
-
* (错了不报错、只是话说错那一族),所以由生产者转义而不是指望摘要里恰好没有。
|
|
33
|
-
*/
|
|
20
|
+
/** Escape `|` and newlines: they delimit catalog lines, and one unescaped splits a capability in two. */
|
|
34
21
|
function encodeDirectoryField(value) {
|
|
35
22
|
return value
|
|
36
23
|
.replace(/\\/g, '\\\\')
|
|
@@ -42,14 +29,13 @@ function encodeDirectoryField(value) {
|
|
|
42
29
|
function isPlainRecord(value) {
|
|
43
30
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
44
31
|
}
|
|
45
|
-
/** 说明段里不许有换行:一个字段变成两行会让字典的"一行一字段"承诺失效。 */
|
|
46
32
|
function inlineFieldText(value) {
|
|
47
33
|
if (typeof value !== 'string')
|
|
48
34
|
return undefined;
|
|
49
35
|
const text = value.replace(/\s+/g, ' ').trim();
|
|
50
36
|
return text.length === 0 ? undefined : text;
|
|
51
37
|
}
|
|
52
|
-
/**
|
|
38
|
+
/** Recursion bound for field dictionaries; real contracts are at most 3 deep. */
|
|
53
39
|
const MAX_FIELD_DEPTH = 5;
|
|
54
40
|
function inspectField(node) {
|
|
55
41
|
if (!isPlainRecord(node))
|
|
@@ -58,7 +44,7 @@ function inspectField(node) {
|
|
|
58
44
|
if (Array.isArray(node.oneOf)) {
|
|
59
45
|
const branches = node.oneOf.filter((branch) => !isPlainRecord(branch) || branch.type !== 'null');
|
|
60
46
|
const nullable = branches.length !== node.oneOf.length;
|
|
61
|
-
//
|
|
47
|
+
// Nullable unions carry their description on the outer node, not the null branch.
|
|
62
48
|
const note = outerNote === undefined ? undefined : { description: outerNote };
|
|
63
49
|
if (branches.length === 1)
|
|
64
50
|
return { ...inspectField(branches[0]), nullable, ...(note ?? {}) };
|
|
@@ -79,8 +65,7 @@ function inspectField(node) {
|
|
|
79
65
|
if (properties !== undefined) {
|
|
80
66
|
return { label: typeof node.type === 'string' ? node.type : 'object', nullable: false, nested: properties, ...(description === undefined ? {} : { description }) };
|
|
81
67
|
}
|
|
82
|
-
//
|
|
83
|
-
// 标成 `json` 是话说错——模型会以为取值开放,于是写出目录里不存在的值。
|
|
68
|
+
// Fields with enum but no type: infer type and list values instead of calling them open-ended json.
|
|
84
69
|
if (typeof node.type !== 'string' && Array.isArray(node.enum) && node.enum.length > 0) {
|
|
85
70
|
const kinds = new Set(node.enum.map((value) => (typeof value === 'number' && Number.isInteger(value) ? 'integer' : typeof value)));
|
|
86
71
|
const values = node.enum.join('/');
|
|
@@ -101,14 +86,8 @@ function emitFields(properties, prefix, lines, depth) {
|
|
|
101
86
|
}
|
|
102
87
|
}
|
|
103
88
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* 为什么不直接把 JSON Schema 交给模型(实测):详情体积几乎就等于 `output_schema` 体积,
|
|
108
|
-
* 而它按每列 ~55 字符随**列数**线性增长——72 列的可转债档案光 schema 就 4699 字符,第一次
|
|
109
|
-
* 注册就打穿单能力 4096 的预算。字典把每列压到 ~15 字符,列数不再卡住补录。
|
|
110
|
-
* 权威声明仍住 `output_schema`:护栏与返回值契约照旧按 schema 校验,这里只是给模型看的投影,
|
|
111
|
-
* 不是第二份真相。
|
|
89
|
+
* Project output_schema into one line per field. Raw JSON Schema costs ~55 chars per column and
|
|
90
|
+
* blows the per-capability budget; this is display-only, output_schema stays authoritative.
|
|
112
91
|
*/
|
|
113
92
|
function renderOutputFields(schema) {
|
|
114
93
|
if (!isPlainRecord(schema))
|
|
@@ -142,20 +121,14 @@ function paramsDigest(request) {
|
|
|
142
121
|
}
|
|
143
122
|
return hash.toString(16).padStart(16, '0').repeat(4);
|
|
144
123
|
}
|
|
145
|
-
/**
|
|
124
|
+
/** In-flight merge key: same capability + params + task share one execution. */
|
|
146
125
|
function mergeKey(request) {
|
|
147
126
|
return `${request.capability}:${request.task_id ?? ''}:${stableSerialize(request.params)}`;
|
|
148
127
|
}
|
|
149
128
|
function errorMessage(error) {
|
|
150
129
|
return error instanceof Error ? error.message : String(error);
|
|
151
130
|
}
|
|
152
|
-
/**
|
|
153
|
-
* 把上游错误重新包装成 Error 时**保留稳定错误码**。
|
|
154
|
-
*
|
|
155
|
-
* DatasetStoreError 带 `code`(如 workspace_not_writable / dataset_expired),但此前一律被
|
|
156
|
-
* 拍成纯文本 Error,调用方只能从消息前缀里猜。协议要求失败回传携带 `error` 与 `code`,
|
|
157
|
-
* 因此这里把 code 原样挂到新 Error 上,工具层与上层消费者都可直接读取。
|
|
158
|
-
*/
|
|
131
|
+
/** Preserve a stable error `code` when rewrapping upstream errors; callers read it directly. */
|
|
159
132
|
function errorWithCode(message, source) {
|
|
160
133
|
const failure = new Error(message);
|
|
161
134
|
if (source && typeof source === 'object' && typeof source.code === 'string') {
|
|
@@ -165,15 +138,11 @@ function errorWithCode(message, source) {
|
|
|
165
138
|
return failure;
|
|
166
139
|
}
|
|
167
140
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* 明确不做:长期 raw data 内存缓存、按 data_key 的查询、source_preference 路由。
|
|
172
|
-
* 已验证 DatasetRef 的复用由 workspace manifest 提供,内存只保留 in-flight 队列。
|
|
141
|
+
* FIFO executor: success is persisted immediately as an immutable Dataset and only a DatasetRef is
|
|
142
|
+
* returned. In-flight queue is the only in-memory raw data; reuse comes from workspace manifests.
|
|
173
143
|
*/
|
|
174
144
|
export class DataCollectorHub {
|
|
175
145
|
queue = [];
|
|
176
|
-
/** 正在执行的队列项(执行期间不在 queue 中,但仍参与合并与容量判断)。 */
|
|
177
146
|
active = null;
|
|
178
147
|
sources = new Map();
|
|
179
148
|
cacheLookups = new Map();
|
|
@@ -208,13 +177,8 @@ export class DataCollectorHub {
|
|
|
208
177
|
this.sources.delete(capability); };
|
|
209
178
|
}
|
|
210
179
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* in-flight 请求仍共享一次执行,成功后立即由宿主 store 原子落盘。
|
|
214
|
-
*
|
|
215
|
-
* 参数规范化(source 可选)发生在这里:merge key 与 digest 都用规范化后的参数,
|
|
216
|
-
* 因此「大小写/空白/重复项不同、语义相同」的请求命中同一个 Dataset。未知
|
|
217
|
-
* capability 不做处理,仍由执行阶段抛出统一的 no data source 错误。
|
|
180
|
+
* Enqueue and wait: reuse a valid manifest unless force_refresh; identical in-flight requests share
|
|
181
|
+
* one execution. Params are normalized before merge key and digest.
|
|
218
182
|
*/
|
|
219
183
|
async request(request, options = {}) {
|
|
220
184
|
if (!request.capability)
|
|
@@ -231,9 +195,7 @@ export class DataCollectorHub {
|
|
|
231
195
|
if (normalized.force_refresh !== true && this.store.findLatest) {
|
|
232
196
|
let lookup = this.cacheLookups.get(key);
|
|
233
197
|
if (!lookup) {
|
|
234
|
-
//
|
|
235
|
-
// 其中任何一个人的 AbortSignal:首个调用者取消会让其余调用者一起收到同一个
|
|
236
|
-
// lookup 失败,而不是各自独立等待。取消只作用于调用者自己的 waiter。
|
|
198
|
+
// Shared by every caller with this mergeKey, so it must not bind any caller's AbortSignal.
|
|
237
199
|
lookup = this.store.findLatest({
|
|
238
200
|
session: normalized.session,
|
|
239
201
|
capability: normalized.capability,
|
|
@@ -262,14 +224,12 @@ export class DataCollectorHub {
|
|
|
262
224
|
void this.processNext();
|
|
263
225
|
return pending;
|
|
264
226
|
}
|
|
265
|
-
/** 应用 source 的可选参数规范化;未声明或 capability 未注册时原样返回。 */
|
|
266
227
|
normalizeRequest(request) {
|
|
267
228
|
const source = this.sources.get(request.capability);
|
|
268
229
|
if (!source?.normalizeParams)
|
|
269
230
|
return request;
|
|
270
231
|
return { ...request, params: source.normalizeParams(request.params) };
|
|
271
232
|
}
|
|
272
|
-
/** 挂一个等待者并返回其结算承诺;signal 触发时摘除等待者并拒绝(不影响共享执行)。 */
|
|
273
233
|
awaitSettlement(item, signal) {
|
|
274
234
|
return new Promise((resolve, reject) => {
|
|
275
235
|
const waiter = { resolve, reject, signal };
|
|
@@ -287,7 +247,6 @@ export class DataCollectorHub {
|
|
|
287
247
|
item.waiters.add(waiter);
|
|
288
248
|
});
|
|
289
249
|
}
|
|
290
|
-
/** 结算一个等待者:无论成败都摘除监听,防止重复结算。 */
|
|
291
250
|
settleWaiter(item, waiter, outcome) {
|
|
292
251
|
item.waiters.delete(waiter);
|
|
293
252
|
if (waiter.onAbort && waiter.signal)
|
|
@@ -297,14 +256,7 @@ export class DataCollectorHub {
|
|
|
297
256
|
else
|
|
298
257
|
waiter.reject(outcome.error);
|
|
299
258
|
}
|
|
300
|
-
/**
|
|
301
|
-
* 能力目录(模型可见的最外层发现入口):只含 capability、一行摘要与是否分页。
|
|
302
|
-
*
|
|
303
|
-
* 目录刻意不带 input_schema / output_schema —— 全量 schema 一度达 23KB,
|
|
304
|
-
* 会被工具结果剪枝器截掉中间部分,导致排在中间的能力在发现阶段不可见。
|
|
305
|
-
* 目录必须一次性全部返回(体积预算由测试守住),详情走 describeCapability。
|
|
306
|
-
* 不含任何内部路由字段(name/source/data_key/source_label)。
|
|
307
|
-
*/
|
|
259
|
+
/** Catalog: capability, one-line summary, paginated only. Full schemas would be pruned; details come from describeCapability. */
|
|
308
260
|
listCapabilities() {
|
|
309
261
|
return [...this.sources.values()].map((source) => ({
|
|
310
262
|
capability: source.schema.capability,
|
|
@@ -312,26 +264,16 @@ export class DataCollectorHub {
|
|
|
312
264
|
paginated: source.schema.paginated === true,
|
|
313
265
|
}));
|
|
314
266
|
}
|
|
315
|
-
/** 当前已注册的能力名(用于未知名字的引导与诊断)。 */
|
|
316
267
|
capabilityNames() {
|
|
317
268
|
return [...this.sources.keys()];
|
|
318
269
|
}
|
|
319
|
-
/**
|
|
320
|
-
* 能力目录的模型可见形态:一行一条 `capability|summary|paginated`(1=分页、0=不分页)。
|
|
321
|
-
* 同等信息量下 JSON 数组要 5530 字符(69 条实测,其中 58% 是键名与标点),行编码 2435、
|
|
322
|
-
* 均摊从 80 降到 35 字符——目录余量从 +7 条变成 +105 条。信息量没变、依然一次性全量返回,
|
|
323
|
-
* §1.1 那条"不分页、不裁剪"的硬约束原样成立。结构化形态仍由 listCapabilities 提供(测试与内部消费)。
|
|
324
|
-
*/
|
|
270
|
+
/** Model-visible catalog: one `capability|summary|paginated` line each; JSON costs about twice the tokens. */
|
|
325
271
|
capabilityDirectory() {
|
|
326
272
|
return this.listCapabilities()
|
|
327
273
|
.map((entry) => `${encodeDirectoryField(entry.capability)}|${encodeDirectoryField(entry.summary)}|${entry.paginated ? 1 : 0}`)
|
|
328
274
|
.join('\n');
|
|
329
275
|
}
|
|
330
|
-
/**
|
|
331
|
-
* 单个能力的完整契约;名字未注册时返回 undefined,由工具层分类成可引导的错误。
|
|
332
|
-
* 返回值只含模型可见字段,是新建的普通对象,不回传内部 live data。
|
|
333
|
-
* `output_fields` 是 `output_schema` 的投影:详情体积按列数增长会被宽表打穿,见 renderOutputFields。
|
|
334
|
-
*/
|
|
276
|
+
/** Single capability contract; undefined for unknown names (the tool layer classifies it). */
|
|
335
277
|
describeCapability(capability) {
|
|
336
278
|
const source = this.sources.get(capability);
|
|
337
279
|
if (!source)
|
|
@@ -361,9 +303,7 @@ export class DataCollectorHub {
|
|
|
361
303
|
const controller = new AbortController();
|
|
362
304
|
let timeout;
|
|
363
305
|
const operation = source.execute(item.request, controller.signal);
|
|
364
|
-
// Abort cooperative sources and
|
|
365
|
-
// ignores the signal. Observe the underlying promise to avoid an
|
|
366
|
-
// unhandled rejection after the timeout race has settled.
|
|
306
|
+
// Abort cooperative sources and release the FIFO worker even if a source ignores the signal.
|
|
367
307
|
void operation.catch(() => { });
|
|
368
308
|
const timeoutPromise = new Promise((_, reject) => {
|
|
369
309
|
timeout = setTimeout(() => {
|
|
@@ -413,13 +353,7 @@ export class DataCollectorHub {
|
|
|
413
353
|
}
|
|
414
354
|
}
|
|
415
355
|
}
|
|
416
|
-
/**
|
|
417
|
-
* 从数据源声明的行形状推导存储 format 与行数;未声明时退化为形状推断。
|
|
418
|
-
*
|
|
419
|
-
* 之前只认 `data.item`,于是龙虎榜(行数组在 `stock_items`)被整份判成
|
|
420
|
-
* `format: 'json'`,data_junior 连 inspect 都说不可读。行在哪里是数据源的
|
|
421
|
-
* 契约知识,不该由存储层猜。
|
|
422
|
-
*/
|
|
356
|
+
/** Derive storage format and row count from the declared row shape; fall back to inference. */
|
|
423
357
|
function shapeOf(data, hint) {
|
|
424
358
|
if (Array.isArray(data))
|
|
425
359
|
return { format: 'json_rows', rowCount: data.length };
|
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
import { type QuerySpec } from './query.js';
|
|
2
2
|
import type { SessionLike, TimeAxisSnapshot } from './store.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 内嵌的 queries 直接走引擎,于是同一个模型在同一个回合里得到两种行为——
|
|
8
|
-
* - `query_dataset`:`">=": "2026-09"` → 正确当作 9 月区间;
|
|
9
|
-
* - `describe_dataset.queries`:`">=": "2026"` → 被引擎当**数字 2026** 比较,243 行全中
|
|
10
|
-
* (`days_2026: 243`,静默错误);`">=": "2026-07"` → 整条被 `skip_with_warning` 跳过,
|
|
11
|
-
* `matched_row_count: 0`(静默空结果)。
|
|
12
|
-
* 协议与委派消息都写着"时间筛选直接写日期串",所以**只要有一个入口没接上,模型就一定踩**。
|
|
13
|
-
* 新增任何"执行 QuerySpec"的入口都必须复用本模块,不要在别处再写一份。
|
|
4
|
+
* Query-time normalization: the single implementation shared by query_dataset and
|
|
5
|
+
* describe_dataset.queries. Every entry point executing a QuerySpec must call it; an entry that
|
|
6
|
+
* skips it gives date strings a different meaning in the same turn.
|
|
14
7
|
*/
|
|
15
8
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* 为什么在工具边界做(真机会话 1e44050e 实测):`date_ms` 列只接受毫秒,模型于是把
|
|
20
|
-
* "近 1 月 / 近 3 月 / 52 周 / 年初至今 / 历年 10 月"全部手算成 epoch——单步思考 41,961
|
|
21
|
-
* 字符里绝大部分是这类算术,而且算错就是错口径。日历换算是确定性计算,属于宿主:
|
|
22
|
-
* 模型只写日历,宿主按**该列观测到的偏移**换算。
|
|
23
|
-
*
|
|
24
|
-
* 日期串落到数值时间列时**一律响亮失败**:旧路径下 `skip_with_warning` 会把这条 filter
|
|
25
|
-
* 整条跳过(`filter X skipped incompatible value`),于是"筛选消失、返回全量"却看起来成功。
|
|
9
|
+
* Translate date-like filter values into this Dataset's time-axis bounds and add `*_iso` result
|
|
10
|
+
* columns. Date strings against a numeric time column fail loudly; a skipped filter would look
|
|
11
|
+
* like a successful full-table result.
|
|
26
12
|
*/
|
|
27
13
|
export declare function normalizeQueryDates(input: {
|
|
28
14
|
store: TimeAxisProbe;
|
|
@@ -38,7 +24,6 @@ export declare function normalizeQueryDates(input: {
|
|
|
38
24
|
time_zone?: string;
|
|
39
25
|
};
|
|
40
26
|
}>;
|
|
41
|
-
/** 时间轴只读探针:`WorkspaceDatasetStore` 与测试替身都满足这个形状。 */
|
|
42
27
|
export interface TimeAxisProbe {
|
|
43
28
|
describeTimeAxis(input: {
|
|
44
29
|
session: unknown;
|
|
@@ -47,11 +32,7 @@ export interface TimeAxisProbe {
|
|
|
47
32
|
signal?: AbortSignal;
|
|
48
33
|
}): Promise<TimeAxisSnapshot | undefined>;
|
|
49
34
|
}
|
|
50
|
-
/**
|
|
51
|
-
* 结果补 ISO 时间列:`date_ms: 1787414400000` 旁边给 `date_ms_iso: "2026-08-23"`。
|
|
52
|
-
* 只加不删,原值仍是唯一权威(回传口径要求"字段按工具返回原样")。
|
|
53
|
-
* 已有同名 `*_iso` 列时不覆盖,避免与 Dataset 自带字段撞名。
|
|
54
|
-
*/
|
|
35
|
+
/** Add `*_iso` columns without replacing originals; skip an existing column to avoid collisions. */
|
|
55
36
|
export declare function withIsoTimeColumns(result: Record<string, unknown>, axis: {
|
|
56
37
|
column: string;
|
|
57
38
|
time_zone?: string;
|
|
@@ -2,28 +2,14 @@ import { MAX_QUERY_FILTERS, DatasetQueryError } from './query.js';
|
|
|
2
2
|
import { isRecord } from './args.js';
|
|
3
3
|
import { isoDateFromEpoch, translateFilterDateValue } from './time-axis.js';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 内嵌的 queries 直接走引擎,于是同一个模型在同一个回合里得到两种行为——
|
|
9
|
-
* - `query_dataset`:`">=": "2026-09"` → 正确当作 9 月区间;
|
|
10
|
-
* - `describe_dataset.queries`:`">=": "2026"` → 被引擎当**数字 2026** 比较,243 行全中
|
|
11
|
-
* (`days_2026: 243`,静默错误);`">=": "2026-07"` → 整条被 `skip_with_warning` 跳过,
|
|
12
|
-
* `matched_row_count: 0`(静默空结果)。
|
|
13
|
-
* 协议与委派消息都写着"时间筛选直接写日期串",所以**只要有一个入口没接上,模型就一定踩**。
|
|
14
|
-
* 新增任何"执行 QuerySpec"的入口都必须复用本模块,不要在别处再写一份。
|
|
5
|
+
* Query-time normalization: the single implementation shared by query_dataset and
|
|
6
|
+
* describe_dataset.queries. Every entry point executing a QuerySpec must call it; an entry that
|
|
7
|
+
* skips it gives date strings a different meaning in the same turn.
|
|
15
8
|
*/
|
|
16
9
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* 为什么在工具边界做(真机会话 1e44050e 实测):`date_ms` 列只接受毫秒,模型于是把
|
|
21
|
-
* "近 1 月 / 近 3 月 / 52 周 / 年初至今 / 历年 10 月"全部手算成 epoch——单步思考 41,961
|
|
22
|
-
* 字符里绝大部分是这类算术,而且算错就是错口径。日历换算是确定性计算,属于宿主:
|
|
23
|
-
* 模型只写日历,宿主按**该列观测到的偏移**换算。
|
|
24
|
-
*
|
|
25
|
-
* 日期串落到数值时间列时**一律响亮失败**:旧路径下 `skip_with_warning` 会把这条 filter
|
|
26
|
-
* 整条跳过(`filter X skipped incompatible value`),于是"筛选消失、返回全量"却看起来成功。
|
|
10
|
+
* Translate date-like filter values into this Dataset's time-axis bounds and add `*_iso` result
|
|
11
|
+
* columns. Date strings against a numeric time column fail loudly; a skipped filter would look
|
|
12
|
+
* like a successful full-table result.
|
|
27
13
|
*/
|
|
28
14
|
export async function normalizeQueryDates(input) {
|
|
29
15
|
const filters = input.query.filters;
|
|
@@ -39,8 +25,7 @@ export async function normalizeQueryDates(input) {
|
|
|
39
25
|
});
|
|
40
26
|
}
|
|
41
27
|
catch {
|
|
42
|
-
//
|
|
43
|
-
// 让真正的查询路径去报它自己的错,不要在这里把错误提前成另一种。
|
|
28
|
+
// Probe failure only affects this convenience translation; let the real query path report its own error.
|
|
44
29
|
return { query: input.query };
|
|
45
30
|
}
|
|
46
31
|
if (snapshot === undefined)
|
|
@@ -75,11 +60,7 @@ export async function normalizeQueryDates(input) {
|
|
|
75
60
|
axis: { column: snapshot.axis.column, ...(snapshot.axis.time_zone === undefined ? {} : { time_zone: snapshot.axis.time_zone }) },
|
|
76
61
|
};
|
|
77
62
|
}
|
|
78
|
-
/**
|
|
79
|
-
* 结果补 ISO 时间列:`date_ms: 1787414400000` 旁边给 `date_ms_iso: "2026-08-23"`。
|
|
80
|
-
* 只加不删,原值仍是唯一权威(回传口径要求"字段按工具返回原样")。
|
|
81
|
-
* 已有同名 `*_iso` 列时不覆盖,避免与 Dataset 自带字段撞名。
|
|
82
|
-
*/
|
|
63
|
+
/** Add `*_iso` columns without replacing originals; skip an existing column to avoid collisions. */
|
|
83
64
|
export function withIsoTimeColumns(result, axis) {
|
|
84
65
|
const rows = result.rows;
|
|
85
66
|
if (!Array.isArray(rows))
|