@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,50 +1,27 @@
1
1
  import type { ProfileTimeAxis } from './store.js';
2
2
  export type AxisValueFormat = 'epoch_ms' | 'date_string' | 'unknown';
3
- /** epoch 毫秒 → 该时区下的 `YYYY-MM-DD`;时区非法时退化为 UTC,绝不抛错。 */
3
+ /** Epoch to zone-local YYYY-MM-DD; an invalid zone falls back to UTC and never throws. */
4
4
  export declare function isoDateFromEpoch(epochMs: number, zone: string): string;
5
- /**
6
- * 某时区下"该日 00:00 的 epoch"。
7
- *
8
- * 两遍收敛:先用目标日 UTC 午夜的偏移作估计,再按估计时刻的偏移校正一次——
9
- * 对固定偏移时区(A 股数据里全部如此)一遍即准,对 DST 边界也只在跨日时刻才有差别。
10
- */
5
+ /** Zone-local midnight, two-pass convergence; fixed-offset zones are exact on the first pass. */
11
6
  export declare function zoneDayStartMs(date: string, zone: string): number;
12
- /** 某时区下"该日 23:59:59.999 的 epoch",与 {@link zoneDayStartMs} 同一天。 */
13
7
  export declare function zoneDayEndMs(date: string, zone: string): number;
14
- /** 把毫秒偏移写成 `+08:00` / `-03:30` / `+00:00`。 */
15
8
  export declare function formatOffset(offsetMs: number): string;
16
- /**
17
- * 从列值推断时间轴的形态、偏移与对齐方式。
18
- *
19
- * - 全部是数字且都是 86_400_000 的整数倍 → 日粒度 epoch;偏移 = 值 mod 一天;
20
- * - 全部是 `YYYY-MM-DD` 样式字符串 → date_string(ISO 写法,模型可读,无需换算);
21
- * - 其余(秒级 epoch、混合类型、带时间的字符串)→ unknown,**不做猜测**:
22
- * 宿主宁可让模型看到 `unknown` 并用工具确认,也不要给一个看似合理的错误边界。
23
- */
9
+ /** Infer format, offset and alignment from observed values only; anything else is `unknown`, never guessed. */
24
10
  export declare function probeTimeAxis(column: string, values: unknown[]): ProfileTimeAxis;
25
- /** 时间轴上的一次边界换算:日历 → 该列里真正可用的值 / 查询下界。 */
11
+ /** One calendar-to-column conversion: usable bounds plus the real first/last data dates. */
26
12
  export interface AxisBounds {
27
- /** 日历起点(含):`>= value_ge` 即"从这一天开始"。 */
28
13
  value_ge: number | string;
29
- /** 日历终点(含)的日末:`<= value_le` 即"到这一天结束"。 */
30
14
  value_le: number | string;
31
- /** 该窗口内 Dataset 首行/末行的日期(无数据时为 null)。 */
32
15
  data_from: string | null;
33
16
  data_to: string | null;
34
17
  }
35
18
  /**
36
- * 把一条 filter 的日期写法翻译成该时间轴上可比较的值。
37
- *
38
- * 三种结果:
39
- * - `filters`:翻译后的条件(`=` 展开成 `>= value_ge` + `<= value_le` 两条),交给引擎;
40
- * - `undefined`:这条不需要翻译(值不是日期 / 列不是时间列);
41
- * - 抛错:**写法在此列上无意义**(日期字符串落到数值时间列等)。绝不静默放行——
42
- * 旧行为是 `skip_with_warning` 把该行整条跳过,等于"筛选条件消失、返回全量",
43
- * 而模型只会看到一份看起来成功的全量结果(比报错危险得多)。
19
+ * Translate a date-like filter value into comparable bounds. Dates mean whole days: `=` expands
20
+ * to >=/<=, while `>`, `<`, `in` and `!=` fail loudly instead of silently shifting a day.
44
21
  */
45
22
  export declare function translateFilterDateValue(input: {
46
23
  axis: ProfileTimeAxis;
47
- /** 探针看到的日期(升序),period 窗口要用它夹到真实存在的交易日。 */
24
+ /** Probe dates in ascending order; period windows clamp to real trading days. */
48
25
  dates: string[];
49
26
  column: string;
50
27
  operator: string;
@@ -54,19 +31,13 @@ export declare function translateFilterDateValue(input: {
54
31
  value: unknown;
55
32
  }[] | undefined;
56
33
  export interface AxisWindow extends AxisBounds {
57
- /** 窗口名,例如 `last_3_months`、`ytd`、`2026-10`。 */
58
34
  name: string;
59
- /** 解析出的日历区间(半开:start ≤ 日期 ≤ end)。 */
60
35
  start_date: string;
61
36
  end_date: string;
62
37
  }
63
38
  /**
64
- * Dataset 模式的时间窗解析结果:把"近 3 个月"这类说法直接翻译成**可直接使用的查询边界**,
65
- * 并同时给出人类可读的日期与 Dataset 里真实存在的首末日期。
66
- *
67
- * 这是 data_junior 侧 `resolve_data_time_range` 的第二形态(第一形态仍是"给 request_data
68
- * 生成上游时间参数")。两形态共用同一套 period 解析(`time/tools.ts` 的 resolveZonedPeriod),
69
- * 避免同一个词在两侧得到不同答案。
39
+ * Dataset-mode time window: period -> usable query bounds plus the real first/last dates present.
40
+ * Shares period parsing with capability mode so one phrase cannot mean two things.
70
41
  */
71
42
  export interface DatasetTimeResolution {
72
43
  mode: 'dataset';
@@ -74,38 +45,33 @@ export interface DatasetTimeResolution {
74
45
  timezone: string;
75
46
  time_column: string;
76
47
  period: string;
77
- /** 解析出的日历区间(名义区间)。 */
78
48
  range: {
79
49
  start_date: string;
80
50
  end_date: string;
81
51
  };
82
- /** 该区间内**真实存在**的首末日期(非交易日自动夹到相邻交易日)。 */
83
52
  data: {
84
53
  from: string | null;
85
54
  to: string | null;
86
55
  days: number | null;
87
56
  };
88
- /** 可直接使用的边界:`{column: time_column, operator: '>=', value: bound_ge}`。 */
57
+ /** Real dates present in the range; non-trading days clamp to the nearest session. */
89
58
  bounds: {
90
59
  bound_ge: number | string;
91
60
  bound_le: number | string;
92
61
  inclusive: boolean;
93
62
  };
94
63
  bounds_are_intraday: boolean;
95
- /** Dataset 的完整覆盖范围(ISO 形态)。 */
96
64
  dataset: {
97
65
  covered_from: string;
98
66
  covered_to: string;
99
67
  };
100
- /** 名义区间与真实数据不一致等必须让模型知道的事实(协议要求"warnings 必须遵守")。 */
68
+ /** Facts the model must respect (e.g. nominal range differs from actual data). */
101
69
  warnings: string[];
102
70
  }
103
- /** period 的两种写法:字面窗口(`last_3_months`)或 `{unit,count}`。 */
104
71
  export type PeriodInput = string | {
105
72
  unit: string;
106
73
  count: number;
107
74
  };
108
- /** period 的可读名字:对象形态写成 `2_year`,省略或空串写成 `all`。 */
109
75
  export declare function periodName(period: PeriodInput | undefined): string;
110
76
  export declare function resolveDatasetTimeWindow(input: {
111
77
  dataset_id: string;
@@ -116,21 +82,8 @@ export declare function resolveDatasetTimeWindow(input: {
116
82
  zone?: string;
117
83
  }): DatasetTimeResolution;
118
84
  /**
119
- * 把「日历窗口」解析成该时间轴上可用的边界值。
120
- *
121
- * `data_from` / `data_to` 用**该列自己的粒度**把窗口夹到真实存在的日期上:
122
- * 窗口起点落在周末/节假日时前移到下一个有数据的日期,终点回退到最后一个有数据的日期。
123
- * 这样"近 1 个月"这类窗口不会因为非交易日而给出空结果。
124
- */
125
- /**
126
- * 把「日历窗口」解析成该时间轴上可用的边界值。
127
- *
128
- * `data_from` / `data_to` 用**该列自己的粒度**把窗口夹到真实存在的日期上:窗口首日落在
129
- * 周末/节假日时前移到下一个有数据的日期,末日回退到最后一个有数据的日期。
130
- *
131
- * 锚点本身是非交易日时(例如"以周日为锚的近 1 周")末日**再往后认一天**:
132
- * 名义窗口 [9/14, 9/20] 里最后一个交易日是 9/18,而锚点想表达的"最近一周"其实该到 9/21。
133
- * 这不是猜测:只有当紧邻末日的下一个日期确实存在、且它晚于锚点时才会这样延伸。
85
+ * Resolve a calendar window into bounds on this axis. First/last data dates clamp to dates that
86
+ * actually exist; when the anchor is beyond the last data date, the end extends by one session.
134
87
  */
135
88
  export declare function resolveAxisWindow(input: {
136
89
  axis: ProfileTimeAxis;
@@ -139,29 +92,16 @@ export declare function resolveAxisWindow(input: {
139
92
  anchorDate?: string;
140
93
  zone?: string;
141
94
  }): AxisWindow;
142
- /**
143
- * 单值 → 该列里代表"这一天"的取值。
144
- *
145
- * `epoch_ms` 轴取当地午夜(value_ge 与 value_le 相等时的那个值),`date_string` 轴原样返回。
146
- * 用于 filter 的 `=` / `in`:先把日期压成单值,再交给引擎做相等比较。
147
- */
95
+ /** Single value representing a whole day: local midnight on epoch axes, the date itself otherwise. */
148
96
  export declare function axisValueForDate(axis: ProfileTimeAxis, date: string): number | string;
149
- /**
150
- * 读一份 Dataset 的时间轴(形态 + 已出现的日期清单)。
151
- *
152
- * 日期清单只用于把窗口夹到真实存在的交易日,所以按升序逐行收集、遇到更早的值才插入,
153
- * 到上限即停:`date_ms` 数据是有序的,正常情况下一次遍历就装满。
154
- */
97
+ /** Collect distinct axis dates in ascending order, capped at MAX_AXIS_DATES; used to clamp windows. */
155
98
  export declare const MAX_AXIS_DATES = 20000;
156
99
  export declare function readAxisDates(input: {
157
100
  axis: ProfileTimeAxis;
158
101
  rows: unknown[];
159
102
  limit?: number;
160
103
  }): string[];
161
- /** profile 的 `time_facts.windows`:最常见的几个窗口,让模型不必自己算锚点。
162
- *
163
- * 只给"数据里确实存在"的窗口;空窗口不出现,避免模型拿一个空区间去查询再困惑。
164
- */
104
+ /** Default windows for time_facts; windows with no data are omitted rather than returned empty. */
165
105
  export declare const DEFAULT_WINDOW_PERIODS: readonly ["last_1_month", "last_3_months", "last_1_year", "ytd"];
166
106
  export declare function defaultAxisWindows(input: {
167
107
  axis: ProfileTimeAxis;
@@ -1,47 +1,23 @@
1
1
  import { DatasetQueryError } from './query.js';
2
2
  import { dateParts, periodInputOf, resolveZonedPeriod, zoneOffsetMsByDiff } from '../time/tools.js';
3
3
  /**
4
- * Dataset 时间轴:把「日历」与「列里的时间值」双向对齐。
5
- *
6
- * 为什么必须由宿主做(2026-09-23 真机会话实测,问题单 session 1e44050e):
7
- * data_junior 拿到 `time_facts.covered_from = 1695571200000` 之后,要判断覆盖到哪一天、
8
- * 能不能做 52 周窗口、月末锚点是多少,只能自己把 epoch 换算成日历;而 `query_dataset` 的
9
- * filter 值又只接受毫秒,于是"2026-08-23 是多少毫秒"这类问题被反复心算。
10
- * 结果是单步思考 41,961 字符里绝大部分是时间戳算术,且口径随时可能算错(它自己在思考里
11
- * 写了 "Risky")。时间换算是**确定性计算**,属于宿主:
12
- *
13
- * 1. 正向(值 → 日历):{@link isoFromAxisValue} 供 profile 的 time_facts 输出 ISO;
14
- * 2. 反向(日历 → 值):{@link resolveAxisWindow} 把 ISO 日期/相对期解析成该列自己的边界值;
15
- * 3. 语义:{@link probeTimeAxis} 从**列自身观测到的偏移**推断时区,绝不猜。
16
- *
17
- * 日线数据里 `date_ms` = 当地午夜 00:00 的 epoch(实测 1695571200000 = 2023-09-25 00:00 +08:00
18
- * = UTC 前一天 16:00),所以"当天"的边界必须按该列的偏移算:值同时满足
19
- * `>= dayStart` 与 `<= dayEnd` 才算落在这一天。
4
+ * Dataset time axis: aligns calendar dates with the values stored in a column. `date_ms` is
5
+ * local-midnight epoch, so a day means `>= dayStart && <= dayEnd` under the column's observed
6
+ * offset. These conversions are deterministic host work, never left to the model.
20
7
  */
21
- /** 一天的毫秒数。 */
22
8
  const DAY_MS = 86_400_000;
23
- /**
24
- * 从"当地午夜的 epoch"反解时区偏移。
25
- *
26
- * 列里的值就是某地 00:00 的瞬间,所以它对一天的余数恰好是"UTC 当天几点",
27
- * 时区偏移 = 该余数的**相反数**:A 股数据余数 57_600_000(UTC 16:00)⇒ +08:00。
28
- * 余数先归一到 `(-12h, +12h]`(超半天即换到前一天),否则 +08:00 会被读成 -16:00。
29
- */
9
+ /** Recover the zone offset from local-midnight epochs: offset = -(value mod day), normalized to (-12h, +12h]. */
30
10
  function zoneOffsetFromMidnightValue(value) {
31
11
  const remainder = value % DAY_MS;
32
12
  const utcHourOfDay = remainder > DAY_MS / 2 ? remainder - DAY_MS : remainder;
33
13
  return -utcHourOfDay;
34
14
  }
35
- /** 推断出的偏移必须落在真实时区范围内(UTC-12:00 ~ UTC+14:00)。 */
15
+ /** Inferred offset must lie in UTC-12:00 .. UTC+14:00. */
36
16
  const MIN_OFFSET_MS = -12 * 3_600_000;
37
17
  const MAX_OFFSET_MS = 14 * 3_600_000;
38
- /**
39
- * 把数值列当 epoch 毫秒的必要量级。秒级 epoch(10 位,约 1.7e9)与毫秒级(13 位,约 1.8e12)
40
- * 差三个数量级:不加这道闸门时,秒级时间戳会被误判成"日粒度 epoch"(1.7e9 恰好是 86400 的
41
- * 整数倍),于是所有窗口边界整体错到 1970 年附近。1e11 ms ≈ 1973 年,足以把两者分开。
42
- */
18
+ /** Epoch must be >= 1e11: second-level epochs can also look day-aligned and would land near 1970. */
43
19
  const MIN_EPOCH_MS = 100_000_000_000;
44
- /** 能一键还原成 IANA 名的常见股票/基金数据时区(只有偏移等于它时才用名字,避免误标)。 */
20
+ /** Offsets matching these get an IANA name; anything else is rendered as UTC±HH:MM. */
45
21
  const CANONICAL_ZONES = [
46
22
  { zone: 'Asia/Shanghai', offsetMs: 8 * 3_600_000 },
47
23
  { zone: 'UTC', offsetMs: 0 },
@@ -49,7 +25,6 @@ const CANONICAL_ZONES = [
49
25
  function isRecordLike(value) {
50
26
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
51
27
  }
52
- /** 行内取列值:与 store 的 profileCell 同义,只为不把 store 的私有实现引到这里。 */
53
28
  function cellOf(row, column) {
54
29
  if (!isRecordLike(row))
55
30
  return { present: false, value: undefined };
@@ -57,7 +32,6 @@ function cellOf(row, column) {
57
32
  ? { present: true, value: row[column] }
58
33
  : { present: false, value: undefined };
59
34
  }
60
- /** 某时刻在某时区下的日历年月日;时区非法或 Intl 不可用时返回 undefined。 */
61
35
  function readDateParts(epochMs, zone) {
62
36
  try {
63
37
  const parts = new Intl.DateTimeFormat('en-US', { timeZone: zone, year: 'numeric', month: '2-digit', day: '2-digit' })
@@ -69,7 +43,6 @@ function readDateParts(epochMs, zone) {
69
43
  return undefined;
70
44
  }
71
45
  }
72
- /** `YYYY-MM-DD` 的严格解析:非法日期在这里就响亮失败,不带进任何换算。 */
73
46
  function readCalendarDate(value) {
74
47
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
75
48
  if (!match)
@@ -81,7 +54,6 @@ function readCalendarDate(value) {
81
54
  }
82
55
  return parts;
83
56
  }
84
- /** 日历天数推进:只按日/周/月/季度算,调用方只用到这三种。 */
85
57
  function shiftCalendarDate(value, unit, amount) {
86
58
  const parts = dateParts(value);
87
59
  if (unit === 'day') {
@@ -98,30 +70,23 @@ function shiftCalendarDate(value, unit, amount) {
98
70
  function finiteNumbers(values) {
99
71
  return values.filter((value) => typeof value === 'number' && Number.isFinite(value));
100
72
  }
101
- /** epoch 毫秒 → 该时区下的 `YYYY-MM-DD`;时区非法时退化为 UTC,绝不抛错。 */
73
+ /** Epoch to zone-local YYYY-MM-DD; an invalid zone falls back to UTC and never throws. */
102
74
  export function isoDateFromEpoch(epochMs, zone) {
103
75
  const parts = readDateParts(epochMs, zone) ?? readDateParts(epochMs, 'UTC');
104
76
  if (parts === undefined)
105
77
  return new Date(epochMs).toISOString().slice(0, 10);
106
78
  return `${String(parts.year).padStart(4, '0')}-${String(parts.month).padStart(2, '0')}-${String(parts.day).padStart(2, '0')}`;
107
79
  }
108
- /**
109
- * 某时区下"该日 00:00 的 epoch"。
110
- *
111
- * 两遍收敛:先用目标日 UTC 午夜的偏移作估计,再按估计时刻的偏移校正一次——
112
- * 对固定偏移时区(A 股数据里全部如此)一遍即准,对 DST 边界也只在跨日时刻才有差别。
113
- */
80
+ /** Zone-local midnight, two-pass convergence; fixed-offset zones are exact on the first pass. */
114
81
  export function zoneDayStartMs(date, zone) {
115
82
  const parts = readCalendarDate(date);
116
83
  const utcMidnight = Date.UTC(parts.year, parts.month - 1, parts.day);
117
84
  const estimated = utcMidnight - zoneOffsetMsByDiff(utcMidnight, zone);
118
85
  return utcMidnight - zoneOffsetMsByDiff(estimated, zone);
119
86
  }
120
- /** 某时区下"该日 23:59:59.999 的 epoch",与 {@link zoneDayStartMs} 同一天。 */
121
87
  export function zoneDayEndMs(date, zone) {
122
88
  return zoneDayStartMs(shiftCalendarDate(date, 'day', 1), zone) - 1;
123
89
  }
124
- /** 把毫秒偏移写成 `+08:00` / `-03:30` / `+00:00`。 */
125
90
  export function formatOffset(offsetMs) {
126
91
  const sign = offsetMs < 0 ? '-' : '+';
127
92
  const abs = Math.abs(offsetMs);
@@ -129,28 +94,19 @@ export function formatOffset(offsetMs) {
129
94
  const minutes = Math.floor((abs % 3_600_000) / 60_000);
130
95
  return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
131
96
  }
132
- /** 偏移落到常见数据时区时返回 IANA 名,否则返回 `UTC+08:00` 形态的说明串。 */
133
97
  function zoneForOffset(offsetMs) {
134
98
  const canonical = CANONICAL_ZONES.find((entry) => entry.offsetMs === offsetMs);
135
99
  return canonical ? canonical.zone : `UTC${formatOffset(offsetMs)}`;
136
100
  }
137
- /**
138
- * 从列值推断时间轴的形态、偏移与对齐方式。
139
- *
140
- * - 全部是数字且都是 86_400_000 的整数倍 → 日粒度 epoch;偏移 = 值 mod 一天;
141
- * - 全部是 `YYYY-MM-DD` 样式字符串 → date_string(ISO 写法,模型可读,无需换算);
142
- * - 其余(秒级 epoch、混合类型、带时间的字符串)→ unknown,**不做猜测**:
143
- * 宿主宁可让模型看到 `unknown` 并用工具确认,也不要给一个看似合理的错误边界。
144
- */
101
+ /** Infer format, offset and alignment from observed values only; anything else is `unknown`, never guessed. */
145
102
  export function probeTimeAxis(column, values) {
146
103
  const present = values.filter((value) => value !== null && value !== undefined);
147
104
  if (present.length === 0)
148
105
  return { column, value_format: 'unknown' };
149
106
  const numbers = finiteNumbers(present);
150
107
  if (numbers.length === present.length) {
151
- // 日粒度 = 每个值都等于"某天 + 同一个日内偏移",且这个偏移落在合法时区范围内。
152
- // 只检查 `value % DAY_MS === 0` 是不够的:A 股数据的当地午夜等于 UTC 前一天 16:00,
153
- // 余数是 57_600_000 而不是 0——按那个判据会把整列判成 unknown(实测踩过)。
108
+ // Daily granularity means every value shares one intra-day remainder, not remainder 0: local
109
+ // midnight is 16:00 UTC for A-share data, so `value % DAY_MS === 0` would reject the whole column.
154
110
  const offsetMs = zoneOffsetFromMidnightValue(numbers[0]);
155
111
  const dayAligned = numbers.every((value) => Number.isSafeInteger(value)
156
112
  && value >= MIN_EPOCH_MS
@@ -171,14 +127,8 @@ export function probeTimeAxis(column, values) {
171
127
  return { column, value_format: 'unknown' };
172
128
  }
173
129
  /**
174
- * 把一条 filter 的日期写法翻译成该时间轴上可比较的值。
175
- *
176
- * 三种结果:
177
- * - `filters`:翻译后的条件(`=` 展开成 `>= value_ge` + `<= value_le` 两条),交给引擎;
178
- * - `undefined`:这条不需要翻译(值不是日期 / 列不是时间列);
179
- * - 抛错:**写法在此列上无意义**(日期字符串落到数值时间列等)。绝不静默放行——
180
- * 旧行为是 `skip_with_warning` 把该行整条跳过,等于"筛选条件消失、返回全量",
181
- * 而模型只会看到一份看起来成功的全量结果(比报错危险得多)。
130
+ * Translate a date-like filter value into comparable bounds. Dates mean whole days: `=` expands
131
+ * to >=/<=, while `>`, `<`, `in` and `!=` fail loudly instead of silently shifting a day.
182
132
  */
183
133
  export function translateFilterDateValue(input) {
184
134
  const { axis, dates, column, operator, value } = input;
@@ -186,7 +136,7 @@ export function translateFilterDateValue(input) {
186
136
  return undefined;
187
137
  const asDate = typeof value === 'string' ? dateFromText(value) : undefined;
188
138
  const asPeriod = isRecordLike(value) ? periodInputOf(value.period) : undefined;
189
- // `in` 的数组形态:逐个识别,一个日期都不认识才算"不是日期写法"。
139
+ // `in`: recognize dates element-wise; the value counts as a date form if any element is a date.
190
140
  const inDates = operator === 'in' && Array.isArray(value)
191
141
  ? value.filter((item) => typeof item === 'string' && dateFromText(item) !== undefined)
192
142
  : [];
@@ -195,13 +145,10 @@ export function translateFilterDateValue(input) {
195
145
  const label = asDate ?? (asPeriod === undefined ? inDates.join(',') : `${asPeriod}(period)`);
196
146
  const range = /^(>=|<=|=|>|<)$/.exec(operator);
197
147
  if (range === null) {
198
- // `in` / `!=` 在"整天"语义下表达的都是"排除"或"多天集合":前者要求把每个日期
199
- // 展开成两条边界(会被 filter 上限打回),后者根本不是范围查询。
200
- // 与其给一个差一天的答案,不如让模型改用 >= / <= 明确写出要哪些天。
148
+ // `in`/`!=` cannot express whole-day exclusion or multi-day sets without changing semantics.
201
149
  throw new DatasetQueryError('query_type_conflict', `filter ${column} cannot take a date-like value with operator ${operator}; dates always mean whole days, so write the range you want with >= and <= (got ${label})`);
202
150
  }
203
- // 日期只有"整天"的语义:> 与 < 会把边界挪到前一天/后一天的 23:59:59.999,
204
- // 等于悄悄换一个窗口——宁可响亮失败,也不给一个差一天的答案。
151
+ // Strict comparisons would shift the window by a day; fail loudly instead.
205
152
  if (operator === '>' || operator === '<') {
206
153
  throw new DatasetQueryError('query_type_conflict', `filter ${column} cannot take a date-like value with operator ${operator}; a date means the whole day, so use >= (or <=) instead of comparison ${operator} (for a window, pass YYYY-MM-DD or { period })`);
207
154
  }
@@ -225,7 +172,6 @@ export function translateFilterDateValue(input) {
225
172
  throw new DatasetQueryError('query_type_conflict', `filter ${column} cannot exclude a whole day by ${label} with ${operator}; ask for the days you want with >= / <= instead`);
226
173
  }
227
174
  }
228
- /** `YYYY-MM-DD` / `YYYY-MM` / `YYYY` → 该区间的首日;其余文本返回 undefined。 */
229
175
  function dateFromText(value) {
230
176
  const text = value.trim();
231
177
  const full = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
@@ -246,7 +192,6 @@ function dateFromText(value) {
246
192
  const year = /^(\d{4})$/.exec(text);
247
193
  return year ? `${year[1]}-01-01` : undefined;
248
194
  }
249
- /** period 的可读名字:对象形态写成 `2_year`,省略或空串写成 `all`。 */
250
195
  export function periodName(period) {
251
196
  if (period === undefined)
252
197
  return 'all';
@@ -299,21 +244,8 @@ function boundsFor(axis, startDate, endDate) {
299
244
  };
300
245
  }
301
246
  /**
302
- * 把「日历窗口」解析成该时间轴上可用的边界值。
303
- *
304
- * `data_from` / `data_to` 用**该列自己的粒度**把窗口夹到真实存在的日期上:
305
- * 窗口起点落在周末/节假日时前移到下一个有数据的日期,终点回退到最后一个有数据的日期。
306
- * 这样"近 1 个月"这类窗口不会因为非交易日而给出空结果。
307
- */
308
- /**
309
- * 把「日历窗口」解析成该时间轴上可用的边界值。
310
- *
311
- * `data_from` / `data_to` 用**该列自己的粒度**把窗口夹到真实存在的日期上:窗口首日落在
312
- * 周末/节假日时前移到下一个有数据的日期,末日回退到最后一个有数据的日期。
313
- *
314
- * 锚点本身是非交易日时(例如"以周日为锚的近 1 周")末日**再往后认一天**:
315
- * 名义窗口 [9/14, 9/20] 里最后一个交易日是 9/18,而锚点想表达的"最近一周"其实该到 9/21。
316
- * 这不是猜测:只有当紧邻末日的下一个日期确实存在、且它晚于锚点时才会这样延伸。
247
+ * Resolve a calendar window into bounds on this axis. First/last data dates clamp to dates that
248
+ * actually exist; when the anchor is beyond the last data date, the end extends by one session.
317
249
  */
318
250
  export function resolveAxisWindow(input) {
319
251
  const { axis, dates } = input;
@@ -322,11 +254,9 @@ export function resolveAxisWindow(input) {
322
254
  if (sorted.length === 0)
323
255
  throw new Error('dataset has no usable time values on the time axis');
324
256
  const anchor = input.anchorDate ?? sorted[sorted.length - 1];
325
- // 锚点就是 Dataset 最后一天时,"到锚点为止"不该再往后认一天(那是把窗口拖到未来);
326
- // 只有当锚点明确超出现有数据、且窗口末端被截断时,末日的下一个交易日才属于该窗口。
257
+ // An anchor at the dataset end must not extend into the future; only an anchor beyond the data may.
327
258
  const anchorIsDatasetEnd = anchor === sorted[sorted.length - 1];
328
- // 名字只用于展示:对象形态的 period(`{unit,count}`)必须**原样**交给解析器,
329
- // 不能先转成展示名再解析(`2_year` 不是合法 period,会掉进 unsupported period)。
259
+ // Name is display-only: object periods must reach the resolver as-is (`2_year` is not a valid period).
330
260
  const period = periodName(input.period);
331
261
  const resolved = resolveZonedPeriod(input.period ?? 'all', anchor);
332
262
  const clipped = clampToDates(sorted, resolved.start, resolved.end, { anchor, extend: !anchorIsDatasetEnd });
@@ -342,36 +272,24 @@ export function resolveAxisWindow(input) {
342
272
  data_to: clipped.to,
343
273
  };
344
274
  }
345
- /** 把窗口夹到真实存在的日期:起点取"第一个 ≥ start",终点取"最后一个 ≤ end"。 */
275
+ /** Clamp to existing dates: first >= start, last <= end (optionally one session past). */
346
276
  function clampToDates(sorted, start, end, boundary) {
347
277
  const inside = sorted.filter((date) => date >= start && date <= end);
348
278
  if (inside.length === 0)
349
279
  return undefined;
350
280
  const from = inside[0];
351
281
  const to = inside[inside.length - 1];
352
- // 窗口末端被前截(末日在锚点之前)、且该日之后确实还有下一个交易日时,才把它认进来。
353
- // 锚点就是数据最后一天时不延伸:那时窗口本来就该"到锚点为止",多认一天是拖到未来。
354
282
  const next = sorted[sorted.indexOf(to) + 1];
355
283
  const extended = boundary.extend && next !== undefined && end <= boundary.anchor ? next : to;
356
284
  return { from, to: extended };
357
285
  }
358
- /**
359
- * 单值 → 该列里代表"这一天"的取值。
360
- *
361
- * `epoch_ms` 轴取当地午夜(value_ge 与 value_le 相等时的那个值),`date_string` 轴原样返回。
362
- * 用于 filter 的 `=` / `in`:先把日期压成单值,再交给引擎做相等比较。
363
- */
286
+ /** Single value representing a whole day: local midnight on epoch axes, the date itself otherwise. */
364
287
  export function axisValueForDate(axis, date) {
365
288
  if (axis.value_format === 'date_string')
366
289
  return date;
367
290
  return zoneDayStartMs(date, axis.time_zone ?? 'UTC');
368
291
  }
369
- /**
370
- * 读一份 Dataset 的时间轴(形态 + 已出现的日期清单)。
371
- *
372
- * 日期清单只用于把窗口夹到真实存在的交易日,所以按升序逐行收集、遇到更早的值才插入,
373
- * 到上限即停:`date_ms` 数据是有序的,正常情况下一次遍历就装满。
374
- */
292
+ /** Collect distinct axis dates in ascending order, capped at MAX_AXIS_DATES; used to clamp windows. */
375
293
  export const MAX_AXIS_DATES = 20_000;
376
294
  export function readAxisDates(input) {
377
295
  const { axis, rows } = input;
@@ -397,10 +315,7 @@ export function readAxisDates(input) {
397
315
  }
398
316
  return dates.sort();
399
317
  }
400
- /** profile 的 `time_facts.windows`:最常见的几个窗口,让模型不必自己算锚点。
401
- *
402
- * 只给"数据里确实存在"的窗口;空窗口不出现,避免模型拿一个空区间去查询再困惑。
403
- */
318
+ /** Default windows for time_facts; windows with no data are omitted rather than returned empty. */
404
319
  export const DEFAULT_WINDOW_PERIODS = ['last_1_month', 'last_3_months', 'last_1_year', 'ytd'];
405
320
  export function defaultAxisWindows(input) {
406
321
  const windows = [];
@@ -412,7 +327,6 @@ export function defaultAxisWindows(input) {
412
327
  windows.push(window);
413
328
  }
414
329
  catch {
415
- // 数据覆盖不到这个窗口(例如上市不足一年)时不输出该窗口,不是错误。
416
330
  }
417
331
  }
418
332
  return windows;
@@ -1,6 +1,6 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  import { DataCollectorHub } from './hub.js';
3
- /** dc_status 诊断信息注入:探测凭据解析状态(只含有无/source,不含密钥值)。 */
3
+ /** dc_status diagnostics: credential presence only, never values. */
4
4
  export interface DataCollectorDiagnostics {
5
5
  probeApiKey: () => Promise<{
6
6
  present: boolean;
@@ -8,18 +8,18 @@ export interface DataCollectorDiagnostics {
8
8
  error?: string;
9
9
  }>;
10
10
  /**
11
- * 根 Agent 工具收敛的现场记录(`src/agents/root-tool-policy.ts` 的 `RootPolicyProbe`)。
12
- * 结构在这里重述一份(不按 import 拿类型):data_collector 不该依赖 agents 层。
13
- */
11
+ * Tool-surface restriction snapshot (root/role policies), restated locally so this layer does
12
+ * not depend on the agents layer. `allowed` stays empty now that allow-lists are gone.
13
+ */
14
14
  rootPolicy?: () => Array<{
15
15
  agentId: string | undefined;
16
16
  root: boolean;
17
17
  presetId: string;
18
18
  outcome: string;
19
19
  denied: string[];
20
+ allowed?: string[];
20
21
  failed: string[];
21
22
  }>;
22
- /** 每次 dc_status 真实执行时回调(写执行痕迹用;模型可伪造文本,但宿主文件痕迹与计数无法伪造)。 */
23
23
  onCall?: (snapshot: {
24
24
  at: number;
25
25
  call: number;
@@ -28,5 +28,5 @@ export interface DataCollectorDiagnostics {
28
28
  }) => void;
29
29
  }
30
30
  export declare function registerDataCollectorTools(ctx: Context, hub: DataCollectorHub, diagnostics?: DataCollectorDiagnostics): void;
31
- /** 供测试与宿主侧断言复用:模型可见的 DatasetRef 字段白名单。 */
31
+ /** Model-visible DatasetRef field whitelist (tests and host assertions). */
32
32
  export declare const DATASET_REF_FIELDS: readonly ["dataset_id", "task_id", "session_id", "artifact_ref", "format", "capability", "source_label", "schema", "row_count", "captured_at", "retention_until", "params_digest"];