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