@v587d/capital-generation 2.8.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +41 -20
  3. package/chart-ui/README.md +1 -1
  4. package/chart-ui/client.src.cjs +7 -16
  5. package/chart-ui/index.js +12 -33
  6. package/chart-ui/package.json +2 -2
  7. package/config/client.js +18 -39
  8. package/config/client.src.cjs +79 -217
  9. package/config/index.js +22 -72
  10. package/config/package.json +2 -2
  11. package/cordis.patch.yml +46 -51
  12. package/lib/agents/bash-guard.d.ts +9 -55
  13. package/lib/agents/bash-guard.js +27 -67
  14. package/lib/agents/preset-roster.d.ts +22 -0
  15. package/lib/agents/preset-roster.js +87 -0
  16. package/lib/agents/role-tool-policy.d.ts +46 -0
  17. package/lib/agents/role-tool-policy.js +205 -0
  18. package/lib/agents/root-tool-policy.d.ts +28 -124
  19. package/lib/agents/root-tool-policy.js +164 -237
  20. package/lib/agents/selected-skills-provider.d.ts +2 -6
  21. package/lib/agents/selected-skills-provider.js +27 -84
  22. package/lib/capital-settings.d.ts +27 -0
  23. package/lib/capital-settings.js +23 -0
  24. package/lib/chart/artifact-ref.d.ts +3 -8
  25. package/lib/chart/artifact-ref.js +3 -8
  26. package/lib/chart/downsample.d.ts +2 -5
  27. package/lib/chart/downsample.js +3 -8
  28. package/lib/chart/errors.d.ts +3 -7
  29. package/lib/chart/errors.js +1 -2
  30. package/lib/chart/event-context.d.ts +8 -0
  31. package/lib/chart/event-context.js +39 -0
  32. package/lib/chart/events.d.ts +21 -78
  33. package/lib/chart/events.js +40 -189
  34. package/lib/chart/html.d.ts +2 -5
  35. package/lib/chart/html.js +8 -15
  36. package/lib/chart/runtime.d.ts +5 -18
  37. package/lib/chart/runtime.js +14 -34
  38. package/lib/chart/series.d.ts +10 -17
  39. package/lib/chart/series.js +22 -47
  40. package/lib/chart/source-token.d.ts +5 -13
  41. package/lib/chart/source-token.js +18 -17
  42. package/lib/chart/spec.d.ts +6 -12
  43. package/lib/chart/spec.js +7 -31
  44. package/lib/chart/tool.d.ts +6 -29
  45. package/lib/chart/tool.js +9 -23
  46. package/lib/chart-render-entry.d.ts +9 -0
  47. package/lib/chart-render-entry.js +17 -0
  48. package/lib/chart-source-entry.d.ts +13 -0
  49. package/lib/chart-source-entry.js +20 -0
  50. package/lib/collect-entry.d.ts +9 -0
  51. package/lib/collect-entry.js +10 -0
  52. package/lib/collector-runtime.d.ts +22 -0
  53. package/lib/collector-runtime.js +97 -0
  54. package/lib/data-collector/args.d.ts +6 -24
  55. package/lib/data-collector/args.js +6 -24
  56. package/lib/data-collector/dataset-tools.d.ts +7 -1
  57. package/lib/data-collector/dataset-tools.js +27 -56
  58. package/lib/data-collector/describe.d.ts +8 -33
  59. package/lib/data-collector/describe.js +17 -70
  60. package/lib/data-collector/hub.d.ts +27 -85
  61. package/lib/data-collector/hub.js +19 -85
  62. package/lib/data-collector/query-time.d.ts +7 -26
  63. package/lib/data-collector/query-time.js +8 -27
  64. package/lib/data-collector/store.d.ts +40 -117
  65. package/lib/data-collector/store.js +32 -111
  66. package/lib/data-collector/time-axis.d.ts +16 -76
  67. package/lib/data-collector/time-axis.js +25 -111
  68. package/lib/data-collector/tools.d.ts +6 -6
  69. package/lib/data-collector/tools.js +34 -43
  70. package/lib/dataset-read-entry.d.ts +9 -0
  71. package/lib/dataset-read-entry.js +7 -0
  72. package/lib/index.d.ts +7 -117
  73. package/lib/index.js +43 -422
  74. package/lib/net/eastmoney-client.d.ts +13 -65
  75. package/lib/net/eastmoney-client.js +12 -53
  76. package/lib/net/throttle.d.ts +2 -13
  77. package/lib/net/throttle.js +3 -14
  78. package/lib/ocr/client.d.ts +12 -21
  79. package/lib/ocr/client.js +23 -40
  80. package/lib/ocr/markdown.d.ts +20 -26
  81. package/lib/ocr/markdown.js +23 -36
  82. package/lib/ocr/store.d.ts +14 -47
  83. package/lib/ocr/store.js +15 -45
  84. package/lib/ocr-entry.d.ts +9 -0
  85. package/lib/ocr-entry.js +10 -0
  86. package/lib/policy-entry.d.ts +17 -0
  87. package/lib/policy-entry.js +22 -0
  88. package/lib/preset-claim.d.ts +22 -0
  89. package/lib/preset-claim.js +24 -0
  90. package/lib/preset-ids.d.ts +38 -0
  91. package/lib/preset-ids.js +46 -0
  92. package/lib/retriever-config.d.ts +37 -0
  93. package/lib/retriever-config.js +36 -0
  94. package/lib/retriever-runtime.d.ts +31 -0
  95. package/lib/retriever-runtime.js +82 -0
  96. package/lib/root-extras-entry.d.ts +17 -0
  97. package/lib/root-extras-entry.js +25 -0
  98. package/lib/runtime-entry.d.ts +15 -0
  99. package/lib/runtime-entry.js +5 -0
  100. package/lib/runtime.d.ts +21 -0
  101. package/lib/runtime.js +33 -0
  102. package/lib/search-entry.d.ts +9 -0
  103. package/lib/search-entry.js +9 -0
  104. package/lib/sources/eastmoney-http.js +20 -68
  105. package/lib/sources/ftshare-rest.d.ts +6 -31
  106. package/lib/sources/ftshare-rest.js +1194 -225
  107. package/lib/sources/fuyao-core.d.ts +8 -18
  108. package/lib/sources/fuyao-core.js +10 -21
  109. package/lib/sources/fuyao-rest.d.ts +2 -6
  110. package/lib/sources/fuyao-rest.js +23 -102
  111. package/lib/sources/security-code.d.ts +6 -12
  112. package/lib/sources/security-code.js +6 -12
  113. package/lib/sources/security-identity.d.ts +1 -4
  114. package/lib/sources/security-identity.js +3 -8
  115. package/lib/sources/tencent-http.js +14 -28
  116. package/lib/sources/tencent-public-core.d.ts +8 -60
  117. package/lib/sources/tencent-public-core.js +12 -77
  118. package/lib/sources/wind-mcp.d.ts +0 -5
  119. package/lib/sources/wind-mcp.js +23 -78
  120. package/lib/sources-entry.d.ts +9 -0
  121. package/lib/sources-entry.js +9 -0
  122. package/lib/time/tools.d.ts +8 -31
  123. package/lib/time/tools.js +39 -64
  124. package/lib/time-entry.d.ts +9 -0
  125. package/lib/time-entry.js +7 -0
  126. package/lib/tool-exec.d.ts +45 -11
  127. package/lib/tool-exec.js +59 -6
  128. package/lib/user-customization.d.ts +14 -0
  129. package/lib/user-customization.js +47 -0
  130. package/lib/watchlist/tools.d.ts +3 -13
  131. package/lib/watchlist/tools.js +10 -30
  132. package/lib/watchlist-entry.d.ts +9 -0
  133. package/lib/watchlist-entry.js +6 -0
  134. package/lib/web-retriever/engines.d.ts +6 -11
  135. package/lib/web-retriever/engines.js +9 -14
  136. package/lib/web-retriever/html-markdown.d.ts +14 -34
  137. package/lib/web-retriever/html-markdown.js +39 -75
  138. package/lib/web-retriever/local-fetch.d.ts +22 -58
  139. package/lib/web-retriever/local-fetch.js +33 -77
  140. package/lib/web-retriever/retriever.d.ts +6 -18
  141. package/lib/web-retriever/retriever.js +7 -7
  142. package/lib/web-retriever/sources.d.ts +37 -124
  143. package/lib/web-retriever/sources.js +64 -196
  144. package/lib/web-retriever/tools.d.ts +43 -27
  145. package/lib/web-retriever/tools.js +99 -137
  146. package/lib/web-retriever/wind-client.d.ts +14 -31
  147. package/lib/web-retriever/wind-client.js +16 -32
  148. package/locale/zh.json +1 -1
  149. package/package.json +67 -7
  150. package/preset/capital-generation/README.md +87 -53
  151. package/preset/capital-generation/agent.patch.yml +526 -337
  152. package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
  153. package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
  154. package/preset/capital-generation/skills/capital-equity-research/SKILL.md +1 -1
  155. package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
  156. package/preset/capital-generation/skills/capital-orchestration/SKILL.md +15 -19
  157. package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
  158. package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +9 -12
  159. package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
  160. package/watchlist/client.js +15 -148
  161. package/watchlist/client.src.cjs +148 -662
  162. package/watchlist/holding-rules.js +11 -38
  163. package/watchlist/index.js +127 -397
  164. package/watchlist/package.json +2 -2
@@ -1,9 +1,7 @@
1
1
  import { type QueryResult, type QuerySpec } from './query.js';
2
2
  /**
3
- * Phase 1/2 workspace-local Dataset store(设计约定见 AGENTS.md「数据布局」)。
4
- *
5
- * 原始 Dataset 只由宿主写入,Dataset/Profile 都通过 createIfAbsent 发布。
6
- * Agent 只能使用 dataset_id 和 opaque artifact_ref,不能传入真实路径。
3
+ * Workspace-local Dataset/profile store: immutable artifacts, host-written only. Agents receive
4
+ * dataset_id / opaque artifact_ref, never real paths; raw rows never enter agent messages.
7
5
  */
8
6
  export declare const WORKSPACE_DATA_DIR = "capital-data";
9
7
  export declare const DATASETS_SUBDIR = "capital-data/datasets";
@@ -14,23 +12,17 @@ export declare const PROFILE_FILE = "profile.json";
14
12
  export declare const DEFAULT_RETENTION_MS: number;
15
13
  export declare const MAX_RAW_READ_BYTES: number;
16
14
  export declare const MAX_PROFILE_BYTES: number;
17
- /** 类别事实:最多跟踪多少个不同取值;超过则 distinct_count 记 null 并标记 truncated。 */
15
+ /** Max distinct category values tracked; beyond this distinct_count is null and truncated is set. */
18
16
  export declare const MAX_CATEGORY_DISTINCT = 2000;
19
- /** `top_values` 只展示前 N 个高频取值,其余由 truncated 显式披露。 */
20
17
  export declare const MAX_CATEGORY_TOP_VALUES = 5;
21
- /** 结构摘要:路径条目上限与递归深度上限,保证 profile 体积有界。 */
18
+ /** Structure-digest caps that keep profile.json bounded. */
22
19
  export declare const MAX_STRUCTURE_ENTRIES = 24;
23
20
  /**
24
- * 文档型 Dataset(顶层对象、没有行数组,如财务指标 / 回测结果)交给 data_junior 的
25
- * 内容预算(Unicode 码点数)。取值必须留在 preset 的工具结果剪枝阈值(8192)以内:
26
- * 超过就会被剪掉中间段,而不是被完整读到。长数组按下面的档位逐级截断,截断了什么
27
- * 一律写进 `omitted`,不做静默省略。
21
+ * Document content budget in code points; must stay under the preset's 8192 prune threshold.
22
+ * Truncated arrays are reported in `omitted`, never silently dropped.
28
23
  */
29
24
  export declare const MAX_DOCUMENT_CHARS = 6000;
30
- /**
31
- * 呈现层(图表)一次性读取行集的上限。超过就拒绝,而不是把几百 MB 拉进宿主内存:
32
- * 图表是**有界呈现**,不是通用分析通道。
33
- */
25
+ /** Presentation rows are capped: charts are bounded rendering, not a general analysis channel. */
34
26
  export declare const MAX_PRESENTATION_ROWS = 200000;
35
27
  export declare const ARTIFACT_SCHEME = "workspace://";
36
28
  export interface DatasetRef {
@@ -50,7 +42,7 @@ export interface DatasetRef {
50
42
  export type DatasetShape = 'array' | 'envelope_item' | 'document' | 'none';
51
43
  export interface DatasetInspection extends DatasetRef {
52
44
  query_access: {
53
- /** true = 宿主可读取并产出事实(rows 可 query,document 只能 profile)。 */
45
+ /** true = host can read it and produce facts (rows queryable; documents profile-only). */
54
46
  readable: boolean;
55
47
  shape: DatasetShape;
56
48
  reason?: string;
@@ -94,16 +86,9 @@ export interface ProfileValidation {
94
86
  violations: ProfileValidationViolation[];
95
87
  }
96
88
  /**
97
- * Profile 的职责:把「原始行」翻译成模型可以安全引用的事实。
98
- *
99
- * 除了逐列的类型与缺失统计(Phase 1),Phase 2 起补齐四类事实,它们对应
100
- * Fuyao 数据里最常见的四类问题:
101
- * - `statistics` 加 `count` / `sum`:回答「合计多少、覆盖多少条」;
102
- * - `categories`:字符串列的 distinct 与高频取值,回答「有哪些类别、分布如何」;
103
- * - `time_facts`:时间列的覆盖范围 + 首行/末行的数值,回答「最新值、区间涨跌」——
104
- * 只报 min/max 会被模型讲成「先涨到最高再回落」,这是实测过的失真来源;
105
- * - `structure`:单元格里的数组/对象展开成有界路径摘要,回答「里面有几层、
106
- * 多少个叶子」,避免只报一句「这一格是 object」。
89
+ * Profile turns raw rows into facts the model can safely cite: per-column type/missing counts,
90
+ * statistics, category values, time facts and bounded structure digests. Time facts carry
91
+ * first/last row values because min/max alone get narrated as a trend.
107
92
  */
108
93
  export interface ProfileStatistic {
109
94
  count?: number;
@@ -120,58 +105,41 @@ export interface ProfileCategoryValue {
120
105
  count: number;
121
106
  }
122
107
  export interface ProfileCategory {
123
- /** 精确去重数;超过枚举上限时为 null(此时 truncated=true)。 */
124
108
  distinct_count: number | null;
125
109
  top_values: ProfileCategoryValue[];
126
- /** true = 还有未展示的取值,`top_values` 不是全部。 */
127
110
  truncated: boolean;
128
111
  }
129
112
  /**
130
- * 时间轴的**语义**(形态 + 该列自身观测到的时区偏移)。
131
- *
132
- * 存在的理由:`date_ms` 这类列的取值是 epoch 毫秒,模型既无法直接读出"这是哪一天",
133
- * 也无法把"近 1 个月"翻译回毫秒;没有这一块事实,它只能自己推算——实测单步
134
- * 41,961 字符的思考里绝大部分是时间戳算术(真机会话 1e44050e)。
135
- * 块内只放**观测到的**事实,不做猜测:推断不出就报 `unknown`。
113
+ * Time-axis semantics: value format plus the offset observed in the column itself. Never guessed;
114
+ * an undecidable axis is reported as `unknown`.
136
115
  */
137
116
  export interface ProfileTimeAxis {
138
117
  column: string;
139
118
  value_format: 'epoch_ms' | 'date_string' | 'unknown';
140
- /** 该列的取值按哪个时区解释(epoch_ms 且能推断时才有)。 */
141
119
  time_zone?: string;
142
- /** 上面的时区的 `+08:00` 形态;`time_zone` 已是 `UTC+08:00` 时不重复(避免同一事实写两遍)。 */
143
120
  utc_offset?: string;
144
- /** 取值对齐到当地午夜、日历日,还是无法判断。 */
145
121
  aligned_to?: 'local_midnight' | 'calendar_day' | 'unknown';
146
122
  }
147
- /** 一个可直接使用的分析窗口:日历区间 + 该列里真正的查询边界。 */
123
+ /** A ready-to-use window: calendar range plus the real query bounds for this column. */
148
124
  export interface ProfileTimeWindow {
149
125
  name: string;
150
126
  start_date: string;
151
127
  end_date: string;
152
- /** `>= value_ge` 即"从这个窗口的第一天开始"(epoch 轴为当地午夜)。 */
153
128
  value_ge: number | string;
154
- /** `<= value_le` 即"到这个窗口的最后一天结束"。 */
155
129
  value_le: number | string;
156
- /** 该窗口在 Dataset 里**真实存在**的首末日期;起点落在非交易日时这里是下一个交易日。 */
130
+ /** Real first/last date present in the Dataset; a non-trading start moves to the next session. */
157
131
  data_from: string | null;
158
132
  data_to: string | null;
159
133
  }
160
134
  export interface ProfileTimeFacts {
161
135
  time_column: string;
162
- /** 文件顺序是否随时间递增;null = 时间列有缺失,无法判断。 */
163
136
  ordered_ascending: boolean | null;
164
- /** 时间列的最小/最大值(覆盖范围),原值不改写。 */
165
137
  covered_from: unknown;
166
138
  covered_to: unknown;
167
- /** 覆盖范围的人类可读日期;只对 epoch_ms 轴出现(date_string 轴本身可读)。 */
168
139
  covered_from_iso?: string;
169
140
  covered_to_iso?: string;
170
- /** 时间轴语义:形态与时区偏移。 */
171
141
  axis: ProfileTimeAxis;
172
- /** 常用窗口(近 1 月 / 近 3 月 / 近 1 年 / 年初至今)的现成边界。 */
173
142
  windows?: ProfileTimeWindow[];
174
- /** 首行与末行的「时间列 + 数值列」取值(文件顺序),用于首末值与区间变化。 */
175
143
  first: Record<string, unknown>;
176
144
  last: Record<string, unknown>;
177
145
  }
@@ -181,17 +149,13 @@ export interface ProfileStructureField {
181
149
  }
182
150
  export interface ProfileStructure {
183
151
  type: 'array' | 'object';
184
- /** 该路径在数据中出现的次数(行内多次出现会累加)。 */
185
152
  occurrences: number;
186
- /** 未抽样时,所有出现位置的元素总数;对象路径不返回该字段。 */
187
153
  total_elements?: number;
188
- /** 该路径受上限影响时,实际检查到的元素/对象数量;存在时不代表完整数量。 */
154
+ /** Present only when sampling capped the scan; it is not the complete count. */
189
155
  sampled_elements?: number;
190
- /** 数组:空数组的出现次数与长度极值。 */
191
156
  empty_count: number;
192
157
  min_length: number;
193
158
  max_length: number;
194
- /** 对象:直接字段名与观察到的类型(有界)。 */
195
159
  fields: ProfileStructureField[];
196
160
  }
197
161
  export interface ProfilePayload {
@@ -254,8 +218,8 @@ export interface FsLike {
254
218
  }, signal?: AbortSignal, sandboxPolicy?: SandboxPolicyResultLike): Promise<unknown>;
255
219
  readText(target: FsTargetLike, signal?: AbortSignal): Promise<string>;
256
220
  /**
257
- * 原样读字节(本地 PDF / 图片交给外部解析服务时用)。宿主把上限放在这道缝上:
258
- * 超过 `maxBytes` 抛 `FS_TOO_LARGE`,**不会**返回截断结果。
221
+ * Raw bytes for external parsers (local PDF/images). The host enforces maxBytes on this seam:
222
+ * overflow throws FS_TOO_LARGE and never returns truncated content.
259
223
  */
260
224
  readBytes?(target: FsTargetLike, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
261
225
  listDir(target: FsTargetLike, signal?: AbortSignal): Promise<Array<{
@@ -278,18 +242,12 @@ export interface QueryDatasetInput {
278
242
  query: QuerySpec;
279
243
  signal?: AbortSignal;
280
244
  }
281
- /**
282
- * 时间轴探针结果:形态 + 已出现的日期 + 列清单。
283
- *
284
- * 三条消费路径共用同一份事实,避免"谁猜时间列"出现第二套判据:
285
- * ① `query_dataset` 的日期筛选归一;② `resolve_data_time_range` 的数据集模式;
286
- * ③ 报错信息里列出可选列。
287
- */
245
+ /** Time-axis probe shared by date normalization, dataset time windows and error hints. */
288
246
  export interface TimeAxisSnapshot {
289
247
  dataset_id: string;
290
248
  columns: string[];
291
249
  axis: ProfileTimeAxis;
292
- /** 探针看到的日期(升序、去重);用于把窗口夹到真实存在的交易日。 */
250
+ /** Probe dates (ascending, deduplicated); used to clamp windows to real trading days. */
293
251
  dates: string[];
294
252
  }
295
253
  export interface ProfileDatasetResult extends ProfileRef {
@@ -303,14 +261,13 @@ export interface ProfileDatasetResult extends ProfileRef {
303
261
  schema?: ProfilePayload['schema'];
304
262
  validation?: ProfilePayload['validation'];
305
263
  warnings: string[];
306
- /** 仅文档型 Dataset:有界内容,不随 profile 持久化。 */
264
+ /** Document Datasets only: bounded content, never persisted with the profile. */
307
265
  document?: ProfileDocument;
308
266
  }
309
267
  export interface ProfileDocument {
310
- /** 有界后的文档内容;连最小档位都放不下时为 null。 */
311
268
  content: unknown;
312
269
  truncated: boolean;
313
- /** 被截断的数组路径与省略条数,逐个披露,不做静默省略。 */
270
+ /** Every truncation is disclosed here; no silent omission. */
314
271
  omitted: Array<{
315
272
  path: string;
316
273
  kept: number;
@@ -333,7 +290,7 @@ export interface SaveDatasetInput {
333
290
  schema: object | null;
334
291
  row_count: number | null;
335
292
  data: unknown;
336
- /** Host-only row array key for envelope-shaped json_rows data. */
293
+ /** Host-only row key for envelope-shaped json_rows data. */
337
294
  row_key?: string;
338
295
  signal?: AbortSignal;
339
296
  }
@@ -353,11 +310,8 @@ export interface WorkspaceDatasetStoreOptions {
353
310
  }
354
311
  export declare const DATASET_ID_PATTERN: RegExp;
355
312
  /**
356
- * 规范化并校验 workspace **相对**路径。
357
- *
358
- * 拒绝:空串、绝对路径(含 Windows 盘符)、任何 `..` 段、反斜杠(本仓统一 `/`)。
359
- * 这里只做形态校验;真正的沙箱归属由 `resolveContained` 证明。两道关卡都要过——
360
- * 形态校验是为了给出可读错误,归属校验才是安全边界。
313
+ * Validate a workspace-relative path shape. Containment is proven separately by resolveContained;
314
+ * both checks are required, and this one only makes errors readable.
361
315
  */
362
316
  export declare function normalizeWorkspaceRelativePath(value: string): string;
363
317
  export declare class WorkspaceDatasetStore {
@@ -375,11 +329,8 @@ export declare class WorkspaceDatasetStore {
375
329
  profileDataset(input: ProfileDatasetInput): Promise<ProfileDatasetResult>;
376
330
  queryDataset(input: QueryDatasetInput): Promise<QueryResult>;
377
331
  /**
378
- * 时间轴只读探针(`query_dataset` 的日期归一边界与 `resolve_data_time_range` 的数据集模式共用)。
379
- *
380
- * 只读前 {@link MAX_AXIS_PROBE_ROWS} 行推断形态与偏移:时间列一定在每行都出现,日线数据
381
- * 的有序性也让前若干行足以定出粒度;这样这个探针**可以随查询逐次调用**,
382
- * 代价与"读一份 16MB raw.json 只为一个问题"不同量级。
332
+ * Read-only time-axis probe shared by the date normalizer and dataset time-window mode.
333
+ * Samples at most MAX_AXIS_PROBE_ROWS rows, so it is cheap enough to call per query.
383
334
  */
384
335
  describeTimeAxis(input: {
385
336
  session: SessionLike;
@@ -389,15 +340,9 @@ export declare class WorkspaceDatasetStore {
389
340
  }): Promise<TimeAxisSnapshot | undefined>;
390
341
  writeProfile(input: WriteProfileInput): Promise<ProfileRef>;
391
342
  /**
392
- * 宿主内部**呈现层**读取有界行集(图表用)。
393
- *
394
- * 契约(不可违反):
395
- * - 返回值**不得**进入任何 Agent 消息、工具结果或模型上下文;调用方只能在宿主进程内
396
- * 把它变成有界可视化载荷(见 src/chart/series.ts)。
397
- * - 这是唯一一处把原始 rows 交给 store 之外代码的入口,因此命名、文档与调用点都必须是
398
- * "呈现"语义。需要数值结论一律走 profile_dataset / query_dataset。
399
- * - session 作用域与 profile/query 同源(`sessionScopeFor` 取父会话),所以主 Agent
400
- * 用自己会话即可读到自己子 Agent 采到的 Dataset。
343
+ * Host-internal presentation read (charts). The returned rows must never enter any agent message,
344
+ * tool result or model context; this is the only path that hands raw rows outside the store.
345
+ * Session scope follows the parent session, so the main agent can present a child's Datasets.
401
346
  */
402
347
  readPresentationRows(input: {
403
348
  session: SessionLike;
@@ -409,11 +354,8 @@ export declare class WorkspaceDatasetStore {
409
354
  rows: unknown[];
410
355
  }>;
411
356
  /**
412
- * 宿主内部读取 workspace 内的**文本**文件(图表 spec、OCR 产物、用户本地数据)。
413
- *
414
- * 这是 workspace 读取的**唯一**实现:`readWorkspaceJson` / `readWorkspaceBytes` 都从
415
- * `workspaceFileTarget` 取归属与体积闸门,不各自再写一遍路径校验(§9.7——同一个事实
416
- * 有两份实现就一定会漂移)。
357
+ * Host-internal workspace text read; with readWorkspaceBytes it shares containment and size
358
+ * gates via workspaceFileTarget.
417
359
  */
418
360
  readWorkspaceText(input: {
419
361
  session: SessionLike;
@@ -422,11 +364,8 @@ export declare class WorkspaceDatasetStore {
422
364
  maxBytes?: number;
423
365
  }): Promise<string>;
424
366
  /**
425
- * 宿主内部读取 workspace 内的**原始字节**(本地 PDF / 图片上传给外部解析服务的入口)。
426
- *
427
- * 上限必须显式给出:这道缝后面的调用方要把字节整个装进内存,没有默认值可兜。
428
- * `fs.readBytes` 缺席(载体未挂载该能力)时**响亮失败**,不回退成 `readText`——
429
- * 二进制走文本读取会得到被解码污染的字节。
367
+ * Host-internal raw-byte read (local PDF/images). maxBytes is mandatory and fails loudly; never
368
+ * fall back to readText, which would corrupt binary data.
430
369
  */
431
370
  readWorkspaceBytes(input: {
432
371
  session: SessionLike;
@@ -435,25 +374,15 @@ export declare class WorkspaceDatasetStore {
435
374
  maxBytes: number;
436
375
  }): Promise<Uint8Array>;
437
376
  private workspaceFileTarget;
438
- /** 宿主内部呈现层读取 workspace 内的 JSON 文件(用户本地数据直接可视化的入口)。 */
439
377
  readWorkspaceJson(input: {
440
378
  session: SessionLike;
441
379
  path: string;
442
380
  signal?: AbortSignal;
443
381
  }): Promise<unknown>;
444
382
  /**
445
- * 宿主内部**产物层**把一组文本文件写进 workspace 的一个子目录(图表与 OCR 产物用)。
446
- *
447
- * 走与 Dataset 落盘完全相同的沙箱策略与归属校验;默认 `createIfAbsent`,保证不会静默
448
- * 覆盖已有产物(图表 id 唯一,重复即 bug,应当响亮失败)。
449
- *
450
- * `overwrite: true` 是给**内容寻址产物**重跑用的(OCR:上次在两个文件之间被中断,
451
- * 半件产物必须能被下一次解析自愈)。它不是"跳过幂等"的口子:幂等判断在调用方,
452
- * 只有已经决定重新生成时才传。
453
- *
454
- * 返回值同时给出**相对路径**(可以进 Agent 消息、可以 present 给用户)与**绝对路径**
455
- * (只供宿主内部使用,例如登记给 host 平面的取数路由)。这个区分是本仓的数据纪律:
456
- * 绝对路径不出宿主进程。
383
+ * Host-internal artifact write (charts, OCR): same sandbox and containment checks as Datasets,
384
+ * createIfAbsent by default. `overwrite` exists only for content-addressed reruns.
385
+ * Returns a relative path (agent-visible) and an absolute one (host-only).
457
386
  */
458
387
  writeWorkspaceFiles(input: {
459
388
  session: SessionLike;
@@ -471,14 +400,8 @@ export declare class WorkspaceDatasetStore {
471
400
  }>>;
472
401
  private requireRef;
473
402
  private requireStoredRef;
474
- /**
475
- * 读取原始 Dataset。返回两种形状之一:
476
- * - `rows`:顶层数组或 manifest 声明的行数组键,可 profile、可 query;
477
- * - `document`:顶层对象且没有行数组(财务指标、回测结果这类),可 profile
478
- * (宿主算结构摘要 + 有界内容),不可 query。
479
- * 判定放在读取时而不是复用 manifest 的 `format`:`format` 只记录写入时的粗略
480
- * 形状,真正的形状以文件内容为准。
481
- */
403
+ /** Read raw Dataset: rows (array/envelope) are queryable, documents are profile-only. Shape is
404
+ * decided from file content, not the manifest's coarse `format`. */
482
405
  private readRaw;
483
406
  private resolveContained;
484
407
  private assertContained;
@@ -486,7 +409,7 @@ export declare class WorkspaceDatasetStore {
486
409
  private readRoot;
487
410
  private mapWriteError;
488
411
  }
489
- /** 行内取列值:`present=false` 表示该列在这行不存在(缺失),与显式 null 区分开。 */
412
+ /** present=false distinguishes a missing column from an explicit null. */
490
413
  export interface ProfileCell {
491
414
  present: boolean;
492
415
  value: unknown;
@@ -1,10 +1,8 @@
1
1
  import { DatasetQueryError, executeJsonRowsQuery } from './query.js';
2
2
  import { defaultAxisWindows, isoDateFromEpoch, probeTimeAxis, readAxisDates } from './time-axis.js';
3
3
  /**
4
- * Phase 1/2 workspace-local Dataset store(设计约定见 AGENTS.md「数据布局」)。
5
- *
6
- * 原始 Dataset 只由宿主写入,Dataset/Profile 都通过 createIfAbsent 发布。
7
- * Agent 只能使用 dataset_id 和 opaque artifact_ref,不能传入真实路径。
4
+ * Workspace-local Dataset/profile store: immutable artifacts, host-written only. Agents receive
5
+ * dataset_id / opaque artifact_ref, never real paths; raw rows never enter agent messages.
8
6
  */
9
7
  export const WORKSPACE_DATA_DIR = 'capital-data';
10
8
  export const DATASETS_SUBDIR = `${WORKSPACE_DATA_DIR}/datasets`;
@@ -15,39 +13,26 @@ export const PROFILE_FILE = 'profile.json';
15
13
  export const DEFAULT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
16
14
  export const MAX_RAW_READ_BYTES = 16 * 1024 * 1024;
17
15
  export const MAX_PROFILE_BYTES = 64 * 1024;
18
- /** 类别事实:最多跟踪多少个不同取值;超过则 distinct_count 记 null 并标记 truncated。 */
16
+ /** Max distinct category values tracked; beyond this distinct_count is null and truncated is set. */
19
17
  export const MAX_CATEGORY_DISTINCT = 2_000;
20
- /** `top_values` 只展示前 N 个高频取值,其余由 truncated 显式披露。 */
21
18
  export const MAX_CATEGORY_TOP_VALUES = 5;
22
19
  const MAX_CATEGORY_VALUE_LENGTH = 64;
23
- /** 结构摘要:路径条目上限与递归深度上限,保证 profile 体积有界。 */
20
+ /** Structure-digest caps that keep profile.json bounded. */
24
21
  export const MAX_STRUCTURE_ENTRIES = 24;
25
- /**
26
- * 递归深度上限。行集合里 depth 0 就是列本身,文档里 depth 0 是 `$` 根,
27
- * 因此文档需要多一层才够到 `abilities[].indicators[]` 这种叶子。
28
- * 条目上限(24)才是真正的体积闸门,深度只是防止病态深嵌套。
29
- */
22
+ /** Depth 0 is the column itself for rows but `$` for documents; the entry cap is the real size gate. */
30
23
  const MAX_STRUCTURE_DEPTH = 4;
31
24
  const MAX_STRUCTURE_FIELDS = 8;
32
25
  const MAX_STRUCTURE_SAMPLED_ELEMENTS = 50;
33
- /**
34
- * `time_facts.windows` 的条数上限。默认只给 4 个常用窗口(近 1 月/3 月/1 年/年初至今),
35
- * 上限是防回涨的闸门:窗口是"现成坐标",不是让宿主把任意期间都算一遍。
36
- */
26
+ /** Window cap: windows are ready-made coordinates, not an invitation to precompute arbitrary ranges. */
37
27
  const MAX_TIME_WINDOWS = 8;
38
- /** 推断时间轴时最多检查多少行:命中的列通常在前若干行就出现,不必为一次探针读全量。 */
28
+ /** Probe only leading rows: the time column appears early and daily data is ordered. */
39
29
  const MAX_AXIS_PROBE_ROWS = 500;
40
30
  /**
41
- * 文档型 Dataset(顶层对象、没有行数组,如财务指标 / 回测结果)交给 data_junior 的
42
- * 内容预算(Unicode 码点数)。取值必须留在 preset 的工具结果剪枝阈值(8192)以内:
43
- * 超过就会被剪掉中间段,而不是被完整读到。长数组按下面的档位逐级截断,截断了什么
44
- * 一律写进 `omitted`,不做静默省略。
31
+ * Document content budget in code points; must stay under the preset's 8192 prune threshold.
32
+ * Truncated arrays are reported in `omitted`, never silently dropped.
45
33
  */
46
34
  export const MAX_DOCUMENT_CHARS = 6_000;
47
- /**
48
- * 呈现层(图表)一次性读取行集的上限。超过就拒绝,而不是把几百 MB 拉进宿主内存:
49
- * 图表是**有界呈现**,不是通用分析通道。
50
- */
35
+ /** Presentation rows are capped: charts are bounded rendering, not a general analysis channel. */
51
36
  export const MAX_PRESENTATION_ROWS = 200_000;
52
37
  const DOCUMENT_ARRAY_KEEP_STEPS = [8, 3, 1, 0];
53
38
  const DOCUMENT_MAX_DEPTH = 6;
@@ -73,11 +58,8 @@ function isAbsolutePath(value) {
73
58
  return /^([a-zA-Z]:[\\/]|\/)/.test(value);
74
59
  }
75
60
  /**
76
- * 规范化并校验 workspace **相对**路径。
77
- *
78
- * 拒绝:空串、绝对路径(含 Windows 盘符)、任何 `..` 段、反斜杠(本仓统一 `/`)。
79
- * 这里只做形态校验;真正的沙箱归属由 `resolveContained` 证明。两道关卡都要过——
80
- * 形态校验是为了给出可读错误,归属校验才是安全边界。
61
+ * Validate a workspace-relative path shape. Containment is proven separately by resolveContained;
62
+ * both checks are required, and this one only makes errors readable.
81
63
  */
82
64
  export function normalizeWorkspaceRelativePath(value) {
83
65
  if (typeof value !== 'string')
@@ -250,7 +232,7 @@ export class WorkspaceDatasetStore {
250
232
  profile,
251
233
  signal: input.signal,
252
234
  });
253
- // 结构摘要随 profile 持久化;有界内容只随本次工具结果返回,不进入 profile 产物。
235
+ // Structure digest is persisted with the profile; bounded content is returned only.
254
236
  return { ...profileRef, ...profile, document: boundDocument(raw.value) };
255
237
  }
256
238
  const profile = buildProfile(raw.rows, input.time_column, input.primary_key, extractRowContracts(ref.schema));
@@ -275,11 +257,8 @@ export class WorkspaceDatasetStore {
275
257
  return executeJsonRowsQuery(raw.rows, ref.schema, input.query);
276
258
  }
277
259
  /**
278
- * 时间轴只读探针(`query_dataset` 的日期归一边界与 `resolve_data_time_range` 的数据集模式共用)。
279
- *
280
- * 只读前 {@link MAX_AXIS_PROBE_ROWS} 行推断形态与偏移:时间列一定在每行都出现,日线数据
281
- * 的有序性也让前若干行足以定出粒度;这样这个探针**可以随查询逐次调用**,
282
- * 代价与"读一份 16MB raw.json 只为一个问题"不同量级。
260
+ * Read-only time-axis probe shared by the date normalizer and dataset time-window mode.
261
+ * Samples at most MAX_AXIS_PROBE_ROWS rows, so it is cheap enough to call per query.
283
262
  */
284
263
  async describeTimeAxis(input) {
285
264
  const stored = await this.requireStoredRef(input.dataset_id, input.session, input.signal);
@@ -338,15 +317,9 @@ export class WorkspaceDatasetStore {
338
317
  return profileRef;
339
318
  }
340
319
  /**
341
- * 宿主内部**呈现层**读取有界行集(图表用)。
342
- *
343
- * 契约(不可违反):
344
- * - 返回值**不得**进入任何 Agent 消息、工具结果或模型上下文;调用方只能在宿主进程内
345
- * 把它变成有界可视化载荷(见 src/chart/series.ts)。
346
- * - 这是唯一一处把原始 rows 交给 store 之外代码的入口,因此命名、文档与调用点都必须是
347
- * "呈现"语义。需要数值结论一律走 profile_dataset / query_dataset。
348
- * - session 作用域与 profile/query 同源(`sessionScopeFor` 取父会话),所以主 Agent
349
- * 用自己会话即可读到自己子 Agent 采到的 Dataset。
320
+ * Host-internal presentation read (charts). The returned rows must never enter any agent message,
321
+ * tool result or model context; this is the only path that hands raw rows outside the store.
322
+ * Session scope follows the parent session, so the main agent can present a child's Datasets.
350
323
  */
351
324
  async readPresentationRows(input) {
352
325
  const stored = await this.requireStoredRef(input.dataset_id, input.session, input.signal);
@@ -362,11 +335,8 @@ export class WorkspaceDatasetStore {
362
335
  return { ref, rows: raw.rows };
363
336
  }
364
337
  /**
365
- * 宿主内部读取 workspace 内的**文本**文件(图表 spec、OCR 产物、用户本地数据)。
366
- *
367
- * 这是 workspace 读取的**唯一**实现:`readWorkspaceJson` / `readWorkspaceBytes` 都从
368
- * `workspaceFileTarget` 取归属与体积闸门,不各自再写一遍路径校验(§9.7——同一个事实
369
- * 有两份实现就一定会漂移)。
338
+ * Host-internal workspace text read; with readWorkspaceBytes it shares containment and size
339
+ * gates via workspaceFileTarget.
370
340
  */
371
341
  async readWorkspaceText(input) {
372
342
  const maxBytes = input.maxBytes ?? MAX_RAW_READ_BYTES;
@@ -386,11 +356,8 @@ export class WorkspaceDatasetStore {
386
356
  return text;
387
357
  }
388
358
  /**
389
- * 宿主内部读取 workspace 内的**原始字节**(本地 PDF / 图片上传给外部解析服务的入口)。
390
- *
391
- * 上限必须显式给出:这道缝后面的调用方要把字节整个装进内存,没有默认值可兜。
392
- * `fs.readBytes` 缺席(载体未挂载该能力)时**响亮失败**,不回退成 `readText`——
393
- * 二进制走文本读取会得到被解码污染的字节。
359
+ * Host-internal raw-byte read (local PDF/images). maxBytes is mandatory and fails loudly; never
360
+ * fall back to readText, which would corrupt binary data.
394
361
  */
395
362
  async readWorkspaceBytes(input) {
396
363
  const { relative, target } = await this.workspaceFileTarget(input.session, input.path, input.signal, input.maxBytes);
@@ -423,7 +390,6 @@ export class WorkspaceDatasetStore {
423
390
  }
424
391
  return { relative, target };
425
392
  }
426
- /** 宿主内部呈现层读取 workspace 内的 JSON 文件(用户本地数据直接可视化的入口)。 */
427
393
  async readWorkspaceJson(input) {
428
394
  const text = await this.readWorkspaceText(input);
429
395
  try {
@@ -434,18 +400,9 @@ export class WorkspaceDatasetStore {
434
400
  }
435
401
  }
436
402
  /**
437
- * 宿主内部**产物层**把一组文本文件写进 workspace 的一个子目录(图表与 OCR 产物用)。
438
- *
439
- * 走与 Dataset 落盘完全相同的沙箱策略与归属校验;默认 `createIfAbsent`,保证不会静默
440
- * 覆盖已有产物(图表 id 唯一,重复即 bug,应当响亮失败)。
441
- *
442
- * `overwrite: true` 是给**内容寻址产物**重跑用的(OCR:上次在两个文件之间被中断,
443
- * 半件产物必须能被下一次解析自愈)。它不是"跳过幂等"的口子:幂等判断在调用方,
444
- * 只有已经决定重新生成时才传。
445
- *
446
- * 返回值同时给出**相对路径**(可以进 Agent 消息、可以 present 给用户)与**绝对路径**
447
- * (只供宿主内部使用,例如登记给 host 平面的取数路由)。这个区分是本仓的数据纪律:
448
- * 绝对路径不出宿主进程。
403
+ * Host-internal artifact write (charts, OCR): same sandbox and containment checks as Datasets,
404
+ * createIfAbsent by default. `overwrite` exists only for content-addressed reruns.
405
+ * Returns a relative path (agent-visible) and an absolute one (host-only).
449
406
  */
450
407
  async writeWorkspaceFiles(input) {
451
408
  const { session, signal } = input;
@@ -510,14 +467,8 @@ export class WorkspaceDatasetStore {
510
467
  throw new DatasetStoreError('dataset_expired', `Dataset ${datasetId} has expired`);
511
468
  return stored;
512
469
  }
513
- /**
514
- * 读取原始 Dataset。返回两种形状之一:
515
- * - `rows`:顶层数组或 manifest 声明的行数组键,可 profile、可 query;
516
- * - `document`:顶层对象且没有行数组(财务指标、回测结果这类),可 profile
517
- * (宿主算结构摘要 + 有界内容),不可 query。
518
- * 判定放在读取时而不是复用 manifest 的 `format`:`format` 只记录写入时的粗略
519
- * 形状,真正的形状以文件内容为准。
520
- */
470
+ /** Read raw Dataset: rows (array/envelope) are queryable, documents are profile-only. Shape is
471
+ * decided from file content, not the manifest's coarse `format`. */
521
472
  async readRaw(ref, session, signal, rowKey) {
522
473
  const root = this.readRoot(session);
523
474
  if (!root || !this.fs)
@@ -805,11 +756,7 @@ function buildProfile(rows, requestedTimeColumn, primaryKey, contracts = {}) {
805
756
  warnings,
806
757
  };
807
758
  }
808
- /**
809
- * 挑时间列:显式指定优先;否则按列名候选,并要求该列的非空值全是数字或日期样式
810
- * 字符串。只按列名取第一个匹配列会把 `report_type`(季度枚举)误当时间轴,
811
- * 于是首末值与覆盖范围全是错的口径。
812
- */
759
+ /** Prefer an explicit column; otherwise require a name match plus numeric/date-only values (report_type must not win). */
813
760
  function pickTimeColumn(rows, columns, requested) {
814
761
  if (requested !== undefined)
815
762
  return columns.includes(requested) ? requested : requested;
@@ -830,17 +777,7 @@ function pickTimeColumn(rows, columns, requested) {
830
777
  return undefined;
831
778
  }
832
779
  const DATE_LIKE_PATTERN = /^\d{4}(-\d{1,2}(-\d{1,2})?)?$/;
833
- /**
834
- * 时间事实:覆盖范围 + 首行/末行的「时间列与数值列」取值(文件顺序)。
835
- *
836
- * 只报 min/max 时,模型会把区间极值讲成走势(实测样本里出现过「从 74 涨到 105
837
- * 以上随后可能回落」这种没有依据的叙述)。首末行给出的是真实首末取值,
838
- * 区间变化因此可追溯;`ordered_ascending` 说明文件顺序,避免把末行当成最新。
839
- */
840
- /**
841
- * 时间事实的**唯一实现**(profile 与时间轴探针共用):
842
- * 覆盖范围(原值 + ISO)、时间轴语义、常用窗口、首末行取值。
843
- */
780
+ /** Sole implementation of time facts (profile and probe): coverage, axis, windows, first/last rows. */
844
781
  function timeFactsFor(rows, timeColumn, orderedAscending, numericColumns) {
845
782
  const project = (row) => {
846
783
  const projected = {};
@@ -896,14 +833,7 @@ function structureAccumulator(entries, path, type) {
896
833
  entries.set(path, created);
897
834
  return created;
898
835
  }
899
- /**
900
- * 把单元格里的数组/对象展开成有界路径摘要(深度 ≤3、条目 ≤24)。
901
- *
902
- * 路径约定:对象字段用 `.name`,数组元素用 `[]`,例如 QDII 额度的三层嵌套会得到
903
- * `sub_tab`(数组,共 3 项)→ `sub_tab[].fund_list`(数组,共 40 项)。
904
- * 未抽样时用 `total_elements` 表示完整数量;超过 50 项时改用 `sampled_elements`,明确只统计了实际检查到的部分,
905
- * 而不是让模型面对一句「sub_tab 的类型是 object」。
906
- */
836
+ /** Bounded path digest: `.name` for object fields, `[]` for array elements, depth and count capped. */
907
837
  function collectStructure(value, path, depth, entries, sampled = false) {
908
838
  if (depth > MAX_STRUCTURE_DEPTH)
909
839
  return;
@@ -985,12 +915,7 @@ function tallyCategoryValue(tally, column, value) {
985
915
  }
986
916
  columnTally.counts.set(key, 1);
987
917
  }
988
- /**
989
- * 文档型 Dataset 的 profile:没有行,所以不给行列统计,只给结构摘要。
990
- *
991
- * `$` 表示文档根,其余路径沿用 structure 的约定(对象字段用 `.name`、数组元素用 `[]`),
992
- * 例如财务指标会得到 `abilities`(共 5 项)与 `abilities[].indicators`(共 40 项)。
993
- */
918
+ /** Document profile: structure digest only, rooted at `$` (no row statistics). */
994
919
  function buildDocumentProfile(value) {
995
920
  const entries = new Map();
996
921
  collectStructure(value, '$', 0, entries);
@@ -1010,11 +935,7 @@ function buildDocumentProfile(value) {
1010
935
  warnings,
1011
936
  };
1012
937
  }
1013
- /**
1014
- * 把文档内容裁到预算内:长数组按档位逐级截断(保留前 N 项),每次尝试都重新
1015
- * 量体积,直到放得下。返回实际采用的档位与被省略的路径,供模型如实披露
1016
- * 「完整共 N 项,本次只给了前 K 项」。
1017
- */
938
+ /** Shrink long arrays in steps until the document fits; report every omitted path. */
1018
939
  function boundDocument(value) {
1019
940
  const measure = (candidate) => {
1020
941
  try {