@v587d/capital-generation 2.8.3 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +81 -1
  2. package/README.md +40 -19
  3. package/chart-ui/README.md +5 -4
  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 +13 -8
  25. package/lib/chart/artifact-ref.js +27 -10
  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 +43 -189
  34. package/lib/chart/html.d.ts +2 -5
  35. package/lib/chart/html.js +8 -15
  36. package/lib/chart/present-tool.d.ts +28 -0
  37. package/lib/chart/present-tool.js +128 -0
  38. package/lib/chart/runtime.d.ts +5 -18
  39. package/lib/chart/runtime.js +14 -34
  40. package/lib/chart/series.d.ts +10 -17
  41. package/lib/chart/series.js +22 -47
  42. package/lib/chart/source-token.d.ts +5 -13
  43. package/lib/chart/source-token.js +18 -17
  44. package/lib/chart/spec.d.ts +6 -12
  45. package/lib/chart/spec.js +7 -31
  46. package/lib/chart/tool.d.ts +7 -29
  47. package/lib/chart/tool.js +15 -25
  48. package/lib/chart-render-entry.d.ts +10 -0
  49. package/lib/chart-render-entry.js +14 -0
  50. package/lib/chart-source-entry.d.ts +13 -0
  51. package/lib/chart-source-entry.js +20 -0
  52. package/lib/collect-entry.d.ts +9 -0
  53. package/lib/collect-entry.js +10 -0
  54. package/lib/collector-runtime.d.ts +22 -0
  55. package/lib/collector-runtime.js +97 -0
  56. package/lib/data-collector/args.d.ts +6 -24
  57. package/lib/data-collector/args.js +6 -24
  58. package/lib/data-collector/dataset-tools.d.ts +7 -1
  59. package/lib/data-collector/dataset-tools.js +27 -56
  60. package/lib/data-collector/describe.d.ts +8 -33
  61. package/lib/data-collector/describe.js +17 -70
  62. package/lib/data-collector/hub.d.ts +27 -85
  63. package/lib/data-collector/hub.js +19 -85
  64. package/lib/data-collector/query-time.d.ts +7 -26
  65. package/lib/data-collector/query-time.js +8 -27
  66. package/lib/data-collector/store.d.ts +41 -117
  67. package/lib/data-collector/store.js +32 -111
  68. package/lib/data-collector/time-axis.d.ts +16 -76
  69. package/lib/data-collector/time-axis.js +25 -111
  70. package/lib/data-collector/tools.d.ts +6 -6
  71. package/lib/data-collector/tools.js +34 -43
  72. package/lib/dataset-read-entry.d.ts +9 -0
  73. package/lib/dataset-read-entry.js +7 -0
  74. package/lib/index.d.ts +7 -117
  75. package/lib/index.js +43 -422
  76. package/lib/net/eastmoney-client.d.ts +13 -65
  77. package/lib/net/eastmoney-client.js +12 -53
  78. package/lib/net/throttle.d.ts +2 -13
  79. package/lib/net/throttle.js +3 -14
  80. package/lib/ocr/client.d.ts +12 -21
  81. package/lib/ocr/client.js +23 -40
  82. package/lib/ocr/markdown.d.ts +20 -26
  83. package/lib/ocr/markdown.js +23 -36
  84. package/lib/ocr/store.d.ts +14 -47
  85. package/lib/ocr/store.js +15 -45
  86. package/lib/ocr-entry.d.ts +9 -0
  87. package/lib/ocr-entry.js +10 -0
  88. package/lib/policy-entry.d.ts +17 -0
  89. package/lib/policy-entry.js +22 -0
  90. package/lib/present-entry.d.ts +9 -0
  91. package/lib/present-entry.js +13 -0
  92. package/lib/preset-claim.d.ts +22 -0
  93. package/lib/preset-claim.js +24 -0
  94. package/lib/preset-ids.d.ts +38 -0
  95. package/lib/preset-ids.js +46 -0
  96. package/lib/retriever-config.d.ts +37 -0
  97. package/lib/retriever-config.js +36 -0
  98. package/lib/retriever-runtime.d.ts +31 -0
  99. package/lib/retriever-runtime.js +82 -0
  100. package/lib/root-extras-entry.d.ts +17 -0
  101. package/lib/root-extras-entry.js +25 -0
  102. package/lib/runtime-entry.d.ts +15 -0
  103. package/lib/runtime-entry.js +5 -0
  104. package/lib/runtime.d.ts +21 -0
  105. package/lib/runtime.js +33 -0
  106. package/lib/search-entry.d.ts +9 -0
  107. package/lib/search-entry.js +9 -0
  108. package/lib/sources/eastmoney-http.js +20 -68
  109. package/lib/sources/ftshare-rest.d.ts +6 -34
  110. package/lib/sources/ftshare-rest.js +84 -292
  111. package/lib/sources/fuyao-core.d.ts +8 -18
  112. package/lib/sources/fuyao-core.js +10 -21
  113. package/lib/sources/fuyao-rest.d.ts +2 -6
  114. package/lib/sources/fuyao-rest.js +23 -102
  115. package/lib/sources/security-code.d.ts +6 -12
  116. package/lib/sources/security-code.js +6 -12
  117. package/lib/sources/security-identity.d.ts +1 -4
  118. package/lib/sources/security-identity.js +3 -8
  119. package/lib/sources/tencent-http.js +14 -28
  120. package/lib/sources/tencent-public-core.d.ts +8 -60
  121. package/lib/sources/tencent-public-core.js +12 -77
  122. package/lib/sources/wind-mcp.d.ts +0 -5
  123. package/lib/sources/wind-mcp.js +23 -78
  124. package/lib/sources-entry.d.ts +9 -0
  125. package/lib/sources-entry.js +9 -0
  126. package/lib/time/tools.d.ts +8 -31
  127. package/lib/time/tools.js +30 -66
  128. package/lib/time-entry.d.ts +9 -0
  129. package/lib/time-entry.js +7 -0
  130. package/lib/tool-exec.d.ts +45 -11
  131. package/lib/tool-exec.js +59 -6
  132. package/lib/user-customization.d.ts +14 -0
  133. package/lib/user-customization.js +47 -0
  134. package/lib/watchlist/tools.d.ts +3 -13
  135. package/lib/watchlist/tools.js +10 -30
  136. package/lib/watchlist-entry.d.ts +9 -0
  137. package/lib/watchlist-entry.js +6 -0
  138. package/lib/web-retriever/engines.d.ts +6 -11
  139. package/lib/web-retriever/engines.js +9 -14
  140. package/lib/web-retriever/html-markdown.d.ts +14 -34
  141. package/lib/web-retriever/html-markdown.js +39 -75
  142. package/lib/web-retriever/local-fetch.d.ts +22 -58
  143. package/lib/web-retriever/local-fetch.js +33 -77
  144. package/lib/web-retriever/retriever.d.ts +6 -18
  145. package/lib/web-retriever/retriever.js +7 -7
  146. package/lib/web-retriever/sources.d.ts +37 -124
  147. package/lib/web-retriever/sources.js +64 -196
  148. package/lib/web-retriever/tools.d.ts +43 -27
  149. package/lib/web-retriever/tools.js +99 -137
  150. package/lib/web-retriever/wind-client.d.ts +14 -31
  151. package/lib/web-retriever/wind-client.js +16 -32
  152. package/locale/zh.json +1 -1
  153. package/package.json +72 -7
  154. package/preset/capital-generation/README.md +87 -53
  155. package/preset/capital-generation/agent.patch.yml +532 -337
  156. package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +6 -4
  157. package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
  158. package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
  159. package/preset/capital-generation/skills/capital-equity-research/SKILL.md +4 -2
  160. package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
  161. package/preset/capital-generation/skills/capital-orchestration/SKILL.md +30 -33
  162. package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
  163. package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +14 -16
  164. package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
  165. package/watchlist/client.js +15 -148
  166. package/watchlist/client.src.cjs +148 -662
  167. package/watchlist/holding-rules.js +11 -38
  168. package/watchlist/index.js +127 -397
  169. package/watchlist/package.json +2 -2
@@ -3,39 +3,23 @@ import { isoDateFromEpoch } from '../data-collector/time-axis.js';
3
3
  import { getDataTimeContract } from '../time/tools.js';
4
4
  import { createWindClient, WIND_SERVER_ENDPOINTS } from '../web-retriever/wind-client.js';
5
5
  /**
6
- * Wind(万得)MCP 数据源——`docs/design/wind-market-source.md` §6.7 改口后的 P1 三条能力。
7
- *
8
- * 只接 fuyao 明确没有的:宏观/行业/汇率 EDB 序列(唯一来源)、按日期区间取的历史 K 线、
9
- * 每根带成交额/换手/均价的 K 线。与腾讯重叠的港美股快照与日周月线不接,NL 工具一概不接
10
- * (列名是 question 的函数,`output_schema` 无处锚定,§6.3)。
11
- *
12
- * ⛔ 下面每条解析约束都来自真报文(§6.2–§6.6 与 2026-10-06 第二轮),不是从上游文档抄的:
13
- * 1. 表格工具回 `{columns:[{name}], rows:[[标量]]}`,行**不是对象**,必须按列名转置;
14
- * 2. `columns[].type` 一律写 `string`,而 K 线/分钟的数值确实是字符串、EDB 的是 number——
15
- * 类型元数据不可信,数值由本文件解析,解析不了就响亮失败(§9.5);
16
- * 3. 时间写法一张表三种:K 线带**该行自己的**交易所偏移,EDB 是 `20260731` 紧凑串。
17
- * epoch 逐行按自带偏移换算,整段按 +08:00 处理会把美股错位 12 小时(§6.4);
18
- * 4. 行键全部 ASCII snake_case:`updateDate` → `update_date`、`CHANGEHANDRATE` →
19
- * `turnover_rate_pct`(§10.6,映射不到的列丢弃而不是透传)。
20
- */
21
- /**
22
- * 数据域的上游预算。健康响应实测 0.59–2.3 秒(EDB 取数 590ms、A 股分钟 653ms、日 K 2296ms),
23
- * 而"该日没有数据"与"上游故障"在线上都表现为**挂到超时**(§6.6;2026-10-06 连昨天还正常的
24
- * 港股日线也挂了三次)。Hub 的执行超时是 30 秒,而 `callTool` 每次要走 initialize + tools/call
25
- * 两跳,所以单次取 12 秒:健康请求有 5 倍余量,病态请求在 Hub 给出无信息的
26
- * `request timed out` 之前先由本文件给出**带原因**的失败。
6
+ * Wind MCP: only capabilities Fuyao lacks — EDB macro/industry/FX series, range K-lines with
7
+ * amount/turnover/avg. No NL tools (columns depend on the question; nothing to anchor in schema).
8
+ * Payload quirks: tables are `{columns, rows:[scalars]}`; `columns[].type` is always `string`;
9
+ * timestamps carry each row's exchange offset; unmapped columns are dropped.
27
10
  */
11
+ /** 12s: healthy calls take 0.6–2.3s; hangs and no-data look identical, so fail before Hub's 30s. */
28
12
  const WIND_SOURCE_TIMEOUT_MS = 12_000;
29
- /** 只放**每档都打过真报文**的 period(§6.5);`60 分钟`两次都挂,所以不放。 */
13
+ /** Only periods with verified payloads; 60-minute hangs both times and is excluded. */
30
14
  const KLINE_PERIOD_CODES = {
31
15
  m1: '1', m5: '3', m15: '5', m30: '6', day: '10', week: '11', month: '12',
32
16
  };
33
17
  const KLINE_PERIOD_LIST = Object.keys(KLINE_PERIOD_CODES).join('/');
34
18
  const MINUTE_PERIODS = new Set(['m1', 'm5', 'm15', 'm30']);
35
- /** 分钟档区间上限:单日约 240 条(§1.3),而本能力不暴露 count,跨日体积没有别的闸门。 */
19
+ /** Minute ranges cap at 10 days: ~240 rows/day and no count parameter gates volume. */
36
20
  const MINUTE_MAX_SPAN_DAYS = 10;
37
21
  const DAILY_MAX_SPAN_DAYS = 3660;
38
- /** 上游 K 线的列名(§6.3:`MATCH` 就是收盘价,Wind 的 K 线没有 CLOSE 列)。 */
22
+ /** Upstream K-line columns: MATCH is close (Wind has no CLOSE column). */
39
23
  const KLINE_TIME = 'TIME';
40
24
  const KLINE_OPEN = 'OPEN';
41
25
  const KLINE_MATCH = 'MATCH';
@@ -45,7 +29,6 @@ const KLINE_TURNOVER = 'TURNOVER';
45
29
  const KLINE_VOLUME = 'VOLUME';
46
30
  const KLINE_RATE = 'CHANGEHANDRATE';
47
31
  const KLINE_AVG = 'AVPRICE';
48
- /** 缺任何一列就构不成一根 K 线:回别的列形态即"薄表"(§6.6 的不存在代码只回两列)。 */
49
32
  const KLINE_REQUIRED = [KLINE_TIME, KLINE_OPEN, KLINE_MATCH, KLINE_HIGH, KLINE_LOW];
50
33
  function isRecord(value) {
51
34
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
@@ -75,7 +58,6 @@ function strictDate(value, name) {
75
58
  function dayDistance(from, to) {
76
59
  return Math.round((Date.parse(`${to}T00:00:00Z`) - Date.parse(`${from}T00:00:00Z`)) / 86_400_000);
77
60
  }
78
- /** 上游紧凑日期 `20260731` → `2026-07-31`;写不出合法日历日就失败,不放行畸形标签。 */
79
61
  function compactDate(value, context) {
80
62
  if (value === null || value === undefined || value === '')
81
63
  return null;
@@ -89,14 +71,13 @@ function compactDate(value, context) {
89
71
  }
90
72
  return date;
91
73
  }
92
- /** EDB 的报告期标签没有时区;按本仓宏观表(东财那九张)同一口径落 Asia/Shanghai 零点。 */
74
+ /** EDB period labels have no timezone; use Asia/Shanghai midnight like the Eastmoney macro tables. */
93
75
  function reportDateMs(date) {
94
76
  return Date.parse(`${date}T00:00:00+08:00`);
95
77
  }
96
78
  /**
97
- * 单元格 → 数值。`columns[].type` 一律写 `string`,所以必须自己解析(§6.2):K 线/分钟全是
98
- * 字符串(`"1318.00"`),EDB 与快照是真 number,两种都得吃。`INVALID` 是后端的"缺失/不适用",
99
- * 一律 null,**不许当 0 参与计算**(§1.4)。认不出的字符串是形状变了,不是缺数——响亮失败。
79
+ * `columns[].type` is always `string`; parse here. `INVALID` = missing → null, never 0.
80
+ * An unparseable string means the shape changed: fail loudly.
100
81
  */
101
82
  function windNumber(cell, context, field) {
102
83
  if (cell === null || cell === undefined)
@@ -113,7 +94,6 @@ function windNumber(cell, context, field) {
113
94
  throw windError(`Wind ${context} returned unparseable ${field} ${JSON.stringify(cell)}`, 'wind_invalid_response');
114
95
  return value;
115
96
  }
116
- /** 文本单元格:空串与 INVALID 都是"没有值";其余保留上游原文(含中文列值)。 */
117
97
  function windText(cell) {
118
98
  if (cell === null || cell === undefined)
119
99
  return null;
@@ -122,10 +102,7 @@ function windText(cell) {
122
102
  return null;
123
103
  return text;
124
104
  }
125
- /**
126
- * `unit` 的键是 `"HIGH 单位:"`——**空格 + 全角冒号**(§6.3)。逐列把上游原文摘进行里,
127
- * 不翻译也不猜币种:同一列在港美股可能是"港元"/"美元",抄下上游那三个字是唯一不骗人的做法。
128
- */
105
+ /** `unit` keys look like `"HIGH 单位:"` (space + fullwidth colon); copy the upstream unit text, never translate. */
129
106
  function windUnit(unit, column) {
130
107
  if (!isRecord(unit))
131
108
  return null;
@@ -135,7 +112,7 @@ function windUnit(unit, column) {
135
112
  }
136
113
  return null;
137
114
  }
138
- /** 上游的业务错误有两种形态:能解析成 JSON 的走 `error` 字段,不能的整段就是文本。 */
115
+ /** Business errors come as JSON (`error` field) or plain text; text must fail with the original text, not become 0 rows. */
139
116
  function requirePayload(result, context) {
140
117
  if (!result.ok) {
141
118
  const message = result.error ?? 'unknown Wind error';
@@ -155,13 +132,9 @@ function requirePayload(result, context) {
155
132
  }
156
133
  return result.data;
157
134
  }
158
- // 正文不是 JSON 时 wind-client 原样透传 `content`。上游的参数性抱怨(实测
159
- // "observation或者[beginDate、endDate]必须填一个")就是这一类文本:必须带着原文失败,
160
- // 不许退化成 0 行——那是把"你说错了"报成"没有数据"。
161
135
  const text = typeof result.content === 'string' ? result.content.trim() : '';
162
136
  throw windError(`Wind ${context} returned a non-table response: ${text.slice(0, 400) || '(empty)'}`, 'wind_unexpected_response');
163
137
  }
164
- /** 表格解包:`{data:{columns,rows,unit}}`(K 线/分钟)与顶层直接是表身的形态都接住。 */
165
138
  function requireTable(payload, context) {
166
139
  const node = isRecord(payload.data) ? payload.data : payload;
167
140
  const { columns, rows } = node;
@@ -183,9 +156,8 @@ function optionalColumnIndex(table, name) {
183
156
  return table.columns.includes(name) ? table.columns.indexOf(name) : -1;
184
157
  }
185
158
  /**
186
- * 指标元信息 → 行键。键集**不固定**:2026-10-06 实测汇率类没有 `unit`/`magnitude`/`currency`,
187
- * GDP 类三者齐全;缺的键落 null,不猜单位(§10.6 第 2 条的显式映射表就在这五行里)。
188
- * `series_begin_date`/`series_end_date` 是**该指标序列**的起止,不是本次请求的窗口。
159
+ * Metric metadata → row keys. Key sets vary (FX lacks unit/magnitude/currency); missing keys become
160
+ * null, never guessed. `series_*` bound the series, not the request window.
189
161
  */
190
162
  function metricMeta(meta, context) {
191
163
  const code = windText(meta.code);
@@ -225,11 +197,7 @@ async function executeEdbSearch(client, params, signal) {
225
197
  });
226
198
  return { data: rows, schema: edbSearchOutput };
227
199
  }
228
- /**
229
- * EDB 取数:`{metrics:[{meta, date[], value[]}]}` 的**并行数组**转置成一行一期(§3.3)。
230
- * 日期与数值长度不一致时不猜——多出来的日期没有值、多出来的值没有期间,两种都是把数值挂错
231
- * 期间,与 §1.4 里"excelTotalCount 不可信"同一族。
232
- */
200
+ /** Transpose `{metrics:[{meta, date[], value[]}]}` parallel arrays into one row per period; length mismatch fails. */
233
201
  async function executeEdbQuery(client, params, signal) {
234
202
  const payload = requirePayload(await client.callTool('query_economic_indicator_data', {
235
203
  question: params.indicator,
@@ -258,10 +226,7 @@ async function executeEdbQuery(client, params, signal) {
258
226
  }
259
227
  return { data: rows, schema: edbQueryOutput };
260
228
  }
261
- /**
262
- * K 线时间戳**必须自带偏移**(§6.4:A股/港股 +08:00、美股 -04:00,是交易所当地时间)。
263
- * 没写偏移就没法确定这是哪一刻,`Date.parse` 会按宿主本地时区猜——所以先失败。
264
- */
229
+ /** K-line timestamps must carry an explicit offset (exchange local time); `Date.parse` would guess host TZ. */
265
230
  function klineInstant(value, context) {
266
231
  const text = String(value ?? '').trim();
267
232
  if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:?\d{2})$/.test(text)) {
@@ -283,15 +248,11 @@ async function executeKline(client, params, signal) {
283
248
  period: KLINE_PERIOD_CODES[period],
284
249
  aftype: adjust === 'qfq' ? '0' : '2',
285
250
  };
286
- // 前复权必须给 afdate,且不得早于区间——两者任一缺失都是挂满 60 秒而不是报错(§6.5)。
287
- // 锁"当日"而不是暴露成参数:后复权以 afdate 为锚向未来放大,把锚默认成今天会得到
288
- // 一个逐位等于不复权却叫 hfq 的序列,所以 hfq 根本不放(参数层已拒)。
251
+ // qfq needs afdate within the range or it hangs 60s. Lock to today; hfq is not exposed.
289
252
  if (adjust === 'qfq')
290
253
  args.afdate = isoDateFromEpoch(Date.now(), 'Asia/Shanghai');
291
254
  const payload = requirePayload(await client.callTool('get_stock_kline', args, signal), 'get_stock_kline');
292
255
  const table = requireTable(payload, 'get_stock_kline');
293
- // 空表是合法成功:非交易日实测快回 rows=[] 且 unit=null(§6.2)——"这一区间没有行情"
294
- // 与"取不到"是两种结论,前者落 0 行 Dataset,后者由上面的错误分类负责。
295
256
  if (table.rows.length === 0)
296
257
  return { data: [], schema: klineOutput };
297
258
  const at = KLINE_REQUIRED.reduce((acc, name) => ({ ...acc, [name]: columnIndex(table, name, 'get_stock_kline') }), {});
@@ -412,8 +373,7 @@ function normalizeEdbSearch(params) {
412
373
  return { question };
413
374
  }
414
375
  function normalizeEdbQuery(params) {
415
- // `observation` 走这道显式拒绝,而不是靠 assertKnown:默默忽略时间参数是最坏的一种失败,
416
- // 而实测上游对 observation "给 3 只回 2 期"(§6.7),期数不可信。
376
+ // observation is rejected explicitly: it returns 2 periods for 3 requested, so counts are untrustworthy.
417
377
  if (params.observation !== undefined)
418
378
  throw new Error('parameter observation is not supported: 实测该口径给 3 期只回 2 期,期数不可信;请给 start_date 与 end_date');
419
379
  const raw = typeof params.indicator === 'string' ? params.indicator.trim() : '';
@@ -429,10 +389,7 @@ function normalizeEdbQuery(params) {
429
389
  throw new Error('parameter start_date must not be later than end_date');
430
390
  return { indicator: raw.replace(/\s+/g, ' '), start_date, end_date };
431
391
  }
432
- /**
433
- * 代码照 Wind 的写法收下,**不补也不猜交易所后缀**(§3.3):实测美股 `AAPL.O` 与 `AAPL.N`
434
- * 回完全相同的行(后缀不参与解析),所以猜一个后缀不会报错、只会静默认错标的。
435
- */
392
+ /** Codes are taken as Wind spells them, never with a guessed exchange suffix: AAPL.O and AAPL.N return identical rows. */
436
393
  function normalizeWindCode(value) {
437
394
  const raw = typeof value === 'string' ? value.trim().toUpperCase() : '';
438
395
  if (!/^[A-Z0-9][A-Z0-9.]*\.[A-Z]{1,3}$/.test(raw) || raw.includes('..')) {
@@ -441,8 +398,7 @@ function normalizeWindCode(value) {
441
398
  return raw;
442
399
  }
443
400
  function normalizeKline(params) {
444
- // `count` 进 allowed 只为了给出**可用**的拒绝理由(默默忽略"我要 N 根"是最坏的一种失败,
445
- // 同 tencent 港美股那两条对 start/end 的处理):负值/0 的语义只有上游蓝本的说法、没打过真报文(§6.8 第 2 条)。
401
+ // count is in `allowed` only to reject it with a reason: its negative/zero semantics are untested.
446
402
  if (params.count !== undefined)
447
403
  throw new Error('parameter count is not supported: 本能力按 start_date..end_date 取全区间,count 的正负/零语义(往后 N 条 / 往前 N 条 / 全区间)未实测,不暴露');
448
404
  const code = normalizeWindCode(params.code);
@@ -470,11 +426,7 @@ function normalizeKline(params) {
470
426
  }
471
427
  return { code, period, adjust, start_date, end_date };
472
428
  }
473
- /**
474
- * 单能力的 DataSource:客户端在**定义期**构造是安全的(它不持有 Key——每次 callTool 现解析,
475
- * 也不持有会话),所以这里没有任何"装配期读到凭据才注册"的门槛(§9.7⑤,桌面端那 61 颗
476
- * 静默缺席的事故就在这一条上)。
477
- */
429
+ /** Build at definition time is safe: no key (resolved per call), no session; registration is not gated on credentials. */
478
430
  function createWindSource(options) {
479
431
  const schema = {
480
432
  capability: options.capability,
@@ -496,7 +448,7 @@ function createWindSource(options) {
496
448
  timeoutMs: WIND_SOURCE_TIMEOUT_MS,
497
449
  resolveApiKey: options.resolveApiKey,
498
450
  });
499
- // 归一化会被跑两遍(Hub 入队前一次、execute 一次,§10.7),所以只换值不改键名、且必须幂等。
451
+ // normalize runs twice (Hub enqueue and execute), so keep key names and make it idempotent.
500
452
  const normalize = (params) => {
501
453
  if (!isRecord(params))
502
454
  throw new Error('params must be an object');
@@ -506,7 +458,6 @@ function createWindSource(options) {
506
458
  return {
507
459
  schema,
508
460
  normalizeParams: normalize,
509
- // 空结果是合法成功(非交易日分钟线实测快回 rows=[]),所以这道护栏只认形状、不认条数(§3.3)。
510
461
  validateOutput: (data) => Array.isArray(data),
511
462
  execute: (request, signal) => options.execute(client, normalize(request.params), signal),
512
463
  };
@@ -514,18 +465,12 @@ function createWindSource(options) {
514
465
  const EDB_SEARCH_DESCRIPTION = 'Wind EDB(经济数据库)指标**检索**:只找指标不取数,一行一个候选指标(code/name/unit/freq/source + 该序列的起止与更新日期)。这是 fuyao 与东财都没有的面:宏观、行业、汇率、全球序列。⛔ question 走后端 NER,字面请求不等于字面结果——实测「人民币兑美元汇率 指标」回的是**美元兑人民币**(source 为 美联储 / 台湾统计局),先核对 name 与 source 再取数。键集随指标族变化:实测汇率类没有 unit/magnitude/currency 三个键,缺的一律 null,不猜单位。取数走 wind_edb_query:确定性靠"先 search 拿 code、再按 code 取数"这两步拿到,不是靠 NL 稳定。没有匹配时返回 0 行,是合法空结果不是失败。';
515
466
  const EDB_QUERY_DESCRIPTION = 'Wind EDB 时间序列**取数**:上游回 {meta, date[], value[]} 并行数组,本能力转置成**一行一个观测期**(meta 逐行拼接,多指标可分次取后按 code 拼对比)。⛔ indicator 一次只填**一个**指标代码或一条中文问句:2026-10-06 实测逗号分隔的多码挂满 60 秒无应答,上游"多个代码用英文逗号分隔"的说法不成立,要多个就分次调用。start_date/end_date 必填——上游在日期区间与 observation 之间二选一,而 observation 实测"给 3 期只回 2 期"、期数不可信,本能力直接拒绝它。report_date 由上游紧凑串 20260731 归一而来,report_date_ms 按 Asia/Shanghai 零点(EDB 的日期本身没有时区)。unit 是上游单位原文;后端 INVALID 一律 null,**不得当 0 参与计算**。已实测指标代码直接当 indicator 与同义中文问句回同一份序列(M0000612 = 中国 CPI 当月同比):先 wind_edb_search 拿码再回来,确定性就在这两步。';
516
467
  const KLINE_DESCRIPTION = 'Wind K 线:单只标的按**日期区间**取 日/周/月 与 1/5/15/30 分钟。相对腾讯的增量(§6.6.1):按日期区间取 640 根之前的历史、每根带成交额/成交量/换手/均价的美股 K 线、以及分钟档;⚠️ **分钟档只有 A 股取到过行**,港股与美股的分钟到 2026-10-06 为止四次全挂、仍未证实,要港美股分时先小范围试一次。港美股日线与快照请走 tencent_*。⛔ code 必须自带交易所后缀(600519.SH / 00700.HK / AAPL.O),本能力不猜后缀——实测 AAPL.O 与 AAPL.N 回完全相同的行(后缀不参与解析),所以 BRK.B 一类双市场代码有认错标的的风险;每行的 code 只是请求写法回显,上游 K 线表里没有代码列。⛔ period 只放逐档打过真报文的七档,60 分钟实测两次都挂满超时、不提供。adjust 只有 none 与 qfq:后复权以 afdate 为锚向未来放大,锚默认成今天会得到一个逐位等于不复权却叫 hfq 的序列,所以宁可不给;qfq 的 afdate 由本能力锁成当日,因此 end_date 不得晚于当日;分钟档只允许 none。close 来自上游 MATCH 列(Wind 没有 CLOSE 列);上游多给的列不收录,行里以 output_fields 为准。数值列上游全是字符串(columns[].type 却一律写 string),解析不了即失败。time_ms 按**该行自带偏移**换算(A股/港股 +08:00、美股 -04:00 是交易所当地时间),整段按北京时间处理会让美股错位 12 小时。⚠️ volume 实测 A 股是**股**(腾讯 A 股 K 线是手,跨源先换);turnover_rate_pct 是百分数原值(0.2892 = 0.2892%),上游不给它的单位与分母口径。非交易日快回 0 行、是合法成功(实测 A 股国庆休市日 653ms 回空表);而"该日该市场分钟尚未落地"与上游故障都表现为挂到 12 秒超时(wind_upstream_timeout)——不要盲目重试同一区间,改更早日期或改走日线。积分计费:AUTH/RATE_LIMIT/后端错误一律不自动重试。';
517
- /**
518
- * §6.7 改口后的 P1 三条。`wind_edb_*` 是 P0(EDB 是唯一来源),`wind_stock_kline` 是 P1。
519
- * 不接的一概有理由:price_indicators 与全部 NL 工具(中文键 / 列名是 question 的函数)、
520
- * bond 与 fund/index(重叠或未测)、analytics_data(实测连 initialize 都挂)。
521
- */
522
468
  export function createWindSources(resolveApiKey) {
523
469
  return [
524
470
  createWindSource({
525
471
  capability: 'wind_edb_search',
526
472
  tool: 'search_economic_indicator',
527
473
  server: 'economic_data',
528
- // 指标目录与序列发布都是日/季粒度;缓存拉长是为了少烧积分,而不是省 RTT。
529
474
  cacheMaxAgeMs: 6 * 3_600_000,
530
475
  summary: 'Wind EDB 宏观/行业/汇率指标检索(只找码不取数)',
531
476
  description: EDB_SEARCH_DESCRIPTION,
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `tools/retrieve-sources` row: the nine named-source tools, gated by
3
+ * `localFetch.enabled`. When disabled they stay registered and fail loudly per call;
4
+ * unregistering at boot would freeze one config read for the process lifetime.
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ export declare const name = "capital-retrieve-sources-tools";
8
+ export declare const inject: string[];
9
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,9 @@
1
+ import { createRetrieverRuntime } from './retriever-runtime.js';
2
+ import { registerSourceTools } from './web-retriever/tools.js';
3
+ import { readRetrieverConfig } from './capital-settings.js';
4
+ export const name = 'capital-retrieve-sources-tools';
5
+ export const inject = ['tools'];
6
+ export function apply(ctx) {
7
+ const runtime = createRetrieverRuntime(ctx, readRetrieverConfig(ctx));
8
+ registerSourceTools(ctx, { ...runtime.localFetchConfig });
9
+ }
@@ -1,9 +1,5 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  import type { TimeAxisSnapshot } from '../data-collector/store.js';
3
- /**
4
- * 时间工具需要的 Dataset 通道。只依赖 store 的**只读探针**,
5
- * 因此 time 平面不需要认识 Dataset 的写入、profile 或查询实现。
6
- */
7
3
  export interface TimeAxisReader {
8
4
  describeTimeAxis(input: {
9
5
  session: unknown;
@@ -22,20 +18,14 @@ export interface DataTimeContract {
22
18
  warning?: string;
23
19
  }
24
20
  export declare function getDataTimeContract(capability: string): DataTimeContract | undefined;
25
- /** 校验并规范化 IANA 时区名;undefined 表示宿主本地时区。'UTC'/'GMT' 为合法特殊值。 */
26
21
  export declare function resolveTimeZone(timeZone: unknown): string | undefined;
27
- /** 宿主进程的本地 IANA 时区名。 */
28
22
  export declare function localTimeZone(): string;
29
- /**
30
- * 差值法计算某 IANA 时区在 now 时刻的 UTC 偏移毫秒数(timeZoneName 缺失时的回退)。
31
- */
23
+ /** UTC offset from formatted parts; fallback when timeZoneName is unavailable. */
32
24
  export declare function zoneOffsetMsByDiff(now: number, zone: string): number;
33
- /** 当前时刻读数(内部纯函数,便于测试):now 为 epoch 毫秒。 */
34
25
  export declare function readClock(now: number, timeZone?: string): Record<string, unknown>;
35
26
  /**
36
- * 注册时间工具(会话组作用域:主 Agent 与所有子 Agent 均可见,与数据工具同一
37
- * 注册通道)。模型知识截止日期不是当前时间;涉及"今天/现在/此刻/周几"的判断
38
- * 必须先调用本工具,绝不凭记忆猜测。
27
+ * Two time tools, not role-gated: get_local_datetime is the clock reading, resolve_data_time_range
28
+ * is conversion. Dataset-mode authorization is enforced at call time by src/tool-exec.ts.
39
29
  */
40
30
  export declare function registerTimeTool(ctx: Context, store?: TimeAxisReader): void;
41
31
  type DateParts = {
@@ -44,13 +34,8 @@ type DateParts = {
44
34
  day: number;
45
35
  };
46
36
  /**
47
- * period 参数的**唯一归一入口**:接受字面窗口字符串、`{unit,count}`,以及被序列化成
48
- * 字符串的 `{"unit":"year","count":2}`。
49
- *
50
- * 最后一种不是宽容癖好:真机实测模型会这么传(会话 cf464cb6 与 8002e358 各一次,
51
- * 两次都掉进 `unsupported period` 白跑一轮)。宿主对 QuerySpec 的数组/对象参数早有同一套
52
- * 宽容解析(`src/data-collector/args.ts`),period 没有理由更严格。
53
- * 返回值 `undefined` = 调用方没给 period(取数形态按当前日期、Dataset 形态按数据最后一天)。
37
+ * Sole period normalization: literal windows, {unit,count}, and stringified {unit,count} (models
38
+ * really send that). undefined means the caller omitted period.
54
39
  */
55
40
  export declare function periodInputOf(value: unknown): string | {
56
41
  unit: string;
@@ -58,19 +43,11 @@ export declare function periodInputOf(value: unknown): string | {
58
43
  } | undefined;
59
44
  export declare function dateParts(value: string): DateParts;
60
45
  export declare function shiftDate(value: string, unit: 'day' | 'week' | 'month' | 'quarter' | 'year', amount: number): string;
61
- /**
62
- * 两个日历日之间相差的天数(同区间的 `YYYY-MM-DD` 对,按 UTC 午夜算,不受时区与夏令时影响)。
63
- * 对外只有一份:`get_watchlist` 报"这条持仓标了几天"用的就是它,不许在消费方再写一遍
64
- * `Date.parse(...)/86_400_000`(AGENTS.md §9.7①)。
65
- */
46
+ /** Whole days between two dates, computed at UTC midnight so timezone/DST cannot shift it (one implementation). */
66
47
  export declare function dayDistance(start: string, end: string): number;
67
48
  /**
68
- * 单窗口解析(**共享**入口):`resolve_data_time_range`(能力参数那个模式)与
69
- * `time-axis.ts`(Dataset 时间轴模式)都用它,避免两处对同一个 period 词表给出不同答案。
70
- *
71
- * `resolvePeriod` 之外再认三种字面窗口:`all`(全区间)、`ytd`(年初到锚点)、
72
- * `YYYY-MM`(自然月)。它们不新增相对期语义,只是把"年内/某月"这两个最常用的
73
- * 分析窗口写成模型本来就会用的词。对象形态 `{unit,count}` 原样交给 `resolvePeriod`。
49
+ * Shared single-window resolver; also accepts all, ytd and YYYY-MM. Object periods go to
50
+ * resolvePeriod as-is.
74
51
  */
75
52
  export declare function resolveZonedPeriod(input: unknown, anchor: string): {
76
53
  start: string;