@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,17 +1,11 @@
1
1
  import type { SessionLike } from '../data-collector/store.js';
2
- /**
3
- * 登记本轮交付物用的官方事件名。
4
- *
5
- * 必须留在 `KNOWN_SESSION_EVENT_TYPES` 内——它是 `dsh-tool-present` 追加的同一类型,
6
- * 因此在**任何** harness 版本上都能被读回。改这里等于改会话日志契约,
7
- * `test/chart-turn-events.test.mjs` 有一条断言把它钉在 first-party 词汇表上。
8
- */
2
+ /** Must stay in `KNOWN_SESSION_EVENT_TYPES`; test/chart-turn-events.test.mjs pins it. */
9
3
  export declare const CHART_DELIVERABLE_EVENT = "deliverables/presented";
10
- /** `callId` 前缀:交付事件里携带的图表句柄(官方契约只要求非空字符串)。 */
4
+ /** `callId` only needs to be a non-empty string; carries the chart handle for log tracing. */
11
5
  export declare const CHART_CALL_ID_PREFIX = "capital-chart:";
12
- /** 同一 (session, turn) 的事件上限:防模型循环,超出只告警。 */
6
+ /** Per (session, turn) cap: stops model loops; overflow warns only. */
13
7
  export declare const MAX_CHARTS_PER_TURN = 8;
14
- /** 向上找根会话的最大跳数(main → data_junior → specialist 只有一跳,留余量)。 */
8
+ /** Root lookup cap (main → data_junior → specialist is one hop, plus headroom). */
15
9
  export declare const MAX_OWNER_HOPS = 4;
16
10
  interface AppendableSession extends SessionLike {
17
11
  append(type: string, data: unknown): unknown;
@@ -19,102 +13,51 @@ interface AppendableSession extends SessionLike {
19
13
  interface SessionsService {
20
14
  get(id: string): unknown;
21
15
  }
22
- /** 宿主 ctx 里本模块用到的部分;focused 结构类型便于单测注入假实现。 */
16
+ /** Subset of host ctx used here; structural so tests can inject fakes. */
23
17
  export interface ChartEventContext {
24
18
  get(name: string): unknown;
25
- /** 监听会话事件(`turn/start`,仅作**兜底**冲刷)。必须注册在未打 scope 标签的 root context 上。 */
19
+ /** Fallback flush listener; must be registered on the unscoped root context. */
26
20
  on?(event: string, listener: (...args: unknown[]) => void): unknown;
27
- /**
28
- * 监听 `agent/turn-stopping`:**首选的冲刷时机**。
29
- *
30
- * 为什么必须是它而不是 `turn/start`:一轮是否"最终"在事件发生的那一刻不可知——子 Agent 的
31
- * 结算随时会再触发新一轮。实测(2026-09-20 真机 session `38bad3f9`):图表在 turn 4 与 5 之间
32
- * 寄存,旧规则在下一次 `turn/start` 立刻冲刷,于是交付行落进了**空的过程轮** turn 5,而主 Agent
33
- * 写下总结答复的是 turn 6——卡片出现在中间步骤。`agent/turn-stopping` 是官方"该轮即将关闭"的
34
- * 钩子(`@mode serial`,轮仍打开、可安全 append),在它上面冲刷,交付行就落进**消费这份图的那一轮**。
35
- *
36
- * 必须注册在未打 scope 标签的 root context 上:`dsh-scope` 的准入规则会让 standing-scope 的
37
- * 监听器收不到子 Agent 的事件,而我们要的正是 owner 自己那轮关闭的事实。
38
- */
21
+ /** Preferred flush hook: open-but-closing turn, on the unscoped root ctx (scoped listeners miss
22
+ * child events). A missing disposer must keep the turn/start fallback active. */
39
23
  onTurnStopping?(listener: (...args: unknown[]) => void): unknown;
40
24
  effect?(callback: () => unknown, label?: string): unknown;
41
- /** Root context(未打标签);拿不到时退回自身(宿主平面行即未打标签)。 */
25
+ /** Unscoped root context; falls back to self when absent. */
42
26
  readonly root?: ChartEventContext;
43
27
  logger?: {
44
28
  warn(message: string): void;
45
29
  };
46
30
  }
47
31
  export interface ChartTurnEventInput {
48
- /** 出图时 chart_ref 的 owner scope(与 ChartArtifactRegistry 同一套语义)。 */
32
+ /** chart_ref owner scope (same semantics as ChartArtifactRegistry). */
49
33
  ownerSessionId: string;
50
34
  chart_id: string;
51
35
  title: string;
52
- /** 工作区相对的 chart.html 路径(官方 preview 按 session.cwd 解析它)。 */
36
+ /** Workspace-relative chart.html path; the official preview resolves it against session.cwd. */
53
37
  html_path: string;
54
38
  }
55
39
  export interface ChartEventPublisher {
56
- /**
57
- * 记录一张图。
58
- * @returns 是否已受理:立即写入、或已寄存等下一个回合都算受理。
59
- */
60
40
  publish(input: ChartTurnEventInput): boolean;
61
- /** 诊断:还没拿到回合、寄存中的图表数。 */
41
+ /** Diagnostics: queued charts not yet written. */
62
42
  pendingCount?(ownerSessionId?: string): number;
63
- /**
64
- * 诊断:最近一次 publish 的逐步结论(给回执/落盘用)。
65
- *
66
- * 为什么需要它:交付行"静默不出现"是本项目最难查的故障,而宿主终端并不总能拿到
67
- * (cordis logger 只进内存;stderr 也可能落在用户看不到的地方)。把结论带回
68
- * `render_chart` 的回执 / 落盘产物,就能**从磁盘**定位,不必依赖终端。
69
- */
43
+ /** Diagnostics: last publish trace, surfaced in receipts / disk so failures are visible without a terminal. */
70
44
  lastTrace?(): string;
71
45
  }
72
- /** 只读:最近一次 publish 轨迹。 */
73
46
  export declare function chartEventPublishTrace(): string;
74
- /**
75
- * 交付通道的**诊断开关**:`CAPITAL_CHART_TRACE=1` 时才输出进度日志与落盘 trace。
76
- *
77
- * 默认关闭——对外发布不该往用户终端刷进度、也不该往 workspace 写调试文件。
78
- * 排查"图表/交付行不出现"时打开它(见 `deliverable-trace.txt` 与 docs/dev/chart-delivery-events.md §6.3 的说明)。
79
- */
47
+ /** Diagnostic switch; off by default to keep terminals and workspaces clean. */
80
48
  export declare function chartTraceEnabled(): boolean;
81
- /**
82
- * 从 owner scope 出发向上走到根会话:用户看的是根会话的对话流,卡片必须挂在它上面。
83
- * 找不到(会话已回收)时返回 undefined,调用方静默跳过。
84
- */
49
+ /** Walk up to the root session — the user watches the root conversation. Missing session → undefined. */
85
50
  export declare function resolveOwnerSession(sessions: SessionsService, ownerSessionId: string, maxHops?: number): AppendableSession | undefined;
86
- /**
87
- * 官方 `deliverables/presented` 载荷。
88
- *
89
- * 形状与 `dsh-tool-present` 逐字段对齐(那边是 `{ turn, callId, files }`,
90
- * 客户端 `isPresentedData()` 要求 `turn` 是 >=1 的安全整数、`callId` 非空字符串、
91
- * `files` 是数组)。`files[].path` 必须是**工作区相对**路径:官方打开动作会拿它去
92
- * `workspaceFiles.stat({ sessionId, workspaceRoot: session.cwd })` 解析,绝对路径过不了
93
- * 「Presented file has no verified Host path」这一关。
94
- *
95
- * `callId` 只是官方契约里的一个非空字符串句柄,没有配对约束(`present` 那边用来关联
96
- * tool/result),这里借它携带 `chart_id`,便于在日志里把一条交付回溯到具体图表。
97
- */
51
+ /** Matches `dsh-tool-present` ({ turn, callId, files }); files[].path must be workspace-relative
52
+ * because the official open action resolves it against session.cwd. */
98
53
  export declare function buildChartEventPayload(input: ChartTurnEventInput, turn: number): Record<string, unknown>;
99
- /**
100
- * 进程内待登记条目的总量上限(安全阀)。
101
- *
102
- * 正常流程下条目会在 owner 那一轮关闭时被冲刷清空;但如果冲刷时机始终没接上
103
- * (上游事件名消失、监听注册失败且 turn/start 也没来),模块级 Map 会一直攒着。
104
- * 这是纯内存元数据(只有 chart_id / 标题 / 相对路径),上限只为防无界增长,
105
- * 与 `MAX_CHARTS_PER_TURN` 的"防模型循环"是两件事。
106
- */
54
+ /** Safety valve if flushing never fires; metadata only, bounded against unbounded growth. */
107
55
  export declare const MAX_PENDING_CHARTS = 64;
108
- /**
109
- * 只给测试用:清空待登记队列。
110
- *
111
- * `pending` 是模块级的(必须跨发布器实例存活,见下方说明),因此进程内所有测试
112
- * 共享它。测试之间要隔离,就在用例开始时调用一次。
113
- */
56
+ /** Test-only: clear the shared queue between cases. */
114
57
  export declare function resetChartEventPending(): void;
115
58
  export declare function createChartEventPublisher(ctx: ChartEventContext): ChartEventPublisher | undefined;
116
- /** 只给测试用:丢弃单例缓存(配合 resetChartEventPending 做用例隔离)。 */
59
+ /** Test-only: drop the singleton cache. */
117
60
  export declare function resetChartEventPublisher(): void;
118
- /** 出图时的 owner scope:与 chart_ref 同源(specialist 出图时正好是主会话)。 */
61
+ /** Owner scope at render time; same source as chart_ref (specialist renders land on the main session). */
119
62
  export declare function chartEventOwnerSessionId(session: SessionLike, sourceSession?: SessionLike): string;
120
63
  export {};
@@ -1,77 +1,23 @@
1
1
  /**
2
- * 图表 → 对话流的宿主侧事件通道(**first-party 口径**)。
3
- *
4
- * 问题:图表由 `visualization_specialist`(主 Agent 的**孙** Agent)生成,它的 tool call
5
- * 落在自己的会话里;用户在客户端看的是主会话,子会话的事件不会流过来。
6
- *
7
- * 做法:`render_chart` 成功后,宿主往**用户正在看的那条会话**追加官方
8
- * `deliverables/presented` 事件(`present` 工具用的同一条通道),把
9
- * `capital-analysis/charts/<chart_id>/chart.html` 登记为本轮交付物。用户在收尾的
10
- * 「本轮文件改动 / 交付」行点开该文件,右侧即用官方 document preview 的
11
- * script-enabled iframe 渲染自包含图表。
12
- *
13
- * ⚠️ 为什么**绝不能**再发明自己的事件类型(2026-09-18 事故,实测根因):
14
- * 会话日志的事件词汇表是**闭集**——`@deepseek-ai/dsh-session` 的
15
- * `KNOWN_SESSION_EVENT_TYPES` 由 harness 仓库自己的 `SessionEventMap` 生成,
16
- * 注释明写 "downstream (out-of-repo) plugin events are outside this list by
17
- * construction"。读取侧 `validateStoredEvents()` 是 fail-closed 的:
18
- *
19
- * `KNOWN_SESSION_EVENT_TYPES.has(event.type) || event.ignorable === true`
20
- *
21
- * 否则抛 `SessionFormatUnsupportedError` 并拒绝解释**整份日志**。而 `Session.append`
22
- * 的 options 只转发 `sourceEventSeqs` / `surfaceOp`——**调用方无法设置 `ignorable`**
23
- * (实测 0.1.5-rc.2 与 0.1.6-alpha.2 都没有该选项)。于是自定义事件是"写时静默通过、
24
- * 冷加载时整份会话打不开":本仓曾发 `capital/chart-rendered`,导致 6 份会话在重启后
25
- * 全部 `failed to observe session`。只有 first-party 事件能持久化。
26
- *
27
- * ⚠️ 回合归属(实测教训 2026-09-17):交付行是**按回合**渲染的,只会出现在它所属回合的
28
- * 收尾位置。而主 Agent 在等子 Agent 时会**先结束自己的回合**,于是出图往往发生在两个回合
29
- * 之间——此时 `turnBoundary.lastTurn` 指向那个**已经结束**的回合,照抄它会把交付登记到
30
- * 上一轮。因此:
31
- * - 有回合打开(`openTurnStartSeq` 是数字)→ 立即写入该回合;
32
- * - 没有回合打开 → 先寄存,等 owner 的**下一个 `turn/start`** 再写入(那正是会消费这份
33
- * 子 Agent 结果、写下最终答复的回合)。
34
- * 寄存的冲刷必须延到微任务:`session/event` 观察者在 `Session.append` 的 appending 窗口内同步执行,
35
- * 在那里再次 append 会被 "session append cannot reenter" 拒绝。
36
- *
37
- * 为什么这不"污染 transcripts":
38
- * - `deliverables/presented` 不在 surface 事件表里,`deriveEventMessage()` 返回 null,
39
- * 因此它**永不进入** `deriveMessages()`(模型看到的消息历史);
40
- * - payload 只有 `turn` / `callId`(`capital-chart:<chart_id>` 句柄)/ `files[]`
41
- * (工作区相对 `html_path` + 标题),没有 rows、series、HTML、绝对路径。
42
- *
43
- * 容错:`sessions` / `sessionProjections` 任一不可用、或 append 抛错,都只告警并跳过——
44
- * 出图本身绝不因此失败(文件已落盘、回执照样给 html_path)。
2
+ * Chart → conversation deliverable channel (first-party events only).
3
+ * Event types are a closed set: anything outside KNOWN_SESSION_EVENT_TYPES without `ignorable`
4
+ * makes the whole log unreadable on cold load, and append() cannot set it.
5
+ * Only `deliverables/presented` is safe; attribution and the owner-keyed queue are the invariants.
45
6
  */
46
7
  import { chartSessionScopeId } from './artifact-ref.js';
47
- /**
48
- * 登记本轮交付物用的官方事件名。
49
- *
50
- * 必须留在 `KNOWN_SESSION_EVENT_TYPES` 内——它是 `dsh-tool-present` 追加的同一类型,
51
- * 因此在**任何** harness 版本上都能被读回。改这里等于改会话日志契约,
52
- * `test/chart-turn-events.test.mjs` 有一条断言把它钉在 first-party 词汇表上。
53
- */
8
+ /** Must stay in `KNOWN_SESSION_EVENT_TYPES`; test/chart-turn-events.test.mjs pins it. */
54
9
  export const CHART_DELIVERABLE_EVENT = 'deliverables/presented';
55
- /** `callId` 前缀:交付事件里携带的图表句柄(官方契约只要求非空字符串)。 */
10
+ /** `callId` only needs to be a non-empty string; carries the chart handle for log tracing. */
56
11
  export const CHART_CALL_ID_PREFIX = 'capital-chart:';
57
- /** 同一 (session, turn) 的事件上限:防模型循环,超出只告警。 */
12
+ /** Per (session, turn) cap: stops model loops; overflow warns only. */
58
13
  export const MAX_CHARTS_PER_TURN = 8;
59
- /** 向上找根会话的最大跳数(main → data_junior → specialist 只有一跳,留余量)。 */
14
+ /** Root lookup cap (main → data_junior → specialist is one hop, plus headroom). */
60
15
  export const MAX_OWNER_HOPS = 4;
61
- /**
62
- * 最近一次 publish 的逐步轨迹(模块级:发布器每次出图都新建,轨迹要跨实例可读)。
63
- */
64
16
  let lastPublishTrace = '';
65
- /** 只读:最近一次 publish 轨迹。 */
66
17
  export function chartEventPublishTrace() {
67
18
  return lastPublishTrace;
68
19
  }
69
- /**
70
- * 交付通道的**诊断开关**:`CAPITAL_CHART_TRACE=1` 时才输出进度日志与落盘 trace。
71
- *
72
- * 默认关闭——对外发布不该往用户终端刷进度、也不该往 workspace 写调试文件。
73
- * 排查"图表/交付行不出现"时打开它(见 `deliverable-trace.txt` 与 docs/dev/chart-delivery-events.md §6.3 的说明)。
74
- */
20
+ /** Diagnostic switch; off by default to keep terminals and workspaces clean. */
75
21
  export function chartTraceEnabled() {
76
22
  try {
77
23
  const env = globalThis.process?.env;
@@ -87,14 +33,14 @@ function isAppendableSession(value) {
87
33
  && typeof value.append === 'function'
88
34
  && typeof value.id === 'string';
89
35
  }
36
+ /** `parentSession` alone is seed lineage (a user fork); only `origin: 'subagent'` is delegation. */
90
37
  function parentSessionOf(session) {
38
+ if (session.header?.origin !== 'subagent')
39
+ return undefined;
91
40
  const parent = session.header?.parentSession;
92
41
  return typeof parent === 'string' && parent.length > 0 ? parent : undefined;
93
42
  }
94
- /**
95
- * 从 owner scope 出发向上走到根会话:用户看的是根会话的对话流,卡片必须挂在它上面。
96
- * 找不到(会话已回收)时返回 undefined,调用方静默跳过。
97
- */
43
+ /** Walk up to the root session — the user watches the root conversation. Missing session → undefined. */
98
44
  export function resolveOwnerSession(sessions, ownerSessionId, maxHops = MAX_OWNER_HOPS) {
99
45
  let current;
100
46
  try {
@@ -123,18 +69,8 @@ export function resolveOwnerSession(sessions, ownerSessionId, maxHops = MAX_OWNE
123
69
  }
124
70
  return current;
125
71
  }
126
- /**
127
- * 官方 `deliverables/presented` 载荷。
128
- *
129
- * 形状与 `dsh-tool-present` 逐字段对齐(那边是 `{ turn, callId, files }`,
130
- * 客户端 `isPresentedData()` 要求 `turn` 是 >=1 的安全整数、`callId` 非空字符串、
131
- * `files` 是数组)。`files[].path` 必须是**工作区相对**路径:官方打开动作会拿它去
132
- * `workspaceFiles.stat({ sessionId, workspaceRoot: session.cwd })` 解析,绝对路径过不了
133
- * 「Presented file has no verified Host path」这一关。
134
- *
135
- * `callId` 只是官方契约里的一个非空字符串句柄,没有配对约束(`present` 那边用来关联
136
- * tool/result),这里借它携带 `chart_id`,便于在日志里把一条交付回溯到具体图表。
137
- */
72
+ /** Matches `dsh-tool-present` ({ turn, callId, files }); files[].path must be workspace-relative
73
+ * because the official open action resolves it against session.cwd. */
138
74
  export function buildChartEventPayload(input, turn) {
139
75
  return {
140
76
  turn,
@@ -147,58 +83,17 @@ export function buildChartEventPayload(input, turn) {
147
83
  ],
148
84
  };
149
85
  }
150
- /**
151
- * 待登记队列(**进程级**,不能放进发布器实例)。
152
- *
153
- * ⚠️ 2026-09-20 真机事故的根因:`createChartEventPublisher()` 是在**每次 `render_chart`
154
- * 时**惰性调用的(见 `src/index.ts` 的 `chartEvents: () => createChartEventPublisher(...)`),
155
- * 所以每个实例都拿到一份**自己的** `pending`。于是:
156
- * 1. 出图时回合是关的 → 图被寄存进**实例 A** 的 pending;
157
- * 2. 稍后 owner 那一轮关闭 → 冲刷回调跑在**实例 B**(甚至没有实例)上;
158
- * 3. B 的 pending 是空的 → 什么都没写 → 图**永久丢失**,且静默无报错。
159
- * 旧代码之所以看不出这个问题,是因为"回合打开时立即 append"这条路径占多数,
160
- * 只有"回合之间出图"(本项目的主 Agent 等子 Agent 时必然发生)才走寄存。
161
- *
162
- * 因此 pending 必须与进程内的服务实例同生命周期:模块级,并在注册冲刷监听时才清理。
163
- */
86
+ /** Process-level, not per publisher (which is rebuilt per render): keyed by owner (root) session
87
+ * id — the id flush events carry — and cleared only by flushing or an explicit reset. */
164
88
  const pending = new Map();
165
- /**
166
- * 进程内待登记条目的总量上限(安全阀)。
167
- *
168
- * 正常流程下条目会在 owner 那一轮关闭时被冲刷清空;但如果冲刷时机始终没接上
169
- * (上游事件名消失、监听注册失败且 turn/start 也没来),模块级 Map 会一直攒着。
170
- * 这是纯内存元数据(只有 chart_id / 标题 / 相对路径),上限只为防无界增长,
171
- * 与 `MAX_CHARTS_PER_TURN` 的"防模型循环"是两件事。
172
- */
89
+ /** Safety valve if flushing never fires; metadata only, bounded against unbounded growth. */
173
90
  export const MAX_PENDING_CHARTS = 64;
174
- /**
175
- * 只给测试用:清空待登记队列。
176
- *
177
- * `pending` 是模块级的(必须跨发布器实例存活,见下方说明),因此进程内所有测试
178
- * 共享它。测试之间要隔离,就在用例开始时调用一次。
179
- */
91
+ /** Test-only: clear the shared queue between cases. */
180
92
  export function resetChartEventPending() {
181
93
  pending.clear();
182
94
  }
183
- /**
184
- * 惰性构造发布器:`sessions` / `sessionProjections` 缺一不可(都是宿主平面服务,
185
- * 在 preset scope 里正常可解析;缺失表示宿主组合不完整,此时静默降级)。
186
- */
187
- /**
188
- * 进程级单例发布器。
189
- *
190
- * ⚠️ 2026-09-20 真机事故的**真根因**:`src/index.ts` 的 `chartEvents: () => createChartEventPublisher(...)`
191
- * 是**每次 `render_chart` 都调用一次**的。若每次都新建发布器:
192
- * 1. 实例 A 出图时把图寄存进自己的队列,并注册了**绑定在 A 上**的冲刷监听;
193
- * 2. 下一次出图再建实例 B(又注册一套监听);
194
- * 3. 冲刷回调里 `flush` 闭包引用的是某个实例的队列,而实例不断增多,
195
- * 寄存与冲刷落在不同实例上 —— 于是"专家确实出图,但主会话一条交付事件都没有"。
196
- *
197
- * 所以发布器必须**进程内唯一**:队列、监听、perTurn 计数都只有一份。
198
- * 缓存键用 `ctx.root ?? ctx`(cordis 的 root context 是稳定对象;`index.ts` 传进来的
199
- * 是个每次新建的 spread 字面量,不能用它做身份判断)。构造失败不缓存——宿主服务
200
- * 可能稍后才就绪,下次出图要能重新解析(保留原有的惰性语义)。
201
- */
95
+ /** Process-wide singleton (queue, listeners, per-turn counts) keyed by `ctx.root ?? ctx`;
96
+ * never cache a failed build so later-mounting host services can be picked up. */
202
97
  let cachedPublisher;
203
98
  export function createChartEventPublisher(ctx) {
204
99
  const ref = ctx.root ?? ctx;
@@ -209,7 +104,7 @@ export function createChartEventPublisher(ctx) {
209
104
  cachedPublisher = { ref, publisher };
210
105
  return publisher;
211
106
  }
212
- /** 只给测试用:丢弃单例缓存(配合 resetChartEventPending 做用例隔离)。 */
107
+ /** Test-only: drop the singleton cache. */
213
108
  export function resetChartEventPublisher() {
214
109
  cachedPublisher = undefined;
215
110
  }
@@ -222,23 +117,15 @@ function buildChartEventPublisher(ctx) {
222
117
  return undefined;
223
118
  const perTurn = new Map();
224
119
  let warned = false;
225
- /**
226
- * 输出一条诊断。
227
- *
228
- * ⚠️ 实测(2026-09-20):cordis 的 `ctx.logger` **只挂了一个内存 exporter**
229
- * (`cordis/lib/index.js` 的 `LoggerService` 构造器),全仓没有任何地方注册 console
230
- * exporter —— 也就是说 `ctx.logger.warn` 只进内存缓冲,**不会出现在 dsh 终端里**。
231
- * 这正是"卡片不出现却零报错"、两轮排查都只能靠猜的原因。
232
- * 因此这里**同时**写 `process.stderr`(宿主 stderr 一定能在终端看到);
233
- * logger 照发,保留结构化疗日志,将来有人接 exporter 就能收到。
234
- */
120
+ /** `ctx.logger` only buffers in memory (no console exporter), so also write stderr; otherwise
121
+ * delivery failures are invisible. */
235
122
  const emit = (message) => {
236
123
  const line = `capital-generation: ${message}`;
237
124
  try {
238
125
  ctx.logger?.warn(line);
239
126
  }
240
127
  catch {
241
- // logger 不可用不影响诊断输出。
128
+ // Logger unavailable; diagnostics still go to stderr below.
242
129
  }
243
130
  try {
244
131
  const stderr = globalThis.process?.stderr;
@@ -246,7 +133,7 @@ function buildChartEventPublisher(ctx) {
246
133
  stderr.write(`${line}\n`);
247
134
  }
248
135
  catch {
249
- // 没有 process(非 Node 载体)时静默跳过。
136
+ // No process (non-Node host).
250
137
  }
251
138
  };
252
139
  const warn = (message) => {
@@ -255,16 +142,7 @@ function buildChartEventPublisher(ctx) {
255
142
  warned = true;
256
143
  emit(message);
257
144
  };
258
- /**
259
- * 进度日志:**只在 `CAPITAL_CHART_TRACE=1` 时输出**。
260
- *
261
- * 寄存/登记这两处状态迁移是排查"交付行不出现"的关键线索,但对**普通用户是噪音**
262
- * (每张图两条)。所以默认静默、按需打开:
263
- *
264
- * CAPITAL_CHART_TRACE=1 dsh web
265
- *
266
- * 失败用的 `warn` 不受此开关影响——真出问题必须让人看见。
267
- */
145
+ /** Progress log only under CAPITAL_CHART_TRACE=1; warnings bypass the switch. */
268
146
  const note = (message) => {
269
147
  if (!chartTraceEnabled())
270
148
  return;
@@ -278,7 +156,6 @@ function buildChartEventPublisher(ctx) {
278
156
  return undefined;
279
157
  }
280
158
  };
281
- /** 真正写一条事件;返回是否写入。 */
282
159
  const append = (owner, turn, input) => {
283
160
  const key = `${owner.id}:${turn}`;
284
161
  const used = perTurn.get(key) ?? 0;
@@ -294,16 +171,10 @@ function buildChartEventPublisher(ctx) {
294
171
  return false;
295
172
  }
296
173
  perTurn.set(key, used + 1);
297
- // 与"已寄存"成对的成功日志:排查交付行不出现时,一眼能看出是没写还是没渲染。
298
174
  note(`图表已登记为本轮交付(owner=${owner.id},turn=${turn},${input.chart_id})`);
299
175
  return true;
300
176
  };
301
- /**
302
- * 把寄存的图表写进某一轮。
303
- *
304
- * 冲刷时机决定交付行的落点:首选 `agent/turn-stopping`(该轮即将关闭),
305
- * 只有在拿不到这个钩子时才退回 `turn/start`。
306
- */
177
+ /** Flush queued charts into a turn; the preferred hook is turn-stopping. */
307
178
  const flush = (ownerSessionId, turn) => {
308
179
  const queued = pending.get(ownerSessionId);
309
180
  if (queued === undefined || queued.length === 0)
@@ -317,14 +188,9 @@ function buildChartEventPublisher(ctx) {
317
188
  for (const item of queued)
318
189
  append(owner, turn, item);
319
190
  };
320
- /**
321
- * 首选时机是否可用。
322
- *
323
- * 关键:`turn-stopping` 缺席时**必须**退回 `turn/start`,否则寄存的图会永远不写(静默丢失)。
324
- * 安装成功才置 true;effect 卸载时复位,避免失效后仍以为首选可用。
325
- */
191
+ /** Only a real registration may flip this; otherwise turn/start fallback stays live. */
326
192
  let turnStoppingActive = false;
327
- /** 不能在任何 appending 窗口内重入 append,统一延到微任务。 */
193
+ /** Never append inside the appending window; defer to a microtask. */
328
194
  const flushSoon = (ownerSessionId, turn) => {
329
195
  queueMicrotask(() => {
330
196
  try {
@@ -343,8 +209,7 @@ function buildChartEventPublisher(ctx) {
343
209
  const event = args[1];
344
210
  if (event?.type !== 'turn/start')
345
211
  return;
346
- // 首选钩子在场时,`turn/start` 不得抢跑:那一轮往往还是过程轮(2026-09-20 真机教训),
347
- // 交付行会被落到空轮里。只有拿不到 `agent/turn-stopping` 时才用它兜底。
212
+ // turn/start must not race ahead while turn-stopping is active; it is only the fallback.
348
213
  if (turnStoppingActive)
349
214
  return;
350
215
  const sessionId = typeof session?.id === 'string' ? session.id : undefined;
@@ -356,7 +221,7 @@ function buildChartEventPublisher(ctx) {
356
221
  flushSoon(sessionId, turn);
357
222
  }
358
223
  catch {
359
- // 事件观察者永不抛出。
224
+ // Observers never throw.
360
225
  }
361
226
  });
362
227
  if (typeof ctx.effect === 'function') {
@@ -379,21 +244,18 @@ function buildChartEventPublisher(ctx) {
379
244
  flushSoon(sessionId, turn);
380
245
  }
381
246
  catch {
382
- // 事件观察者永不抛出。
247
+ // Observers must not throw.
383
248
  }
384
249
  });
385
- // ⚠️ 只有**真的拿到 disposer** 才算首选可用(2026-09-20 真机事故):
386
- // 早先这里无条件置 true,于是 `ctx.on('agent/turn-stopping')` 注册失败
387
- // (适配器 catch 后返回 undefined)时,闸门仍被打开、`turn/start` 兜底被永久关掉,
388
- // 图就**永远滞留 pending**——表现是交付卡片从头到尾不出现,且零报错。
389
- // 注册失败必须让 `turnStoppingActive` 保持 false,由 `turn/start` 接上。
250
+ // Only a real disposer means the preferred hook is active; failed registration must leave
251
+ // the turn/start fallback on, or queued charts stay pending forever.
390
252
  if (typeof disposeTurnStopping === 'function')
391
253
  turnStoppingActive = true;
392
254
  else
393
255
  warn('交付行的首选冲刷时机不可用(agent/turn-stopping 注册失败),已退回 turn/start 兜底;落点可能偏早一轮');
394
256
  if (typeof ctx.effect === 'function') {
395
257
  ctx.effect(() => () => {
396
- // 卸载即视为"首选不可用",让 `turn/start` 兜底重新接上,避免图静默丢失。
258
+ // Teardown re-enables the fallback so queued charts are not lost.
397
259
  turnStoppingActive = false;
398
260
  if (typeof disposeTurnStopping === 'function')
399
261
  disposeTurnStopping();
@@ -414,20 +276,15 @@ function buildChartEventPublisher(ctx) {
414
276
  const openSeq = boundary?.openTurnStartSeq;
415
277
  const lastTurn = boundary?.lastTurn;
416
278
  if (typeof openSeq !== 'number') {
417
- // 回合之间出图(主 Agent 等子 Agent 时会先关轮):寄存,等 owner 那一轮
418
- // **即将关闭**(`agent/turn-stopping`)时再写——那才是消费这份图、写下答复的回合。
419
- //
420
- // ⚠️ 寄存队列必须按 **owner**(根会话)的 id 归档,不能按 `input.ownerSessionId`:
421
- // 后者是发起出图的 scope(visualization_specialist,孙会话),而冲刷时机
422
- // (`turn/start` / `agent/turn-stopping`)拿到的都是 owner 的 id。
423
- // 两边键不一致时 `flush()` 永远查不到,图会**永久滞留在 pending**、静默不登记
424
- // (2026-09-20 实测:3 个 specialist 会话 render_chart=2 而 deliverables=0)。
279
+ // Between turns (main agent closes its turn while waiting on children): queue until
280
+ // the owner's turn closes. Key by owner.id — flush events carry that id; a caller-scope
281
+ // key would never match and strands charts forever.
425
282
  const queued = pending.get(owner.id) ?? [];
426
283
  if (queued.length >= MAX_CHARTS_PER_TURN) {
427
284
  warn(`图表对话流事件跳过:${owner.id} 已寄存 ${MAX_CHARTS_PER_TURN} 张,等不到新回合`);
428
285
  return false;
429
286
  }
430
- // 安全阀:正常不该触发(冲刷会清空);触发说明冲刷链路坏了,宁可丢图也要有界。
287
+ // Unreachable in normal flow; bound memory if flushing is broken.
431
288
  let total = 0;
432
289
  for (const list of pending.values())
433
290
  total += list.length;
@@ -437,14 +294,11 @@ function buildChartEventPublisher(ctx) {
437
294
  }
438
295
  queued.push(input);
439
296
  pending.set(owner.id, queued);
440
- // 出图时回合是关的 ⇒ 走寄存。这条日志是**必要的可观测性**:它之后若没有对应的
441
- // "已登记" 日志,就说明冲刷时机没接上(2026-09-20 的静默丢失正是死在这里,
442
- // 两轮排查都因为没有日志而只能猜)。只在进程内报一次,不刷屏。
443
297
  note(`图表已寄存,待 owner 回合关闭时登记(owner=${owner.id},lastTurn=${String(lastTurn)},寄存 ${queued.length} 张)`);
444
298
  lastPublishTrace = `${trace.join(' ')} 回合未打开(openTurnStartSeq=${String(openSeq)} lastTurn=${String(lastTurn)}) → 已寄存 ${queued.length} 张,等 turn-stopping/turn-start 冲刷`;
445
299
  return true;
446
300
  }
447
- // turn 0 / 非法值表示该会话还没真正开过回合。
301
+ // turn 0 / invalid means the session never really opened a turn.
448
302
  if (typeof lastTurn !== 'number' || !Number.isSafeInteger(lastTurn) || lastTurn < 1) {
449
303
  lastPublishTrace = `${trace.join(' ')} 回合号非法(lastTurn=${String(lastTurn)}) → 未写入`;
450
304
  warn('图表对话流事件跳过:owner 会话没有打开中的 turn');
@@ -454,7 +308,7 @@ function buildChartEventPublisher(ctx) {
454
308
  lastPublishTrace = `${trace.join(' ')} 回合打开(turn=${lastTurn}) → ${written ? '已直接登记' : '写入失败'}`;
455
309
  return written;
456
310
  },
457
- /** 诊断/测试用:当前寄存未冲刷的图表数。 */
311
+ /** Diagnostics/tests: queued charts not yet flushed. */
458
312
  pendingCount(ownerSessionId) {
459
313
  if (ownerSessionId !== undefined)
460
314
  return pending.get(ownerSessionId)?.length ?? 0;
@@ -465,7 +319,7 @@ function buildChartEventPublisher(ctx) {
465
319
  },
466
320
  };
467
321
  }
468
- /** 出图时的 owner scope:与 chart_ref 同源(specialist 出图时正好是主会话)。 */
322
+ /** Owner scope at render time; same source as chart_ref (specialist renders land on the main session). */
469
323
  export function chartEventOwnerSessionId(session, sourceSession) {
470
324
  return chartSessionScopeId(sourceSession ?? session);
471
325
  }
@@ -5,16 +5,13 @@ export interface VendoredChartLibrary {
5
5
  license: string;
6
6
  }
7
7
  /**
8
- * 读取随包发布的 lightweight-charts standalone 构建。
9
- *
10
- * 路径相对 `import.meta.url`:编译后是 `lib/chart/vendor/...`(由 scripts/copy-assets.mjs
11
- * 从 `vendor/` 复制过去)。缺失时给出可操作错误,而不是让 HTML 里静默少一段脚本。
8
+ * Path is relative to import.meta.url (assets copied into lib by scripts/copy-assets.mjs);
9
+ * a missing library is a hard, actionable error rather than silently missing script bytes.
12
10
  */
13
11
  export declare function loadVendoredChartLibrary(): VendoredChartLibrary;
14
12
  export interface BuildStandaloneHtmlInput {
15
13
  payload: ChartPayload;
16
14
  library: VendoredChartLibrary;
17
- /** 生成时刻(epoch ms),仅用于页脚署名,不参与渲染。 */
18
15
  generatedAt: number;
19
16
  }
20
17
  export declare function buildStandaloneHtml(input: BuildStandaloneHtmlInput): string;
package/lib/chart/html.js CHANGED
@@ -1,12 +1,7 @@
1
1
  /**
2
- * 自包含 HTML 图表的生成器。
3
- *
4
- * 这是整个方案的**降级底线**(内部设计文档 docs/design/chart-visualization.md §5.4 的 L2):
5
- * 它只依赖"用户有一个浏览器",不依赖 DSH 的客户端插件系统、不依赖网络、
6
- * 不依赖任何上游扩展点。即使 Slot / bundle / 路由全部被上游改动打坏,
7
- * 用户依然能拿到一张可交互的图。
8
- *
9
- * 因此它必须:内联 vendored 库 + 内联数据 + 内联运行时,**零外部请求**。
2
+ * Standalone HTML: the L2 fallback that needs only a browser. Vendored library, data and
3
+ * runtime are inlined (zero external requests) so a broken client plugin, route or network
4
+ * cannot take charts down.
10
5
  */
11
6
  import { ChartError } from './errors.js';
12
7
  import { CHART_RUNTIME_SOURCE } from './runtime.js';
@@ -20,10 +15,8 @@ function nodeFs() {
20
15
  }
21
16
  let cachedLibrary;
22
17
  /**
23
- * 读取随包发布的 lightweight-charts standalone 构建。
24
- *
25
- * 路径相对 `import.meta.url`:编译后是 `lib/chart/vendor/...`(由 scripts/copy-assets.mjs
26
- * 从 `vendor/` 复制过去)。缺失时给出可操作错误,而不是让 HTML 里静默少一段脚本。
18
+ * Path is relative to import.meta.url (assets copied into lib by scripts/copy-assets.mjs);
19
+ * a missing library is a hard, actionable error rather than silently missing script bytes.
27
20
  */
28
21
  export function loadVendoredChartLibrary() {
29
22
  if (cachedLibrary)
@@ -41,15 +34,15 @@ export function loadVendoredChartLibrary() {
41
34
  throw new ChartError('chart_runtime_unavailable', `vendored lightweight-charts is missing (${error instanceof Error ? error.message : String(error)}); run npm run vendor:charts && npm run build`);
42
35
  }
43
36
  }
44
- /** 内联脚本的安全转义:`</script` 会提前结束 script 元素。 */
37
+ /** `</script` would close the inline script element early. */
45
38
  function escapeScript(source) {
46
39
  return source.replace(/<\/script/gi, '<\\/script');
47
40
  }
48
- /** 内联 JSON 的安全转义:`<` 一律写成 `\u003c`(JSON 里合法,且彻底杜绝标签闭合)。 */
41
+ /** `<` becomes `\u003c` (valid JSON) so data can never close a tag. */
49
42
  function escapeJson(value) {
50
43
  return JSON.stringify(value).replace(/</g, '\\u003c');
51
44
  }
52
- /** 数据截至时间:按本仓数据口径用 Asia/Shanghai 显示,同时保留 UTC。 */
45
+ /** Data timestamps display in Asia/Shanghai per data convention; UTC is the fallback. */
53
46
  function formatCapturedAt(epochMs) {
54
47
  try {
55
48
  const parts = new Intl.DateTimeFormat('en-CA', {
@@ -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;