@v587d/capital-generation 2.7.9 → 2.7.11

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.
@@ -9,14 +9,15 @@ const {
9
9
  Modal,
10
10
  writeClipboard,
11
11
  IconRightUpOutlineRegular,
12
+ IconWarningTriangleOutlineRegular,
12
13
  } = require('@deepseek-ai/dsh-client-ui-primitives')
13
- const { SELECTED_SKILL_CATALOG, SELECTED_SKILL_TAG_LABELS } = require('../selected-skills/catalog.js')
14
+ const { SELECTED_SKILL_CATALOG, SELECTED_SKILL_TAG_LABELS, SELECTED_SKILLS_MAX_ENABLED } = require('../selected-skills/catalog.js')
14
15
 
15
16
  /**
16
17
  * 卡片读的是**配置条目 id**,坐的是**组合包自己的页面**——两个不同的字符串,各自钉在不同地方:
17
18
  *
18
19
  * - `ENTRY_ID` 是 settings 命名空间(0.1.7 起命名空间恒等于 profile 条目 id),四处必须同字:
19
- * `cordis.patch.yml` 的行 id、`capital-config/index.js` 的 `SETTINGS_ENTRY_ID`、主插件
20
+ * `cordis.patch.yml` 的行 id、`config/index.js` 的 `SETTINGS_ENTRY_ID`、主插件
20
21
  * `src/index.ts` 的 `CAPITAL_CONFIG_ENTRY_ID`、这里。
21
22
  * - `BUNDLE_NAME` 是槽 `plugins.bundle.config` 的 key:页面按**组合包的包名**取该包的配置段
22
23
  * (`renderSlot(..., { entryKey: pkg.name })`),所以它必须逐字等于根 `package.json` 的 `name`。
@@ -59,7 +60,7 @@ const PADDLE_OCR_DEFAULT_REF = 'PADDLE_OCR_TOKEN'
59
60
  */
60
61
  const TABS_ID = 'capital-config-tabs'
61
62
  /**
62
- * 「详情」悬浮里那份 `SKILL.md` 原文的来路:host 半边(`capital-config/index.js`)注册的
63
+ * 「详情」悬浮里那份 `SKILL.md` 原文的来路:host 半边(`config/index.js`)注册的
63
64
  * 只读旁路,按 catalog 的 skill 名寻址——闭集在服务器那侧,这里只拼地址。
64
65
  * 这条字符串与 host 的 `SKILL_DOC_ROUTE` 同字(客户端 import 不到那一层,所以由测试对着
65
66
  * 两侧产物比):漂移的表现是「详情」永远读不到,且不报错。
@@ -107,6 +108,11 @@ const zh = {
107
108
  selectedSkillsTitle: '精选 Skills',
108
109
  selectedSkillsFilterLabel: '按能力筛选',
109
110
  selectedSkillsEmpty: '没有匹配这个标签的 Skill。',
111
+ // 上限只挡"再勾一个",不挡"已经勾上的生效":老配置多于上限时照常全部生效,只提示收敛
112
+ // (运行期 provider 不按这个数截断,否则就是"卡片上开着 10 个、会话里只剩 6 个"的静默砍功能)。
113
+ // 被挡住的原因只活在**开关左侧那枚三角警示图标**上(悬停 title / 读屏 aria-label 同一串):
114
+ // {count} 是计数占位("6/6"),由代码拼,字典不存数字。
115
+ selectedSkillsCapTooltip: 'Skill已达上限 ({count})。',
110
116
  selectedSkillsCopyName: '复制技能名',
111
117
  selectedSkillsCopied: '已复制',
112
118
  selectedSkillsDetail: '详情',
@@ -146,6 +152,7 @@ const en = {
146
152
  selectedSkillsTitle: 'Selected Skills',
147
153
  selectedSkillsFilterLabel: 'Filter by capability',
148
154
  selectedSkillsEmpty: 'No skills match this tag.',
155
+ selectedSkillsCapTooltip: 'Skill limit reached ({count}).',
149
156
  selectedSkillsCopyName: 'Copy skill name',
150
157
  selectedSkillsCopied: 'Copied',
151
158
  selectedSkillsDetail: 'Details',
@@ -253,7 +260,12 @@ function installStyles() {
253
260
  .capital-config-switch-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 4px; }
254
261
  .capital-config-switch-label { color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 20px; }
255
262
  .capital-config-hint { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
256
- .capital-config-write-failed { margin: 0; color: var(--dsw-alias-label-error); font-size: 12px; line-height: 18px; }
263
+ /* ⚠️ 语义色只用主题真实定义的 --dsw-alias-state-*:--dsw-alias-label-error 主题里**没定义**,
264
+ var() 判败回继承色 ⇒ light 黑 / dark 白(这枚图标与"保存失败"行实机就是这么错的)。
265
+ warn 色用 --dsw-alias-state-warn-primary(StateDot / Tag 的 warning 同款)。cursor:help 让
266
+ "它有话要说"看得出来;padding 2px + 负 margin 只放大悬停命中区、不占布局(14px 的图标当悬停靶子太小)。 */
267
+ .capital-config-skill-cap-icon { display: inline-flex; align-items: center; flex: none; padding: 2px; margin: -2px; color: var(--dsw-alias-state-warn-primary); cursor: help; }
268
+ .capital-config-write-failed { margin: 0; color: var(--dsw-alias-state-error-primary); font-size: 12px; line-height: 18px; }
257
269
  /* 标题右侧的两枚外链:官方那一格是 flex-wrap + gap:8px,整组自己收成一个 inline-flex 项,
258
270
  免得标题变长时被换行拆成两截。颜色只走 --dsw-* 变量,明暗两套都跟着宿主。 */
259
271
  .capital-config-plugin-links { flex: none; align-items: center; gap: 2px; display: inline-flex; }
@@ -570,7 +582,7 @@ function loadSkillDoc(name) {
570
582
  /** 「已复制」亮多久:要够读完,又不能亮到让人以为它是个状态开关。 */
571
583
  const COPIED_FLASH_MS = 1600
572
584
 
573
- function SelectedSkillRow({ entry, state, disabled, onChange, t }) {
585
+ function SelectedSkillRow({ entry, state, disabled, capReached = false, capTooltip, onChange, t }) {
574
586
  const locale = skillLocale()
575
587
  const label = entry.label[locale]
576
588
  const repositoryUrl = `https://github.com/${entry.owner}/${entry.repository}`
@@ -667,10 +679,22 @@ function SelectedSkillRow({ entry, state, disabled, onChange, t }) {
667
679
  h('div', { className: 'capital-config-skill-tags' }, entry.tags.map((tag) =>
668
680
  h('span', { key: tag, className: 'capital-config-skill-tag' }, SELECTED_SKILL_TAG_LABELS[locale][tag] ?? tag))),
669
681
  ),
682
+ // 被上限挡住的行(到上限 + 没勾):开关**左边**一枚三角警示图标,原因挂在图标的
683
+ // title / aria-label 上(同一串,悬停才见)——开关上不挂 title,列表顶上也没有整行提示。
684
+ // 图标组件不吃自定义 props(artwork 只解构 size/className/strokeWidth),所以 title /
685
+ // aria 挂在包着它的 span 上;`role=img` 让读屏在开关之前先听到"为什么它拨不动"。
686
+ capReached && state?.on !== true ? h('span', {
687
+ className: 'capital-config-skill-cap-icon',
688
+ title: capTooltip,
689
+ role: 'img',
690
+ 'aria-label': capTooltip,
691
+ }, h(IconWarningTriangleOutlineRegular, { size: 14 })) : null,
670
692
  h(Switch, {
671
693
  label,
672
694
  checked: state?.on === true,
673
- disabled: disabled || state?.writing === true,
695
+ // 上限只挡"再勾一个":**已勾上的行永远关得掉**——把用户关进一个既不能再选、也不能取消的
696
+ // 格子里,比不给上限糟得多。被挡住的原因由左边那枚警示图标回答(控件不许对自己的状态说谎)。
697
+ disabled: disabled || state?.writing === true || (capReached && state?.on !== true),
674
698
  onChange,
675
699
  }),
676
700
  ),
@@ -774,19 +798,29 @@ function CapitalCard(props) {
774
798
  onClick: () => setTagFilter(tag),
775
799
  }, tagLabels[tag] ?? tag, h('span', { className: 'capital-config-selected-filter-count' }, String(tagCounts.get(tag))))),
776
800
  )
801
+ // 已选数按开关现算(tab 上那个 n/68 也用它)。上限**只在编辑期**生效:它挡"再勾一个",
802
+ // 不挡"已经勾上的生效"——老配置超过上限时运行期照常全部加载,这里只提示收敛。
803
+ const enabledSkills = SELECTED_SKILL_CATALOG.filter(
804
+ (entry) => state.selectedSkills?.[entry.key]?.on === true,
805
+ ).length
806
+ const capReached = enabledSkills >= SELECTED_SKILLS_MAX_ENABLED
807
+ // 被挡住的行共用同一串悬停文案(含计数),拼一次传下去:62 行各自重拼,漂移就是两处对不齐。
808
+ const capTooltip = t('selectedSkillsCapTooltip')
809
+ .replace('{count}', `${enabledSkills}/${SELECTED_SKILLS_MAX_ENABLED}`)
777
810
  const skillRows = visibleSkills.length > 0
778
811
  ? visibleSkills.map((entry) => h(SelectedSkillRow, {
779
812
  key: entry.key,
780
813
  entry,
781
814
  state: state.selectedSkills?.[entry.key],
782
815
  disabled: disabled || state.saving,
816
+ capReached,
817
+ capTooltip,
783
818
  onChange: (value) => props.toggleSelectedSkill(entry.key, value),
784
819
  t,
785
820
  }))
786
821
  : [h('p', { key: 'empty', className: 'capital-config-hint' }, t('selectedSkillsEmpty'))]
787
- const enabledSkills = SELECTED_SKILL_CATALOG.filter(
788
- (entry) => state.selectedSkills?.[entry.key]?.on === true,
789
- ).length
822
+ // 到上限的提示不再占一整行:原因逐行挂在被挡住的开关左侧那枚三角警示图标上(SelectedSkillRow),
823
+ // 这里不画任何汇总提示——一行红字 + 62 个灰开关是两套说法,只留图标一套。
790
824
  // 行数取不到就**只留标题**:把"还不知道"画成 `0` 是控件对自己的状态说谎(数字缺席本身就是信号)。
791
825
  const withCount = (label, count) => (Number.isInteger(count) ? `${label} ${count}` : label)
792
826
  const options = [
@@ -821,10 +855,12 @@ function CapitalCard(props) {
821
855
  },
822
856
  }),
823
857
  panel('sources', [form, localFetchSection]),
824
- panel('skills', [h('div', { className: 'capital-config-selected-layout' },
825
- skillsFilter,
826
- h('div', { className: 'capital-config-selected-list' }, ...skillRows),
827
- )]),
858
+ panel('skills', [
859
+ h('div', { className: 'capital-config-selected-layout' },
860
+ skillsFilter,
861
+ h('div', { className: 'capital-config-selected-list' }, ...skillRows),
862
+ ),
863
+ ]),
828
864
  // 第三块的内容是宿主自己画的那段清单(切过来才在下方显形)。画面上不再写那句"清单在下方"——
829
865
  // 用户点这一格看到的就是清单,多余的一行字只是噪音。但 `aria-controls` 指的这块面板不许是空的
830
866
  // (空面板不报错,只有读屏用户付账),所以那句话改挂在 sr-only 上:零布局贡献、读得到。
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "@v587d/capital-config",
3
- "version": "2.7.9",
2
+ "name": "@v587d/config",
3
+ "version": "2.7.11",
4
4
  "description": "Capital 模式的取数凭据与偏好配置行:卡片坐在 @v587d/capital-generation 插件主页的「配置」段,点这一行本身没有表单。",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/cordis.patch.yml CHANGED
@@ -33,9 +33,13 @@
33
33
  # locatePkgJson 随后按"最近的 package.json"归属,正好是 chart-ui/package.json,
34
34
  # 包身份为 @v587d/capital-charts,与主包不撞名。
35
35
  # 回归断言见 test/chart-composition.test.mjs(name 必须是字面量且 path-like,并真的解析一次)。
36
+ #
37
+ # ⚠ 目录名 / 包名与**行 id 是两条独立的链**(AGENTS.md §5.1):下面两颗行住在 `config/` 与
38
+ # `watchlist/`,行 id 却仍是 `capital-config` / `capital-watchlist`——后者是 settings 命名空间与
39
+ # 「包含的组件」里那两行的身份,改了就会吃掉用户已保存的配置。不许"顺手对齐"。
36
40
  - insert:
37
41
  - id: capital-config
38
- name: './capital-config/index.js'
42
+ name: './config/index.js'
39
43
  config:
40
44
  customPersona: ''
41
45
  fuyaoCredentialRef: FUYAO_API_KEY
@@ -64,9 +68,9 @@
64
68
  # `test/watchlist-quote.test.mjs` 的**模块同一性探针**钉住:两处 import 必须拿到同一实例,
65
69
  # 探针不过就重新设计,而不是先上线再说。
66
70
  #
67
- # `id` 与 `./capital-watchlist/index.js` 的 path-like 字面量规则同上(!!js / subpath 两个坑
71
+ # `id` 与 `./watchlist/index.js` 的 path-like 字面量规则同上(!!js / subpath 两个坑
68
72
  # 对这颗行同样成立)。
69
73
  - insert:
70
74
  - id: capital-watchlist
71
- name: './capital-watchlist/index.js'
75
+ name: './watchlist/index.js'
72
76
 
@@ -13,6 +13,16 @@ export declare const SELECTED_SKILL_CATALOG: readonly SelectedSkillCatalogEntry[
13
13
  export type SelectedSkillKey = typeof SELECTED_SKILL_CATALOG[number]['key'];
14
14
  export type SelectedSkillSelection = Partial<Record<SelectedSkillKey, boolean>>;
15
15
  export type SelectedSkillSelectionSource = SelectedSkillSelection | (() => SelectedSkillSelection);
16
+ type RuntimeSummary = {
17
+ name: string;
18
+ description: string;
19
+ };
20
+ /**
21
+ * 索引块正文。**唯一实现**:条目集合来自 catalog 的 `selectedSkillsForTask`(与目录、闸门同源),
22
+ * 描述用**运行时那一份**(上游 frontmatter,也就是会话目录里模型看到的那句话),不是设置卡片里
23
+ * 策展过的中文摘要——同一个 skill 在同一段上下文里出现两种描述,只会让模型两处对照。
24
+ */
25
+ export declare function renderTaskSkillIndex(taskId: string, summaries: readonly RuntimeSummary[]): string | undefined;
16
26
  type FileSystemSkillConfig = {
17
27
  providerName: string;
18
28
  includeDefaultRoots: false;
@@ -1,10 +1,12 @@
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';
10
12
  /** 本 preset 的身份:两条注册路径(`agent/created` 与 `agent-preset/selected`)共用一份。 */
@@ -13,6 +15,86 @@ const CAPITAL_PRESET_ID = 'capital-generation';
13
15
  // plugins/us-stock-analysis/skills,不在 <repo>/skills 底下。写死目录列表的表现不是报错,
14
16
  // 而是"用户在卡片上勾了那一格、技能永远不出现"(AGENTS.md §9.7)。
15
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
+ }
16
98
  function createFilesystemSkillProvider(ctx, control, config) {
17
99
  return new FileSystemSkillProvider(ctx, control, config);
18
100
  }
@@ -95,21 +177,71 @@ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvi
95
177
  customSkillDirs: SKILL_ROOTS,
96
178
  watch: true,
97
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
+ };
98
194
  const provider = {
99
195
  name: PROVIDER_NAME,
100
196
  async list(options) {
101
197
  // 调用期现读:开关是 settings 偏好,不许在 Agent 创建时定格。
102
198
  const enabled = selectedNames(readSelection(selection));
103
199
  // 全关是默认常态:短路掉,不碰快照目录(filesystem watcher 在它的 list()
104
- // 里才启动,短路保证默认关闭的会话不起任何 watcher)。
105
- if (enabled.size === 0)
200
+ // 里才启动,短路保证默认关闭的会话不起任何 watcher),**也不遮蔽任何内置 skill**:
201
+ // 一颗都没启用时用户看到的必须与没有这个 provider 时逐字相同。
202
+ if (enabled.size === 0) {
203
+ runtimeSummaries = [];
106
204
  return [];
205
+ }
107
206
  const result = await filesystemProvider.list(options);
108
- if (Array.isArray(result))
109
- return result.filter((candidate) => enabled.has(candidate.name));
110
- return { ...result, candidates: result.candidates.filter((candidate) => enabled.has(candidate.name)) };
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 };
111
227
  },
112
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
+ }
113
245
  if (!selectedNames(readSelection(selection)).has(candidate.name)) {
114
246
  throw new Error(`selected skill is not enabled: ${candidate.name}`);
115
247
  }
package/lib/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare const name = "capital-generation";
7
7
  * 取数配置所在的 **profile 条目 id**(随包的 `capital-config` 行)。0.1.7 的 settings 面
8
8
  * 以条目 id 为命名空间,本插件经 `settings.describe()` 按它读回用户配的引用名与开关。
9
9
  *
10
- * 这个字符串有三处必须一致:`cordis.patch.yml` 的行 id、`capital-config/index.js` 的
10
+ * 这个字符串有三处必须一致:`cordis.patch.yml` 的行 id、`config/index.js` 的
11
11
  * `SETTINGS_ENTRY_ID`、以及浏览器半边 `client.src.cjs` 的 `ENTRY_ID`;由
12
12
  * `test/capital-config.test.mjs` 逐一对齐(漂移的表现为卡片静默消失)。
13
13
  */
package/lib/index.js CHANGED
@@ -28,7 +28,7 @@ export const name = 'capital-generation';
28
28
  * 取数配置所在的 **profile 条目 id**(随包的 `capital-config` 行)。0.1.7 的 settings 面
29
29
  * 以条目 id 为命名空间,本插件经 `settings.describe()` 按它读回用户配的引用名与开关。
30
30
  *
31
- * 这个字符串有三处必须一致:`cordis.patch.yml` 的行 id、`capital-config/index.js` 的
31
+ * 这个字符串有三处必须一致:`cordis.patch.yml` 的行 id、`config/index.js` 的
32
32
  * `SETTINGS_ENTRY_ID`、以及浏览器半边 `client.src.cjs` 的 `ENTRY_ID`;由
33
33
  * `test/capital-config.test.mjs` 逐一对齐(漂移的表现为卡片静默消失)。
34
34
  */
@@ -131,7 +131,7 @@ const RetrieverSchema = z.object({
131
131
  paddleOcr: PaddleOcrSchema.default({ ...PADDLE_OCR_DEFAULTS })
132
132
  .description('PaddleOCR 文档解析(ocr 工具)配置;Token 走 credentials 引用 PADDLE_OCR_TOKEN'),
133
133
  });
134
- // 开关清单只有一处来源:selected-skills 的 catalog。卡片那侧(capital-config/index.js)一直按
134
+ // 开关清单只有一处来源:selected-skills 的 catalog。卡片那侧(config/index.js)一直按
135
135
  // catalog 现算,主插件 Config 却手抄过一份 key 列表——接第三颗快照时这两处就漂了(默认值在
136
136
  // 条目→主插件 边界被静默丢弃,表现是卡片能拨、运行期读不到)。新增快照因此不必再动 src/。
137
137
  const SelectedSkillsSchema = z.object(Object.fromEntries(SELECTED_SKILL_CATALOG.map(({ key, name }) => [
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Fuyao REST 的最小请求内核:**零 import 的叶子模块**。
3
3
  *
4
- * 为什么单独一份:host 平面的嵌套包(`capital-watchlist/index.js`)与插件侧的数据能力
4
+ * 为什么单独一份:host 平面的嵌套包(`watchlist/index.js`)与插件侧的数据能力
5
5
  * (`fuyao-rest.ts`)必须共用同一套 base URL、鉴权头、信封 `code` 判定与错误文案——
6
6
  * "同一知识只有一份实现"(AGENTS.md §9.7)。这一层一旦被别的模块拖进来,host 半边
7
7
  * 就会顺带加载整棵插件依赖树,所以这里不放校验器、能力定义、也不放任何类型依赖。
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Fuyao REST 的最小请求内核:**零 import 的叶子模块**。
3
3
  *
4
- * 为什么单独一份:host 平面的嵌套包(`capital-watchlist/index.js`)与插件侧的数据能力
4
+ * 为什么单独一份:host 平面的嵌套包(`watchlist/index.js`)与插件侧的数据能力
5
5
  * (`fuyao-rest.ts`)必须共用同一套 base URL、鉴权头、信封 `code` 判定与错误文案——
6
6
  * "同一知识只有一份实现"(AGENTS.md §9.7)。这一层一旦被别的模块拖进来,host 半边
7
7
  * 就会顺带加载整棵插件依赖树,所以这里不放校验器、能力定义、也不放任何类型依赖。
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 腾讯公开接口的最小内核:**零 import 的叶子模块**(只依赖 `net/throttle.js`,它自己也是叶子)。
3
3
  *
4
- * 为什么单独一份:host 平面的自选股(`capital-watchlist/index.js`)与插件侧的行情能力
4
+ * 为什么单独一份:host 平面的自选股(`watchlist/index.js`)与插件侧的行情能力
5
5
  * (`tencent-http.ts`)必须共用三样东西——同一条出口节流链、同一份港美股字段位序表、同一套守卫。
6
6
  * 两条独立节流等于没有限流(东财那次的教训),两份位序表迟早只会修好一份(AGENTS.md §9.7)。
7
7
  * 这一层一旦被别的模块拖进来,host 半边就会顺带加载整棵插件依赖树,所以这里不放能力定义、
@@ -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";
9
+ export declare const LOCAL_FETCH_CLIENT_VERSION = "2.7.11";
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.9';
10
+ export const LOCAL_FETCH_CLIENT_VERSION = '2.7.11';
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.9";
55
+ export declare const WIND_CLIENT_VERSION = "2.7.11";
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.9';
38
+ export const WIND_CLIENT_VERSION = '2.7.11';
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.9",
3
+ "version": "2.7.11",
4
4
  "description": "Capital Generation:面向中国散户的 AI 投资助手插件(Capital mode Agent Preset)",
5
5
  "icon": "assets/cg-icon.png",
6
6
  "author": "v587d",
@@ -29,8 +29,8 @@
29
29
  "lib",
30
30
  "locale",
31
31
  "chart-ui",
32
- "capital-config",
33
- "capital-watchlist",
32
+ "config",
33
+ "watchlist",
34
34
  "preset",
35
35
  "selected-skills/catalog.js",
36
36
  "selected-skills/README.md",
@@ -60,8 +60,8 @@
60
60
  "clean": "node -e \"import('node:fs').then(({ rmSync }) => rmSync('lib', { recursive: true, force: true }))\"",
61
61
  "build": "npm run clean && tsc && node scripts/copy-assets.mjs && npm run build:client && npm run build:config-client && npm run build:watchlist-client",
62
62
  "build:client": "node scripts/build-client.mjs",
63
- "build:config-client": "node scripts/build-capital-config-client.mjs",
64
- "build:watchlist-client": "node scripts/build-capital-watchlist-client.mjs",
63
+ "build:config-client": "node scripts/build-config-client.mjs",
64
+ "build:watchlist-client": "node scripts/build-watchlist-client.mjs",
65
65
  "dev": "tsc -w",
66
66
  "vendor:charts": "node scripts/vendor-lightweight-charts.mjs",
67
67
  "check:dsh": "node scripts/check-dsh-compat.mjs",