@v587d/capital-generation 2.9.2 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/README.md +8 -5
- package/chart-ui/package.json +1 -1
- package/config/client.js +8 -6
- package/config/client.src.cjs +74 -34
- package/config/index.js +8 -2
- package/config/package.json +1 -1
- package/lib/agents/selected-skills-provider.d.ts +29 -10
- package/lib/agents/selected-skills-provider.js +262 -139
- package/lib/agents/settled-wake.d.ts +43 -0
- package/lib/agents/settled-wake.js +81 -0
- package/lib/agents/skill-usage.d.ts +22 -0
- package/lib/agents/skill-usage.js +92 -0
- package/lib/agents/wait-for-agents.d.ts +79 -0
- package/lib/agents/wait-for-agents.js +256 -0
- package/lib/capital-settings.d.ts +12 -1
- package/lib/capital-settings.js +23 -3
- package/lib/chart/artifact-ref.d.ts +4 -1
- package/lib/chart/artifact-ref.js +2 -1
- package/lib/chart/delivery.d.ts +15 -1
- package/lib/chart/delivery.js +17 -8
- package/lib/chart/events.d.ts +1 -1
- package/lib/chart/html.js +16 -0
- package/lib/chart/present-tool.js +52 -3
- package/lib/chart/tool.js +63 -9
- package/lib/data-collector/store.d.ts +1 -0
- package/lib/data-collector/store.js +2 -2
- package/lib/index.d.ts +6 -3
- package/lib/index.js +10 -19
- package/lib/orchestration-entry.d.ts +11 -0
- package/lib/orchestration-entry.js +6 -0
- package/lib/present-entry.js +26 -5
- package/lib/root-extras-entry.d.ts +2 -5
- package/lib/root-extras-entry.js +1 -3
- package/lib/selected-skills-entry.d.ts +14 -0
- package/lib/selected-skills-entry.js +43 -0
- 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 +15 -2
- package/preset/capital-generation/README.md +3 -3
- package/preset/capital-generation/agent.patch.yml +43 -20
- package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +3 -2
- package/preset/capital-generation/skills/capital-data-query/SKILL.md +32 -0
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +5 -0
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +3 -0
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +8 -10
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +3 -0
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +7 -1
- package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -0
- package/preset/capital-generation/skills/capital-web-protocol/SKILL.md +3 -0
- package/selected-skills/README.md +5 -4
- package/selected-skills/ROUTING.md +88 -0
- package/selected-skills/catalog.js +32 -121
- package/selected-skills/routing.js +1036 -0
- package/watchlist/package.json +1 -1
package/lib/chart/html.js
CHANGED
|
@@ -59,7 +59,9 @@ function formatCapturedAt(epochMs) {
|
|
|
59
59
|
export function buildStandaloneHtml(input) {
|
|
60
60
|
const { payload, library } = input;
|
|
61
61
|
const meta = payload.meta;
|
|
62
|
+
const kindLabel = payload.kind === 'candlestick' ? 'K 线图' : payload.kind === 'bar' ? 'OHLC 图' : payload.kind === 'column' ? '柱状图' : payload.kind === 'area' ? '面积图' : '折线图';
|
|
62
63
|
const facts = [];
|
|
64
|
+
facts.push(kindLabel);
|
|
63
65
|
facts.push(`${meta.points} 个数据点`);
|
|
64
66
|
if (meta.downsampled)
|
|
65
67
|
facts.push(`已从 ${meta.original_points} 点下采样(保留极值形状,非全量)`);
|
|
@@ -73,6 +75,18 @@ export function buildStandaloneHtml(input) {
|
|
|
73
75
|
facts.push(`时间字段 ${meta.time_field}`);
|
|
74
76
|
if (meta.axis === 'index')
|
|
75
77
|
facts.push('分类轴(按行序)');
|
|
78
|
+
const guide = [];
|
|
79
|
+
guide.push(meta.axis === 'index' ? '横轴为分类,按数据行序排列;纵轴为数值。' : '横轴为时间;纵轴为数值。');
|
|
80
|
+
if (payload.ohlc)
|
|
81
|
+
guide.push('K 线红涨绿跌,下方成交量柱按当日涨跌着色。');
|
|
82
|
+
else if (payload.volume)
|
|
83
|
+
guide.push('下方成交量柱按涨跌方向着色。');
|
|
84
|
+
if (payload.series.length > 0)
|
|
85
|
+
guide.push(`序列:${payload.series.map((item) => item.label).join('、')}。`);
|
|
86
|
+
if (meta.downsampled)
|
|
87
|
+
guide.push('曲线为下采样后的形状,数值结论请回到表格/查询结果。');
|
|
88
|
+
if (meta.warnings.length > 0)
|
|
89
|
+
guide.push('数据提示见下方 warnings。');
|
|
76
90
|
const warnings = meta.warnings.length > 0
|
|
77
91
|
? `<ul class="warnings">${meta.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join('')}</ul>`
|
|
78
92
|
: '';
|
|
@@ -108,6 +122,7 @@ export function buildStandaloneHtml(input) {
|
|
|
108
122
|
body { font: 14px/1.6 -apple-system, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif; padding: 16px 20px 24px; }
|
|
109
123
|
h1 { margin: 0 0 4px; font-size: 16px; font-weight: 600; }
|
|
110
124
|
.facts { margin: 0; color: var(--dsw-alias-label-secondary); font-size: 12px; }
|
|
125
|
+
.guide { margin: 6px 0 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 1.7; }
|
|
111
126
|
main { margin-top: 12px; height: 460px; min-height: 320px; }
|
|
112
127
|
.warnings { margin: 12px 0 0; padding-left: 18px; color: var(--dsw-alias-label-secondary); font-size: 12px; }
|
|
113
128
|
footer { margin-top: 16px; color: var(--dsw-alias-label-secondary); font-size: 11px; }
|
|
@@ -117,6 +132,7 @@ export function buildStandaloneHtml(input) {
|
|
|
117
132
|
<header>
|
|
118
133
|
<h1>${escapeHtml(payload.title)}</h1>
|
|
119
134
|
<p class="facts">${facts.map((fact) => escapeHtml(fact)).join(' · ')}</p>
|
|
135
|
+
<p class="guide">${guide.map((line) => escapeHtml(line)).join(' ')}</p>
|
|
120
136
|
</header>
|
|
121
137
|
<main id="chart"></main>
|
|
122
138
|
${warnings}
|
|
@@ -55,6 +55,52 @@ function normalizeTaskId(value) {
|
|
|
55
55
|
throw new Error('present_charts 的 task_id 必须是非空字符串');
|
|
56
56
|
return value.trim();
|
|
57
57
|
}
|
|
58
|
+
const TITLE_SUBJECT_CODE = /(?<!\d)(\d{6})(?!\d)/g;
|
|
59
|
+
function titleSubjectCodes(title) {
|
|
60
|
+
return [...title.matchAll(TITLE_SUBJECT_CODE)].map((match) => match[1]);
|
|
61
|
+
}
|
|
62
|
+
/** Strip six-digit codes and punctuation so “同花顺 300033 日K” and “同花顺 300803 日K” share a key. */
|
|
63
|
+
function titleDedupeKey(title) {
|
|
64
|
+
return title
|
|
65
|
+
.normalize('NFKC')
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
.replace(TITLE_SUBJECT_CODE, '')
|
|
68
|
+
.replace(/[\s\p{P}\p{S}]+/gu, '');
|
|
69
|
+
}
|
|
70
|
+
function artifactQualityFailure(artifact) {
|
|
71
|
+
const codes = titleSubjectCodes(artifact.title);
|
|
72
|
+
if (codes.length === 0 || artifact.subject_code === null)
|
|
73
|
+
return undefined;
|
|
74
|
+
const mismatched = [...new Set(codes.filter((code) => code !== artifact.subject_code))];
|
|
75
|
+
if (mismatched.length === 0)
|
|
76
|
+
return undefined;
|
|
77
|
+
return `标题里的股票代码 ${mismatched.join('、')} 与 dataset 对应的 ${artifact.subject_code} 不一致;请改正标题后重绘,未登记这张图`;
|
|
78
|
+
}
|
|
79
|
+
function dedupeArtifacts(artifacts) {
|
|
80
|
+
const kept = [];
|
|
81
|
+
const failed = [];
|
|
82
|
+
const seen = new Map();
|
|
83
|
+
for (const artifact of artifacts) {
|
|
84
|
+
const qualityFailure = artifactQualityFailure(artifact);
|
|
85
|
+
if (qualityFailure !== undefined) {
|
|
86
|
+
failed.push({ chart_ref: artifact.chart_ref, reason: qualityFailure });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const titleKey = titleDedupeKey(artifact.title);
|
|
90
|
+
const key = `${artifact.dataset_id ?? 'no-dataset'}|${artifact.kind}|${titleKey.length > 0 ? titleKey : artifact.chart_ref}`;
|
|
91
|
+
const previous = seen.get(key);
|
|
92
|
+
if (previous !== undefined) {
|
|
93
|
+
failed.push({
|
|
94
|
+
chart_ref: artifact.chart_ref,
|
|
95
|
+
reason: `重复图表:与 ${previous.chart_ref} 的 dataset/kind/标题相同;只登记第一张`,
|
|
96
|
+
});
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
seen.set(key, artifact);
|
|
100
|
+
kept.push(artifact);
|
|
101
|
+
}
|
|
102
|
+
return { kept, failed };
|
|
103
|
+
}
|
|
58
104
|
/** Present refs first; otherwise present the whole task so compaction cannot strand the charts. */
|
|
59
105
|
function selectArtifacts(input, session, refs, taskId) {
|
|
60
106
|
const failed = [];
|
|
@@ -89,8 +135,9 @@ export function registerPresentChartsTool(ctx, input) {
|
|
|
89
135
|
return;
|
|
90
136
|
const definition = {
|
|
91
137
|
name: 'present_charts',
|
|
92
|
-
description: '把本会话已生成的图表登记为**最终答复的交付物**(官方 deliverables/presented):用户会在答复末尾的「本轮文件改动 / 交付」行看到卡片,点开即渲染自包含
|
|
138
|
+
description: '把本会话已生成的图表登记为**最终答复的交付物**(官方 deliverables/presented):用户会在答复末尾的「本轮文件改动 / 交付」行看到卡片,点开即渲染自包含 HTML(文件名带代码/标题或图形类型,如 `300803-kline-<短id>.html`)。'
|
|
93
139
|
+ '**由主 Agent 调用一次**:参数是 data_junior 回传的 chart_refs(一次传入,最多 32 个);refs 已丢失时改传 task_id 登记该任务的全部图表。'
|
|
140
|
+
+ '登记前会按 dataset + kind + 标题去重,并校验标题里的 6 位股票代码与 dataset 一致;重复或错标题的 ref 会进 failed,不会出现在交付卡片里。'
|
|
94
141
|
+ '这里只记录本轮要交付的图,宿主会在**最终答复之前**统一写入交付行——提前调用不会落进中间轮,不必等“最后一刻”,也不要在过程轮反复调用。'
|
|
95
142
|
+ '不要传文件路径或 HTML(路径由宿主解析);不调用时宿主仍会在收尾时自动登记本 exchange 已生成的图表。',
|
|
96
143
|
parameters,
|
|
@@ -101,7 +148,9 @@ export function registerPresentChartsTool(ctx, input) {
|
|
|
101
148
|
if (refs.length === 0 && taskId === undefined) {
|
|
102
149
|
throw new Error('present_charts 需要 chart_refs 或 task_id:两者都不给就无法确定要登记哪几张图');
|
|
103
150
|
}
|
|
104
|
-
const { selected, failed } = selectArtifacts(input, session, refs, taskId);
|
|
151
|
+
const { selected: selectedRaw, failed } = selectArtifacts(input, session, refs, taskId);
|
|
152
|
+
const { kept: selected, failed: qualityFailed } = dedupeArtifacts(selectedRaw);
|
|
153
|
+
failed.push(...qualityFailed);
|
|
105
154
|
const receipt = { presented: [], failed };
|
|
106
155
|
if (selected.length === 0)
|
|
107
156
|
return receipt;
|
|
@@ -124,7 +173,7 @@ export function registerPresentChartsTool(ctx, input) {
|
|
|
124
173
|
throw new Error(blocked);
|
|
125
174
|
const publisher = input.chartEvents();
|
|
126
175
|
if (publisher === undefined) {
|
|
127
|
-
throw new Error('图表交付通道不可用(sessions / sessionProjections 解析不到):图表已落盘,可在 workspace 打开
|
|
176
|
+
throw new Error('图表交付通道不可用(sessions / sessionProjections 解析不到):图表已落盘,可在 workspace 打开 html_path 指向的 HTML');
|
|
128
177
|
}
|
|
129
178
|
const accepted = publisher.publishMany !== undefined
|
|
130
179
|
? publisher.publishMany(session.id, inputs)
|
package/lib/chart/tool.js
CHANGED
|
@@ -16,6 +16,49 @@ function newChartId() {
|
|
|
16
16
|
const suffix = cryptoLike?.randomUUID ? cryptoLike.randomUUID() : `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
17
17
|
return `ch_${suffix}`;
|
|
18
18
|
}
|
|
19
|
+
const SUBJECT_KEYS = ['thscode', 'ts_code', 'ticker', 'symbol', 'stock_code', 'sec_code', 'code'];
|
|
20
|
+
/** Six-digit A-share/HK/US ticker; `thscode` wins over generic API status fields such as `code`. */
|
|
21
|
+
function subjectCodeOf(value) {
|
|
22
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
23
|
+
return null;
|
|
24
|
+
const record = value;
|
|
25
|
+
for (const key of SUBJECT_KEYS) {
|
|
26
|
+
const raw = record[key];
|
|
27
|
+
if (typeof raw === 'string') {
|
|
28
|
+
const match = /(?<!\d)(\d{6})(?!\d)/.exec(raw);
|
|
29
|
+
if (match)
|
|
30
|
+
return match[1];
|
|
31
|
+
}
|
|
32
|
+
if (typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 100_000 && raw <= 999_999) {
|
|
33
|
+
return String(raw);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function chartSubjectCode(envelope, rows) {
|
|
39
|
+
const fromEnvelope = subjectCodeOf(envelope);
|
|
40
|
+
if (fromEnvelope !== null)
|
|
41
|
+
return fromEnvelope;
|
|
42
|
+
for (const row of rows.slice(0, 3)) {
|
|
43
|
+
const fromRow = subjectCodeOf(row);
|
|
44
|
+
if (fromRow !== null)
|
|
45
|
+
return fromRow;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/** Readable, collision-resistant filename: ASCII title slug + short chart id. The store's artifact
|
|
50
|
+
* name gate is deliberately ASCII, so Chinese titles fall back to the chart kind (code stays when
|
|
51
|
+
* the title/envelope carries one). */
|
|
52
|
+
function chartHtmlName(title, kind, chartId, subjectCode) {
|
|
53
|
+
const kindSlug = kind === 'candlestick' ? 'kline' : kind === 'bar' ? 'ohlc' : kind === 'column' ? 'column' : kind === 'area' ? 'area' : 'line';
|
|
54
|
+
const words = title.normalize('NFKC').match(/[A-Za-z0-9]+/g) ?? [];
|
|
55
|
+
const parts = subjectCode !== null && !words.includes(subjectCode) ? [subjectCode, ...words] : [...words];
|
|
56
|
+
if (parts.length === 0 || (subjectCode !== null && parts.every((part) => part === subjectCode)))
|
|
57
|
+
parts.push(kindSlug);
|
|
58
|
+
const safe = parts.join('-').replace(/-+/g, '-').replace(/^[.\-]+|[.\-]+$/g, '').slice(0, 48);
|
|
59
|
+
const suffix = chartId.replace(/^ch_/, '').slice(0, 8);
|
|
60
|
+
return `${safe.length > 0 ? safe : kindSlug}-${suffix}.html`;
|
|
61
|
+
}
|
|
19
62
|
function sessionOf(exec) {
|
|
20
63
|
const session = exec.agent?.session;
|
|
21
64
|
if (!session || typeof session.id !== 'string' || session.id.length === 0) {
|
|
@@ -125,18 +168,27 @@ export async function renderChart(input, args, exec) {
|
|
|
125
168
|
spec.title = args.title.trim().slice(0, 200);
|
|
126
169
|
// The token carries the direct data-agent session; the nested child never sees raw rows.
|
|
127
170
|
let rows;
|
|
171
|
+
let subjectCode;
|
|
128
172
|
let meta;
|
|
129
173
|
if (datasetId !== undefined) {
|
|
130
174
|
const readSession = tokenSource?.sourceSession ?? session;
|
|
131
|
-
const { ref, rows: datasetRows } = await input.store.readPresentationRows({ session: readSession, dataset_id: datasetId, signal: exec.signal });
|
|
175
|
+
const { ref, rows: datasetRows, envelope } = await input.store.readPresentationRows({ session: readSession, dataset_id: datasetId, signal: exec.signal });
|
|
132
176
|
rows = datasetRows;
|
|
133
|
-
|
|
177
|
+
subjectCode = chartSubjectCode(envelope, datasetRows);
|
|
178
|
+
meta = {
|
|
179
|
+
source_kind: 'dataset',
|
|
180
|
+
dataset_id: ref.dataset_id,
|
|
181
|
+
source_label: ref.source_label,
|
|
182
|
+
captured_at: ref.captured_at,
|
|
183
|
+
...(subjectCode === null ? {} : { subject_code: subjectCode }),
|
|
184
|
+
};
|
|
134
185
|
}
|
|
135
186
|
else {
|
|
136
187
|
const parsed = await input.store.readWorkspaceJson({ session, path: path, signal: exec.signal });
|
|
137
188
|
const extracted = extractRows(parsed);
|
|
138
189
|
rows = extracted.rows;
|
|
139
|
-
|
|
190
|
+
subjectCode = chartSubjectCode(parsed, extracted.rows);
|
|
191
|
+
meta = { source_kind: 'path', ...(subjectCode === null ? {} : { subject_code: subjectCode }) };
|
|
140
192
|
}
|
|
141
193
|
const chartId = newChartId();
|
|
142
194
|
const payload = buildChartPayload({ chart_id: chartId, spec, rows, meta });
|
|
@@ -144,13 +196,14 @@ export async function renderChart(input, args, exec) {
|
|
|
144
196
|
const generatedAt = now();
|
|
145
197
|
const html = buildStandaloneHtml({ payload, library, generatedAt });
|
|
146
198
|
const dir = `${CHART_ARTIFACT_DIR}/${chartId}`;
|
|
199
|
+
const htmlName = chartHtmlName(payload.title, payload.kind, chartId, subjectCode);
|
|
147
200
|
const written = await input.store.writeWorkspaceFiles({
|
|
148
201
|
session,
|
|
149
202
|
dir,
|
|
150
203
|
files: [
|
|
151
|
-
{ name: 'spec.json', content: `${JSON.stringify({ chart_id: chartId, created_at: generatedAt, source: { dataset_id: meta.dataset_id ?? null, path: path ?? null }, spec }, null, 2)}\n` },
|
|
204
|
+
{ name: 'spec.json', content: `${JSON.stringify({ chart_id: chartId, created_at: generatedAt, source: { dataset_id: meta.dataset_id ?? null, path: path ?? null, subject_code: subjectCode }, spec }, null, 2)}\n` },
|
|
152
205
|
{ name: 'series.json', content: `${JSON.stringify(payload)}\n` },
|
|
153
|
-
{ name:
|
|
206
|
+
{ name: htmlName, content: html },
|
|
154
207
|
],
|
|
155
208
|
signal: exec.signal,
|
|
156
209
|
});
|
|
@@ -175,13 +228,14 @@ export async function renderChart(input, args, exec) {
|
|
|
175
228
|
dataset_id: meta.dataset_id ?? null,
|
|
176
229
|
source_label: meta.source_label ?? null,
|
|
177
230
|
captured_at: meta.captured_at ?? null,
|
|
231
|
+
subject_code: subjectCode,
|
|
178
232
|
kind: payload.kind,
|
|
179
233
|
axis: payload.axis,
|
|
180
234
|
title: payload.title,
|
|
181
235
|
chart_url: chartUrl,
|
|
182
236
|
spec_path: pathOf('spec.json'),
|
|
183
237
|
series_path: pathOf('series.json'),
|
|
184
|
-
html_path: pathOf(
|
|
238
|
+
html_path: pathOf(htmlName),
|
|
185
239
|
created_at: generatedAt,
|
|
186
240
|
});
|
|
187
241
|
// Unit callers without the registry still get a stable receipt.
|
|
@@ -202,7 +256,7 @@ export async function renderChart(input, args, exec) {
|
|
|
202
256
|
ownerSessionId,
|
|
203
257
|
chart_id: chartId,
|
|
204
258
|
title: payload.title,
|
|
205
|
-
html_path: pathOf(
|
|
259
|
+
html_path: pathOf(htmlName),
|
|
206
260
|
});
|
|
207
261
|
deliverableTrace = `publish=${accepted ? '受理' : '拒绝'} ownerSessionId=${ownerSessionId} | ${publisher.lastTrace?.() ?? '(无轨迹)'}`;
|
|
208
262
|
}
|
|
@@ -242,7 +296,7 @@ export async function renderChart(input, args, exec) {
|
|
|
242
296
|
chart_url: chartUrl,
|
|
243
297
|
spec_path: pathOf('spec.json'),
|
|
244
298
|
series_path: pathOf('series.json'),
|
|
245
|
-
html_path: pathOf(
|
|
299
|
+
html_path: pathOf(htmlName),
|
|
246
300
|
warnings: payload.meta.warnings,
|
|
247
301
|
};
|
|
248
302
|
}
|
|
@@ -252,7 +306,7 @@ export function registerChartTool(ctx, options) {
|
|
|
252
306
|
return;
|
|
253
307
|
const definition = {
|
|
254
308
|
name: 'render_chart',
|
|
255
|
-
description: `把已经取到的数据渲染成一张可交互图,产物落在 workspace 的 ${CHART_ARTIFACT_DIR}/<chart_id>/(
|
|
309
|
+
description: `把已经取到的数据渲染成一张可交互图,产物落在 workspace 的 ${CHART_ARTIFACT_DIR}/<chart_id>/(HTML 以标题/代码命名,如「600519-kline-<短id>.html」,可离线打开)。` +
|
|
256
310
|
'**只回一条小回执,不回任何原始数据行**:序列走旁路,不进入上下文。' +
|
|
257
311
|
'来源三选一:dataset_id(已授权的 DatasetRef)、path(workspace 相对路径的 JSON,支持顶层数组或含行数组的 envelope),或 chart_source_ref(宿主签发给 visualization_specialist 的短期 token;使用它时还要提供 task_id)。' +
|
|
258
312
|
'spec 是自由对象,至少给出 kind(line / area / column / candlestick / bar;后两者要 ohlc 四字段与真实时间列)与字段选择(series 或 ohlc),' +
|
|
@@ -353,6 +353,7 @@ export declare class WorkspaceDatasetStore {
|
|
|
353
353
|
}): Promise<{
|
|
354
354
|
ref: DatasetRef;
|
|
355
355
|
rows: unknown[];
|
|
356
|
+
envelope?: Record<string, unknown>;
|
|
356
357
|
}>;
|
|
357
358
|
/**
|
|
358
359
|
* Host-internal workspace text read; with readWorkspaceBytes it shares containment and size
|
|
@@ -332,7 +332,7 @@ export class WorkspaceDatasetStore {
|
|
|
332
332
|
if (raw.rows.length > maxRows) {
|
|
333
333
|
throw new DatasetStoreError('dataset_too_large', `Dataset has ${raw.rows.length} rows; presentation reads are capped at ${maxRows}`);
|
|
334
334
|
}
|
|
335
|
-
return { ref, rows: raw.rows };
|
|
335
|
+
return { ref, rows: raw.rows, ...(raw.envelope === undefined ? {} : { envelope: raw.envelope }) };
|
|
336
336
|
}
|
|
337
337
|
/**
|
|
338
338
|
* Host-internal workspace text read; with readWorkspaceBytes it shares containment and size
|
|
@@ -500,7 +500,7 @@ export class WorkspaceDatasetStore {
|
|
|
500
500
|
if (isRecord(value)) {
|
|
501
501
|
const rows = rowsAtPath(value, rowKey ?? 'item');
|
|
502
502
|
if (rows !== undefined)
|
|
503
|
-
return { kind: 'rows', rows, shape: 'envelope_item' };
|
|
503
|
+
return { kind: 'rows', rows, shape: 'envelope_item', envelope: value };
|
|
504
504
|
}
|
|
505
505
|
if (isRecord(value))
|
|
506
506
|
return { kind: 'document', value };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import z from '@deepseek-ai/schemastery';
|
|
3
|
-
import { type SelectedSkillSelection } from './agents/selected-skills-provider.js';
|
|
3
|
+
import { type SelectedSkillSelection, type SelectedSkillsMode } from './agents/selected-skills-provider.js';
|
|
4
|
+
import { CAPITAL_CONFIG_ENTRY_ID } from './capital-settings.js';
|
|
4
5
|
import { type RetrieverConfig } from './retriever-config.js';
|
|
5
6
|
export { LOCAL_FETCH_DEFAULTS, PADDLE_OCR_DEFAULTS, resolveLocalFetchConfig, type LocalFetchConfig, type PaddleOcrConfig, type RetrieverConfig, type WindDocsConfig, } from './retriever-config.js';
|
|
6
7
|
export { resolveUserCustomizationSection } from './user-customization.js';
|
|
7
8
|
export declare const name = "capital-generation";
|
|
8
|
-
|
|
9
|
-
export declare const CAPITAL_CONFIG_ENTRY_ID = "capital-config";
|
|
9
|
+
export { CAPITAL_CONFIG_ENTRY_ID };
|
|
10
10
|
export interface Config {
|
|
11
11
|
customPersona?: string;
|
|
12
12
|
selectedSkills?: SelectedSkillSelection;
|
|
13
|
+
selectedSkillsMode?: SelectedSkillsMode;
|
|
13
14
|
fuyaoCredentialRef?: string;
|
|
14
15
|
ftshareCredentialRef?: string;
|
|
15
16
|
retriever?: RetrieverConfig;
|
|
@@ -21,6 +22,7 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
21
22
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
22
23
|
[k: string]: z<boolean, boolean, "defined">;
|
|
23
24
|
}>>, "defined">;
|
|
25
|
+
selectedSkillsMode: z<"manual" | "auto", "manual" | "auto", "defined">;
|
|
24
26
|
fuyaoCredentialRef: z<string, string, "defined">;
|
|
25
27
|
ftshareCredentialRef: z<string, string, "defined">;
|
|
26
28
|
retriever: z<Schemastery.ObjectS<NoInfer<{
|
|
@@ -107,6 +109,7 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
107
109
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
108
110
|
[k: string]: z<boolean, boolean, "defined">;
|
|
109
111
|
}>>, "defined">;
|
|
112
|
+
selectedSkillsMode: z<"manual" | "auto", "manual" | "auto", "defined">;
|
|
110
113
|
fuyaoCredentialRef: z<string, string, "defined">;
|
|
111
114
|
ftshareCredentialRef: z<string, string, "defined">;
|
|
112
115
|
retriever: z<Schemastery.ObjectS<NoInfer<{
|
package/lib/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { registerChartTool } from './chart/tool.js';
|
|
|
10
10
|
import { createChartEventPublisher } from './chart/events.js';
|
|
11
11
|
import { chartEventContextOf } from './chart/event-context.js';
|
|
12
12
|
import { SELECTED_SKILL_CATALOG } from './agents/selected-skills-provider.js';
|
|
13
|
+
import { CAPITAL_CONFIG_ENTRY_ID, readCapitalEntry } from './capital-settings.js';
|
|
13
14
|
import { registerChartSourceTool } from './chart/source-token.js';
|
|
14
15
|
import { registerSourceTools, registerWebRetrieverTools } from './web-retriever/tools.js';
|
|
15
16
|
import { LOCAL_FETCH_DEFAULTS, PADDLE_OCR_DEFAULTS, } from './retriever-config.js';
|
|
@@ -17,8 +18,7 @@ import { createRetrieverRuntime } from './retriever-runtime.js';
|
|
|
17
18
|
export { LOCAL_FETCH_DEFAULTS, PADDLE_OCR_DEFAULTS, resolveLocalFetchConfig, } from './retriever-config.js';
|
|
18
19
|
export { resolveUserCustomizationSection } from './user-customization.js';
|
|
19
20
|
export const name = 'capital-generation';
|
|
20
|
-
|
|
21
|
-
export const CAPITAL_CONFIG_ENTRY_ID = 'capital-config';
|
|
21
|
+
export { CAPITAL_CONFIG_ENTRY_ID };
|
|
22
22
|
const WindDocsSchema = z.object({
|
|
23
23
|
endpoint: z.string().default('').description('可选:覆盖 Wind 文档检索端点'),
|
|
24
24
|
credentialRef: z.string().default('').description('可选:DSH credentials 引用名,空 = WIND_API_KEY'),
|
|
@@ -51,13 +51,16 @@ const RetrieverSchema = z.object({
|
|
|
51
51
|
// Derive toggles from the catalog: a hand-copied list silently drops defaults at the entry→plugin boundary.
|
|
52
52
|
const SelectedSkillsSchema = z.object(Object.fromEntries(SELECTED_SKILL_CATALOG.map(({ key, name }) => [
|
|
53
53
|
key,
|
|
54
|
-
z.boolean().default(false).description(`Enable the community
|
|
54
|
+
z.boolean().default(false).description(`Enable the community method "${name}" for the Capital role(s) that own it`),
|
|
55
55
|
]))).default(Object.fromEntries(SELECTED_SKILL_CATALOG.map(({ key }) => [key, false])));
|
|
56
56
|
export const Config = z.object({
|
|
57
57
|
customPersona: z.string()
|
|
58
58
|
.default('')
|
|
59
59
|
.description('自动模式的附加人设文本(独立 section,非 deployment:persona);核心安全约束始终保留'),
|
|
60
|
-
selectedSkills: SelectedSkillsSchema.description('Community selected
|
|
60
|
+
selectedSkills: SelectedSkillsSchema.description('Community selected methods; default off, each visible only to the Capital role(s) that own it'),
|
|
61
|
+
selectedSkillsMode: z.union(['manual', 'auto'])
|
|
62
|
+
.default('manual')
|
|
63
|
+
.description('精选 Skill 暴露方式:manual = 只用勾选;auto = 按角色开放全部方法,运行期忽略勾选'),
|
|
61
64
|
fuyaoCredentialRef: z.string()
|
|
62
65
|
.default('FUYAO_API_KEY')
|
|
63
66
|
.description('Fuyao credentials 引用名,空 = FUYAO_API_KEY'),
|
|
@@ -70,22 +73,10 @@ export const Config = z.object({
|
|
|
70
73
|
/** Fallback order: settings.describe → configEditor.entries → boot preset, so unreadable never
|
|
71
74
|
* reads as "all toggles off". Resolve services per call: boot fibers apply in parallel. */
|
|
72
75
|
export function readCapitalSettings(ctx, bootConfig) {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const resolved = settings?.describe?.().find((entry) => entry.ns === CAPITAL_CONFIG_ENTRY_ID)?.value;
|
|
76
|
+
const entry = readCapitalEntry(ctx);
|
|
77
|
+
if (entry.value !== undefined) {
|
|
76
78
|
// Re-parse through this plugin's schema: fills defaults and surfaces schema drift.
|
|
77
|
-
|
|
78
|
-
return { config: Config(resolved), source: 'settings-describe' };
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
}
|
|
82
|
-
const configEditor = ctx.get('configEditor');
|
|
83
|
-
try {
|
|
84
|
-
const raw = configEditor?.entries?.().find((row) => row?.options?.id === CAPITAL_CONFIG_ENTRY_ID)?.options?.config;
|
|
85
|
-
if (raw && typeof raw === 'object')
|
|
86
|
-
return { config: Config(raw), source: 'config-editor' };
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
79
|
+
return { config: Config(entry.value), source: entry.source ?? 'settings-describe' };
|
|
89
80
|
}
|
|
90
81
|
return { config: bootConfig, source: 'preset' };
|
|
91
82
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tools/orchestration` row: the root-only `wait_for_agents` tool.
|
|
3
|
+
*
|
|
4
|
+
* Leaves don't mount this row: a child has no delegated tree to wait for and must use send_message
|
|
5
|
+
* to report upward. The tool also rejects the subagent origin at call time, so a mis-mounted row
|
|
6
|
+
* cannot turn into a child-side polling loop.
|
|
7
|
+
*/
|
|
8
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
9
|
+
export declare const name = "capital-orchestration-tools";
|
|
10
|
+
export declare const inject: string[];
|
|
11
|
+
export declare function apply(ctx: Context): void;
|
package/lib/present-entry.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createChartDelivery } from './chart/delivery.js';
|
|
|
2
2
|
import { createChartEventPublisher } from './chart/events.js';
|
|
3
3
|
import { chartEventContextOf } from './chart/event-context.js';
|
|
4
4
|
import { registerPresentChartsTool } from './chart/present-tool.js';
|
|
5
|
+
import { createSettledWakeGuard } from './agents/settled-wake.js';
|
|
5
6
|
import { capitalSharedRuntimeOf } from './chart-source-entry.js';
|
|
6
7
|
export const name = 'capital-present-tools';
|
|
7
8
|
export const inject = ['tools'];
|
|
@@ -59,10 +60,12 @@ export function apply(ctx) {
|
|
|
59
60
|
const shared = capitalSharedRuntimeOf(ctx);
|
|
60
61
|
const publisherOf = () => createChartEventPublisher(chartEventContextOf(ctx));
|
|
61
62
|
const eventCtx = ctx;
|
|
63
|
+
const wakeGuard = createSettledWakeGuard();
|
|
62
64
|
const delivery = createChartDelivery({
|
|
63
65
|
artifacts: shared.artifacts,
|
|
64
66
|
publish: (sessionId, inputs) => publisherOf()?.publishMany(sessionId, inputs) ?? false,
|
|
65
67
|
inboxOf: inboxOf(ctx),
|
|
68
|
+
onClosing: (_session, agent) => wakeGuard.acceptClosing(agent),
|
|
66
69
|
});
|
|
67
70
|
// The preset row is applied in the root agent's scope: these events are the root's own.
|
|
68
71
|
const disposers = [];
|
|
@@ -71,10 +74,22 @@ export function apply(ctx) {
|
|
|
71
74
|
const payload = args[0];
|
|
72
75
|
if (payload?.frame === undefined)
|
|
73
76
|
return;
|
|
74
|
-
delivery.acceptStream(payload.agent?.session, payload.frame);
|
|
77
|
+
delivery.acceptStream(payload.agent?.session, payload.frame, payload.agent);
|
|
78
|
+
}));
|
|
79
|
+
disposers.push(eventCtx.on('subagent/start', (...args) => {
|
|
80
|
+
const info = args[0];
|
|
81
|
+
delivery.acceptSubagentStart(info ?? {});
|
|
82
|
+
wakeGuard.acceptSubagentStart(info ?? {});
|
|
75
83
|
}));
|
|
76
|
-
disposers.push(eventCtx.on('subagent/start', (...args) => delivery.acceptSubagentStart(args[0])));
|
|
77
84
|
disposers.push(eventCtx.on('subagent/end', (...args) => delivery.acceptSubagentEnd(args[0])));
|
|
85
|
+
// Scoped to the root agent: only the root inbox is inspected. A settled notice inserted after
|
|
86
|
+
// the closing frame would otherwise make the loop open a step for a redundant "done" message.
|
|
87
|
+
disposers.push(eventCtx.on('agent/inbox/inserted', (...args) => {
|
|
88
|
+
const payload = args[0];
|
|
89
|
+
if (payload?.agent === undefined || payload.message === undefined)
|
|
90
|
+
return;
|
|
91
|
+
wakeGuard.acceptInboxInserted(payload.agent, payload.message);
|
|
92
|
+
}));
|
|
78
93
|
}
|
|
79
94
|
// Session events are not agent-scoped: listen on the unscoped root and filter by payload.
|
|
80
95
|
const listen = (eventCtx.root ?? ctx);
|
|
@@ -92,11 +107,17 @@ export function apply(ctx) {
|
|
|
92
107
|
delivery.acceptSubagentEnd({ id: source.senderSessionId });
|
|
93
108
|
return;
|
|
94
109
|
}
|
|
95
|
-
if (
|
|
110
|
+
if (typeof session?.id !== 'string' || source === undefined)
|
|
96
111
|
return;
|
|
97
|
-
if (
|
|
112
|
+
if (source.kind === 'user') {
|
|
113
|
+
delivery.acceptUserMessage(session.id, typeof event.time === 'number' ? event.time : Date.now());
|
|
114
|
+
wakeGuard.acceptUserMessage(session, source);
|
|
98
115
|
return;
|
|
99
|
-
|
|
116
|
+
}
|
|
117
|
+
// `user/message` is committed only when admitted, so this is exactly the "child already
|
|
118
|
+
// reported its result" marker the closing guard needs.
|
|
119
|
+
if (source.kind === 'agent-message')
|
|
120
|
+
wakeGuard.acceptUserMessage(session, source);
|
|
100
121
|
}));
|
|
101
122
|
}
|
|
102
123
|
ctx.effect(() => () => {
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `tools/root-extras` row:
|
|
3
|
-
*
|
|
4
|
-
* every turn so saved changes apply on the next step.
|
|
2
|
+
* `tools/root-extras` row: the `customPersona` system-prompt section. Root-only; this row
|
|
3
|
+
* registers no tools. Selected skills moved to the role-scoped `tools/selected-skills` row.
|
|
5
4
|
*/
|
|
6
5
|
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
-
import { type SelectedSkillSelection } from './agents/selected-skills-provider.js';
|
|
8
6
|
export declare const name = "capital-root-extras";
|
|
9
7
|
export declare const inject: string[];
|
|
10
8
|
interface RootExtrasConfig {
|
|
11
9
|
customPersona?: string;
|
|
12
|
-
selectedSkills?: SelectedSkillSelection;
|
|
13
10
|
}
|
|
14
11
|
/** Re-read per turn so a settings save applies to the same session on the next step. */
|
|
15
12
|
export declare function readRootExtrasConfig(ctx: Context): RootExtrasConfig;
|
package/lib/root-extras-entry.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { registerRootSelectedSkills } from './agents/selected-skills-provider.js';
|
|
2
1
|
import { userCustomizationSection } from './user-customization.js';
|
|
3
2
|
import { readCapitalConfig } from './capital-settings.js';
|
|
4
3
|
export const name = 'capital-root-extras';
|
|
@@ -6,10 +5,9 @@ export const inject = ['systemPrompt'];
|
|
|
6
5
|
/** Re-read per turn so a settings save applies to the same session on the next step. */
|
|
7
6
|
export function readRootExtrasConfig(ctx) {
|
|
8
7
|
const config = readCapitalConfig(ctx);
|
|
9
|
-
return { customPersona: config.customPersona
|
|
8
|
+
return { customPersona: config.customPersona };
|
|
10
9
|
}
|
|
11
10
|
export function apply(ctx) {
|
|
12
|
-
registerRootSelectedSkills(ctx, () => readRootExtrasConfig(ctx).selectedSkills ?? {});
|
|
13
11
|
let lastPersonaError;
|
|
14
12
|
const section = userCustomizationSection(() => readRootExtrasConfig(ctx).customPersona, (error) => {
|
|
15
13
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tools/selected-skills` row: role-scoped selected-skill catalog plus its role prompt section.
|
|
3
|
+
* The row is mounted once per Capital preset (`config.role`) and registers into that Agent's
|
|
4
|
+
* own scope only; it never writes to the preset standing scope or the host scope.
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import { type SelectedSkillRole } from './agents/selected-skills-provider.js';
|
|
8
|
+
export declare const name = "capital-selected-skills";
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
interface Config {
|
|
11
|
+
role?: SelectedSkillRole;
|
|
12
|
+
}
|
|
13
|
+
export declare function apply(ctx: Context, config?: Config): void;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { registerSelectedSkills, renderSelectedSkillsBrief, SELECTED_SKILL_ENTRY_ROLES, } from './agents/selected-skills-provider.js';
|
|
2
|
+
import { readCapitalConfig } from './capital-settings.js';
|
|
3
|
+
export const name = 'capital-selected-skills';
|
|
4
|
+
export const inject = ['systemPrompt'];
|
|
5
|
+
const SECTION_NAME = 'capital:selected-skills';
|
|
6
|
+
/** After the persona, before the user-customization section (order 100). */
|
|
7
|
+
const SECTION_ORDER = 60;
|
|
8
|
+
const accessOf = (ctx) => ({
|
|
9
|
+
selection: () => readCapitalConfig(ctx).selectedSkills ?? {},
|
|
10
|
+
mode: () => (readCapitalConfig(ctx).selectedSkillsMode === 'auto' ? 'auto' : 'manual'),
|
|
11
|
+
});
|
|
12
|
+
export function apply(ctx, config = {}) {
|
|
13
|
+
const role = config.role ?? 'main';
|
|
14
|
+
if (!SELECTED_SKILL_ENTRY_ROLES.includes(role)) {
|
|
15
|
+
throw new Error(`capital-selected-skills: unknown role "${String(role)}"`);
|
|
16
|
+
}
|
|
17
|
+
registerSelectedSkills(ctx, role, accessOf(ctx));
|
|
18
|
+
let lastRenderError;
|
|
19
|
+
ctx.effect(() => {
|
|
20
|
+
const dispose = ctx.systemPrompt.section({
|
|
21
|
+
name: SECTION_NAME,
|
|
22
|
+
order: SECTION_ORDER,
|
|
23
|
+
// Upstream-derived prose can contain `{{...}}`; this section is literal.
|
|
24
|
+
interpolate: false,
|
|
25
|
+
text: (context) => {
|
|
26
|
+
try {
|
|
27
|
+
// dsh-agent passes its Agent as the assembly scope; the usage ledger is keyed by session.
|
|
28
|
+
const agent = context?.agent;
|
|
29
|
+
return renderSelectedSkillsBrief(role, accessOf(ctx), agent);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
if (message !== lastRenderError) {
|
|
34
|
+
lastRenderError = message;
|
|
35
|
+
ctx.logger?.warn?.(`capital-generation: selected skill prompt unreadable (${message})`);
|
|
36
|
+
}
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
return typeof dispose === 'function' ? dispose : () => { };
|
|
42
|
+
}, 'capital-generation.selected-skills()');
|
|
43
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Known limit: DNS resolves per request but the IP is not pinned to the connection,
|
|
4
4
|
* so a rebinding window remains (same trade-off as dsh-search-first).
|
|
5
5
|
*/
|
|
6
|
-
export declare const LOCAL_FETCH_CLIENT_VERSION = "2.
|
|
6
|
+
export declare const LOCAL_FETCH_CLIENT_VERSION = "2.10.1";
|
|
7
7
|
export declare const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
|
|
8
8
|
export type LocalFetchErrorCode = 'ABORTED' | 'TIMEOUT' | 'INVALID_URL' | 'BLOCKED_URL' | 'DNS' | 'REDIRECT' | 'HTTP' | 'TOO_LARGE' | 'UNSUPPORTED_CONTENT_TYPE';
|
|
9
9
|
export declare class LocalFetchError extends Error {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* so a rebinding window remains (same trade-off as dsh-search-first).
|
|
5
5
|
*/
|
|
6
6
|
import { htmlToMarkdown, stripInvisibleText } from './html-markdown.js';
|
|
7
|
-
export const LOCAL_FETCH_CLIENT_VERSION = '2.
|
|
7
|
+
export const LOCAL_FETCH_CLIENT_VERSION = '2.10.1';
|
|
8
8
|
export const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
|
|
9
9
|
export class LocalFetchError extends Error {
|
|
10
10
|
code;
|
|
@@ -35,7 +35,7 @@ export interface WindCallResult {
|
|
|
35
35
|
code?: WindErrorCode;
|
|
36
36
|
}
|
|
37
37
|
/** Must match `package.json` version; a test pins the two together. */
|
|
38
|
-
export declare const WIND_CLIENT_VERSION = "2.
|
|
38
|
+
export declare const WIND_CLIENT_VERSION = "2.10.1";
|
|
39
39
|
export interface WindClient {
|
|
40
40
|
callTool(toolName: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<WindCallResult>;
|
|
41
41
|
}
|
|
@@ -19,7 +19,7 @@ export const WIND_SERVER_ENDPOINTS = {
|
|
|
19
19
|
analytics_data: 'https://mcp.wind.com.cn/vserver_analytics_data/mcp/',
|
|
20
20
|
};
|
|
21
21
|
/** Must match `package.json` version; a test pins the two together. */
|
|
22
|
-
export const WIND_CLIENT_VERSION = '2.
|
|
22
|
+
export const WIND_CLIENT_VERSION = '2.10.1';
|
|
23
23
|
function errorText(error) {
|
|
24
24
|
return error instanceof Error ? error.message : String(error);
|
|
25
25
|
}
|