@v587d/capital-generation 2.9.0 → 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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@
7
7
  - **第三位(patch)**:不破坏既有工具 / 配置 / 会话的新增与修复(如 2.1.1、2.1.2)。
8
8
  - **第二位(minor)**:有需要用户知晓的行为变更,且段内附迁移说明(如 2.1.0 的图表呈现通道重做、2.2.0 的数据源与检索来源扩容、2.4.0 的**必须换宿主版本**、2.9.0 的选择器收敛与质检前置——本插件自己的工具与数据契约没变,所以只抬第二位)。
9
9
 
10
+ ## [2.9.1] - 2026-10-21
11
+
12
+ 修复实机 `dsh web` 反馈的两处收尾体验:图表交付不再散落在等待轮,最终答复不再只剩一句
13
+ “分析已完成…见上”。不需要用户改配置,也没有宿主版本变化。
14
+
15
+ ### Added
16
+
17
+ - **`present_charts` 收尾交付步骤**(仅主 Agent):`render_chart` 出图后只回 `chart_ref`,主 Agent
18
+ 在写最终答复前一次性登记本轮全部图表,卡片因此落在**最后一轮**答复末尾的「本轮文件改动 /
19
+ 交付」行;refs 因上下文压缩丢失时可用 `task_id` 兜底。
20
+ - **图表登记支持按 task 回查**:registry 记录标题,并可按 root session / 工作区 / task 列出;
21
+ fork / branch 出来的顶层会话仍能解析并登记自己子 Agent 出的图。
22
+
23
+ ### Changed
24
+
25
+ - **自动模式不再在出图时自动登记交付**(单体载体保留):旧行为会把卡片写进主 Agent 等待子 Agent
26
+ 时所在的中间轮,DSH 折叠后用户容易错过。
27
+ - **最终答复必须是自足总结**:禁止“分析已完成 / 结论见上 / 如上”一类指针式收尾;被结算通知
28
+ 唤醒且无新信息时,要么不输出可见正文,要么重发一份自足总结。
29
+ - 收口顺序写进人设与编排协议:委派全部结算 → 对账通过 → `present_charts` → 最终答复。
30
+
31
+ ### Fixed
32
+
33
+ - fork / branch 顶层会话的 `parentSession` 是 seed 谱系,不再被当成委派链往上走(否则图表会
34
+ 登记到被 fork 的旧会话,且 root 自己解析不到 `chart_ref`)。
35
+ - 交付登记不再把内部记录字段 `owner_cwd` 混进模型可见回执。
36
+
10
37
  ## [2.9.0] - 2026-10-20
11
38
 
12
39
  新会话选择器收敛到三个用户模式:**自动模式 / 行情专家 / 资讯专员**。分析师与绘图专员继续注册,
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
  <p align="center">
19
19
  <a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin"></a>
20
20
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-green" alt="License"></a>
21
- <a href="https://github.com/v587d/capital-generation/releases"><img src="https://img.shields.io/badge/version-2.9.0-9cf" alt="Version"></a>
21
+ <a href="https://github.com/v587d/capital-generation/releases"><img src="https://img.shields.io/badge/version-2.9.1-9cf" alt="Version"></a>
22
22
  <a href="https://www.npmjs.com/package/@v587d/capital-generation"><img src="https://img.shields.io/npm/v/%40v587d%2Fcapital-generation" alt="npm version"></a>
23
23
  </p>
24
24
 
@@ -154,7 +154,7 @@ https://github.com/v587d/capital-generation
154
154
 
155
155
  - **查数据和资料:** 覆盖 A 股、港股、美股与期货等品种,由专用角色按需获取行情、财务、指数、基金、宏观行业与期货数据,检索公告、新闻和研报。共计 **128** 个数据收集能力和 **14** 个网页检索能力,部分公开数据源无需额外密钥;具体覆盖范围见 [数据收集能力表](docs/data-collector-capabilities.md) 和 [网页检索能力表](docs/web-retriever-capabilities.md)。
156
156
  - **数据质检:** 结构化数据不会直接进入模型上下文。每份数据先由分析师检查结构、时间轴、缺失与基础统计,再按受控方式汇总;原始数据行始终留在工具层。
157
- - **可视化:** 分析师判断数据适合图表,或你明确要图时,绘图专员会生成可交互图表;明确不要图时只完成质检。图表作为本轮交付物出现在会话里,可在侧栏查看或离线打开。了解处理流程可看 [Agent 角色说明](docs/agent-roles.md)。
157
+ - **可视化:** 分析师判断数据适合图表,或你明确要图时,绘图专员会生成可交互图表;明确不要图时只完成质检。图表会登记为**最后一轮答复**的交付物,在答复末尾集中出现,可在侧栏查看或离线打开。了解处理流程可看 [Agent 角色说明](docs/agent-roles.md)。
158
158
 
159
159
  | 走势折线 | K 线与量价 |
160
160
  | :---: | :---: |
@@ -190,7 +190,7 @@ https://github.com/v587d/capital-generation
190
190
  - **安装后找不到模式?** 确认插件出现在「插件」→ 已安装列表;Web / CLI profile 安装后要重启该 profile,并在新会话选择模式。选择器里应看到自动模式、行情专家、资讯专员;分析师和绘图专员是自动模式的内部角色,不会出现在这里。
191
191
  - **配置在哪?** 打开「插件」→ 已安装的 **Capital Generation**,页面顶部就是「数据源 / 精选 Skills / 包含的组件」三格,五把密钥都在第一格;不在 DSH 的普通「设置」页面,也不用点进任何一行组件。保存密钥后新建会话再试。
192
192
  - **升级到 2.9.0 后,分析师和绘图专员去哪了?** 这一版把它们收进自动模式内部,不再出现在选择器与预设管理里,避免误开一个没有取数入口的空会话。旧会话仍能按原身份恢复;新任务直接用自动模式提出即可。
193
- - **桌面端升级后仍是旧版?** 桌面端安装时会固定所解析的版本,不会自动升级。卸载后重新安装,再到插件详情查看版本号;如需指定版本,可在地址末尾加 `#v2.9.0` 或 `@2.9.0`。
193
+ - **桌面端升级后仍是旧版?** 桌面端安装时会固定所解析的版本,不会自动升级。卸载后重新安装,再到插件详情查看版本号;如需指定版本,可在地址末尾加 `#v2.9.1` 或 `@2.9.1`。
194
194
  - **刚发布的新版本装不到?** 桌面端取包时会跳过**刚发布不久**的版本(宿主侧包管理器的发布冷却策略,实测会静默装成上一个版本),国内镜像的同步也可能滞后一段时间。急着用最新代码,改用仓库地址安装最直接。
195
195
  - **网页抓取失败?** 配置卡片中的「允许启动本地提取网页内容」默认开启:AnySearch 提取失败时可尝试本机直连。关闭后,本机直连的具名来源查询也不可用;请检查该开关和网络连接。
196
196
  - **Wind 那部分会消耗积分?** 会。公告与新闻检索、宏观 / 行业 / 汇率指标、按日期区间取的历史 K 线都按次消耗你在 Wind 侧的额度;额度与条款以 Wind 为准,插件不做用量封顶。没配 `WIND_API_KEY` 时这几类不可用,其余功能照常。
@@ -90,9 +90,10 @@ route owner 自己负责,平台自己的 `/api` 与首页分别走 `connection
90
90
 
91
91
  ## 本轮呈现(官方交付通道)
92
92
 
93
- 出图成功后,**宿主**(`@v587d/capital-generation` 的 `render_chart`)会往"用户正在看的根会话"
94
- 追加一条**官方** `deliverables/presented` 事件,把 `capital-analysis/charts/<chart_id>/chart.html`
95
- 登记为**本轮交付物**:
93
+ auto 模式下 `render_chart` 不再自动登记:主 Agent 在写最终答复前调用一次 `present_charts`,
94
+ 把本轮 `chart_ref` 汇总成一条**官方** `deliverables/presented` 事件写进"用户正在看的根会话",
95
+ 把 `capital-analysis/charts/<chart_id>/chart.html` 登记为**收尾轮交付物**(单体载体仍在出图时
96
+ 自动登记):
96
97
 
97
98
  - 载荷只有官方契约字段:`turn` / `callId`(`capital-chart:<chart_id>` 句柄)/ `files[]`
98
99
  (工作区相对 `html_path` + 标题)——绝不含 rows、series、HTML 或绝对路径;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/capital-charts",
3
- "version": "2.9.0",
3
+ "version": "2.9.1",
4
4
  "description": "自动模式的图表前端组件(浏览器半边 + 序列旁路路由):由 @v587d/capital-generation 随包携带并作为 host 平面行挂载。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/config",
3
- "version": "2.9.0",
3
+ "version": "2.9.1",
4
4
  "description": "自动模式的取数凭据与偏好配置行:卡片坐在 @v587d/capital-generation 插件主页的「配置」段,点这一行本身没有表单。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,5 +1,8 @@
1
1
  import type { SessionLike } from '../data-collector/store.js';
2
- /** One-hop scope: a token lets a nested child reuse its owner's scope. */
2
+ /**
3
+ * Subagent children carry `origin: 'subagent'`; a user fork/branch root also has `parentSession`
4
+ * (seed lineage) and must stay its own scope. One hop: the token lets a nested child reuse its owner.
5
+ */
3
6
  export declare function chartSessionScopeId(session: SessionLike): string;
4
7
  export interface ChartArtifactRef {
5
8
  chart_ref: string;
@@ -10,6 +13,7 @@ export interface ChartArtifactRef {
10
13
  captured_at: number | null;
11
14
  kind: string;
12
15
  axis: 'time' | 'index';
16
+ title: string;
13
17
  chart_url: string | null;
14
18
  spec_path: string;
15
19
  series_path: string;
@@ -42,6 +46,7 @@ export declare class ChartArtifactRegistry {
42
46
  captured_at?: number | null;
43
47
  kind: string;
44
48
  axis: 'time' | 'index';
49
+ title: string;
45
50
  chart_url: string | null;
46
51
  spec_path: string;
47
52
  series_path: string;
@@ -55,6 +60,11 @@ export declare class ChartArtifactRegistry {
55
60
  task_id?: string | null;
56
61
  }): ChartArtifactRef;
57
62
  revoke(chartRef: string): boolean;
63
+ /** Charts of one session scope, for the model's final-turn delivery step; newest last. */
64
+ list(input: {
65
+ session: SessionLike;
66
+ task_id?: string | null;
67
+ }): ChartArtifactRef[];
58
68
  size(): number;
59
69
  private publicRecord;
60
70
  }
@@ -8,9 +8,12 @@ function defaultChartRef() {
8
8
  : `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
9
9
  return `chart_${suffix}`;
10
10
  }
11
- /** One-hop scope: a token lets a nested child reuse its owner's scope. */
11
+ /**
12
+ * Subagent children carry `origin: 'subagent'`; a user fork/branch root also has `parentSession`
13
+ * (seed lineage) and must stay its own scope. One hop: the token lets a nested child reuse its owner.
14
+ */
12
15
  export function chartSessionScopeId(session) {
13
- const parent = session.header?.parentSession;
16
+ const parent = session.header?.origin === 'subagent' ? session.header.parentSession : undefined;
14
17
  return typeof parent === 'string' && parent.length > 0 ? parent : session.id;
15
18
  }
16
19
  /**
@@ -56,6 +59,7 @@ export class ChartArtifactRegistry {
56
59
  captured_at: input.captured_at ?? null,
57
60
  kind: input.kind,
58
61
  axis: input.axis,
62
+ title: input.title,
59
63
  chart_url: input.chart_url,
60
64
  spec_path: input.spec_path,
61
65
  series_path: input.series_path,
@@ -91,12 +95,30 @@ export class ChartArtifactRegistry {
91
95
  revoke(chartRef) {
92
96
  return this.records.delete(chartRef);
93
97
  }
98
+ /** Charts of one session scope, for the model's final-turn delivery step; newest last. */
99
+ list(input) {
100
+ const now = this.now();
101
+ this.purgeExpired(now);
102
+ const scope = chartSessionScopeId(input.session);
103
+ const cwd = input.session.header?.cwd ?? null;
104
+ const records = [];
105
+ for (const record of this.records.values()) {
106
+ if (record.owner_scope_id !== scope || record.owner_cwd !== cwd)
107
+ continue;
108
+ if (input.task_id !== undefined && input.task_id !== null && record.task_id !== input.task_id)
109
+ continue;
110
+ records.push(record);
111
+ }
112
+ return records
113
+ .sort((left, right) => left.created_at - right.created_at)
114
+ .map((record) => this.publicRecord(record));
115
+ }
94
116
  size() {
95
117
  this.purgeExpired(this.now());
96
118
  return this.records.size;
97
119
  }
98
120
  publicRecord(record) {
99
- const { owner_scope_id: _ownerScope, ...publicRecord } = record;
121
+ const { owner_scope_id: _ownerScope, owner_cwd: _ownerCwd, ...publicRecord } = record;
100
122
  return publicRecord;
101
123
  }
102
124
  }
@@ -33,7 +33,10 @@ function isAppendableSession(value) {
33
33
  && typeof value.append === 'function'
34
34
  && typeof value.id === 'string';
35
35
  }
36
+ /** `parentSession` alone is seed lineage (a user fork); only `origin: 'subagent'` is delegation. */
36
37
  function parentSessionOf(session) {
38
+ if (session.header?.origin !== 'subagent')
39
+ return undefined;
37
40
  const parent = session.header?.parentSession;
38
41
  return typeof parent === 'string' && parent.length > 0 ? parent : undefined;
39
42
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * present_charts: the root Agent's final-turn delivery step.
3
+ *
4
+ * render_chart only mints a chart_ref (the specialist row no longer auto-delivers). DSH's
5
+ * deliverable card replays only declarations recorded before the closing assistant message, so
6
+ * the card has to be written by the turn that carries the final answer: the root Agent presents
7
+ * its collected refs/deliverables here immediately before writing that answer. Never a custom
8
+ * event type — the session-event vocabulary is closed (see events.ts).
9
+ */
10
+ import type { Context } from '@deepseek-ai/cordis';
11
+ import type { ChartArtifactRegistry } from './artifact-ref.js';
12
+ import { type ChartEventPublisher } from './events.js';
13
+ export interface PresentChartsInput {
14
+ artifacts: ChartArtifactRegistry;
15
+ /** Lazy: the host projection may mount after this row; an absent publisher fails the call loudly. */
16
+ chartEvents: () => ChartEventPublisher | undefined;
17
+ }
18
+ export interface PresentChartsReceipt {
19
+ presented: Array<{
20
+ chart_ref: string;
21
+ title: string;
22
+ }>;
23
+ failed: Array<{
24
+ chart_ref: string;
25
+ reason: string;
26
+ }>;
27
+ }
28
+ export declare function registerPresentChartsTool(ctx: Context, input: PresentChartsInput): void;
@@ -0,0 +1,128 @@
1
+ import { MAX_CHARTS_PER_TURN } from './events.js';
2
+ import { callerSession } from '../tool-exec.js';
3
+ const jsonObject = (properties = {}, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
4
+ const parameters = jsonObject({
5
+ chart_refs: {
6
+ type: 'array',
7
+ items: { type: 'string' },
8
+ description: 'data_junior 回传的全部 chart_ref,一次传完(最多 8 个,不猜写)。与 task_id 至少给一个。',
9
+ },
10
+ task_id: {
11
+ type: 'string',
12
+ description: '本任务 canonical task_id:省略 chart_refs 时按它登记本会话该任务的全部图表(上下文压缩后记不清 refs 时用)。',
13
+ },
14
+ });
15
+ const receiptSchema = jsonObject({
16
+ presented: {
17
+ type: 'array',
18
+ items: jsonObject({
19
+ chart_ref: { type: 'string' },
20
+ title: { type: 'string' },
21
+ }, ['chart_ref', 'title']),
22
+ },
23
+ failed: {
24
+ type: 'array',
25
+ items: jsonObject({
26
+ chart_ref: { type: 'string' },
27
+ reason: { type: 'string' },
28
+ }, ['chart_ref', 'reason']),
29
+ },
30
+ }, ['presented', 'failed']);
31
+ function normalizeRefs(value) {
32
+ if (value === undefined)
33
+ return [];
34
+ if (!Array.isArray(value))
35
+ throw new Error('present_charts 的 chart_refs 必须是字符串数组');
36
+ const refs = [];
37
+ for (const item of value) {
38
+ if (typeof item !== 'string' || item.trim().length === 0)
39
+ throw new Error('present_charts 的 chart_refs 里不能有空值');
40
+ const ref = item.trim();
41
+ if (!refs.includes(ref))
42
+ refs.push(ref);
43
+ }
44
+ if (refs.length > MAX_CHARTS_PER_TURN) {
45
+ throw new Error(`present_charts 一次最多登记 ${MAX_CHARTS_PER_TURN} 张图(收到 ${refs.length} 个 chart_ref)`);
46
+ }
47
+ return refs;
48
+ }
49
+ function normalizeTaskId(value) {
50
+ if (value === undefined)
51
+ return undefined;
52
+ if (typeof value !== 'string' || value.trim().length === 0)
53
+ throw new Error('present_charts 的 task_id 必须是非空字符串');
54
+ return value.trim();
55
+ }
56
+ /** Present refs first; otherwise present the whole task so compaction cannot strand the charts. */
57
+ function selectArtifacts(input, session, refs, taskId) {
58
+ const failed = [];
59
+ if (refs.length > 0) {
60
+ const selected = [];
61
+ for (const chartRef of refs) {
62
+ try {
63
+ selected.push(input.artifacts.resolve({ session, chart_ref: chartRef, task_id: taskId }));
64
+ }
65
+ catch (error) {
66
+ failed.push({ chart_ref: chartRef, reason: error instanceof Error ? error.message : String(error) });
67
+ }
68
+ }
69
+ // A stale ref set must not strand charts the task_id can still find (compaction can mix both).
70
+ if (selected.length > 0 || taskId === undefined)
71
+ return { selected, failed };
72
+ }
73
+ if (taskId === undefined)
74
+ throw new Error('present_charts 需要 chart_refs 或 task_id:两者都不给就无法确定要登记哪几张图');
75
+ const all = input.artifacts.list({ session, task_id: taskId });
76
+ if (all.length === 0)
77
+ failed.push({ chart_ref: '(task)', reason: `本会话该 task_id 没有可登记的图表:${taskId}` });
78
+ const selected = all.slice(0, MAX_CHARTS_PER_TURN);
79
+ if (all.length > MAX_CHARTS_PER_TURN) {
80
+ failed.push({ chart_ref: '(task)', reason: `该 task_id 有 ${all.length} 张图,本轮最多登记 ${MAX_CHARTS_PER_TURN} 张;其余未展示` });
81
+ }
82
+ return { selected, failed };
83
+ }
84
+ export function registerPresentChartsTool(ctx, input) {
85
+ const tools = ctx.get('tools');
86
+ if (!tools)
87
+ return;
88
+ const definition = {
89
+ name: 'present_charts',
90
+ description: '把本会话已生成的图表登记为**最终答复的交付物**(官方 deliverables/presented):用户会在本轮答复末尾的「本轮文件改动 / 交付」行看到卡片,点开即渲染自包含 chart.html。'
91
+ + '**只由主 Agent 在写最终答复前调用一次**:参数是 data_junior 回传的 chart_refs(一次全部传入,最多 8 个);refs 已丢失时改传 task_id 登记该任务的全部图表;调用后立刻写最终答复。'
92
+ + '不要在过程轮 / 等待行前调用,不要分批重复调用;不要传文件路径或 HTML(路径由宿主解析)。'
93
+ + '不调用本工具,图表不会出现在对话里。',
94
+ parameters,
95
+ async execute(args, exec) {
96
+ const session = callerSession(exec);
97
+ const refs = normalizeRefs(args.chart_refs);
98
+ const taskId = normalizeTaskId(args.task_id);
99
+ if (refs.length === 0 && taskId === undefined) {
100
+ throw new Error('present_charts 需要 chart_refs 或 task_id:两者都不给就无法确定要登记哪几张图');
101
+ }
102
+ const publisher = input.chartEvents();
103
+ if (publisher === undefined) {
104
+ throw new Error('图表交付通道不可用(sessions / sessionProjections 解析不到):图表已落盘,可在 workspace 打开 chart.html');
105
+ }
106
+ const { selected, failed } = selectArtifacts(input, session, refs, taskId);
107
+ const receipt = { presented: [], failed };
108
+ for (const artifact of selected) {
109
+ const accepted = publisher.publish({
110
+ ownerSessionId: session.id,
111
+ chart_id: artifact.chart_id,
112
+ title: artifact.title,
113
+ html_path: artifact.html_path,
114
+ });
115
+ if (accepted)
116
+ receipt.presented.push({ chart_ref: artifact.chart_ref, title: artifact.title });
117
+ else
118
+ receipt.failed.push({ chart_ref: artifact.chart_ref, reason: `本轮登记已达 ${MAX_CHARTS_PER_TURN} 张上限或回合已关闭,未写入交付行` });
119
+ }
120
+ return receipt;
121
+ },
122
+ output: {
123
+ schema: receiptSchema,
124
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
125
+ },
126
+ };
127
+ ctx.effect(() => tools.register(definition), 'capital-generation.tool(present_charts)');
128
+ }
@@ -12,6 +12,7 @@ type AgentExecutionLike = {
12
12
  header?: {
13
13
  cwd?: string;
14
14
  parentSession?: string;
15
+ origin?: 'subagent';
15
16
  };
16
17
  };
17
18
  };
package/lib/chart/tool.js CHANGED
@@ -112,7 +112,7 @@ export async function renderChart(input, args, exec) {
112
112
  if (sourceCount !== 1) {
113
113
  throw new ChartError('chart_source_invalid', 'provide exactly one data source: dataset_id, path, or chart_source_ref');
114
114
  }
115
- if (session.header?.parentSession && chartSourceRef === undefined) {
115
+ if (session.header?.origin === 'subagent' && chartSourceRef === undefined) {
116
116
  throw new ChartError('chart_source_scope_mismatch', 'delegated visualization callers must use chart_source_ref');
117
117
  }
118
118
  const tokenSource = chartSourceRef === undefined ? undefined : chartSourceTokenForRender(input.sourceTokens, {
@@ -177,6 +177,7 @@ export async function renderChart(input, args, exec) {
177
177
  captured_at: meta.captured_at ?? null,
178
178
  kind: payload.kind,
179
179
  axis: payload.axis,
180
+ title: payload.title,
180
181
  chart_url: chartUrl,
181
182
  spec_path: pathOf('spec.json'),
182
183
  series_path: pathOf('series.json'),
@@ -185,9 +186,10 @@ export async function renderChart(input, args, exec) {
185
186
  });
186
187
  // Unit callers without the registry still get a stable receipt.
187
188
  const chartRef = artifact?.chart_ref ?? `chart_${chartId.slice(3)}`;
188
- // Register as this turn's deliverable via first-party deliverables/presented — never a custom
189
- // event type (closed vocabulary breaks cold load; see src/chart/events.ts). Owner scope matches
190
- // chart_ref; these events never enter model message history.
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.
191
193
  let deliverableTrace;
192
194
  try {
193
195
  const publisher = input.chartEvents?.();
@@ -257,7 +259,9 @@ export function registerChartTool(ctx, options) {
257
259
  `最多保留 ${MAX_CHART_POINTS} 个点(超出由宿主下采样并如实标注)。` +
258
260
  '字段语义、可用键与错误码见 skill capital-chart-protocol —— 首次画图前先加载它。' +
259
261
  '是否出图由 data_junior 的可视化协议决定;本工具只负责安全生成图表产物和小型回执。' +
260
- '出图成功后宿主会把这张图登记为当前对话的**本轮交付物**(官方 deliverables/presented),用户可从收尾的交付行点开自包含图表;调用方**不要**在回传或正文里罗列图表文件、路径或 HTML。' +
262
+ (typeof options.chartEvents === 'function'
263
+ ? '出图成功后宿主会把这张图登记为当前对话的**本轮交付物**(官方 deliverables/presented),用户可从收尾的交付行点开自包含图表;调用方**不要**在回传或正文里罗列图表文件、路径或 HTML。'
264
+ : '交付登记由主 Agent 在最终答复前调用 present_charts 完成:你只把 chart_ref 回传给 data_junior 汇总,**不要**调用 present_charts、不要在回传或正文里罗列图表文件、路径或 HTML。') +
261
265
  '图表是呈现不是分析:需要数值结论(首末值、涨跌幅、分位数等)仍走 data_junior 的 profile / query,不要用图去推断数字。',
262
266
  parameters,
263
267
  output: { schema: receiptSchema, render },
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * `tools/chart-render` row: render_chart only. Row-level expression of "only the
3
3
  * visualization specialist holds the chart pipeline"; shares token/artifact state with
4
- * `tools/chart-source`.
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.
5
6
  */
6
7
  import type { Context } from '@deepseek-ai/cordis';
7
8
  export declare const name = "capital-chart-render-tools";
@@ -1,5 +1,3 @@
1
- import { createChartEventPublisher } from './chart/events.js';
2
- import { chartEventContextOf } from './chart/event-context.js';
3
1
  import { registerChartTool } from './chart/tool.js';
4
2
  import { capitalDatasetStore } from './runtime.js';
5
3
  import { capitalSharedRuntimeOf } from './chart-source-entry.js';
@@ -12,6 +10,5 @@ export function apply(ctx) {
12
10
  sourceTokens: shared.sourceTokens,
13
11
  artifacts: shared.artifacts,
14
12
  charts: () => ctx.get('capitalCharts'),
15
- chartEvents: () => createChartEventPublisher(chartEventContextOf(ctx)),
16
13
  });
17
14
  }
@@ -184,6 +184,7 @@ export interface SessionLike {
184
184
  readonly header?: {
185
185
  readonly cwd?: string;
186
186
  readonly parentSession?: string;
187
+ readonly origin?: 'subagent';
187
188
  };
188
189
  }
189
190
  export interface SandboxPolicyResultLike {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `tools/present` row: present_charts only. The root Agent's final-turn delivery step — the
3
+ * auto-mode chart pipeline returns refs upward and the main Agent redeems them here right before
4
+ * writing the closing answer (rationale: docs/dev/chart-delivery-events.md §6.3).
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ export declare const name = "capital-present-tools";
8
+ export declare const inject: string[];
9
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,13 @@
1
+ import { createChartEventPublisher } from './chart/events.js';
2
+ import { chartEventContextOf } from './chart/event-context.js';
3
+ import { registerPresentChartsTool } from './chart/present-tool.js';
4
+ import { capitalSharedRuntimeOf } from './chart-source-entry.js';
5
+ export const name = 'capital-present-tools';
6
+ export const inject = ['tools'];
7
+ export function apply(ctx) {
8
+ const shared = capitalSharedRuntimeOf(ctx);
9
+ registerPresentChartsTool(ctx, {
10
+ artifacts: shared.artifacts,
11
+ chartEvents: () => createChartEventPublisher(chartEventContextOf(ctx)),
12
+ });
13
+ }
@@ -3,7 +3,7 @@
3
3
  * Known limit: DNS resolves per request but the IP is not pinned to the connection,
4
4
  * so a rebinding window remains (same trade-off as dsh-search-first).
5
5
  */
6
- export declare const LOCAL_FETCH_CLIENT_VERSION = "2.9.0";
6
+ export declare const LOCAL_FETCH_CLIENT_VERSION = "2.9.1";
7
7
  export declare const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
8
8
  export type LocalFetchErrorCode = 'ABORTED' | 'TIMEOUT' | 'INVALID_URL' | 'BLOCKED_URL' | 'DNS' | 'REDIRECT' | 'HTTP' | 'TOO_LARGE' | 'UNSUPPORTED_CONTENT_TYPE';
9
9
  export declare class LocalFetchError extends Error {
@@ -4,7 +4,7 @@
4
4
  * so a rebinding window remains (same trade-off as dsh-search-first).
5
5
  */
6
6
  import { htmlToMarkdown, stripInvisibleText } from './html-markdown.js';
7
- export const LOCAL_FETCH_CLIENT_VERSION = '2.9.0';
7
+ export const LOCAL_FETCH_CLIENT_VERSION = '2.9.1';
8
8
  export const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
9
9
  export class LocalFetchError extends Error {
10
10
  code;
@@ -35,7 +35,7 @@ export interface WindCallResult {
35
35
  code?: WindErrorCode;
36
36
  }
37
37
  /** Must match `package.json` version; a test pins the two together. */
38
- export declare const WIND_CLIENT_VERSION = "2.9.0";
38
+ export declare const WIND_CLIENT_VERSION = "2.9.1";
39
39
  export interface WindClient {
40
40
  callTool(toolName: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<WindCallResult>;
41
41
  }
@@ -19,7 +19,7 @@ export const WIND_SERVER_ENDPOINTS = {
19
19
  analytics_data: 'https://mcp.wind.com.cn/vserver_analytics_data/mcp/',
20
20
  };
21
21
  /** Must match `package.json` version; a test pins the two together. */
22
- export const WIND_CLIENT_VERSION = '2.9.0';
22
+ export const WIND_CLIENT_VERSION = '2.9.1';
23
23
  function errorText(error) {
24
24
  return error instanceof Error ? error.message : String(error);
25
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/capital-generation",
3
- "version": "2.9.0",
3
+ "version": "2.9.1",
4
4
  "description": "Capital Generation:面向中国散户的 AI 投资助手插件(自动模式 Agent Preset)",
5
5
  "icon": "assets/cg-icon.png",
6
6
  "author": "v587d",
@@ -57,6 +57,11 @@
57
57
  "import": "./lib/ocr-entry.js",
58
58
  "default": "./lib/ocr-entry.js"
59
59
  },
60
+ "./tools/present": {
61
+ "types": "./lib/present-entry.d.ts",
62
+ "import": "./lib/present-entry.js",
63
+ "default": "./lib/present-entry.js"
64
+ },
60
65
  "./tools/retrieve-search": {
61
66
  "types": "./lib/search-entry.d.ts",
62
67
  "import": "./lib/search-entry.js",
@@ -511,8 +511,8 @@
511
511
  # OUTPUT CONVENTIONS
512
512
  关键指标与截止用表格;子 Agent 回传按原样引用。
513
513
  图表由 data_junior 的可视化 gate 统一出品:你不画图、不评价图;派的时机与收窄见下方「数据调度纪律」。
514
- 有图表时,图表会在本轮答复后的「交付」行里自动出现:正文不要提图表、不在正文里罗列图表文件;图表是呈现不是分析,不用图推断数字,数值结论一律以 profile / query 回传为准。
515
- 最终答:工具完成后再答,必须含结论、关键证据与来源标注、局限与风险;归因准确,给出观察 / 验证 / 防守选项;勿只报“已交付/已完成”。
514
+ 图表交付(硬):收到 chart_refs 后,**写最终答复前先调用一次 present_charts**(一次传全部 ref;refs 丢了就传 task_id),它把图表登记为最终答复的交付物(答复末尾的「交付」行);不要在过程轮调用、不要分批、正文不要提图表、不在正文里罗列图表文件 / 路径 / HTML。图表是呈现不是分析:不用图推断数字,数值结论一律以 profile / query 回传为准。
515
+ 最终答(硬):必须自足——结论、关键证据与来源标注、局限与风险、观察 / 验证 / 防守选项;归因准确。**禁止“分析已完成 / 结论见上 / 如上”这类指针式收尾**:界面只展示最后一轮答复、前面轮次会被折叠,指针句会让用户什么也看不到。被通知唤醒且无新增信息时:不输出可见正文(让上一条完整结论成为最后一轮),或重发一份自足总结;绝不写指针句。
516
516
 
517
517
  # RULES & DISCIPLINE
518
518
  读文件只用 read:只读 skill 分册与工作区文件;不读 capital-data/ 落盘(原始行不进入上下文),不读 ~/.dsh/;
@@ -565,8 +565,9 @@
565
565
  是否已收到结算通知(回传正文再像完成也不算 ✓,细则见 skill capital-orchestration);
566
566
  缺一条本轮只写等待行,结论留到收齐那一步;结论后又唤醒谁就重新对账。
567
567
  结算通知只证明该子 Agent 本轮结束,仍不等于任务完成;所有委派行都结算后才可收口。
568
+ 收口顺序(硬):委派全部结算 → 对账通过 → 有 chart_refs 先 present_charts → 再写最终答复。
568
569
  等待期间不做外部检索、不反复 list_agents、不轮询。
569
- 已答复且无新增信息、之后无工具调用 → 不再重复输出;之后有工具调用 → 重新答复。
570
+ 已答复且无新增信息:不输出可见正文(绝不写“已完成/见上”),或重发自足最终答复;之后有工具调用 → 重新答复。
570
571
 
571
572
  5.1 子 Agent 中途请求(硬):收到子 Agent 的中途消息(就绪登记,或
572
573
  type=data_gap 的补数据请求)时,它是工单不是完成:就绪登记继续等其正式回传;
@@ -593,8 +594,8 @@
593
594
  不重复派发。data_failed 是单次请求失败,不等于整轮结束;
594
595
  只在收到 data_collection_completed 后才判定取数结束;等 junior 已收数据的 profile
595
596
  和补数缺口对齐后,再让它复用已有 profile 汇总并完成可视化 gate;用户明确不要图时
596
- 把「不要图」一并传入,由 junior 停在 gate 结论;把 profile_ref 记入委派清单;
597
- 失败按 error/code 修正或指示重试。
597
+ 把「不要图」一并传入,由 junior 停在 gate 结论;把 profile_ref / chart_refs 记入委派清单
598
+ (chart_refs 是最终答复前 present_charts 的入参,不要丢);失败按 error/code 修正或指示重试。
598
599
 
599
600
  4. 输出必须披露 capability、source_label、captured_at 与缺失字段。
600
601
  原始行永不进入你的上下文;网页比对仅作异常抽样,不写成绝对真实。
@@ -718,5 +719,10 @@
718
719
  - id: capital-watchlist-tools
719
720
  name: '@v587d/capital-generation/tools/watchlist'
720
721
 
722
+ # Final-turn delivery: present_charts. Charts are rendered by the specialist but presented
723
+ # here so the card sits in the closing turn (docs/dev/chart-delivery-events.md §6.3).
724
+ - id: capital-present-tools
725
+ name: '@v587d/capital-generation/tools/present'
726
+
721
727
  - id: capital-root-extras
722
728
  name: '@v587d/capital-generation/tools/root-extras'
@@ -131,7 +131,9 @@ description: Use before calling render_chart — the full chart spec vocabulary
131
131
  `series_labels`、`has_volume`、`markers`、来源标注(`dataset_id`/`source_label`/`captured_at`)、
132
132
  三个产物路径、`warnings`。**没有任何数据行** —— 序列不进上下文,这正是这套设计的意义。
133
133
 
134
- 呈现路径:`render_chart` 只负责生成图产物和小型回执。出图成功后,**宿主**会把这张图登记为
135
- 当前对话的**本轮交付物**(官方 `deliverables/presented` 事件 → 收尾「本轮文件改动 / 交付」行,
136
- 点开即在右侧渲染自包含 `chart.html`)。因此调用方**不要**在回传或正文里罗列图表文件、路径或 HTML
137
- —— 那既进不了交付行,也只会污染上下文。结论的文字部分仍需写清来源与口径。
134
+ 呈现路径:`render_chart` 只负责生成图产物和小型回执,**不会自动登记交付**。`chart_ref` 随
135
+ data_junior 的汇总回传主 Agent;主 Agent 在写最终答复前调用一次 `present_charts`,这些图才成为
136
+ **最后一轮**答复末尾的交付物(官方 `deliverables/presented` 事件 → 收尾「本轮文件改动 / 交付」行,
137
+ 点开即在右侧渲染自包含 `chart.html`)。因此 data_junior 与 specialist 都**不要**调用
138
+ `present_charts`,也不要罗列图表文件、路径或 HTML —— 那既进不了交付行,也只会污染上下文。
139
+ 结论的文字部分仍需写清来源与口径。
@@ -42,4 +42,6 @@ description: Use when the user requests a complete research report or comprehens
42
42
 
43
43
  先给出带时间边界的核心判断与最大风险,再按实际证据组织:研究范围、业务与财务、估值、价格/技术、事件与行业、风险与反方、关键证据及冲突、未评估项、后续观察/验证/防守选项。优先呈现影响结论的数值、证据链和仍未裁定的冲突;同一来源与采集时间可在表头合并注明,不逐项堆砌原始序列、新闻目录或全部 Dataset 数量。各段可以合并,但不得把无证据的段落写成已完成;不要给所有维度机械打分。
44
44
 
45
- 每个会影响判断的数值注明能力/来源、报告期或交易日及采集时间;材料结论给可追溯的原文链接,或文件标题、发布主体与页码(无法提供原文时明说)。不要用已实施分红或历史价格推断股价下限,不把同业倍数排序写成“便宜/贵”的充分证据。对缺失、冲突和数据延迟给出它们如何改变判断的解释;未评估清单仅列与本轮问题或结论有关的缺口。区分投资者可采取的验证步骤与交易指令;不承诺收益、不输出没有可复现模型支持的目标价、概率或胜率。图表由宿主在交付行呈现,正文不罗列图表文件,也不从图上反推数字。
45
+ 每个会影响判断的数值注明能力/来源、报告期或交易日及采集时间;材料结论给可追溯的原文链接,或文件标题、发布主体与页码(无法提供原文时明说)。不要用已实施分红或历史价格推断股价下限,不把同业倍数排序写成“便宜/贵”的充分证据。对缺失、冲突和数据延迟给出它们如何改变判断的解释;未评估清单仅列与本轮问题或结论有关的缺口。区分投资者可采取的验证步骤与交易指令;不承诺收益、不输出没有可复现模型支持的目标价、概率或胜率。
46
+
47
+ **这一段就是最后一轮的唯一可见内容**:写之前先调用一次 `present_charts`(有 chart_refs 时),报告正文不罗列图表文件,也不从图上反推数字;写完不要再补一行“已完成/结论见上”把报告盖掉。
@@ -91,7 +91,7 @@ A 角色的请求投给 B 角色。官方机制是 **`list_agents` 即权威 id
91
91
  不能 → `web_retriever`;② 使用者要拿它做算术(聚合、排序、时序对比、进图)吗?要 → `data_collector`;
92
92
  ③ 文本是内容本身(正文、纪要、新闻)还是行上的一个标注字段(标题、简称)?前者 → `web_retriever`;
93
93
  ④ 需要对单位、缺失值含义、时间口径做承诺吗?要 → `data_collector`。
94
- - 终判据是**错法**而不是形状:`data_collector` 出错给的是响亮错误码,`web_retriever` 是抓到什么算什么、
94
+ - 终判据是**错法**而不是形状:`data_collector` 出错给响亮错误码,`web_retriever` 抓到什么算什么、
95
95
  上游改版只会静默变样。**凡要写进用户结论的数字,一律委派 `data_collector`**;网页里的数字只能当线索
96
96
  或异常抽样,回传里标明"来源是某篇文章"而不是承诺口径。
97
97
  - 多角色可并行委派,但每个角色都必须先按第 2 节复用或创建,各记清单,等结算通知齐了再综合
@@ -118,7 +118,9 @@ A 角色的请求投给 B 角色。官方机制是 **`list_agents` 即权威 id
118
118
  不要向用户断言它已完成,也不要新建替代实例。
119
119
  - 等待回传期间:不做任何外部检索/抓取、不反复 `list_agents`、不轮询等待。
120
120
  - 回传到达后集中核对材料与结论,再决定继续委派或汇总输出。
121
- - 已向用户输出完整答案后被唤醒,且没有新增信息或纠正时不再重复输出,仅在确有必要时补充。
121
+ - **收口顺序(硬)**:所有委派行结算 → 对账通过 → 有 `chart_refs` 先 `present_charts` 一次
122
+ (全部 ref;记不清就传 `task_id`)→ 再写最终答复;登记必须早于最终答复。
123
+ - 已答复后被唤醒且无新信息:不输出可见正文,或重发自足总结(§6.1);绝不写“已完成/见上”。
122
124
  - 永远不要设置 `run_in_background: false`;所有委派必须是 continuable。
123
125
 
124
126
  ### 6.1 结算对账表(写结论前必交,硬)
@@ -142,17 +144,16 @@ A 角色的请求投给 B 角色。官方机制是 **`list_agents` 即权威 id
142
144
  - 有任何一行是 ✗ → 本步**只写等待行**(欠谁、在等什么),结论留到 ✗ 全部翻正的那一步。
143
145
  - 这张表写在 reasoning 里即可,不要作为正文发给用户(用户只看到结论)。
144
146
 
145
- **为什么必须等,量化过:** 真机会话 `cst7n9j3` turn 11——data_junior 的汇总回执先到,结算通知
146
- **14 秒后**才到,而那份结论本身生成了 **156 秒**。看到回执就立刻综合,结算通知必然落在结论之后;
147
- 框架此时只能再开一个 step 把它投递给你(inbox 非空就不结束 turn),结论被切成两条消息、
148
- 尾部多出一段"无新增信息"的确认。倒过去等的成本是一行等待,被追上的成本是整份结论重写。
147
+ **为什么必须等:** 真机上结论生成 156 秒、汇总回执比结算通知早到 14 秒——先综合,结算通知必然落在
148
+ 结论之后,框架会再开一个 step 投递,结论被切成两条消息。
149
149
 
150
150
  **已经被追上(结论后才有结算)**:
151
151
 
152
- - 内容与已用材料一致、**没有新信息也没有纠正** → 只回一行确认(可含一处口径说明),
153
- **禁止复述或重排结论**,不要重贴表格。
154
- - 带来新事实或推翻口径 → 那才是新结论,重写一份完整的,不要只补一段。
155
- - 补齐后重新对账:只要这步之后又有人被唤醒,回到本节重来。
152
+ - 无新信息/纠正 → **不要写一行确认**(它会成为折叠后唯一可见的最后一轮):要么不输出可见正文,
153
+ 要么重发自足总结(结论/证据/来源、局限、观察/验证/防守);**绝不写
154
+ “分析已完成/结论见上/如上”**。
155
+ - 有新事实或推翻口径 → 重写一份完整结论,不要只补一段。
156
+ - 补齐后重新对账:这步之后又有人被唤醒,回到本节重来。
156
157
 
157
158
  ## 7. 异常分支
158
159
 
@@ -163,13 +164,13 @@ A 角色的请求投给 B 角色。官方机制是 **`list_agents` 即权威 id
163
164
  | 子 Agent 中途发来 `data_gap` 补数据请求 | 按清单复用对应角色取数,`send_message` 发回同一个子 Agent;取数失败也要回执,不能让它一直等 |
164
165
  | 清单里有 `waiting_data` 的子 Agent 发来结算通知 | 那是它在等补数据,不是完成:不要向用户断言完成,也不要新建同角色实例 |
165
166
  | 只有回传、没有结算通知,却"想写结论了" | 按 §6.1 先交对账表:✗ 未被结算翻正就本步只写等待行,结论留到收齐那一步 |
166
- | 结论已输出后才被结算通知唤醒 | 无新增信息/纠正 → 只回一行确认,禁止复述结论;有新事实或推翻口径 → 重写完整结论(§6.1) |
167
- | `send_message` 投错对象(目标 child 的回应与角色不符) | `interrupt_agent` 停错投目标当前轮(已结束则为 no-op)→ 重新 `list_agents` → 向正确 id 重发;委派消息正文自带角色字样与任务标识可让错投被及时发现 |
167
+ | 结论已输出后才被结算通知唤醒 | 无新增信息/纠正 → 不输出可见正文或重发自足总结,禁止一行“已完成/见上”;有新事实或推翻口径 → 重写完整结论(§6.1) |
168
+ | `send_message` 投错对象(目标 child 的回应与角色不符) | `interrupt_agent` 停当前轮(已结束为 no-op)→ 重新 `list_agents` → 向正确 id 重发;委派正文自带角色字样与任务标识,错投可被及时发现 |
168
169
  | 子 Agent 回告「工具未挂载」 | 如实转述状态与检查建议,不要编造工具或数据;数据侧提示检查 `request_data`/`list_capabilities`,统计侧提示检查 `inspect_dataset`/`profile_dataset` |
169
170
  | 子 Agent 回传 `failed` | 按 `error` 与 `code` 判断:参数问题修正后 `send_message` 重试;网络/服务类最多重试 1 次;同一请求累计最多重试 2 次仍失败就如实告知用户 |
170
- | 子 Agent 回传 wind_docs 失败(AUTH/RATE_LIMIT/额度类) | Wind 检索暂不可用(Key 缺失/过期或额度不足):如实告知用户 Wind 能力暂不可用,本轮材料由 anysearch 承担(公告线索搜索 + 官方披露页 fetch);不要重试 Wind,也不要索取或改配 Key |
171
+ | 子 Agent 回传 wind_docs 失败(AUTH/RATE_LIMIT/额度类) | Wind 能力暂不可用(Key 缺失/过期/额度不足):如实告知用户;本轮材料由 anysearch 承担(公告线索 + 官方披露页 fetch),不要重试 Wind、不要索取或改配 Key |
171
172
  | 出现「想直接读取 Dataset 内容」的冲动 | 改为委派 `data_junior`(`inspect_dataset` / `profile_dataset`);主 Agent 直接调用 Dataset 系列工具会被工具层拒绝(`dataset_session_mismatch`),被拒后不要重试直读,改走委派 |
172
- | 任务需要分钟级数据 | 检查 `tencent_kline`:支持 m1/m5/m15/m30/m60,仅支持最近 `count` 根(最多 320),不支持 `start/end` 历史分钟区间;旧分钟/小时历史不可编造,不满足时再披露边界 |
173
+ | 任务需要分钟级数据 | 检查 `tencent_kline`:支持 m1/m5/m15/m30/m60,仅支持最近 `count` 根(最多 320),无 `start/end` 历史区间;旧分钟历史不可编造,不满足就披露边界 |
173
174
  | 需要周期刷新(如每日收盘快照) | 用 `send_message` 指示 `data_collector` 再次获取并回传;不存在订阅机制——新需求 = 新消息,回传即唤醒 |
174
175
 
175
176
  载荷字段、`force_refresh` 语义与 dataset 逐份回传见 skill `capital-data-protocol`;profile 回传与
@@ -273,10 +273,11 @@ warnings / failures
273
273
 
274
274
  图表成功后,data_junior 回传 `chart_ref`;图表失败、blocked 或 not_needed 也必须回传明确的终态、warnings/failures 和 task_id,不能让主 Agent把“无图表”误解为流程未完成。
275
275
 
276
- **呈现由宿主完成,不需要任何 Agent 再做什么**:`render_chart` 成功时宿主会把这张图登记为
277
- **本轮交付物**(官方 `deliverables/presented`),用户在收尾的「本轮文件改动 / 交付」行点开
278
- `chart.html`,右侧即渲染可交互图表。因此 data_junior 与 specialist **都不得**在回传里罗列图表文件、
279
- 路径或 HTML,主 Agent 也不在正文里重复这些内容。
276
+ **呈现由主 Agent 的最终答复承载**:`render_chart` 只出图并回 `chart_ref`,不会自动登记交付。
277
+ data_junior 在汇总里回传全部 `charts[].chart_ref`;主 Agent 在写最终答复前调用一次
278
+ `present_charts`,这些图才成为**最后一轮**答复末尾的交付物(官方 `deliverables/presented`)。
279
+ 因此 data_junior 与 specialist **都不得**调用 `present_charts`,也不得在回传里罗列图表文件、路径或
280
+ HTML;主 Agent 也不在正文里重复这些内容。
280
281
 
281
282
  **图表跟着 Dataset 一对一,宁多勿叠**:一个 `chart_source_ref` 对应一张图;需要多个视图就出多张
282
283
  (各自独立的 `render_chart`)。不要试图把多个来源、多种单位或需要归一化的序列叠进一张图 ——
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/watchlist",
3
- "version": "2.9.0",
3
+ "version": "2.9.1",
4
4
  "description": "自动模式的用户自选股(宿主级清单 + Fuyao 报价快照 + /capital-watchlist 路由 + 输入框菜单弹窗):由 @v587d/capital-generation 随包携带并作为 host 平面行挂载。",
5
5
  "license": "MIT",
6
6
  "type": "module",