@v587d/capital-generation 2.8.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +41 -20
  3. package/chart-ui/README.md +1 -1
  4. package/chart-ui/client.src.cjs +7 -16
  5. package/chart-ui/index.js +12 -33
  6. package/chart-ui/package.json +2 -2
  7. package/config/client.js +18 -39
  8. package/config/client.src.cjs +79 -217
  9. package/config/index.js +22 -72
  10. package/config/package.json +2 -2
  11. package/cordis.patch.yml +46 -51
  12. package/lib/agents/bash-guard.d.ts +9 -55
  13. package/lib/agents/bash-guard.js +27 -67
  14. package/lib/agents/preset-roster.d.ts +22 -0
  15. package/lib/agents/preset-roster.js +87 -0
  16. package/lib/agents/role-tool-policy.d.ts +46 -0
  17. package/lib/agents/role-tool-policy.js +205 -0
  18. package/lib/agents/root-tool-policy.d.ts +28 -124
  19. package/lib/agents/root-tool-policy.js +164 -237
  20. package/lib/agents/selected-skills-provider.d.ts +2 -6
  21. package/lib/agents/selected-skills-provider.js +27 -84
  22. package/lib/capital-settings.d.ts +27 -0
  23. package/lib/capital-settings.js +23 -0
  24. package/lib/chart/artifact-ref.d.ts +3 -8
  25. package/lib/chart/artifact-ref.js +3 -8
  26. package/lib/chart/downsample.d.ts +2 -5
  27. package/lib/chart/downsample.js +3 -8
  28. package/lib/chart/errors.d.ts +3 -7
  29. package/lib/chart/errors.js +1 -2
  30. package/lib/chart/event-context.d.ts +8 -0
  31. package/lib/chart/event-context.js +39 -0
  32. package/lib/chart/events.d.ts +21 -78
  33. package/lib/chart/events.js +40 -189
  34. package/lib/chart/html.d.ts +2 -5
  35. package/lib/chart/html.js +8 -15
  36. package/lib/chart/runtime.d.ts +5 -18
  37. package/lib/chart/runtime.js +14 -34
  38. package/lib/chart/series.d.ts +10 -17
  39. package/lib/chart/series.js +22 -47
  40. package/lib/chart/source-token.d.ts +5 -13
  41. package/lib/chart/source-token.js +18 -17
  42. package/lib/chart/spec.d.ts +6 -12
  43. package/lib/chart/spec.js +7 -31
  44. package/lib/chart/tool.d.ts +6 -29
  45. package/lib/chart/tool.js +9 -23
  46. package/lib/chart-render-entry.d.ts +9 -0
  47. package/lib/chart-render-entry.js +17 -0
  48. package/lib/chart-source-entry.d.ts +13 -0
  49. package/lib/chart-source-entry.js +20 -0
  50. package/lib/collect-entry.d.ts +9 -0
  51. package/lib/collect-entry.js +10 -0
  52. package/lib/collector-runtime.d.ts +22 -0
  53. package/lib/collector-runtime.js +97 -0
  54. package/lib/data-collector/args.d.ts +6 -24
  55. package/lib/data-collector/args.js +6 -24
  56. package/lib/data-collector/dataset-tools.d.ts +7 -1
  57. package/lib/data-collector/dataset-tools.js +27 -56
  58. package/lib/data-collector/describe.d.ts +8 -33
  59. package/lib/data-collector/describe.js +17 -70
  60. package/lib/data-collector/hub.d.ts +27 -85
  61. package/lib/data-collector/hub.js +19 -85
  62. package/lib/data-collector/query-time.d.ts +7 -26
  63. package/lib/data-collector/query-time.js +8 -27
  64. package/lib/data-collector/store.d.ts +40 -117
  65. package/lib/data-collector/store.js +32 -111
  66. package/lib/data-collector/time-axis.d.ts +16 -76
  67. package/lib/data-collector/time-axis.js +25 -111
  68. package/lib/data-collector/tools.d.ts +6 -6
  69. package/lib/data-collector/tools.js +34 -43
  70. package/lib/dataset-read-entry.d.ts +9 -0
  71. package/lib/dataset-read-entry.js +7 -0
  72. package/lib/index.d.ts +7 -117
  73. package/lib/index.js +43 -422
  74. package/lib/net/eastmoney-client.d.ts +13 -65
  75. package/lib/net/eastmoney-client.js +12 -53
  76. package/lib/net/throttle.d.ts +2 -13
  77. package/lib/net/throttle.js +3 -14
  78. package/lib/ocr/client.d.ts +12 -21
  79. package/lib/ocr/client.js +23 -40
  80. package/lib/ocr/markdown.d.ts +20 -26
  81. package/lib/ocr/markdown.js +23 -36
  82. package/lib/ocr/store.d.ts +14 -47
  83. package/lib/ocr/store.js +15 -45
  84. package/lib/ocr-entry.d.ts +9 -0
  85. package/lib/ocr-entry.js +10 -0
  86. package/lib/policy-entry.d.ts +17 -0
  87. package/lib/policy-entry.js +22 -0
  88. package/lib/preset-claim.d.ts +22 -0
  89. package/lib/preset-claim.js +24 -0
  90. package/lib/preset-ids.d.ts +38 -0
  91. package/lib/preset-ids.js +46 -0
  92. package/lib/retriever-config.d.ts +37 -0
  93. package/lib/retriever-config.js +36 -0
  94. package/lib/retriever-runtime.d.ts +31 -0
  95. package/lib/retriever-runtime.js +82 -0
  96. package/lib/root-extras-entry.d.ts +17 -0
  97. package/lib/root-extras-entry.js +25 -0
  98. package/lib/runtime-entry.d.ts +15 -0
  99. package/lib/runtime-entry.js +5 -0
  100. package/lib/runtime.d.ts +21 -0
  101. package/lib/runtime.js +33 -0
  102. package/lib/search-entry.d.ts +9 -0
  103. package/lib/search-entry.js +9 -0
  104. package/lib/sources/eastmoney-http.js +20 -68
  105. package/lib/sources/ftshare-rest.d.ts +6 -31
  106. package/lib/sources/ftshare-rest.js +1194 -225
  107. package/lib/sources/fuyao-core.d.ts +8 -18
  108. package/lib/sources/fuyao-core.js +10 -21
  109. package/lib/sources/fuyao-rest.d.ts +2 -6
  110. package/lib/sources/fuyao-rest.js +23 -102
  111. package/lib/sources/security-code.d.ts +6 -12
  112. package/lib/sources/security-code.js +6 -12
  113. package/lib/sources/security-identity.d.ts +1 -4
  114. package/lib/sources/security-identity.js +3 -8
  115. package/lib/sources/tencent-http.js +14 -28
  116. package/lib/sources/tencent-public-core.d.ts +8 -60
  117. package/lib/sources/tencent-public-core.js +12 -77
  118. package/lib/sources/wind-mcp.d.ts +0 -5
  119. package/lib/sources/wind-mcp.js +23 -78
  120. package/lib/sources-entry.d.ts +9 -0
  121. package/lib/sources-entry.js +9 -0
  122. package/lib/time/tools.d.ts +8 -31
  123. package/lib/time/tools.js +39 -64
  124. package/lib/time-entry.d.ts +9 -0
  125. package/lib/time-entry.js +7 -0
  126. package/lib/tool-exec.d.ts +45 -11
  127. package/lib/tool-exec.js +59 -6
  128. package/lib/user-customization.d.ts +14 -0
  129. package/lib/user-customization.js +47 -0
  130. package/lib/watchlist/tools.d.ts +3 -13
  131. package/lib/watchlist/tools.js +10 -30
  132. package/lib/watchlist-entry.d.ts +9 -0
  133. package/lib/watchlist-entry.js +6 -0
  134. package/lib/web-retriever/engines.d.ts +6 -11
  135. package/lib/web-retriever/engines.js +9 -14
  136. package/lib/web-retriever/html-markdown.d.ts +14 -34
  137. package/lib/web-retriever/html-markdown.js +39 -75
  138. package/lib/web-retriever/local-fetch.d.ts +22 -58
  139. package/lib/web-retriever/local-fetch.js +33 -77
  140. package/lib/web-retriever/retriever.d.ts +6 -18
  141. package/lib/web-retriever/retriever.js +7 -7
  142. package/lib/web-retriever/sources.d.ts +37 -124
  143. package/lib/web-retriever/sources.js +64 -196
  144. package/lib/web-retriever/tools.d.ts +43 -27
  145. package/lib/web-retriever/tools.js +99 -137
  146. package/lib/web-retriever/wind-client.d.ts +14 -31
  147. package/lib/web-retriever/wind-client.js +16 -32
  148. package/locale/zh.json +1 -1
  149. package/package.json +67 -7
  150. package/preset/capital-generation/README.md +87 -53
  151. package/preset/capital-generation/agent.patch.yml +526 -337
  152. package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
  153. package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
  154. package/preset/capital-generation/skills/capital-equity-research/SKILL.md +1 -1
  155. package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
  156. package/preset/capital-generation/skills/capital-orchestration/SKILL.md +15 -19
  157. package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
  158. package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +9 -12
  159. package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
  160. package/watchlist/client.js +15 -148
  161. package/watchlist/client.src.cjs +148 -662
  162. package/watchlist/holding-rules.js +11 -38
  163. package/watchlist/index.js +127 -397
  164. package/watchlist/package.json +2 -2
@@ -3,49 +3,27 @@ import { readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { agentSessionId, isRootAgent } from './root-tool-policy.js';
6
- // The catalog is a shipped data file outside the TypeScript root; runtime keeps this
7
- // import so the published lib resolves the same metadata beside the pinned snapshots.
6
+ // Catalog is package data outside the TS root; the runtime import keeps lib resolving it beside pinned snapshots.
8
7
  // @ts-expect-error catalog.js is intentionally maintained as package data.
9
8
  import { SELECTED_SKILL_CATALOG as CATALOG, SELECTED_SKILL_SKILL_DIRS, SELECTED_SKILL_TASK_IDS, selectedSkillsForTask } from '../../selected-skills/catalog.js';
10
9
  export const SELECTED_SKILL_CATALOG = CATALOG;
11
10
  const PROVIDER_NAME = 'capital-selected-skills';
12
- /** 本 preset 的身份:两条注册路径(`agent/created` 与 `agent-preset/selected`)共用一份。 */
13
11
  const CAPITAL_PRESET_ID = 'capital-generation';
14
- // skills 根按 catalog 的「仓库 → 上游 SKILL.md 相对路径」现算:第三颗快照的上游把 skill 放在
15
- // plugins/us-stock-analysis/skills,不在 <repo>/skills 底下。写死目录列表的表现不是报错,
16
- // 而是"用户在卡片上勾了那一格、技能永远不出现"(AGENTS.md §9.7)。
12
+ // Derive roots from the catalog: a hardcoded list silently hides checked boxes (upstream layouts differ).
17
13
  const SKILL_ROOTS = Object.entries(SELECTED_SKILL_SKILL_DIRS).map(([repository, skillsDir]) => fileURLToPath(new URL(`../../selected-skills/${repository}/${skillsDir}`, import.meta.url)));
18
- /**
19
- * 任务型内置 skill 的根目录(`lib/agents/` → 包根 → `preset/capital-generation/skills`)。
20
- *
21
- * 索引**不改磁盘**:`preset/.../SKILL.md` 一个字节都不动,正文在 `get()` 读取期拼上索引块。
22
- * 三个理由,缺一都不是洁癖:装好的包常常整棵只读(全局安装更糟);`skill-filesystem` 的 watcher
23
- * 会被写文件搅动,多会话并行时互相打架;`test/persona.test.mjs` 正钉着这些文件的 frontmatter 与
24
- * 体积,运行期改写会让那些闸门失去意义。
25
- */
14
+ /** Built-in task skill root; the index is appended at read time, never written to disk:
15
+ * installs may be read-only, watchers would churn, and tests pin frontmatter and size. */
26
16
  const TASK_SKILL_ROOT = fileURLToPath(new URL('../../preset/capital-generation/skills', import.meta.url));
27
- /**
28
- * 遮蔽候选的 rank。DSH 的 `compareIndexedCandidates` 先比 rank 再比 provider 顺序,**小的赢**;
29
- * 同层自定义根是 `CUSTOM_RANK = 300`,这里取 150 保证同层时压过 fs provider。
30
- *
31
- * 跨层本来也轮不到 rank:`dsh-agent-preset-registry` 把 preset 的 standing scope 挂成
32
- * **agent scope 的 parent**(`standingKey = scopeParentOf(agentKey)`),而 `chainLayers` 的注释写明
33
- * "farthest ancestor first, the nearest scope has the final word"、`scopeChainOf(...).reverse()` 后
34
- * 逐层 `merged.set(name, ...)` —— 本 provider 注册在 agent scope 里,是最近的一层,同名天然覆盖。
35
- * 两条机制取其一即可,取两条是因为遮蔽失败的症状是"索引静默消失",没有第二个人会发现。
36
- */
17
+ /** Smaller rank wins: 150 beats the same-layer custom root (300). The agent scope usually
18
+ * shadows anyway; both are cheap insurance because failure is silent. */
37
19
  const TASK_SKILL_SHADOW_RANK = 150;
38
20
  const INDEX_HEADING = '## 附:本轮可用的外部精选 Skill(索引)';
39
- /**
40
- * 索引块的框。它把**外部方法论**塞进了**宿主契约 skill** 的正文里,框架被拉平了,所以这四句必须
41
- * 写在块内而不是别的文档里:冲突判据、不可动摇的三处边界、以及"这里只有摘要"。
42
- */
21
+ /** Conflict precedence and the three untouchable boundaries must live inside the block. */
43
22
  const INDEX_FRAME = [
44
23
  '以下是你(用户)在设置里启用的外部精选 Skill,按能力标签与本次任务关联后列出。它们是**方法论材料**,',
45
24
  '需要正文时照常用 `skill` 工具按名加载。它们**不改变**本 skill 的证据门、工具委派路径、输出通道与报告形状;',
46
25
  '与本 skill 冲突时**以本 skill 为准**。这里只给名称与摘要,摘要不等于全文,未加载前不要照它行事。',
47
26
  ];
48
- /** 最小 frontmatter 读取:只要 `name` / `description` / 正文。完整策略解析仍归 fs provider。 */
49
27
  function readFrontmatter(raw) {
50
28
  const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(raw);
51
29
  if (match === null)
@@ -58,10 +36,7 @@ function readFrontmatter(raw) {
58
36
  }
59
37
  return { fields, body: raw.slice(match[0].length) };
60
38
  }
61
- /**
62
- * 载入三颗任务型 skill 的正文。**读不到就不遮蔽**:`list()` 不会列出这个候选,fs provider 照常
63
- * 供原文,用户看到的是"没有索引",而不是"这颗 skill 不见了"(AGENTS.md §9.7 ⑤ 失败要留余地)。
64
- */
39
+ /** Unreadable means no shadow, never a missing skill: the fs provider still serves the original. */
65
40
  function loadTaskSkillBases(root = TASK_SKILL_ROOT) {
66
41
  const bases = new Map();
67
42
  for (const name of SELECTED_SKILL_TASK_IDS) {
@@ -74,16 +49,11 @@ function loadTaskSkillBases(root = TASK_SKILL_ROOT) {
74
49
  bases.set(name, { name, description, content: parsed.body.trim(), directory: join(root, name) });
75
50
  }
76
51
  catch {
77
- // 读不到是 fail-open,不是错误路径:默认态(一颗都没启用)本来就不遮蔽任何东西。
78
52
  }
79
53
  }
80
54
  return bases;
81
55
  }
82
- /**
83
- * 索引块正文。**唯一实现**:条目集合来自 catalog 的 `selectedSkillsForTask`(与目录、闸门同源),
84
- * 描述用**运行时那一份**(上游 frontmatter,也就是会话目录里模型看到的那句话),不是设置卡片里
85
- * 策展过的中文摘要——同一个 skill 在同一段上下文里出现两种描述,只会让模型两处对照。
86
- */
56
+ /** Sole index renderer; descriptions use runtime frontmatter (what the model sees), not card summaries. */
87
57
  export function renderTaskSkillIndex(taskId, summaries) {
88
58
  if (summaries.length === 0)
89
59
  return undefined;
@@ -106,13 +76,11 @@ function selectedNames(selection) {
106
76
  function readSelection(source) {
107
77
  return typeof source === 'function' ? source() : source;
108
78
  }
109
- // 选择指纹只认 catalog 的 key 集合:pre-step 每轮拿它对比"上次交给注册表的选择",
110
- // 变了才失效目录缓存,未变就零成本放行。
79
+ // Fingerprint only the catalog key set: refresh the cache only when the selection changes.
111
80
  function selectionFingerprint(selection) {
112
81
  return SELECTED_SKILL_CATALOG.map(({ key }) => (selection[key] === true ? '1' : '0')).join('');
113
82
  }
114
- // agent/pre-step 是 dsh-agent 声明的事件,类型不在本包的 import 面里;按结构挂到注入后的
115
- // Agent context(其 scope tag 就是该 Agent,dispatch 的 scope filter 才放行这条监听)。
83
+ // `agent/pre-step` belongs to dsh-agent; attach via the injected Agent context so scope filtering admits it.
116
84
  function onPreStepEvent(ctx, listener) {
117
85
  const events = ctx;
118
86
  return events.on?.('agent/pre-step', listener);
@@ -127,19 +95,16 @@ function strictCapitalPreset(ctx, agent) {
127
95
  return false;
128
96
  }
129
97
  }
130
- /** Register explicitly selected community experiments on the Capital root Agent only. */
98
+ /** Registers selected community experiments on the Capital root Agent only. */
131
99
  export function registerRootSelectedSkills(ctx, selection, createFilesystemProvider = createFilesystemSkillProvider) {
132
- // Static callers retain the cheap all-disabled fast path. The Capital plugin
133
- // passes a reader so a settings toggle is observed at the next step of every
134
- // live root Agent: list()/get() filter against the current selection, and the
135
- // pre-step hook below refreshes the visible catalog when the selection changes.
100
+ // Static callers keep the all-off fast path; the plugin passes a reader so live toggles apply
101
+ // at the next step (list/get filter by selection, pre-step refreshes the visible catalog).
136
102
  if (typeof selection !== 'function' && selectedNames(selection).size === 0)
137
103
  return;
138
104
  const listenCtx = ctx.root ?? ctx;
139
105
  if (typeof listenCtx.on !== 'function')
140
106
  return;
141
- // 一个 root Agent 只注册一次:下面两条事件路径会在同一个 Agent 上重叠(建会话时就带 Capital
142
- // 预设 → `agent/created`;先按默认预设建、再 select 到 Capital → `agent-preset/selected`)。
107
+ // One registration per root Agent: both event paths can observe the same Agent.
143
108
  const registered = new WeakSet();
144
109
  const registerFor = async (agent) => {
145
110
  if (!agent || registered.has(agent))
@@ -154,8 +119,7 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
154
119
  ctx.logger?.warn?.('capital-generation: selected skills are unavailable (root Agent cannot inject skills)');
155
120
  return;
156
121
  }
157
- // agent/created 是串行事件、DSH 等监听器 settle 才继续创建:await 注入 Fiber,
158
- // provider 与 pre-step 钩子都在首轮 prompt 前落位(首轮 catalog 不与注册竞态)。
122
+ // agent/created is serial: DSH awaits listeners, so this inject lands before the first prompt.
159
123
  await agentCtx.inject(['skills'], (injectedCtx) => {
160
124
  const skills = injectedCtx.skills;
161
125
  if (typeof skills?.registerProvider !== 'function') {
@@ -164,9 +128,7 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
164
128
  }
165
129
  let filesystemProvider;
166
130
  let control;
167
- // 上次交给 skills 注册表的选择指纹。开关是 settings 偏好,不在 Agent 创建时定格:
168
- // 全关也照常注册 provider(否则会话创建后打开开关永远不出现),靠下面的 pre-step
169
- // 钩子每轮对齐设置。
131
+ // Register even when all toggles are off; otherwise a later enable would never surface. Pre-step aligns settings.
170
132
  let served = selectionFingerprint(readSelection(selection));
171
133
  injectedCtx.effect(() => {
172
134
  const unregister = skills.registerProvider((registration) => {
@@ -177,15 +139,11 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
177
139
  customSkillDirs: SKILL_ROOTS,
178
140
  watch: true,
179
141
  });
180
- // 遮蔽用的底稿只在注册时读一次(三份小文件,读不到就不遮蔽)。
181
142
  const taskBases = loadTaskSkillBases();
182
- // 上一次 `list()` 看到的"已启用精选"运行时摘要。`get()` 拼索引时读它,避免为了几行摘要
183
- // 再枚举一遍快照目录;为空就退回"没有索引",也就是把整颗让给 fs provider 的原文。
143
+ // Summaries from the last `list()`; `get()` reuses them instead of rescanning dirs.
184
144
  let runtimeSummaries = [];
185
145
  const indexFor = (taskId) => {
186
146
  const byName = new Map(runtimeSummaries.map((entry) => [entry.name, entry]));
187
- // 条目集合来自 catalog 的唯一实现(与目录、闸门同源),顺序取 catalog 顺序:
188
- // 同一仓库的几颗排在一起,读起来比按名字打散更像一份索引。
189
147
  const summaries = selectedSkillsForTask(taskId, [...byName.keys()])
190
148
  .map((entry) => byName.get(entry.name))
191
149
  .filter((entry) => entry !== undefined);
@@ -194,11 +152,9 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
194
152
  const provider = {
195
153
  name: PROVIDER_NAME,
196
154
  async list(options) {
197
- // 调用期现读:开关是 settings 偏好,不许在 Agent 创建时定格。
155
+ // Read at call time: toggles are settings, not fixed at Agent creation.
198
156
  const enabled = selectedNames(readSelection(selection));
199
- // 全关是默认常态:短路掉,不碰快照目录(filesystem watcher 在它的 list()
200
- // 里才启动,短路保证默认关闭的会话不起任何 watcher),**也不遮蔽任何内置 skill**:
201
- // 一颗都没启用时用户看到的必须与没有这个 provider 时逐字相同。
157
+ // All-off is the default: skip snapshot dirs (no watcher starts) and shadow nothing, matching no-provider output.
202
158
  if (enabled.size === 0) {
203
159
  runtimeSummaries = [];
204
160
  return [];
@@ -207,9 +163,7 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
207
163
  const candidates = Array.isArray(result) ? result : result.candidates;
208
164
  const selected = candidates.filter((candidate) => enabled.has(candidate.name));
209
165
  runtimeSummaries = selected.map((candidate) => ({ name: candidate.name, description: candidate.description }));
210
- // 任务型内置 skill 的遮蔽候选:只有当这颗**确实有索引可给**时才列出来。
211
- // 没有索引就整颗让给 fs provider(正文照旧),因此"索引缺失"永远不会演变成
212
- // "这颗 skill 不见了"——遮蔽是加法,不是替换。
166
+ // Shadow a task skill only when it has an index; otherwise the fs provider's original wins.
213
167
  const shadows = [...taskBases.values()]
214
168
  .filter((base) => indexFor(base.name) !== undefined)
215
169
  .map((base) => ({
@@ -228,7 +182,6 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
228
182
  get(candidate, options) {
229
183
  const base = taskBases.get(candidate.name);
230
184
  if (base !== undefined) {
231
- // 索引在**读取期**算:开关是 settings 偏好,加载这一刻才是最新的。
232
185
  const index = indexFor(base.name);
233
186
  const content = index === undefined ? base.content : `${base.content}\n\n${index}`;
234
187
  return Promise.resolve({
@@ -250,11 +203,8 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
250
203
  };
251
204
  return provider;
252
205
  });
253
- // 每轮 pre-step 重读开关:集合变了就调 provider control 的 invalidate(),失效
254
- // skills 的目录缓存(revision++),tool-skill 随后的 catalog 快照因此按最新开关
255
- // 重算;未变则 digest 不变、catalog 不重发。必须在放行 next() 之前做完——
256
- // waterfall 里 tool-skill 是 await next() 之后才快照,但监听器注册顺序不归我们
257
- // 管,先做完再放行在两种顺序下都赶得上本轮快照。设置读取失败只降级,不阻断本轮。
206
+ // Invalidate before next() so this turn's snapshot sees the change regardless of listener order.
207
+ // A settings read failure only degrades this hook.
258
208
  const stopPreStep = onPreStepEvent(injectedCtx, (_payload, next) => {
259
209
  try {
260
210
  const fingerprint = selectionFingerprint(readSelection(selection));
@@ -279,20 +229,14 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
279
229
  });
280
230
  }
281
231
  catch (error) {
282
- // selected skills 是可选能力:注入失败只降级告警,不能把整个 Agent 创建挡掉;
283
- // 同时撤掉登记,让后面的事件(下一次 select、别的注册路径)还有重试的余地。
232
+ // Optional capability: degrade with a warning, never block Agent creation; clear so a later event can retry.
284
233
  registered.delete(agent);
285
234
  ctx.logger?.warn?.(`capital-generation: selected skills are unavailable (${String(error)})`);
286
235
  }
287
236
  };
288
237
  const stop = listenCtx.on('agent/created', ({ agent }) => registerFor(agent));
289
- // 第二条路径:**先按默认预设建会话、再切到 Capital**(GUI 的真实顺序)。
290
- // 2026-10-13 复核复现:会话(header `agentPreset: "cordis"`)建好后客户端才发
291
- // `agentPresets.select('capital-generation')`,而 `agent/created` 在 select 之前就已经发完——
292
- // 只挂那一条时本插件的 `apply()` 是在**这次 recompose 的 mount 里**才跑的,监听器出生就错过了
293
- // 自己这个 Agent:设置卡 10 个开关全开、会话目录仍只有 preset 自带的 8 颗(真 boot graph 探针:
294
- // create→select 8 颗 / 建会话时 mount 同一 preset 18 颗)。preset registry 在 mount 完成后才
295
- // `session.append('agent-preset/selected')`,所以本行一定早于这条事件落位。
238
+ // Second path: GUI creates with the default preset, then selects Capital. `agent/created`
239
+ // already fired and apply() runs inside that remount, so `agent-preset/selected` arrives after mount.
296
240
  const onPresetEvent = listenCtx.on;
297
241
  const stopPreset = onPresetEvent?.('agent-preset/selected', (sessionId, agentPreset) => {
298
242
  if (agentPreset !== CAPITAL_PRESET_ID)
@@ -301,8 +245,7 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
301
245
  const agent = agents?.get?.(String(sessionId));
302
246
  if (agent === undefined)
303
247
  return;
304
- // emit 型事件不等待监听器;返回 promise 只为让单测能等到注册落地。registerFor 内部
305
- // 自兜异常(失败只降级告警并撤登记,下一次事件还有重试的余地),这里不会产生未处理拒绝。
248
+ // Emit-style event: the promise only lets tests await registration; errors are handled inside.
306
249
  return registerFor(agent);
307
250
  });
308
251
  ctx.effect?.(() => () => {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Sole reader of the bundled `capital-config` entry. The id must match the host row,
3
+ * settings card and preset row; drift is caught by the four-way assertion in
4
+ * `test/capital-config.test.mjs`. Unreadable returns `{}` and callers fill defaults;
5
+ * only reference names are read here, never resolved secrets.
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import type { RetrieverConfig } from './retriever-config.js';
9
+ import type { SelectedSkillSelection } from './agents/selected-skills-provider.js';
10
+ export declare const CAPITAL_CONFIG_ENTRY_ID = "capital-config";
11
+ /** Fields this repo consumes (the host half owns the rest). */
12
+ export interface CapitalConfigSlice {
13
+ fuyaoCredentialRef?: string;
14
+ ftshareCredentialRef?: string;
15
+ customPersona?: string;
16
+ retriever?: RetrieverConfig;
17
+ selectedSkills?: SelectedSkillSelection;
18
+ }
19
+ export declare function readCapitalConfig(ctx: Context): CapitalConfigSlice;
20
+ /** Flat credential refs needed by the collector domain (same shape as `CollectorConfig`). */
21
+ export declare function readCollectorConfig(ctx: Context): {
22
+ fuyaoCredentialRef?: string;
23
+ ftshareCredentialRef?: string;
24
+ windCredentialRef?: string;
25
+ };
26
+ /** Retriever config slice (baseURL, credential ref, Wind endpoint, local fallback switch). */
27
+ export declare function readRetrieverConfig(ctx: Context): RetrieverConfig;
@@ -0,0 +1,23 @@
1
+ export const CAPITAL_CONFIG_ENTRY_ID = 'capital-config';
2
+ export function readCapitalConfig(ctx) {
3
+ try {
4
+ const settings = ctx.get('settings');
5
+ return settings?.describe?.().find((entry) => entry?.ns === CAPITAL_CONFIG_ENTRY_ID)?.value ?? {};
6
+ }
7
+ catch {
8
+ return {};
9
+ }
10
+ }
11
+ /** Flat credential refs needed by the collector domain (same shape as `CollectorConfig`). */
12
+ export function readCollectorConfig(ctx) {
13
+ const config = readCapitalConfig(ctx);
14
+ return {
15
+ fuyaoCredentialRef: config.fuyaoCredentialRef,
16
+ ftshareCredentialRef: config.ftshareCredentialRef,
17
+ windCredentialRef: config.retriever?.windDocs?.credentialRef,
18
+ };
19
+ }
20
+ /** Retriever config slice (baseURL, credential ref, Wind endpoint, local fallback switch). */
21
+ export function readRetrieverConfig(ctx) {
22
+ return readCapitalConfig(ctx).retriever ?? {};
23
+ }
@@ -1,5 +1,5 @@
1
1
  import type { SessionLike } from '../data-collector/store.js';
2
- /** Dataset/profile 的一跳 scope 规则;token 让 nested child 复用这个 owner scope。 */
2
+ /** One-hop scope: a token lets a nested child reuse its owner's scope. */
3
3
  export declare function chartSessionScopeId(session: SessionLike): string;
4
4
  export interface ChartArtifactRef {
5
5
  chart_ref: string;
@@ -23,13 +23,8 @@ export interface ChartArtifactRegistryOptions {
23
23
  newRef?: () => string;
24
24
  }
25
25
  /**
26
- * In-process registry for chart_ref capabilities.
27
- *
28
- * The chart files live in the workspace, while this registry keeps the
29
- * session/task binding that makes a chart safe to present to its own session
30
- * (and to derive the `deliverables/presented` owner scope).
31
- * Process restart invalidates chart_ref capabilities; chart.html remains the
32
- * explicit offline fallback.
26
+ * In-process chart_ref capabilities: session/task/workspace binding and the deliverable owner
27
+ * scope. Process restart invalidates them; chart.html remains the offline fallback.
33
28
  */
34
29
  export declare class ChartArtifactRegistry {
35
30
  private readonly records;
@@ -8,19 +8,14 @@ function defaultChartRef() {
8
8
  : `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
9
9
  return `chart_${suffix}`;
10
10
  }
11
- /** Dataset/profile 的一跳 scope 规则;token 让 nested child 复用这个 owner scope。 */
11
+ /** One-hop scope: a token lets a nested child reuse its owner's scope. */
12
12
  export function chartSessionScopeId(session) {
13
13
  const parent = session.header?.parentSession;
14
14
  return typeof parent === 'string' && parent.length > 0 ? parent : session.id;
15
15
  }
16
16
  /**
17
- * In-process registry for chart_ref capabilities.
18
- *
19
- * The chart files live in the workspace, while this registry keeps the
20
- * session/task binding that makes a chart safe to present to its own session
21
- * (and to derive the `deliverables/presented` owner scope).
22
- * Process restart invalidates chart_ref capabilities; chart.html remains the
23
- * explicit offline fallback.
17
+ * In-process chart_ref capabilities: session/task/workspace binding and the deliverable owner
18
+ * scope. Process restart invalidates them; chart.html remains the offline fallback.
24
19
  */
25
20
  export class ChartArtifactRegistry {
26
21
  records = new Map();
@@ -1,8 +1,5 @@
1
1
  /**
2
- * Largest-Triangle-Three-Buckets 下采样:返回**保留下来的行下标**。
3
- *
4
- * 为什么返回下标而不是点:一张图上可能同时有主序列、K 线、成交量副图与标记点,
5
- * 它们必须**共用同一组时间轴**。若各自下采样,同一时刻会在不同子图上错位。
6
- * 于是只对"主序列"跑一次 LTTB 求下标,再把这组下标套用到所有子序列上。
2
+ * LTTB downsampling returning kept row indices. All subplots share one time axis, so indices
3
+ * are computed once from the primary series and applied everywhere.
7
4
  */
8
5
  export declare function largestTriangleThreeBuckets(values: readonly number[], threshold: number): number[];
@@ -1,9 +1,6 @@
1
1
  /**
2
- * Largest-Triangle-Three-Buckets 下采样:返回**保留下来的行下标**。
3
- *
4
- * 为什么返回下标而不是点:一张图上可能同时有主序列、K 线、成交量副图与标记点,
5
- * 它们必须**共用同一组时间轴**。若各自下采样,同一时刻会在不同子图上错位。
6
- * 于是只对"主序列"跑一次 LTTB 求下标,再把这组下标套用到所有子序列上。
2
+ * LTTB downsampling returning kept row indices. All subplots share one time axis, so indices
3
+ * are computed once from the primary series and applied everywhere.
7
4
  */
8
5
  export function largestTriangleThreeBuckets(values, threshold) {
9
6
  const count = values.length;
@@ -14,7 +11,6 @@ export function largestTriangleThreeBuckets(values, threshold) {
14
11
  let anchor = 0;
15
12
  sampled.push(anchor);
16
13
  for (let bucket = 0; bucket < threshold - 2; bucket += 1) {
17
- // 下一个桶的均值,作为三角形的一个顶点。
18
14
  const averageStart = Math.floor((bucket + 1) * every) + 1;
19
15
  const averageEnd = Math.min(Math.floor((bucket + 2) * every) + 1, count);
20
16
  const averageLength = Math.max(averageEnd - averageStart, 1);
@@ -26,7 +22,6 @@ export function largestTriangleThreeBuckets(values, threshold) {
26
22
  }
27
23
  averageX /= averageLength;
28
24
  averageY /= averageLength;
29
- // 当前桶里与 anchor、均值构成最大三角形面积的点。
30
25
  const rangeStart = Math.floor(bucket * every) + 1;
31
26
  const rangeEnd = Math.min(Math.floor((bucket + 1) * every) + 1, count);
32
27
  const anchorX = anchor;
@@ -44,7 +39,7 @@ export function largestTriangleThreeBuckets(values, threshold) {
44
39
  anchor = maxAreaIndex;
45
40
  }
46
41
  sampled.push(count - 1);
47
- // 去重并保持升序:LTTB 的桶边界在极端阈值下可能产生重复下标。
42
+ // Bucket edges can yield duplicate indices at extreme thresholds; keep ascending order.
48
43
  const unique = [];
49
44
  for (const index of sampled)
50
45
  if (unique.length === 0 || index > unique[unique.length - 1])
@@ -1,16 +1,12 @@
1
1
  /**
2
- * 图表域的错误类型。
3
- *
4
- * 错误码是**模型可见契约**的一部分:模型据此修正一次即可重试(见
5
- * preset/capital-generation/skills/capital-chart-protocol/SKILL.md)。
6
- * 因此 detail 必须可操作(点名缺哪个字段、可用字段有哪些),而不是笼统的 invalid。
2
+ * Chart errors are model-visible contract: detail must name the missing field and list
3
+ * candidates, not merely say "invalid".
7
4
  */
8
5
  export type ChartErrorCode = 'chart_source_invalid' | 'chart_source_expired' | 'chart_source_scope_mismatch' | 'chart_source_not_found' | 'chart_ref_invalid' | 'chart_ref_scope_mismatch' | 'chart_ref_task_mismatch' | 'chart_spec_invalid' | 'chart_field_not_found' | 'chart_no_rows' | 'chart_too_large' | 'chart_write_failed' | 'chart_runtime_unavailable';
9
6
  export declare class ChartError extends Error {
10
7
  readonly code: ChartErrorCode;
11
- /** 可选的机器可读补充(如 available 字段名列表),会随错误一起序列化回模型。 */
8
+ /** Machine-readable extras (e.g. available fields), serialized back to the model. */
12
9
  readonly details: Record<string, unknown>;
13
10
  constructor(code: ChartErrorCode, detail: string, details?: Record<string, unknown>);
14
- /** 工具边界统一把错误转成结构化信封,让模型能一次修正。 */
15
11
  toEnvelope(): Record<string, unknown>;
16
12
  }
@@ -1,6 +1,6 @@
1
1
  export class ChartError extends Error {
2
2
  code;
3
- /** 可选的机器可读补充(如 available 字段名列表),会随错误一起序列化回模型。 */
3
+ /** Machine-readable extras (e.g. available fields), serialized back to the model. */
4
4
  details;
5
5
  constructor(code, detail, details = {}) {
6
6
  super(`${code}: ${detail}`);
@@ -8,7 +8,6 @@ export class ChartError extends Error {
8
8
  this.code = code;
9
9
  this.details = details;
10
10
  }
11
- /** 工具边界统一把错误转成结构化信封,让模型能一次修正。 */
12
11
  toEnvelope() {
13
12
  return { error: this.code, detail: this.message.slice(this.message.indexOf(':') + 2), ...this.details };
14
13
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Adapt a cordis ctx into ChartEventContext.
3
+ * Never spread ctx (`{ ...ctx }`): get/on/effect live on the prototype, so spreading drops them
4
+ * and delivery silently disappears. Forward each method with the original ctx as receiver.
5
+ * One assembly path serves both the monolith plugin and the tools/chart entry.
6
+ */
7
+ import type { ChartEventContext } from './events.js';
8
+ export declare function chartEventContextOf(ctx: unknown): ChartEventContext;
@@ -0,0 +1,39 @@
1
+ export function chartEventContextOf(ctx) {
2
+ const raw = ctx;
3
+ const context = {
4
+ get: (name) => raw.get(name),
5
+ // Failed registration returns undefined so events.ts can fall back to turn/start.
6
+ on: (event, listener) => {
7
+ if (typeof raw.on !== 'function')
8
+ return undefined;
9
+ return raw.on.call(ctx, event, (...args) => { listener(...args); });
10
+ },
11
+ onTurnStopping: (listener) => {
12
+ if (typeof raw.on !== 'function')
13
+ return undefined;
14
+ try {
15
+ return raw.on.call(ctx, 'agent/turn-stopping', (...args) => { listener(...args); });
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ },
21
+ };
22
+ // root/effect are cordis getters: forward only when present.
23
+ try {
24
+ if (raw.root !== undefined) {
25
+ Object.defineProperty(context, 'root', { get: () => raw.root, enumerable: true });
26
+ }
27
+ }
28
+ catch { /* root unavailable: events.ts falls back to self */ }
29
+ try {
30
+ if (typeof raw.effect === 'function') {
31
+ Object.defineProperty(context, 'effect', {
32
+ value: (callback, label) => raw.effect.call(ctx, callback, label),
33
+ enumerable: true,
34
+ });
35
+ }
36
+ }
37
+ catch { /* no disposer support */ }
38
+ return context;
39
+ }
@@ -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 {};