@v587d/capital-generation 2.7.8 → 2.7.10
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 +93 -0
- package/README.md +6 -5
- package/THIRD_PARTY_NOTICES.md +12 -2
- package/capital-config/client.js +8 -3
- package/capital-config/client.src.cjs +47 -11
- package/capital-config/package.json +1 -1
- package/capital-watchlist/client.js +3 -3
- package/capital-watchlist/client.src.cjs +4 -4
- package/capital-watchlist/package.json +1 -1
- package/chart-ui/package.json +1 -1
- package/lib/agents/selected-skills-provider.d.ts +10 -0
- package/lib/agents/selected-skills-provider.js +179 -14
- package/lib/chart/source-token.js +18 -3
- package/lib/data-collector/dataset-tools.js +4 -2
- package/lib/data-collector/describe.d.ts +4 -0
- package/lib/data-collector/describe.js +11 -1
- package/lib/index.d.ts +17 -0
- package/lib/index.js +50 -18
- package/lib/web-retriever/local-fetch.d.ts +1 -1
- package/lib/web-retriever/local-fetch.js +1 -1
- package/lib/web-retriever/wind-client.d.ts +1 -1
- package/lib/web-retriever/wind-client.js +1 -1
- package/package.json +2 -1
- package/preset/capital-generation/agent.patch.yml +1 -0
- package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +4 -0
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +5 -0
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +7 -7
- package/preset/capital-generation/skills/capital-web-protocol/SKILL.md +10 -3
- package/selected-skills/catalog.js +164 -54
- package/selected-skills/quant-agent-skills/LICENSE +21 -0
- package/selected-skills/quant-agent-skills/UPSTREAM.md +111 -0
- package/selected-skills/quant-agent-skills/skills/cross-market-listing-choice/SKILL.md +105 -0
- package/selected-skills/quant-agent-skills/skills/cross-market-listing-choice/references/adr-hk-overlay.md +45 -0
- package/selected-skills/quant-agent-skills/skills/cross-market-listing-choice/references/ah-overlay.md +44 -0
|
@@ -1,16 +1,100 @@
|
|
|
1
1
|
import { FileSystemSkillProvider } from '@deepseek-ai/dsh-skill-filesystem';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
2
4
|
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { agentSessionId, isRootAgent } from './root-tool-policy.js';
|
|
4
6
|
// The catalog is a shipped data file outside the TypeScript root; runtime keeps this
|
|
5
7
|
// import so the published lib resolves the same metadata beside the pinned snapshots.
|
|
6
8
|
// @ts-expect-error catalog.js is intentionally maintained as package data.
|
|
7
|
-
import { SELECTED_SKILL_CATALOG as CATALOG, SELECTED_SKILL_SKILL_DIRS } from '../../selected-skills/catalog.js';
|
|
9
|
+
import { SELECTED_SKILL_CATALOG as CATALOG, SELECTED_SKILL_SKILL_DIRS, SELECTED_SKILL_TASK_IDS, selectedSkillsForTask } from '../../selected-skills/catalog.js';
|
|
8
10
|
export const SELECTED_SKILL_CATALOG = CATALOG;
|
|
9
11
|
const PROVIDER_NAME = 'capital-selected-skills';
|
|
12
|
+
/** 本 preset 的身份:两条注册路径(`agent/created` 与 `agent-preset/selected`)共用一份。 */
|
|
13
|
+
const CAPITAL_PRESET_ID = 'capital-generation';
|
|
10
14
|
// skills 根按 catalog 的「仓库 → 上游 SKILL.md 相对路径」现算:第三颗快照的上游把 skill 放在
|
|
11
15
|
// plugins/us-stock-analysis/skills,不在 <repo>/skills 底下。写死目录列表的表现不是报错,
|
|
12
16
|
// 而是"用户在卡片上勾了那一格、技能永远不出现"(AGENTS.md §9.7)。
|
|
13
17
|
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
|
+
*/
|
|
26
|
+
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
|
+
*/
|
|
37
|
+
const TASK_SKILL_SHADOW_RANK = 150;
|
|
38
|
+
const INDEX_HEADING = '## 附:本轮可用的外部精选 Skill(索引)';
|
|
39
|
+
/**
|
|
40
|
+
* 索引块的框。它把**外部方法论**塞进了**宿主契约 skill** 的正文里,框架被拉平了,所以这四句必须
|
|
41
|
+
* 写在块内而不是别的文档里:冲突判据、不可动摇的三处边界、以及"这里只有摘要"。
|
|
42
|
+
*/
|
|
43
|
+
const INDEX_FRAME = [
|
|
44
|
+
'以下是你(用户)在设置里启用的外部精选 Skill,按能力标签与本次任务关联后列出。它们是**方法论材料**,',
|
|
45
|
+
'需要正文时照常用 `skill` 工具按名加载。它们**不改变**本 skill 的证据门、工具委派路径、输出通道与报告形状;',
|
|
46
|
+
'与本 skill 冲突时**以本 skill 为准**。这里只给名称与摘要,摘要不等于全文,未加载前不要照它行事。',
|
|
47
|
+
];
|
|
48
|
+
/** 最小 frontmatter 读取:只要 `name` / `description` / 正文。完整策略解析仍归 fs provider。 */
|
|
49
|
+
function readFrontmatter(raw) {
|
|
50
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(raw);
|
|
51
|
+
if (match === null)
|
|
52
|
+
return undefined;
|
|
53
|
+
const fields = new Map();
|
|
54
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
55
|
+
const field = /^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
|
|
56
|
+
if (field !== null)
|
|
57
|
+
fields.set(field[1], field[2].trim());
|
|
58
|
+
}
|
|
59
|
+
return { fields, body: raw.slice(match[0].length) };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 载入三颗任务型 skill 的正文。**读不到就不遮蔽**:`list()` 不会列出这个候选,fs provider 照常
|
|
63
|
+
* 供原文,用户看到的是"没有索引",而不是"这颗 skill 不见了"(AGENTS.md §9.7 ⑤ 失败要留余地)。
|
|
64
|
+
*/
|
|
65
|
+
function loadTaskSkillBases(root = TASK_SKILL_ROOT) {
|
|
66
|
+
const bases = new Map();
|
|
67
|
+
for (const name of SELECTED_SKILL_TASK_IDS) {
|
|
68
|
+
try {
|
|
69
|
+
const raw = readFileSync(join(root, name, 'SKILL.md'), 'utf8');
|
|
70
|
+
const parsed = readFrontmatter(raw);
|
|
71
|
+
const description = parsed?.fields.get('description');
|
|
72
|
+
if (parsed === undefined || description === undefined || description === '')
|
|
73
|
+
continue;
|
|
74
|
+
bases.set(name, { name, description, content: parsed.body.trim(), directory: join(root, name) });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// 读不到是 fail-open,不是错误路径:默认态(一颗都没启用)本来就不遮蔽任何东西。
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return bases;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 索引块正文。**唯一实现**:条目集合来自 catalog 的 `selectedSkillsForTask`(与目录、闸门同源),
|
|
84
|
+
* 描述用**运行时那一份**(上游 frontmatter,也就是会话目录里模型看到的那句话),不是设置卡片里
|
|
85
|
+
* 策展过的中文摘要——同一个 skill 在同一段上下文里出现两种描述,只会让模型两处对照。
|
|
86
|
+
*/
|
|
87
|
+
export function renderTaskSkillIndex(taskId, summaries) {
|
|
88
|
+
if (summaries.length === 0)
|
|
89
|
+
return undefined;
|
|
90
|
+
return [
|
|
91
|
+
INDEX_HEADING,
|
|
92
|
+
'',
|
|
93
|
+
...INDEX_FRAME,
|
|
94
|
+
'',
|
|
95
|
+
...summaries.map((entry) => `- \`${entry.name}\`: ${entry.description}`),
|
|
96
|
+
].join('\n');
|
|
97
|
+
}
|
|
14
98
|
function createFilesystemSkillProvider(ctx, control, config) {
|
|
15
99
|
return new FileSystemSkillProvider(ctx, control, config);
|
|
16
100
|
}
|
|
@@ -37,7 +121,7 @@ function strictCapitalPreset(ctx, agent) {
|
|
|
37
121
|
try {
|
|
38
122
|
const presets = ctx.get?.('agentPresets');
|
|
39
123
|
return typeof presets?.composedPreset === 'function'
|
|
40
|
-
&& presets.composedPreset(agent.ctx) ===
|
|
124
|
+
&& presets.composedPreset(agent.ctx) === CAPITAL_PRESET_ID;
|
|
41
125
|
}
|
|
42
126
|
catch {
|
|
43
127
|
return false;
|
|
@@ -54,15 +138,22 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
|
|
|
54
138
|
const listenCtx = ctx.root ?? ctx;
|
|
55
139
|
if (typeof listenCtx.on !== 'function')
|
|
56
140
|
return;
|
|
57
|
-
|
|
58
|
-
|
|
141
|
+
// 一个 root Agent 只注册一次:下面两条事件路径会在同一个 Agent 上重叠(建会话时就带 Capital
|
|
142
|
+
// 预设 → `agent/created`;先按默认预设建、再 select 到 Capital → `agent-preset/selected`)。
|
|
143
|
+
const registered = new WeakSet();
|
|
144
|
+
const registerFor = async (agent) => {
|
|
145
|
+
if (!agent || registered.has(agent))
|
|
59
146
|
return;
|
|
60
|
-
|
|
61
|
-
if (!agentCtx || typeof agentCtx.inject !== 'function') {
|
|
62
|
-
ctx.logger?.warn?.('capital-generation: selected skills are unavailable (root Agent cannot inject skills)');
|
|
147
|
+
if (!isRootAgent(agent) || !strictCapitalPreset(ctx, agent))
|
|
63
148
|
return;
|
|
64
|
-
|
|
149
|
+
registered.add(agent);
|
|
65
150
|
try {
|
|
151
|
+
const agentCtx = agent.ctx;
|
|
152
|
+
if (!agentCtx || typeof agentCtx.inject !== 'function') {
|
|
153
|
+
registered.delete(agent);
|
|
154
|
+
ctx.logger?.warn?.('capital-generation: selected skills are unavailable (root Agent cannot inject skills)');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
66
157
|
// agent/created 是串行事件、DSH 等监听器 settle 才继续创建:await 注入 Fiber,
|
|
67
158
|
// provider 与 pre-step 钩子都在首轮 prompt 前落位(首轮 catalog 不与注册竞态)。
|
|
68
159
|
await agentCtx.inject(['skills'], (injectedCtx) => {
|
|
@@ -86,21 +177,71 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
|
|
|
86
177
|
customSkillDirs: SKILL_ROOTS,
|
|
87
178
|
watch: true,
|
|
88
179
|
});
|
|
180
|
+
// 遮蔽用的底稿只在注册时读一次(三份小文件,读不到就不遮蔽)。
|
|
181
|
+
const taskBases = loadTaskSkillBases();
|
|
182
|
+
// 上一次 `list()` 看到的"已启用精选"运行时摘要。`get()` 拼索引时读它,避免为了几行摘要
|
|
183
|
+
// 再枚举一遍快照目录;为空就退回"没有索引",也就是把整颗让给 fs provider 的原文。
|
|
184
|
+
let runtimeSummaries = [];
|
|
185
|
+
const indexFor = (taskId) => {
|
|
186
|
+
const byName = new Map(runtimeSummaries.map((entry) => [entry.name, entry]));
|
|
187
|
+
// 条目集合来自 catalog 的唯一实现(与目录、闸门同源),顺序取 catalog 顺序:
|
|
188
|
+
// 同一仓库的几颗排在一起,读起来比按名字打散更像一份索引。
|
|
189
|
+
const summaries = selectedSkillsForTask(taskId, [...byName.keys()])
|
|
190
|
+
.map((entry) => byName.get(entry.name))
|
|
191
|
+
.filter((entry) => entry !== undefined);
|
|
192
|
+
return renderTaskSkillIndex(taskId, summaries);
|
|
193
|
+
};
|
|
89
194
|
const provider = {
|
|
90
195
|
name: PROVIDER_NAME,
|
|
91
196
|
async list(options) {
|
|
92
197
|
// 调用期现读:开关是 settings 偏好,不许在 Agent 创建时定格。
|
|
93
198
|
const enabled = selectedNames(readSelection(selection));
|
|
94
199
|
// 全关是默认常态:短路掉,不碰快照目录(filesystem watcher 在它的 list()
|
|
95
|
-
// 里才启动,短路保证默认关闭的会话不起任何 watcher
|
|
96
|
-
|
|
200
|
+
// 里才启动,短路保证默认关闭的会话不起任何 watcher),**也不遮蔽任何内置 skill**:
|
|
201
|
+
// 一颗都没启用时用户看到的必须与没有这个 provider 时逐字相同。
|
|
202
|
+
if (enabled.size === 0) {
|
|
203
|
+
runtimeSummaries = [];
|
|
97
204
|
return [];
|
|
205
|
+
}
|
|
98
206
|
const result = await filesystemProvider.list(options);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
207
|
+
const candidates = Array.isArray(result) ? result : result.candidates;
|
|
208
|
+
const selected = candidates.filter((candidate) => enabled.has(candidate.name));
|
|
209
|
+
runtimeSummaries = selected.map((candidate) => ({ name: candidate.name, description: candidate.description }));
|
|
210
|
+
// 任务型内置 skill 的遮蔽候选:只有当这颗**确实有索引可给**时才列出来。
|
|
211
|
+
// 没有索引就整颗让给 fs provider(正文照旧),因此"索引缺失"永远不会演变成
|
|
212
|
+
// "这颗 skill 不见了"——遮蔽是加法,不是替换。
|
|
213
|
+
const shadows = [...taskBases.values()]
|
|
214
|
+
.filter((base) => indexFor(base.name) !== undefined)
|
|
215
|
+
.map((base) => ({
|
|
216
|
+
name: base.name,
|
|
217
|
+
description: base.description,
|
|
218
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
219
|
+
provider: PROVIDER_NAME,
|
|
220
|
+
source: 'custom',
|
|
221
|
+
rank: TASK_SKILL_SHADOW_RANK,
|
|
222
|
+
locator: { kind: 'task-skill', name: base.name },
|
|
223
|
+
resourceBase: { kind: 'directory', path: base.directory },
|
|
224
|
+
}));
|
|
225
|
+
const merged = [...selected, ...shadows];
|
|
226
|
+
return Array.isArray(result) ? merged : { ...result, candidates: merged };
|
|
102
227
|
},
|
|
103
228
|
get(candidate, options) {
|
|
229
|
+
const base = taskBases.get(candidate.name);
|
|
230
|
+
if (base !== undefined) {
|
|
231
|
+
// 索引在**读取期**算:开关是 settings 偏好,加载这一刻才是最新的。
|
|
232
|
+
const index = indexFor(base.name);
|
|
233
|
+
const content = index === undefined ? base.content : `${base.content}\n\n${index}`;
|
|
234
|
+
return Promise.resolve({
|
|
235
|
+
name: base.name,
|
|
236
|
+
description: base.description,
|
|
237
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
238
|
+
source: 'custom',
|
|
239
|
+
provider: PROVIDER_NAME,
|
|
240
|
+
resourceBase: { kind: 'directory', path: base.directory },
|
|
241
|
+
path: join(base.directory, 'SKILL.md'),
|
|
242
|
+
content,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
104
245
|
if (!selectedNames(readSelection(selection)).has(candidate.name)) {
|
|
105
246
|
throw new Error(`selected skill is not enabled: ${candidate.name}`);
|
|
106
247
|
}
|
|
@@ -138,12 +279,36 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
|
|
|
138
279
|
});
|
|
139
280
|
}
|
|
140
281
|
catch (error) {
|
|
141
|
-
// selected skills 是可选能力:注入失败只降级告警,不能把整个 Agent
|
|
282
|
+
// selected skills 是可选能力:注入失败只降级告警,不能把整个 Agent 创建挡掉;
|
|
283
|
+
// 同时撤掉登记,让后面的事件(下一次 select、别的注册路径)还有重试的余地。
|
|
284
|
+
registered.delete(agent);
|
|
142
285
|
ctx.logger?.warn?.(`capital-generation: selected skills are unavailable (${String(error)})`);
|
|
143
286
|
}
|
|
287
|
+
};
|
|
288
|
+
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')`,所以本行一定早于这条事件落位。
|
|
296
|
+
const onPresetEvent = listenCtx.on;
|
|
297
|
+
const stopPreset = onPresetEvent?.('agent-preset/selected', (sessionId, agentPreset) => {
|
|
298
|
+
if (agentPreset !== CAPITAL_PRESET_ID)
|
|
299
|
+
return;
|
|
300
|
+
const agents = ctx.get?.('agents');
|
|
301
|
+
const agent = agents?.get?.(String(sessionId));
|
|
302
|
+
if (agent === undefined)
|
|
303
|
+
return;
|
|
304
|
+
// emit 型事件不等待监听器;返回 promise 只为让单测能等到注册落地。registerFor 内部
|
|
305
|
+
// 自兜异常(失败只降级告警并撤登记,下一次事件还有重试的余地),这里不会产生未处理拒绝。
|
|
306
|
+
return registerFor(agent);
|
|
144
307
|
});
|
|
145
308
|
ctx.effect?.(() => () => {
|
|
146
309
|
if (typeof stop === 'function')
|
|
147
310
|
stop();
|
|
311
|
+
if (typeof stopPreset === 'function')
|
|
312
|
+
stopPreset();
|
|
148
313
|
}, 'capital-generation: selected skill root listener');
|
|
149
314
|
}
|
|
@@ -84,12 +84,26 @@ export class ChartSourceTokenStore {
|
|
|
84
84
|
async issue(input) {
|
|
85
85
|
const datasetId = readDatasetId(input.dataset_id);
|
|
86
86
|
const taskId = readTaskId(input.task_id, true);
|
|
87
|
+
const now = this.now();
|
|
88
|
+
this.purgeExpired(now);
|
|
89
|
+
// 幂等:同一 session + task_id + dataset_id 在 TTL 内重复签发返回**同一个** token。
|
|
90
|
+
// 2026-10-10 真机(task_fh_300803_20261010):data_junior 改了视图方案后为同一 dataset
|
|
91
|
+
// 重签,跟着就是 7.9K 字符的思考回合——skill 里的"不要重签"警告拦不住,所以把幂等沉到
|
|
92
|
+
// 工具层。token 是读能力而非一次性凭证(resolve 不消费),复用没有语义损失。
|
|
93
|
+
for (const record of this.tokens.values()) {
|
|
94
|
+
if (record.dataset_id === datasetId && record.task_id === taskId && record.sourceSession.id === input.session.id) {
|
|
95
|
+
return {
|
|
96
|
+
chart_source_ref: record.chart_source_ref,
|
|
97
|
+
dataset_id: record.dataset_id,
|
|
98
|
+
task_id: record.task_id,
|
|
99
|
+
expires_at: record.expires_at,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
87
103
|
const inspection = await input.store.inspectDataset(datasetId, input.session, input.signal);
|
|
88
104
|
if (!inspection.query_access.readable || !['array', 'envelope_item'].includes(inspection.query_access.shape)) {
|
|
89
105
|
throw new ChartError('chart_source_invalid', `Dataset ${datasetId} cannot be used as a chart source (${inspection.query_access.reason ?? inspection.query_access.shape})`);
|
|
90
106
|
}
|
|
91
|
-
const now = this.now();
|
|
92
|
-
this.purgeExpired(now);
|
|
93
107
|
const token = this.newToken();
|
|
94
108
|
if (!CHART_SOURCE_TOKEN_PATTERN.test(token)) {
|
|
95
109
|
throw new ChartError('chart_source_invalid', 'generated chart_source_ref is invalid');
|
|
@@ -160,7 +174,8 @@ export function registerChartSourceTool(ctx, options) {
|
|
|
160
174
|
const definition = {
|
|
161
175
|
name: 'prepare_chart_source',
|
|
162
176
|
description: '为当前 delegated data agent 的指定 Dataset 签发短期 chart_source_ref,供 visualization_specialist 通过受控 render_chart 读取。' +
|
|
163
|
-
'只返回 opaque token 和小型元数据,不返回原始数据、文件路径或数据行;必须提供 dataset_id,可选 task_id。'
|
|
177
|
+
'只返回 opaque token 和小型元数据,不返回原始数据、文件路径或数据行;必须提供 dataset_id,可选 task_id。' +
|
|
178
|
+
'同一 task_id + dataset_id 重复调用幂等返回同一个 chart_source_ref(TTL 内),改了视图方案直接复用即可。',
|
|
164
179
|
parameters: jsonObject({
|
|
165
180
|
dataset_id: { type: 'string', description: '已授权的 DatasetRef dataset_id' },
|
|
166
181
|
task_id: { type: 'string', description: '当前可视化任务标识;用于绑定 token,必填' },
|
|
@@ -337,9 +337,11 @@ const describeResultSchema = jsonObject({
|
|
|
337
337
|
validation: profileValidationSchema,
|
|
338
338
|
warnings: { type: 'array', items: { type: 'string' } },
|
|
339
339
|
queries: { type: 'array', items: describeQueryOutcomeSchema },
|
|
340
|
-
/** 仅 too_large
|
|
340
|
+
/** 仅 too_large:被拦下的形状、字节数、体积归因与重试指引。 */
|
|
341
341
|
shape: { type: 'string', enum: ['array', 'envelope_item', 'document', 'none'] },
|
|
342
342
|
bytes: { type: 'integer' },
|
|
343
|
+
facts_bytes: { type: 'integer' },
|
|
344
|
+
queries_bytes: { type: 'integer' },
|
|
343
345
|
hint: { type: 'string' },
|
|
344
346
|
}, ['status', 'dataset_id']);
|
|
345
347
|
export function registerDatasetTools(ctx, store) {
|
|
@@ -383,7 +385,7 @@ export function registerDatasetTools(ctx, store) {
|
|
|
383
385
|
withIsoTimeColumns(result, prepared.axis);
|
|
384
386
|
return result;
|
|
385
387
|
}),
|
|
386
|
-
tool('describe_dataset', `读取一份 Dataset 的**默认入口**:一次调用完成 inspect(元数据与可读性/形状)、profile(宿主算基础质量与四类事实,写入 profile_ref)与受控 query(可选);原始 rows 不进入模型上下文。一次只处理一个 dataset_id——本轮有多份数据时,请在**同一条消息里一次发出多个调用**(本工具可并发执行,结果会一起返回),不要一份一份等。行集合返回:① 元数据 artifact_ref/capability/source_label/captured_at/retention_until 与 query_access.shape(array / envelope_item = 行集合;document = 顶层文档对象,只能 profile,queries 由宿主跳过);② profile 四类事实——statistics(count/sum/min/max/mean/分位数)、categories(distinct + 最多 5 个高频取值)、time_facts(覆盖范围与首行/末行取值;回答"最新值、区间涨跌"必须用它,不要用 min/max 代替)、structure(嵌套路径摘要);③ queries——每条的 QueryResult,或该条自己的 error(单条失败不影响其余)。columns_of_interest 只对这些列返回 statistics/categories/schema(并同样收窄 time_facts 的首末值投影,时间列永远保留),是收窄结果体积的唯一手段。结果超过 ${SAFE_RESULT_CHARS} 码点时宿主不返回超长载荷,而返回 {status:'too_large', profile_ref, columns, hint}
|
|
388
|
+
tool('describe_dataset', `读取一份 Dataset 的**默认入口**:一次调用完成 inspect(元数据与可读性/形状)、profile(宿主算基础质量与四类事实,写入 profile_ref)与受控 query(可选);原始 rows 不进入模型上下文。一次只处理一个 dataset_id——本轮有多份数据时,请在**同一条消息里一次发出多个调用**(本工具可并发执行,结果会一起返回),不要一份一份等。行集合返回:① 元数据 artifact_ref/capability/source_label/captured_at/retention_until 与 query_access.shape(array / envelope_item = 行集合;document = 顶层文档对象,只能 profile,queries 由宿主跳过);② profile 四类事实——statistics(count/sum/min/max/mean/分位数)、categories(distinct + 最多 5 个高频取值)、time_facts(覆盖范围与首行/末行取值;回答"最新值、区间涨跌"必须用它,不要用 min/max 代替)、structure(嵌套路径摘要);③ queries——每条的 QueryResult,或该条自己的 error(单条失败不影响其余)。columns_of_interest 只对这些列返回 statistics/categories/schema(并同样收窄 time_facts 的首末值投影,时间列永远保留),是收窄结果体积的唯一手段。结果超过 ${SAFE_RESULT_CHARS} 码点时宿主不返回超长载荷,而返回 {status:'too_large', profile_ref, columns, bytes, facts_bytes, queries_bytes, hint}:facts_bytes/queries_bytes 是体积归因(哪边大砍哪边),按 hint 重发一次即可——queries 大就先去掉 queries(聚合改单独走 query_dataset),facts 大就用 columns_of_interest 收窄列(或减少 queries,最多 ${MAX_DESCRIBE_QUERIES} 条)。单点复核仍可用 inspect_dataset / profile_dataset / query_dataset;不接受任何文件路径、脚本内容或自定义表达式。`, describeParametersSchema, describeResultSchema, async (args, exec) => describeDataset({
|
|
387
389
|
store,
|
|
388
390
|
session: delegatedSession(exec, 'describe_dataset'),
|
|
389
391
|
request: normalizeDescribeArgs(args),
|
|
@@ -77,6 +77,10 @@ export interface DescribeTooLarge {
|
|
|
77
77
|
shape: DatasetInspection['query_access']['shape'];
|
|
78
78
|
columns: string[];
|
|
79
79
|
bytes: number;
|
|
80
|
+
/** 体积归因:facts(元数据 + profile 事实块)部分的码点数。 */
|
|
81
|
+
facts_bytes: number;
|
|
82
|
+
/** 体积归因:queries(含各条 result)部分的码点数。 */
|
|
83
|
+
queries_bytes: number;
|
|
80
84
|
hint: string;
|
|
81
85
|
}
|
|
82
86
|
export type DescribeOutput = DescribeResult | DescribeTooLarge;
|
|
@@ -240,6 +240,12 @@ export async function describeDataset(input) {
|
|
|
240
240
|
const encoded = JSON.stringify(value);
|
|
241
241
|
const bytes = codePointLength(encoded);
|
|
242
242
|
if (bytes > SAFE_RESULT_CHARS) {
|
|
243
|
+
// 体积归因:把 queries 部分单量出来,hint 指名先砍哪边。2026-10-10 真机(task_fh_300803_20261010):
|
|
244
|
+
// 两份宽表各带 5-6 聚合的 queries 打穿闸门,而回执只说"用 columns_of_interest 收窄"——列本来
|
|
245
|
+
// 已经很窄,超预算的其实是 queries,模型只好 describe(去 queries)+ query_dataset 各来一轮,
|
|
246
|
+
// 4 次调用里 2 次本可省。归因字段让 hint 直指大头,不引入第二道闸门(⛔ 体积闸门仍只有一道)。
|
|
247
|
+
const queriesBytes = codePointLength(JSON.stringify(value.queries));
|
|
248
|
+
const factsBytes = bytes - queriesBytes;
|
|
243
249
|
return compact({
|
|
244
250
|
status: 'too_large',
|
|
245
251
|
dataset_id: inspection.dataset_id,
|
|
@@ -247,9 +253,13 @@ export async function describeDataset(input) {
|
|
|
247
253
|
shape,
|
|
248
254
|
columns: profile.columns,
|
|
249
255
|
bytes,
|
|
256
|
+
facts_bytes: factsBytes,
|
|
257
|
+
queries_bytes: queriesBytes,
|
|
250
258
|
hint: shape === 'document'
|
|
251
259
|
? '文档型 Dataset 的结果超预算:直接调 profile_dataset 取有界内容(本工具的列过滤对文档无效)'
|
|
252
|
-
:
|
|
260
|
+
: queriesBytes > factsBytes
|
|
261
|
+
? `结果超预算(queries 占 ${queriesBytes} > facts 占 ${factsBytes}):先去掉 queries(只 describe 不带 queries,聚合改单独走 query_dataset),facts 仍超再用 columns_of_interest 收窄列`
|
|
262
|
+
: `结果超预算(facts 占 ${factsBytes} ≥ queries 占 ${queriesBytes}):先用 columns_of_interest 指定本次问题真正需要的列,或减少 queries 条数`,
|
|
253
263
|
});
|
|
254
264
|
}
|
|
255
265
|
return value;
|
package/lib/index.d.ts
CHANGED
|
@@ -261,6 +261,23 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
261
261
|
}>>, "defined">;
|
|
262
262
|
}>>, "defined">;
|
|
263
263
|
}>>, "plain">;
|
|
264
|
+
export type CapitalSettingsSource = 'settings-describe' | 'config-editor' | 'preset';
|
|
265
|
+
/**
|
|
266
|
+
* 读取 `capital-config` 条目的生效配置。三级取数、逐级兜底:
|
|
267
|
+
* 1. `settings.describe()`:活动条目的已应用视图(0.1.7 官方读回面);
|
|
268
|
+
* 2. `configEditor.entries()` 的条目保存层:设置卡写入的就是这层(complete raw config)。
|
|
269
|
+
* describe() 在条目 fiber 未挂好的窗口会**整个跳过条目**,用户已保存的值仍然在这里可读;
|
|
270
|
+
* 3. preset 配置兜底,且必须可辨认(`source === 'preset'`)——"读不到"不许伪装成"用户全关"。
|
|
271
|
+
*
|
|
272
|
+
* ⛔ 服务引用一律**调用期现取**(AGENTS.md §9.7):装配期 `ctx.get()` 押的是"宿主服务已注册"
|
|
273
|
+
* 这个外部时刻——boot 里各 fiber 并行 apply,settings / configEditor 晚于本插件注册时,装配期
|
|
274
|
+
* 捕获到 undefined,整场进程会静默降级成 preset 默认。2026-10-13 复盘(session-bb8635a1:
|
|
275
|
+
* 07:51 保存 10 个开关、07:54 建会话读到全关、08:30 重组后同一条读取路径立刻读对)就是这一族。
|
|
276
|
+
*/
|
|
277
|
+
export declare function readCapitalSettings(ctx: Context, bootConfig: Config): {
|
|
278
|
+
config: Config;
|
|
279
|
+
source: CapitalSettingsSource;
|
|
280
|
+
};
|
|
264
281
|
/**
|
|
265
282
|
* 把可选的用户人设文本转成独立 system-prompt section(官方 systemPrompt
|
|
266
283
|
* registry 的注册对象)。空白输入返回 undefined(不注册);超长抛错;
|
package/lib/index.js
CHANGED
|
@@ -150,6 +150,41 @@ export const Config = z.object({
|
|
|
150
150
|
retriever: RetrieverSchema.default({ baseURL: '', credentialRef: 'ANYSEARCH_API_KEY', windDocs: { endpoint: '', credentialRef: 'WIND_API_KEY', timeoutMs: 60000 }, localFetch: { ...LOCAL_FETCH_DEFAULTS }, paddleOcr: { ...PADDLE_OCR_DEFAULTS } })
|
|
151
151
|
.description('web_retriever 配置(可选;缺省使用 AnySearch 默认地址与凭据名)'),
|
|
152
152
|
});
|
|
153
|
+
/**
|
|
154
|
+
* 读取 `capital-config` 条目的生效配置。三级取数、逐级兜底:
|
|
155
|
+
* 1. `settings.describe()`:活动条目的已应用视图(0.1.7 官方读回面);
|
|
156
|
+
* 2. `configEditor.entries()` 的条目保存层:设置卡写入的就是这层(complete raw config)。
|
|
157
|
+
* describe() 在条目 fiber 未挂好的窗口会**整个跳过条目**,用户已保存的值仍然在这里可读;
|
|
158
|
+
* 3. preset 配置兜底,且必须可辨认(`source === 'preset'`)——"读不到"不许伪装成"用户全关"。
|
|
159
|
+
*
|
|
160
|
+
* ⛔ 服务引用一律**调用期现取**(AGENTS.md §9.7):装配期 `ctx.get()` 押的是"宿主服务已注册"
|
|
161
|
+
* 这个外部时刻——boot 里各 fiber 并行 apply,settings / configEditor 晚于本插件注册时,装配期
|
|
162
|
+
* 捕获到 undefined,整场进程会静默降级成 preset 默认。2026-10-13 复盘(session-bb8635a1:
|
|
163
|
+
* 07:51 保存 10 个开关、07:54 建会话读到全关、08:30 重组后同一条读取路径立刻读对)就是这一族。
|
|
164
|
+
*/
|
|
165
|
+
export function readCapitalSettings(ctx, bootConfig) {
|
|
166
|
+
const settings = ctx.get('settings');
|
|
167
|
+
try {
|
|
168
|
+
const resolved = settings?.describe?.().find((entry) => entry.ns === CAPITAL_CONFIG_ENTRY_ID)?.value;
|
|
169
|
+
// 这里再用本插件的 Config 过一遍:既补默认值,也把 schema 漂移变成可查的错误,
|
|
170
|
+
// 而不是把 capital-config schema 里的未知/缺失字段静默带进运行期配置。
|
|
171
|
+
if (resolved && typeof resolved === 'object')
|
|
172
|
+
return { config: Config(resolved), source: 'settings-describe' };
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// 条目未装配 / schema 漂移:落到保存层再试,不静默吞掉。
|
|
176
|
+
}
|
|
177
|
+
const configEditor = ctx.get('configEditor');
|
|
178
|
+
try {
|
|
179
|
+
const raw = configEditor?.entries?.().find((row) => row?.options?.id === CAPITAL_CONFIG_ENTRY_ID)?.options?.config;
|
|
180
|
+
if (raw && typeof raw === 'object')
|
|
181
|
+
return { config: Config(raw), source: 'config-editor' };
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// 保存层也不可读:只剩 preset 兜底。
|
|
185
|
+
}
|
|
186
|
+
return { config: bootConfig, source: 'preset' };
|
|
187
|
+
}
|
|
153
188
|
/**
|
|
154
189
|
* 把可选的用户人设文本转成独立 system-prompt section(官方 systemPrompt
|
|
155
190
|
* registry 的注册对象)。空白输入返回 undefined(不注册);超长抛错;
|
|
@@ -189,29 +224,26 @@ export function apply(ctx, config) {
|
|
|
189
224
|
// 消失),而是 **profile 条目的表单投影**:可编辑字段由条目自己的 Config schema 里的
|
|
190
225
|
// `.volatile()` 声明,`describe()` 按条目 id 返回解析后的值。所以我们按随包的
|
|
191
226
|
// `capital-config` **条目 id** 找那一行,而不是按自造的命名空间。
|
|
192
|
-
|
|
227
|
+
let lastSettingsSource;
|
|
193
228
|
const readEffectiveConfig = () => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
catch {
|
|
203
|
-
// 条目未注册或解析失败时沿用 preset 配置。
|
|
229
|
+
const { config: resolved, source } = readCapitalSettings(ctx, config);
|
|
230
|
+
if (source !== lastSettingsSource) {
|
|
231
|
+
// 状态翻转才告警(读取是每轮现读的,不能逐次刷屏)。"读不到"被伪装成"用户全关"
|
|
232
|
+
// 是 2026-10-13 精选 Skills 消失 36 分钟无人能定位的直接原因——回落必须留声。
|
|
233
|
+
if (source === 'preset' && (ctx.get('settings') !== undefined || ctx.get('configEditor') !== undefined)) {
|
|
234
|
+
ctx.logger?.warn?.('capital-generation: capital-config 设置暂不可读,回落 preset 配置(精选 Skills 按默认全关;恢复后下一轮自动刷新)');
|
|
235
|
+
}
|
|
236
|
+
lastSettingsSource = source;
|
|
204
237
|
}
|
|
205
|
-
return
|
|
238
|
+
return resolved;
|
|
206
239
|
};
|
|
207
240
|
const effectiveConfig = readEffectiveConfig();
|
|
208
241
|
// selectedSkills 是 live 的 settings 偏好:provider 在每轮 agent/pre-step 与每次
|
|
209
|
-
// list()/get() 调用现读,切换开关不需要新建会话(见 docs/dev/selected-skills.md
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
registerRootSelectedSkills(ctx, selectedSkills);
|
|
242
|
+
// list()/get() 调用现读,切换开关不需要新建会话(见 docs/dev/selected-skills.md)。
|
|
243
|
+
// 一律传 reader 函数、不按"有没有 settings 服务"降级成静态值:那条降级分支正是
|
|
244
|
+
// 2026-10-13 事故的静默死路(装配期捕获不到服务 → 静态全关 → provider 连监听都不挂,
|
|
245
|
+
// 整场进程没救)。
|
|
246
|
+
registerRootSelectedSkills(ctx, () => readEffectiveConfig().selectedSkills ?? {});
|
|
215
247
|
// ── data_collector 域:宿主侧 Dataset 落盘 + 取数执行器 ─────────────────────
|
|
216
248
|
// 落盘通过 DSH 官方 fs/sandbox 服务完成,根目录恒为调用方 session 的
|
|
217
249
|
// workspace cwd(exec.agent.session.header.cwd),服从当前 permission。
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* between validation and connect. Closing that window would require a custom
|
|
7
7
|
* dispatcher; this module keeps the same explicit trade-off as dsh-search-first.
|
|
8
8
|
*/
|
|
9
|
-
export declare const LOCAL_FETCH_CLIENT_VERSION = "2.7.
|
|
9
|
+
export declare const LOCAL_FETCH_CLIENT_VERSION = "2.7.10";
|
|
10
10
|
export declare const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
|
|
11
11
|
export type LocalFetchErrorCode = 'ABORTED' | 'TIMEOUT' | 'INVALID_URL' | 'BLOCKED_URL' | 'DNS' | 'REDIRECT' | 'HTTP' | 'TOO_LARGE' | 'UNSUPPORTED_CONTENT_TYPE';
|
|
12
12
|
export declare class LocalFetchError extends Error {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* dispatcher; this module keeps the same explicit trade-off as dsh-search-first.
|
|
8
8
|
*/
|
|
9
9
|
import { htmlToMarkdown, stripInvisibleText } from './html-markdown.js';
|
|
10
|
-
export const LOCAL_FETCH_CLIENT_VERSION = '2.7.
|
|
10
|
+
export const LOCAL_FETCH_CLIENT_VERSION = '2.7.10';
|
|
11
11
|
export const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
|
|
12
12
|
export class LocalFetchError extends Error {
|
|
13
13
|
code;
|
|
@@ -52,7 +52,7 @@ export interface WindCallResult {
|
|
|
52
52
|
* `test/chart-composition.test.mjs` 守着(会因漂移而失败),这里原本是写死的第二处,
|
|
53
53
|
* 一次发版就得记得改两遍。所以本常量由测试一并核对(见 `test/wind-client.test.mjs`)。
|
|
54
54
|
*/
|
|
55
|
-
export declare const WIND_CLIENT_VERSION = "2.7.
|
|
55
|
+
export declare const WIND_CLIENT_VERSION = "2.7.10";
|
|
56
56
|
export interface WindClient {
|
|
57
57
|
callTool(toolName: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<WindCallResult>;
|
|
58
58
|
}
|
|
@@ -35,7 +35,7 @@ export const WIND_SERVER_ENDPOINTS = {
|
|
|
35
35
|
* `test/chart-composition.test.mjs` 守着(会因漂移而失败),这里原本是写死的第二处,
|
|
36
36
|
* 一次发版就得记得改两遍。所以本常量由测试一并核对(见 `test/wind-client.test.mjs`)。
|
|
37
37
|
*/
|
|
38
|
-
export const WIND_CLIENT_VERSION = '2.7.
|
|
38
|
+
export const WIND_CLIENT_VERSION = '2.7.10';
|
|
39
39
|
function errorText(error) {
|
|
40
40
|
return error instanceof Error ? error.message : String(error);
|
|
41
41
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v587d/capital-generation",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.10",
|
|
4
4
|
"description": "Capital Generation:面向中国散户的 AI 投资助手插件(Capital mode Agent Preset)",
|
|
5
5
|
"icon": "assets/cg-icon.png",
|
|
6
6
|
"author": "v587d",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"selected-skills/trading-skills",
|
|
42
42
|
"selected-skills/claude-for-financial-services-cn",
|
|
43
43
|
"selected-skills/financeskills",
|
|
44
|
+
"selected-skills/quant-agent-skills",
|
|
44
45
|
"cordis.patch.yml",
|
|
45
46
|
"assets/cg-icon.png",
|
|
46
47
|
"README.md",
|
|
@@ -269,6 +269,10 @@ node -e 'const a=98.2,b=120.5; console.log(((b-a)/a*100).toFixed(2))' # 区间
|
|
|
269
269
|
python3 -c "print((120.5/98.2)**(252/60)-1)" # 年化
|
|
270
270
|
```
|
|
271
271
|
|
|
272
|
+
- **只敲最终参与计算的标量,一次 ≤20 个**:不整行/整表复制原始数字进脚本(2026-10-10 真机:
|
|
273
|
+
两次 bash 把整段聚合行逐数敲进去,转写出笔误又花 7.2K 字符发更正);从工具回执到脚本的每个
|
|
274
|
+
数字**逐一核对**再执行,算完抽查 1-2 个对得上来源。要算的数超过 20 个就拆几批。
|
|
275
|
+
|
|
272
276
|
**不许做的**(这是纪律,不是沙箱限制——沙箱只拦写,不拦读与网络):
|
|
273
277
|
|
|
274
278
|
| 不许 | 为什么 | 正确做法 |
|
|
@@ -16,7 +16,7 @@ description: Use when the user requests a complete research report or comprehens
|
|
|
16
16
|
|
|
17
17
|
## 2. 证据计划:完整覆盖,按需取证
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
先列出要回答的核心问题和最小证据集,按对结论的影响排序:先核对最近可得财报、必要的结构化财务与行情、最相关的公司公告,再决定同业、行业或更早历史是否真的会改变判断。缺关键证据时优先核对该缺口,而不是继续扩展边缘材料;重复的时间序列或同一份文件不重复获取。下表是报告的覆盖检查表,不是工具调用配额。委派外部事实核查同样按影响分级:核心事实(审计意见、商誉、质押、现金流背离等)≤10 条逐条核验,外围事实一句话清单,异常线索触发才深挖(口径见 `capital-web-protocol` §5)。
|
|
20
20
|
|
|
21
21
|
| 维度 | 最少回答什么 | 主要证据与不能越过的边界 |
|
|
22
22
|
|---|---|---|
|
|
@@ -100,6 +100,11 @@ A 角色的请求投给 B 角色。官方机制是 **`list_agents` 即权威 id
|
|
|
100
100
|
- 多个角色都要时并行委派,但每个角色都必须先按第 2 节复用或创建,各记清单,等结算通知齐了再综合
|
|
101
101
|
(齐不齐按 §6.1 的对账表逐行判定,不靠"看起来都回了")。
|
|
102
102
|
- 主 Agent 给 collector 的问题写清标的、时间范围、证据用途和所需新鲜度,由 collector 按实际目录选足相关能力,不预设“只取一个”或固定上限。
|
|
103
|
+
- 给 `web_retriever` 的事实核查委派**分级写需求**:核心事实(直接改变结论或风险判断的——审计意见、
|
|
104
|
+
商誉、质押/担保/资金占用、现金流与利润背离、有息负债、再融资/重组等)≤10 条逐条核验、每条一行
|
|
105
|
+
来源;外围事实(分红方案、互动平台问答、媒体花絮)一句话清单即可。不预设"每条 URL+日期+原文/
|
|
106
|
+
转述双分类"的审计级格式;发现质押、商誉、现金流异常再对该项深挖(回传格式同口径,见
|
|
107
|
+
`capital-web-protocol` §5)。
|
|
103
108
|
- 主 Agent 不自行调用任何网络检索/抓取/搜索类工具;外部检索入口只有 `web_retriever` 的回传。
|
|
104
109
|
- 三个已定义角色禁止用通用 `subagent` 创建替代实例;通用委派只用于本 preset 没有专用角色的
|
|
105
110
|
新任务,且同样必须先做本轮预检。
|
|
@@ -122,9 +122,9 @@ prepare_chart_source({ dataset_id, task_id })
|
|
|
122
122
|
|
|
123
123
|
只使用工具返回的 `chart_source_ref`。不要自己构造 token、路径或 `raw.json` 地址。
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
**token 有 15 分钟 TTL,同一 task_id + dataset_id 重复签发幂等返回同一个 token**:仍建议先定好
|
|
126
|
+
要出哪几张图、每张用哪份 Dataset,再一次性签发(省往返);万一改了视图方案再签一次也无妨——
|
|
127
|
+
工具层已保证不会签出第二个 token(2026-10-10 前的版本会重签并浪费一轮大思考)。
|
|
128
128
|
|
|
129
129
|
传给 visualization_specialist 的内容只能包括:
|
|
130
130
|
|
|
@@ -145,12 +145,12 @@ prepare_chart_source({ dataset_id, task_id })
|
|
|
145
145
|
|
|
146
146
|
**分工硬规则:spec 由 specialist 组装,data_junior 只交"要什么视图"。** `kind` / `x` / `series` /
|
|
147
147
|
`markers` / `title` 的取值由 specialist 读 `capital-chart-protocol` 决定;data_junior 的 prompt 只写
|
|
148
|
-
|
|
149
|
-
思考(在替 specialist 算 spec
|
|
148
|
+
视图角色(主视图/辅助视图)、目标窗口与关心的字段名。实测偏离这条要付一笔大账:一次 8.3K 字符的
|
|
149
|
+
思考(在替 specialist 算 spec 与标记位置)。
|
|
150
150
|
|
|
151
|
-
**协议排除的视图要记为 limitation
|
|
151
|
+
**协议排除的视图要记为 limitation,不要改方案。** 需要"归一化对比 / 多源叠加"(例如个股与
|
|
152
152
|
指数同图)时:当前能力不支持,按"图表跟着 Dataset 一对一"拆成各自独立的单源图,并在回传的
|
|
153
|
-
`warnings` 里写明「归一化叠加对比不支持,已拆为 N
|
|
153
|
+
`warnings` 里写明「归一化叠加对比不支持,已拆为 N 张单源图」。不要为此重新规划整套图,
|
|
154
154
|
也不要自造归一化序列。
|
|
155
155
|
|
|
156
156
|
## 3. visualization_specialist 选图规则
|