@v587d/capital-generation 2.6.5 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +79 -26
  3. package/THIRD_PARTY_NOTICES.md +43 -0
  4. package/capital-config/client.js +18 -2
  5. package/capital-config/client.src.cjs +161 -20
  6. package/capital-config/index.js +10 -0
  7. package/capital-config/package.json +1 -1
  8. package/capital-watchlist/client.js +48 -7
  9. package/capital-watchlist/client.src.cjs +383 -66
  10. package/capital-watchlist/holding-rules.js +64 -0
  11. package/capital-watchlist/index.js +117 -2
  12. package/capital-watchlist/package.json +2 -1
  13. package/chart-ui/package.json +1 -1
  14. package/lib/agents/selected-skills-provider.d.ts +26 -0
  15. package/lib/agents/selected-skills-provider.js +99 -0
  16. package/lib/index.d.ts +37 -0
  17. package/lib/index.js +45 -11
  18. package/lib/time/tools.d.ts +6 -0
  19. package/lib/time/tools.js +6 -1
  20. package/lib/watchlist/tools.d.ts +52 -0
  21. package/lib/watchlist/tools.js +146 -0
  22. package/lib/web-retriever/local-fetch.d.ts +1 -1
  23. package/lib/web-retriever/local-fetch.js +1 -1
  24. package/lib/web-retriever/wind-client.d.ts +1 -1
  25. package/lib/web-retriever/wind-client.js +1 -1
  26. package/package.json +12 -3
  27. package/preset/capital-generation/agent.patch.yml +7 -0
  28. package/preset/capital-generation/skills/capital-equity-research/SKILL.md +45 -0
  29. package/preset/capital-generation/skills/capital-event-impact/SKILL.md +29 -0
  30. package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +29 -0
  31. package/selected-skills/README.md +44 -0
  32. package/selected-skills/catalog.js +181 -0
  33. package/selected-skills/china-stock-research-skills/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  34. package/selected-skills/china-stock-research-skills/.github/ISSUE_TEMPLATE/skill_request.md +28 -0
  35. package/selected-skills/china-stock-research-skills/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  36. package/selected-skills/china-stock-research-skills/CHANGELOG.md +59 -0
  37. package/selected-skills/china-stock-research-skills/CONTRIBUTING.md +121 -0
  38. package/selected-skills/china-stock-research-skills/GITHUB_PUBLISHING.md +259 -0
  39. package/selected-skills/china-stock-research-skills/INSTALL.md +151 -0
  40. package/selected-skills/china-stock-research-skills/LICENSE +21 -0
  41. package/selected-skills/china-stock-research-skills/PUBLISH_CHECKLIST.md +165 -0
  42. package/selected-skills/china-stock-research-skills/README.en.md +209 -0
  43. package/selected-skills/china-stock-research-skills/README.md +209 -0
  44. package/selected-skills/china-stock-research-skills/RELEASE_ASSETS.md +222 -0
  45. package/selected-skills/china-stock-research-skills/ROADMAP.md +45 -0
  46. package/selected-skills/china-stock-research-skills/SKILLS.md +85 -0
  47. package/selected-skills/china-stock-research-skills/UPSTREAM.md +11 -0
  48. package/selected-skills/china-stock-research-skills/examples/full-report-flow.md +20 -0
  49. package/selected-skills/china-stock-research-skills/examples/module-select-cases.md +26 -0
  50. package/selected-skills/china-stock-research-skills/examples/quickstart-prompts.md +25 -0
  51. package/selected-skills/china-stock-research-skills/skills/business-decomposition-order-quality/SKILL.md +55 -0
  52. package/selected-skills/china-stock-research-skills/skills/business-decomposition-order-quality/references/idc-order-ledger-guide.md +34 -0
  53. package/selected-skills/china-stock-research-skills/skills/business-decomposition-order-quality/references/order-quality-checklist.md +16 -0
  54. package/selected-skills/china-stock-research-skills/skills/china-stock-research-orchestrator/SKILL.md +75 -0
  55. package/selected-skills/china-stock-research-skills/skills/china-stock-research-orchestrator/references/final-output-contract.md +62 -0
  56. package/selected-skills/china-stock-research-skills/skills/china-stock-research-orchestrator/references/routing-matrix.md +34 -0
  57. package/selected-skills/china-stock-research-skills/skills/china-stock-research-orchestrator/references/sector-overlay-escalation-rules.md +38 -0
  58. package/selected-skills/china-stock-research-skills/skills/financial-health/SKILL.md +54 -0
  59. package/selected-skills/china-stock-research-skills/skills/financial-health/references/idc-balance-sheet-stress-guide.md +31 -0
  60. package/selected-skills/china-stock-research-skills/skills/financial-health/references/red-flag-checklist.md +14 -0
  61. package/selected-skills/china-stock-research-skills/skills/industry-competition-moat/SKILL.md +54 -0
  62. package/selected-skills/china-stock-research-skills/skills/industry-competition-moat/references/idc-value-chain-and-peers.md +42 -0
  63. package/selected-skills/china-stock-research-skills/skills/industry-competition-moat/references/moat-grid.md +13 -0
  64. package/selected-skills/china-stock-research-skills/skills/risk-warning-catalysts/SKILL.md +54 -0
  65. package/selected-skills/china-stock-research-skills/skills/risk-warning-catalysts/references/idc-monitoring-indicators.md +33 -0
  66. package/selected-skills/china-stock-research-skills/skills/risk-warning-catalysts/references/monitoring-dashboard.md +25 -0
  67. package/selected-skills/china-stock-research-skills/skills/shared/references/analysis-pattern-selector.md +24 -0
  68. package/selected-skills/china-stock-research-skills/skills/shared/references/asset-heavy-ramp-overlay.md +28 -0
  69. package/selected-skills/china-stock-research-skills/skills/shared/references/business-model-transition-overlay.md +28 -0
  70. package/selected-skills/china-stock-research-skills/skills/shared/references/citation-standard.md +48 -0
  71. package/selected-skills/china-stock-research-skills/skills/shared/references/company-disclosure-map.md +37 -0
  72. package/selected-skills/china-stock-research-skills/skills/shared/references/consumer-channel-brand-overlay.md +29 -0
  73. package/selected-skills/china-stock-research-skills/skills/shared/references/cyclical-price-driven-overlay.md +29 -0
  74. package/selected-skills/china-stock-research-skills/skills/shared/references/export-manufacturing-supply-chain-overlay.md +29 -0
  75. package/selected-skills/china-stock-research-skills/skills/shared/references/financial-kpi-dictionary.md +43 -0
  76. package/selected-skills/china-stock-research-skills/skills/shared/references/idc-heavy-asset-transition-overlay.md +73 -0
  77. package/selected-skills/china-stock-research-skills/skills/shared/references/language-output-standard.md +36 -0
  78. package/selected-skills/china-stock-research-skills/skills/shared/references/project-order-business-overlay.md +28 -0
  79. package/selected-skills/china-stock-research-skills/skills/shared/references/regulated-healthcare-policy-overlay.md +29 -0
  80. package/selected-skills/china-stock-research-skills/skills/shared/references/research-output-template.md +50 -0
  81. package/selected-skills/china-stock-research-skills/skills/shared/references/sector-adaptation-checklist.md +42 -0
  82. package/selected-skills/china-stock-research-skills/skills/shared/references/source-registry.md +49 -0
  83. package/selected-skills/china-stock-research-skills/skills/shared/templates/catalyst-calendar.md +5 -0
  84. package/selected-skills/china-stock-research-skills/skills/shared/templates/company-fact-sheet.md +25 -0
  85. package/selected-skills/china-stock-research-skills/skills/shared/templates/evidence-table.md +5 -0
  86. package/selected-skills/china-stock-research-skills/skills/shared/templates/idc-project-ledger.md +5 -0
  87. package/selected-skills/china-stock-research-skills/skills/shared/templates/valuation-scenario-table.md +7 -0
  88. package/selected-skills/china-stock-research-skills/skills/strategy-business-transition/SKILL.md +55 -0
  89. package/selected-skills/china-stock-research-skills/skills/strategy-business-transition/references/idc-transition-markers.md +27 -0
  90. package/selected-skills/china-stock-research-skills/skills/strategy-business-transition/references/transition-scorecard.md +13 -0
  91. package/selected-skills/china-stock-research-skills/skills/valuation-investment-strategy/SKILL.md +55 -0
  92. package/selected-skills/china-stock-research-skills/skills/valuation-investment-strategy/references/idc-valuation-overlays.md +31 -0
  93. package/selected-skills/china-stock-research-skills/skills/valuation-investment-strategy/references/method-selection.md +14 -0
  94. package/selected-skills/investment-skills/.github/workflows/validate.yml +25 -0
  95. package/selected-skills/investment-skills/.tbd/config.yml +107 -0
  96. package/selected-skills/investment-skills/LICENSE +21 -0
  97. package/selected-skills/investment-skills/Makefile +29 -0
  98. package/selected-skills/investment-skills/README.md +253 -0
  99. package/selected-skills/investment-skills/UPSTREAM.md +12 -0
  100. package/selected-skills/investment-skills/docs/development.md +37 -0
  101. package/selected-skills/investment-skills/docs/roadmap.md +25 -0
  102. package/selected-skills/investment-skills/skills/buffett-investment-framework/SKILL.md +192 -0
  103. package/selected-skills/investment-skills/skills/buffett-investment-framework/agents/openai.yaml +4 -0
  104. package/selected-skills/investment-skills/skills/buffett-investment-framework/references/00-source-key.md +82 -0
  105. package/selected-skills/investment-skills/skills/buffett-investment-framework/references/01-decision-posture.md +242 -0
  106. package/selected-skills/investment-skills/skills/buffett-investment-framework/references/02-business-economics.md +188 -0
  107. package/selected-skills/investment-skills/skills/buffett-investment-framework/references/03-management-governance.md +213 -0
  108. package/selected-skills/investment-skills/skills/buffett-investment-framework/references/04-financial-reality.md +192 -0
  109. package/selected-skills/investment-skills/skills/buffett-investment-framework/references/05-valuation.md +166 -0
  110. package/selected-skills/investment-skills/skills/buffett-investment-framework/references/06-capital-allocation.md +238 -0
  111. package/selected-skills/investment-skills/skills/buffett-investment-framework/references/07-risk-monitoring.md +189 -0
  112. package/selected-skills/investment-skills/skills/buffett-investment-framework/references/08-specialized-overlays.md +309 -0
  113. package/selected-skills/investment-skills/skills/buffett-investment-framework/scripts/framework.py +470 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * 自选股的**类型表**与**持仓规则**——host 半边与浏览器半边共用这一份。
3
+ *
4
+ * 为什么单独成一个文件而不是各写各的:这两件事各自决定两处行为——
5
+ * · 「哪些标的能被标成持仓」决定面板的「更多」菜单画不画那一行,与 `POST /holding` 收不收这一格;
6
+ * · 「持仓比例加起来不能超过多少」决定步进器的 `+` 什么时候置灰,与宿主什么时候拒绝写入。
7
+ * 两处各抄一份的结局是"菜单里能点、宿主说 invalid_query"或反过来"菜单没得点、直接打路由却写进去了",
8
+ * 而两种都是不报错的错(AGENTS.md §9.7:同一知识只许有一份实现)。
9
+ * `client.src.cjs` 用 `require()` 引它,esbuild 在构建期把本文件内联进 `client.js`;
10
+ * `index.js` 用 `import` 引它。⛔ 本文件必须列进 `package.json` 的 `files`——
11
+ * 漏了的表现是装出去的插件 `ERR_MODULE_NOT_FOUND`、整条 `/capital-watchlist/*` 起不来,
12
+ * 而本机测试全绿(文件就在磁盘上)。闸门:`test/packaging.test.mjs`。
13
+ */
14
+ export const ASSET_TYPES = ['a-share', 'a-share-index', 'fund-etf', 'hk-stock', 'hk-index', 'us-stock', 'us-index']
15
+
16
+ /**
17
+ * 可以是一个**仓位**的类型:个股与场内基金。三种指数不在表上——指数不是一个可持有、
18
+ * 可配比率的标的(它是市场读数,不是券),所以它没有"持仓比例"这件事可言。
19
+ *
20
+ * ⛔ 这是**白名单**而不是"排除 `*-index`":未知或缺失的 `asset_type` 一律不可标。
21
+ * 判据写成反选时,将来往 `ASSET_TYPES` 里加任何一档都会自动获得持仓入口,
22
+ * 而那一步没人复核过;写成正选,新档默认没有这个入口,要给它就得改这一行——改这一行会红。
23
+ */
24
+ export const HOLDABLE_ASSET_TYPES = ['a-share', 'fund-etf', 'hk-stock', 'us-stock']
25
+
26
+ /** 这一条记录能不能被标成持仓(只看类型,不看它现在标没标:已标的取消走另一条路)。 */
27
+ export const isHoldable = (row) => HOLDABLE_ASSET_TYPES.includes(row?.asset_type)
28
+
29
+ /** 单只标的的占比上限(百分比)。 */
30
+ export const MAX_ROW_WEIGHT_PCT = 100
31
+
32
+ /**
33
+ * 全组合的占比合计上限:**只做多、不算 put**,所以留出 20 个百分点的余地给"用户自报的数
34
+ * 本来就凑不满 100%"与保证金敞口,再往上就不是一个说得出口的组合了(2026-10-09 用户点名:
35
+ * 面板原来允许两只各 100%,那明显不合理)。
36
+ *
37
+ * ⚠️ 这句话在四处得是同一条口径:这一行、`markHolding` 的拒绝、步进器的 `+` 置灰与
38
+ * `get_watchlist` 的 `description`。宿主是权威,面板只是提前把没额度这件事画出来。
39
+ */
40
+ export const MAX_TOTAL_WEIGHT_PCT = 120
41
+
42
+ /**
43
+ * 这一条占掉的权重(百分点)。**未填比例的持仓算 0**:勾上但没说数字,就没有任何敞口,
44
+ * 它与"这个数是零"在面板上仍是两句话(读数是 `0~上限` 而不是 `0%`),但在合计里都是 0。
45
+ * ⛔ `?? 0` 之前必须先判 `holding` 这一格在不在——`weight_pct: null` 与没有 `holding` 是两种状态。
46
+ */
47
+ export const holdingWeight = (row) => (
48
+ row?.holding === null || row?.holding === undefined ? 0 : (row?.holding?.weight_pct ?? 0)
49
+ )
50
+
51
+ /** 除这一条之外,其余持仓已经占掉的权重(百分点)——面板与宿主算额度的**同一个**减法。 */
52
+ export const otherHeldWeightPct = (rows, thscode) => rows.reduce(
53
+ (total, row) => (row?.thscode === thscode ? total : total + holdingWeight(row)),
54
+ 0,
55
+ )
56
+
57
+ /**
58
+ * 这一条最多能填到多少:单只上限与"总额度减去其余持仓"取小的,往下托到 0。
59
+ * 步进器用它决定 `+` 置灰与 `0~N` 这句提示;宿主用同一条减法拒绝写入。
60
+ */
61
+ export const weightCeiling = (rows, thscode) => Math.max(
62
+ 0,
63
+ Math.min(MAX_ROW_WEIGHT_PCT, MAX_TOTAL_WEIGHT_PCT - otherHeldWeightPct(rows, thscode)),
64
+ )
@@ -29,6 +29,7 @@ import {
29
29
  fetchSmartbox,
30
30
  normalizeHkCode,
31
31
  } from '../lib/sources/tencent-public-core.js'
32
+ import { ASSET_TYPES, isHoldable, weightCeiling, otherHeldWeightPct, MAX_ROW_WEIGHT_PCT, MAX_TOTAL_WEIGHT_PCT } from './holding-rules.js'
32
33
 
33
34
  export const name = 'capital-watchlist'
34
35
 
@@ -67,7 +68,9 @@ export const SEARCH_BRANCH_LIMITS = { 'a-share': 50, hk: 10, us: 10 }
67
68
  /** A 股那一路的搜索白名单(Fuyao 与报价端点双端都有可靠支撑);场外基金 / 北交所不进候选。
68
69
  * 港美股那两路各有自己的白名单(`SMARTBOX_STOCK_TYPES` + `SMARTBOX_INDEX_TYPE`),互不通用。 */
69
70
  export const SEARCH_ASSET_TYPES = 'a-share,a-share-index,fund-etf'
70
- const ASSET_TYPES = ['a-share', 'a-share-index', 'fund-etf', 'hk-stock', 'hk-index', 'us-stock', 'us-index']
71
+ // `ASSET_TYPES`(域里允许的类型)、"哪些类型可以是一个仓位"与"占比合计封顶多少"都住在
72
+ // `./holding-rules.js` **一份**里:浏览器半边(`client.src.cjs`)也引它,菜单画不画那一行、
73
+ // `+` 什么时候置灰,与这里收不收这一格不可能各说一套。
71
74
  /**
72
75
  * `exchange` 这一格存的是**市场**而不是交易所:A 股沿用 `SH`/`SZ`,港美股落 `HK`/`US`。
73
76
  * 具体交易所(美股的 `OQ` / `N` / `AM` / 粉单 `PS`)留在 canonical 代码里(`AAPL.OQ`)——
@@ -271,6 +274,24 @@ const itemSchema = z.object({
271
274
  * 永远带它——两个方向的兼容因此都不需要迁移、也不需要抬 domain version。
272
275
  */
273
276
  pinned_at: z.number().int().optional(),
277
+ /**
278
+ * 用户**自报**的持仓标记(缺这一格 = 不是持仓,只是关注)。
279
+ *
280
+ * 三态两轴,不许糊成一态:缺 `holding` = 未持仓;`weight_pct === null` = 标了持仓但比例没填;
281
+ * `0` = 用户真填了 0——与上面 `change_pct` 同一条口径,"没有这个数"永远不等于"这个数是零"。
282
+ * 所以这一格**外部可选、内部 nullable**:外面写成 `.nullable()` 会让"未持仓"与"持仓没填比例"
283
+ * 共用一种写法,里面写 `.default(null)` 会把磁盘上手改出来的缺字段读成一份 0% 仓位。
284
+ * 可选同样是为了不迁移、不抬 domain version(见上面 `pinned_at` 那一段)。
285
+ *
286
+ * `marked_at` 记的是**标记时刻**,不是"持仓起始日":比例归用户所有、没有到期日,但"这句话是
287
+ * 哪天说的"必须能自证——`get_watchlist` 把它交给主 Agent 判断要不要向用户复核。合计是否正好
288
+ * 100、分母含不含现金,仍不由我们校验(那本账只有用户自己知道怎么记的);我们只挡一个**上限**:
289
+ * 只做多、不算 put,全组合加起来不许超过 120%(判据在 `./holding-rules.js`,面板与宿主共用)。
290
+ */
291
+ holding: z.object({
292
+ weight_pct: z.number().min(0).max(100).nullable(),
293
+ marked_at: z.number().int(),
294
+ }).optional(),
274
295
  })
275
296
 
276
297
  /**
@@ -710,7 +731,9 @@ export function createWatchlistService(options = {}) {
710
731
  async function list() {
711
732
  await ensureSeeded()
712
733
  const opened = await domain()
713
- return { ok: true, items: rowsOf(opened.table('items')), seeded_at: opened.global.get().seeded_at }
734
+ // `limit` 跟着回包走(与 `add` 的 `list_full` 同一口径):上限这个数字归本包所有,
735
+ // 面板那句话和 `get_watchlist` 那句都不许自己抄一份。
736
+ return { ok: true, items: rowsOf(opened.table('items')), seeded_at: opened.global.get().seeded_at, limit: MAX_ITEMS }
714
737
  }
715
738
 
716
739
  async function add(input) {
@@ -799,6 +822,90 @@ export function createWatchlistService(options = {}) {
799
822
  })
800
823
  }
801
824
 
825
+ /**
826
+ * 标记持仓 = 给这一条写 `holding`;比例留空(`undefined` / `null` / 空串)表示"标了持仓、
827
+ * 比例还没填",落 `null` 而不是 0。`clearHolding` 抹掉这一格,关注条目本身不动。
828
+ *
829
+ * 与 `pin()` 同一条读—改—写口径:**锁内逐键重读**——刷新在途刚被用户删掉的那一行不许被旧记录
830
+ * 连同持仓一起写回来(那还会重新占掉一格)。比例只收 0–100,越界与"根本不是数"都回
831
+ * `invalid_query`:这一格是用户自报的权重,我们不替用户核对券商账,但**也不接一个明显不成立的数**。
832
+ * ⛔ `Number(null)` 是 0——空值一律先在这外面判成"未填",绝不放进 `numberValue` 硬转。
833
+ *
834
+ * 还有一道**类型闸门**(`isHoldable`,与面板共用 `./holding-rules.js` 那一份):指数不是一个可以
835
+ * 持有并配比率的标的。`marked_at` 每次写入都重打——它是"这句话最后一次说出来的时刻",
836
+ * 不是"仓位建立日";用户把 20% 调成 30%,那句自报的话确实就是刚刚说的。
837
+ *
838
+ * 第三道是**合计闸门**(`weightCeiling`,同一份模块):只做多、没有 put,所以全组合的自报占比
839
+ * 合计封顶 120%(2026-10-09 用户点名"我居然可以对多只标的都填超过 50%")。额度按**其余持仓**
840
+ * 算,不含这一格自己,所以 lowering 永远走得通。⛔ 勾上但没填(`weight === null`)不加任何敞口,
841
+ * 因此**不因合计被拒**——否则一条手改出来的超额数据会把"先勾上再慢慢填"这条路一起堵死。
842
+ */
843
+ async function markHolding(input) {
844
+ await ensureSeeded()
845
+ const items = await table()
846
+ const thscode = normalizeThscode(input?.thscode)
847
+ if (thscode === undefined) return { ok: false, code: 'invalid_query', message: '代码格式不符:要带市场或交易所后缀的完整代码' }
848
+ const raw = input?.weight_pct
849
+ let weight = null
850
+ if (raw !== undefined && raw !== null && raw !== '') {
851
+ const parsed = numberValue(raw)
852
+ if (parsed === undefined || parsed < 0 || parsed > MAX_ROW_WEIGHT_PCT) {
853
+ return { ok: false, code: 'invalid_query', message: `持仓比例要 0 到 ${MAX_ROW_WEIGHT_PCT} 之间的数字;留空表示只标记持仓、不填比例` }
854
+ }
855
+ weight = parsed
856
+ }
857
+ return serialize(async () => {
858
+ const existing = items.get(thscode)
859
+ if (existing === undefined) return { ok: false, code: 'not_found', message: '该标的不在自选清单里' }
860
+ // 类型闸门排在 `not_found` **之后**:一个根本不在这份清单里的代码,该听到的是"不在清单里",
861
+ // 不是"指数不能标持仓"——后者会把人引去查类型,而真正的问题是那一行不存在。
862
+ // `existing.holding === undefined` 这一半是给"重标"留的门:已经标过的行(包括在旧版本或
863
+ // 手改数据里标上的指数)改比例仍走得通,⛔ 否则那一格会退不掉——面板不画取消出口、宿主又不收。
864
+ // 只拦"标"、不拦"取消"(见 `clearHolding`):抹一格不声明任何领域事实。
865
+ if (existing.holding === undefined && !isHoldable(existing)) {
866
+ return { ok: false, code: 'invalid_query', message: '这一条不是一个可以持有并配比率的标的(指数是市场读数,不是券),因此不能标记为持仓' }
867
+ }
868
+ // 合计闸门也排在锁内:额度取决于**其余持仓此刻**是多少,读—判—写不在同一环里就会
869
+ // 被并发写入各放一只过去(两行都看到"还有额度",落盘的合计却超了)。
870
+ if (weight !== null) {
871
+ const rows = rowsOf(items)
872
+ const ceiling = weightCeiling(rows, thscode)
873
+ if (weight > ceiling) {
874
+ return { ok: false, code: 'invalid_query', message: `其余持仓已合计 ${otherHeldWeightPct(rows, thscode)}%,这一格最多只能填 ${ceiling}%(只做多的组合合计不超过 ${MAX_TOTAL_WEIGHT_PCT}%)` }
875
+ }
876
+ }
877
+ const item = { ...existing, holding: { weight_pct: weight, marked_at: now() } }
878
+ await items.put(thscode, item)
879
+ return { ok: true, item }
880
+ })
881
+ }
882
+
883
+ /**
884
+ * 取消持仓 = 写回一份**没有** `holding` 那一格的记录。
885
+ *
886
+ * 不删键再插:官方单文件后端是 `Map` + 整份 JSON 原子写,`put` 已有键保留原位置,
887
+ * 而删了重插会把这一行甩到插入顺序的末尾——用户取消一个持仓标记,清单却跟着重排,
888
+ * 那是"动作与位移对不上"的静默副作用(与 `pin()` 不改键序同一条理由,设计文档 §3.2)。
889
+ *
890
+ * ⛔ 这里**故意不查类型**:`markHolding` 拒的是"把指数说成仓位"这句新话,`clearHolding` 只是
891
+ * 不再说那句话。两边都拦,一条遗留或手改出来的"指数持仓"就永远退不掉——面板不画取消出口
892
+ * (它按类型决定画不画),宿主又拒绝写,那一格被锁死在域里。
893
+ */
894
+ async function clearHolding(input) {
895
+ await ensureSeeded()
896
+ const items = await table()
897
+ const thscode = normalizeThscode(input?.thscode)
898
+ if (thscode === undefined) return { ok: false, code: 'invalid_query', message: '代码格式不符:要带市场或交易所后缀的完整代码' }
899
+ return serialize(async () => {
900
+ const existing = items.get(thscode)
901
+ if (existing === undefined) return { ok: false, code: 'not_found', message: '该标的不在自选清单里' }
902
+ const item = { ...existing }
903
+ delete item.holding
904
+ await items.put(thscode, item)
905
+ return { ok: true, item }
906
+ })
907
+ }
908
+
802
909
  /** 一个分组 = 一次快照请求(只有场内 ETF 那一档是逐只);单组失败只记该组,不拖垮其它组。 */
803
910
  function fetchGroup(group) {
804
911
  return group.kind === 'tencent' ? fetchOffshoreGroup(group) : fetchFuyaoGroup(group)
@@ -994,6 +1101,8 @@ export function createWatchlistService(options = {}) {
994
1101
  add,
995
1102
  remove,
996
1103
  pin,
1104
+ markHolding,
1105
+ clearHolding,
997
1106
  refresh,
998
1107
  async close() {
999
1108
  const opened = await domainPromise
@@ -1131,6 +1240,12 @@ export function createRouteHandler(service, options = {}) {
1131
1240
  if (method === 'POST' && pathname === '/add') return sendBusiness(res, await service.add(input))
1132
1241
  if (method === 'POST' && pathname === '/remove') return sendBusiness(res, await service.remove(input))
1133
1242
  if (method === 'POST' && pathname === '/pin') return sendBusiness(res, await service.pin(input))
1243
+ if (method === 'POST' && pathname === '/holding') {
1244
+ // 标记与取消共用一条路由、靠 body 里那一个布尔分流:面板上的概念只有一个("这条是不是
1245
+ // 我的持仓"),拆两条要多一套字典与一份围栏回归,而两者差别只是写不写那一格。
1246
+ const next = input?.clear === true ? service.clearHolding(input) : service.markHolding(input)
1247
+ return sendBusiness(res, await next)
1248
+ }
1134
1249
  if (method === 'POST' && pathname === '/refresh') {
1135
1250
  // 刷新是"部分成功 + 逐条失败"的形态:整批失败也带 items 与 error 回来,
1136
1251
  // 面板按 error.code 居中显示,绝不清空列表。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/capital-watchlist",
3
- "version": "2.6.5",
3
+ "version": "2.7.0",
4
4
  "description": "Capital 模式的用户自选股(宿主级清单 + Fuyao 报价快照 + /capital-watchlist 路由 + 输入框菜单弹窗):由 @v587d/capital-generation 随包携带并作为 host 平面行挂载。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,6 +31,7 @@
31
31
  },
32
32
  "files": [
33
33
  "index.js",
34
+ "holding-rules.js",
34
35
  "client.js",
35
36
  "package.json"
36
37
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/capital-charts",
3
- "version": "2.6.5",
3
+ "version": "2.7.0",
4
4
  "description": "Capital 模式的图表前端组件(浏览器半边 + 序列旁路路由):由 @v587d/capital-generation 随包携带并作为 host 平面行挂载。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,26 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { SkillProviderControl } from '@deepseek-ai/dsh-skill';
3
+ import { FileSystemSkillProvider } from '@deepseek-ai/dsh-skill-filesystem';
4
+ import { type PolicyContext } from './root-tool-policy.js';
5
+ export type SelectedSkillStatus = 'experimental' | 'watchlist';
6
+ export type SelectedSkillCatalogEntry = {
7
+ key: string;
8
+ name: string;
9
+ status: SelectedSkillStatus;
10
+ tags: readonly string[];
11
+ };
12
+ export declare const SELECTED_SKILL_CATALOG: readonly SelectedSkillCatalogEntry[];
13
+ export type SelectedSkillKey = typeof SELECTED_SKILL_CATALOG[number]['key'];
14
+ export type SelectedSkillSelection = Partial<Record<SelectedSkillKey, boolean>>;
15
+ export type SelectedSkillSelectionSource = SelectedSkillSelection | (() => SelectedSkillSelection);
16
+ type FileSystemSkillConfig = {
17
+ providerName: string;
18
+ includeDefaultRoots: false;
19
+ customSkillDirs: string[];
20
+ watch: true;
21
+ };
22
+ type FileSystemSkillSource = Pick<FileSystemSkillProvider, 'list' | 'get' | 'dispose'>;
23
+ type FileSystemSkillFactory = (ctx: Context, control: SkillProviderControl, config: FileSystemSkillConfig) => FileSystemSkillSource;
24
+ /** Register explicitly selected community experiments on the Capital root Agent only. */
25
+ export declare function registerRootSelectedSkills(ctx: PolicyContext, selection: SelectedSkillSelectionSource, createFilesystemProvider?: FileSystemSkillFactory): void;
26
+ export {};
@@ -0,0 +1,99 @@
1
+ import { FileSystemSkillProvider } from '@deepseek-ai/dsh-skill-filesystem';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { agentSessionId, isRootAgent } from './root-tool-policy.js';
4
+ // The catalog is a shipped data file outside the TypeScript root; runtime keeps this
5
+ // import so the published lib resolves the same metadata beside the pinned snapshots.
6
+ // @ts-expect-error catalog.js is intentionally maintained as package data.
7
+ import { SELECTED_SKILL_CATALOG as CATALOG } from '../../selected-skills/catalog.js';
8
+ export const SELECTED_SKILL_CATALOG = CATALOG;
9
+ const PROVIDER_NAME = 'capital-selected-skills';
10
+ const SKILL_ROOTS = [
11
+ fileURLToPath(new URL('../../selected-skills/investment-skills/skills', import.meta.url)),
12
+ fileURLToPath(new URL('../../selected-skills/china-stock-research-skills/skills', import.meta.url)),
13
+ ];
14
+ function createFilesystemSkillProvider(ctx, control, config) {
15
+ return new FileSystemSkillProvider(ctx, control, config);
16
+ }
17
+ function selectedNames(selection) {
18
+ return new Set(SELECTED_SKILL_CATALOG
19
+ .filter(({ key }) => selection[key] === true)
20
+ .map(({ name }) => name));
21
+ }
22
+ function readSelection(source) {
23
+ return typeof source === 'function' ? source() : source;
24
+ }
25
+ function strictCapitalPreset(ctx, agent) {
26
+ try {
27
+ const presets = ctx.get?.('agentPresets');
28
+ return typeof presets?.composedPreset === 'function'
29
+ && presets.composedPreset(agent.ctx) === 'capital-generation';
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /** Register explicitly selected community experiments on the Capital root Agent only. */
36
+ export function registerRootSelectedSkills(ctx, selection, createFilesystemProvider = createFilesystemSkillProvider) {
37
+ // Static callers retain the cheap all-disabled fast path. The Capital plugin
38
+ // passes a reader so a settings toggle is observed for the next root Agent.
39
+ if (typeof selection !== 'function' && selectedNames(selection).size === 0)
40
+ return;
41
+ const listenCtx = ctx.root ?? ctx;
42
+ if (typeof listenCtx.on !== 'function')
43
+ return;
44
+ const stop = listenCtx.on('agent/created', ({ agent }) => {
45
+ if (!agent || !isRootAgent(agent) || !strictCapitalPreset(ctx, agent))
46
+ return;
47
+ const enabled = selectedNames(readSelection(selection));
48
+ if (enabled.size === 0)
49
+ return;
50
+ const agentCtx = agent.ctx;
51
+ if (!agentCtx || typeof agentCtx.inject !== 'function') {
52
+ ctx.logger?.warn?.('capital-generation: selected skills are unavailable (root Agent cannot inject skills)');
53
+ return;
54
+ }
55
+ agentCtx.inject(['skills'], (injectedCtx) => {
56
+ const skills = injectedCtx.skills;
57
+ if (typeof skills?.registerProvider !== 'function') {
58
+ ctx.logger?.warn?.('capital-generation: selected skills are unavailable (root Agent has no skills registry)');
59
+ return;
60
+ }
61
+ let filesystemProvider;
62
+ injectedCtx.effect(() => {
63
+ const unregister = skills.registerProvider((control) => {
64
+ filesystemProvider = createFilesystemProvider(injectedCtx, control, {
65
+ providerName: PROVIDER_NAME,
66
+ includeDefaultRoots: false,
67
+ customSkillDirs: SKILL_ROOTS,
68
+ watch: true,
69
+ });
70
+ const provider = {
71
+ name: PROVIDER_NAME,
72
+ async list(options) {
73
+ const result = await filesystemProvider.list(options);
74
+ if (Array.isArray(result))
75
+ return result.filter((candidate) => enabled.has(candidate.name));
76
+ return { ...result, candidates: result.candidates.filter((candidate) => enabled.has(candidate.name)) };
77
+ },
78
+ get(candidate, options) {
79
+ if (!enabled.has(candidate.name)) {
80
+ throw new Error(`selected skill is not enabled: ${candidate.name}`);
81
+ }
82
+ return filesystemProvider.get(candidate, options);
83
+ },
84
+ };
85
+ return provider;
86
+ });
87
+ return () => {
88
+ unregister();
89
+ void filesystemProvider?.dispose();
90
+ filesystemProvider = undefined;
91
+ };
92
+ }, `capital-generation: selected skills for ${agentSessionId(agent) ?? 'root'}`);
93
+ });
94
+ });
95
+ ctx.effect?.(() => () => {
96
+ if (typeof stop === 'function')
97
+ stop();
98
+ }, 'capital-generation: selected skill root listener');
99
+ }
package/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
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
4
  /** Internal plugin name used by the Capital mode preset. */
4
5
  export declare const name = "capital-generation";
5
6
  /**
@@ -81,6 +82,8 @@ export declare function resolveLocalFetchConfig(config?: LocalFetchConfig): Requ
81
82
  export interface Config {
82
83
  /** Optional additive persona override; core safety guidance is preserved. */
83
84
  customPersona?: string;
85
+ /** Community experiments explicitly selected for the Capital root Agent only. */
86
+ selectedSkills?: SelectedSkillSelection;
84
87
  /** Fuyao credentials 引用名;空值回退到 FUYAO_API_KEY。 */
85
88
  fuyaoCredentialRef?: string;
86
89
  /** web_retriever 配置(可选;缺省使用 AnySearch 默认地址与凭据名)。 */
@@ -89,6 +92,23 @@ export interface Config {
89
92
  /** DSH 0.1.2-rc.1 configuration schema. */
90
93
  export declare const Config: z<Schemastery.ObjectS<NoInfer<{
91
94
  customPersona: z<string, string, "defined">;
95
+ selectedSkills: z<Schemastery.ObjectS<NoInfer<{
96
+ buffettFramework: z<boolean, boolean, "defined">;
97
+ financialHealth: z<boolean, boolean, "defined">;
98
+ riskWarningCatalysts: z<boolean, boolean, "defined">;
99
+ valuationInvestmentStrategy: z<boolean, boolean, "defined">;
100
+ strategyBusinessTransition: z<boolean, boolean, "defined">;
101
+ industryCompetitionMoat: z<boolean, boolean, "defined">;
102
+ businessDecompositionOrderQuality: z<boolean, boolean, "defined">;
103
+ }>>, Schemastery.ObjectT<NoInfer<{
104
+ buffettFramework: z<boolean, boolean, "defined">;
105
+ financialHealth: z<boolean, boolean, "defined">;
106
+ riskWarningCatalysts: z<boolean, boolean, "defined">;
107
+ valuationInvestmentStrategy: z<boolean, boolean, "defined">;
108
+ strategyBusinessTransition: z<boolean, boolean, "defined">;
109
+ industryCompetitionMoat: z<boolean, boolean, "defined">;
110
+ businessDecompositionOrderQuality: z<boolean, boolean, "defined">;
111
+ }>>, "defined">;
92
112
  fuyaoCredentialRef: z<string, string, "defined">;
93
113
  retriever: z<Schemastery.ObjectS<NoInfer<{
94
114
  baseURL: z<string, string, "defined">;
@@ -169,6 +189,23 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
169
189
  }>>, "defined">;
170
190
  }>>, Schemastery.ObjectT<NoInfer<{
171
191
  customPersona: z<string, string, "defined">;
192
+ selectedSkills: z<Schemastery.ObjectS<NoInfer<{
193
+ buffettFramework: z<boolean, boolean, "defined">;
194
+ financialHealth: z<boolean, boolean, "defined">;
195
+ riskWarningCatalysts: z<boolean, boolean, "defined">;
196
+ valuationInvestmentStrategy: z<boolean, boolean, "defined">;
197
+ strategyBusinessTransition: z<boolean, boolean, "defined">;
198
+ industryCompetitionMoat: z<boolean, boolean, "defined">;
199
+ businessDecompositionOrderQuality: z<boolean, boolean, "defined">;
200
+ }>>, Schemastery.ObjectT<NoInfer<{
201
+ buffettFramework: z<boolean, boolean, "defined">;
202
+ financialHealth: z<boolean, boolean, "defined">;
203
+ riskWarningCatalysts: z<boolean, boolean, "defined">;
204
+ valuationInvestmentStrategy: z<boolean, boolean, "defined">;
205
+ strategyBusinessTransition: z<boolean, boolean, "defined">;
206
+ industryCompetitionMoat: z<boolean, boolean, "defined">;
207
+ businessDecompositionOrderQuality: z<boolean, boolean, "defined">;
208
+ }>>, "defined">;
172
209
  fuyaoCredentialRef: z<string, string, "defined">;
173
210
  retriever: z<Schemastery.ObjectS<NoInfer<{
174
211
  baseURL: z<string, string, "defined">;
package/lib/index.js CHANGED
@@ -8,10 +8,12 @@ import { createWindSources } from './sources/wind-mcp.js';
8
8
  import { registerDataCollectorTools } from './data-collector/tools.js';
9
9
  import { registerDatasetTools } from './data-collector/dataset-tools.js';
10
10
  import { registerTimeTool } from './time/tools.js';
11
+ import { registerWatchlistTool } from './watchlist/tools.js';
11
12
  import { registerChartTool } from './chart/tool.js';
12
13
  import { createChartEventPublisher } from './chart/events.js';
13
14
  import { registerRootToolPolicy, rootPolicyProbes } from './agents/root-tool-policy.js';
14
15
  import { registerBashGuard } from './agents/bash-guard.js';
16
+ import { registerRootSelectedSkills } from './agents/selected-skills-provider.js';
15
17
  import { ChartSourceTokenStore, registerChartSourceTool } from './chart/source-token.js';
16
18
  import { ChartArtifactRegistry } from './chart/artifact-ref.js';
17
19
  import { WebRetriever } from './web-retriever/retriever.js';
@@ -129,11 +131,29 @@ const RetrieverSchema = z.object({
129
131
  paddleOcr: PaddleOcrSchema.default({ ...PADDLE_OCR_DEFAULTS })
130
132
  .description('PaddleOCR 文档解析(ocr 工具)配置;Token 走 credentials 引用 PADDLE_OCR_TOKEN'),
131
133
  });
134
+ const SelectedSkillsSchema = z.object({
135
+ buffettFramework: z.boolean().default(false).description('Enable the Finterm Buffett framework experiment for the Capital root Agent'),
136
+ financialHealth: z.boolean().default(false).description('Enable the China-stock financial health experiment for the Capital root Agent'),
137
+ riskWarningCatalysts: z.boolean().default(false).description('Enable the China-stock risk and catalyst monitoring experiment for the Capital root Agent'),
138
+ valuationInvestmentStrategy: z.boolean().default(false).description('Enable the China-stock valuation experiment for the Capital root Agent'),
139
+ strategyBusinessTransition: z.boolean().default(false).description('Enable the China-stock business transition experiment for the Capital root Agent'),
140
+ industryCompetitionMoat: z.boolean().default(false).description('Enable the China-stock industry and competition experiment for the Capital root Agent'),
141
+ businessDecompositionOrderQuality: z.boolean().default(false).description('Enable the China-stock order quality experiment for the Capital root Agent'),
142
+ }).default({
143
+ buffettFramework: false,
144
+ financialHealth: false,
145
+ riskWarningCatalysts: false,
146
+ valuationInvestmentStrategy: false,
147
+ strategyBusinessTransition: false,
148
+ industryCompetitionMoat: false,
149
+ businessDecompositionOrderQuality: false,
150
+ });
132
151
  /** DSH 0.1.2-rc.1 configuration schema. */
133
152
  export const Config = z.object({
134
153
  customPersona: z.string()
135
154
  .default('')
136
155
  .description('Capital 模式 的附加人设文本(独立 section,非 deployment:persona);核心安全约束始终保留'),
156
+ selectedSkills: SelectedSkillsSchema.description('Community selected-skill experiments; default off and visible only to the Capital root Agent'),
137
157
  fuyaoCredentialRef: z.string()
138
158
  .default('FUYAO_API_KEY')
139
159
  .description('Fuyao credentials 引用名,空 = FUYAO_API_KEY'),
@@ -180,17 +200,27 @@ export function apply(ctx, config) {
180
200
  // `.volatile()` 声明,`describe()` 按条目 id 返回解析后的值。所以我们按随包的
181
201
  // `capital-config` **条目 id** 找那一行,而不是按自造的命名空间。
182
202
  const settings = ctx.get('settings');
183
- let effectiveConfig = config;
184
- try {
185
- const resolved = settings?.describe?.().find((entry) => entry.ns === CAPITAL_CONFIG_ENTRY_ID)?.value;
186
- // 这里再用本插件的 Config 过一遍:既补默认值,也把 schema 漂移变成可查的错误,
187
- // 而不是把 capital-config schema 里的未知/缺失字段静默带进运行期配置。
188
- if (resolved && typeof resolved === 'object')
189
- effectiveConfig = Config(resolved);
190
- }
191
- catch {
192
- // 条目未注册或解析失败时沿用 preset 配置。
193
- }
203
+ const readEffectiveConfig = () => {
204
+ let resolvedConfig = config;
205
+ try {
206
+ const resolved = settings?.describe?.().find((entry) => entry.ns === CAPITAL_CONFIG_ENTRY_ID)?.value;
207
+ // 这里再用本插件的 Config 过一遍:既补默认值,也把 schema 漂移变成可查的错误,
208
+ // 而不是把 capital-config schema 里的未知/缺失字段静默带进运行期配置。
209
+ if (resolved && typeof resolved === 'object')
210
+ resolvedConfig = Config(resolved);
211
+ }
212
+ catch {
213
+ // 条目未注册或解析失败时沿用 preset 配置。
214
+ }
215
+ return resolvedConfig;
216
+ };
217
+ const effectiveConfig = readEffectiveConfig();
218
+ // selectedSkills 是 live 的 settings 偏好:按 root Agent 创建时重读;无 settings
219
+ // 服务时沿用 preset 配置,避免为静态关闭配置额外挂一个永远空转的监听器。
220
+ const selectedSkills = typeof settings?.describe === 'function'
221
+ ? () => readEffectiveConfig().selectedSkills ?? {}
222
+ : effectiveConfig.selectedSkills ?? {};
223
+ registerRootSelectedSkills(ctx, selectedSkills);
194
224
  // ── data_collector 域:宿主侧 Dataset 落盘 + 取数执行器 ─────────────────────
195
225
  // 落盘通过 DSH 官方 fs/sandbox 服务完成,根目录恒为调用方 session 的
196
226
  // workspace cwd(exec.agent.session.header.cwd),服从当前 permission。
@@ -234,6 +264,10 @@ export function apply(ctx, config) {
234
264
  // store 只用于 resolve_data_time_range 的 Dataset 形态(按该 Dataset 时间列的偏移
235
265
  // 解析窗口边界)——data_junior 因此不必自己把日期换算成毫秒。
236
266
  registerTimeTool(ctx, store);
267
+ // 用户的自选股(含他自己在面板里标的持仓与占比):只读,数据归 host 平面的 capitalWatchlist 服务。
268
+ // 主 Agent 是它的消费者,所以不进 ROOT_AGENT_DENIED_TOOLS;通用 subagent 行必须显式 deny
269
+ // (这一格里有用户的仓位),四个专用 child 走 allow 白名单、天然拿不到。
270
+ registerWatchlistTool(ctx);
237
271
  // 呈现层图表工具:只有 data_junior 创建的 visualization_specialist 能调用它
238
272
  // (主 Agent 的入口由 registerRootToolPolicy 在它自己的 agent scope 上 deny 掉)。
239
273
  // 只回小回执、不回原始行;序列落在 workspace 产物里,
@@ -58,6 +58,12 @@ export declare function periodInputOf(value: unknown): string | {
58
58
  } | undefined;
59
59
  export declare function dateParts(value: string): DateParts;
60
60
  export declare function shiftDate(value: string, unit: 'day' | 'week' | 'month' | 'quarter' | 'year', amount: number): string;
61
+ /**
62
+ * 两个日历日之间相差的天数(同区间的 `YYYY-MM-DD` 对,按 UTC 午夜算,不受时区与夏令时影响)。
63
+ * 对外只有一份:`get_watchlist` 报"这条持仓标了几天"用的就是它,不许在消费方再写一遍
64
+ * `Date.parse(...)/86_400_000`(AGENTS.md §9.7①)。
65
+ */
66
+ export declare function dayDistance(start: string, end: string): number;
61
67
  /**
62
68
  * 单窗口解析(**共享**入口):`resolve_data_time_range`(能力参数那个模式)与
63
69
  * `time-axis.ts`(Dataset 时间轴模式)都用它,避免两处对同一个 period 词表给出不同答案。
package/lib/time/tools.js CHANGED
@@ -266,7 +266,12 @@ function zonedDateEpoch(value, zone, endOfDay = false) {
266
266
  const base = Date.UTC(parts.year, parts.month - 1, parts.day, endOfDay ? 23 : 0, endOfDay ? 59 : 0, endOfDay ? 59 : 0, endOfDay ? 999 : 0);
267
267
  return base - zoneOffsetMsByDiff(base, zone);
268
268
  }
269
- function dayDistance(start, end) {
269
+ /**
270
+ * 两个日历日之间相差的天数(同区间的 `YYYY-MM-DD` 对,按 UTC 午夜算,不受时区与夏令时影响)。
271
+ * 对外只有一份:`get_watchlist` 报"这条持仓标了几天"用的就是它,不许在消费方再写一遍
272
+ * `Date.parse(...)/86_400_000`(AGENTS.md §9.7①)。
273
+ */
274
+ export function dayDistance(start, end) {
270
275
  const from = dateParts(start);
271
276
  const to = dateParts(end);
272
277
  return Math.round((Date.UTC(to.year, to.month - 1, to.day) - Date.UTC(from.year, from.month - 1, from.day)) / 86_400_000);
@@ -0,0 +1,52 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ /**
3
+ * `capital-watchlist`(host 平面行)provide 的 `capitalWatchlist` 服务里,本工具用到的那一小片。
4
+ * 声明成**可选方法 + 只读**:跨平面的形状归那一边所有,这里只描述"读清单"这条路径需要什么,
5
+ * 任何多余字段都不进回包。清单的增删与持仓标记一律只在面板里做,Agent 侧没有写入口。
6
+ */
7
+ interface WatchlistRow {
8
+ readonly thscode?: string;
9
+ readonly name?: string;
10
+ readonly asset_type?: string;
11
+ readonly added_at?: number;
12
+ readonly pinned_at?: number;
13
+ readonly holding?: {
14
+ readonly weight_pct?: number | null;
15
+ readonly marked_at?: number;
16
+ };
17
+ }
18
+ interface WatchlistListResult {
19
+ readonly ok?: boolean;
20
+ readonly items?: WatchlistRow[];
21
+ readonly limit?: number;
22
+ }
23
+ interface WatchlistServiceLike {
24
+ list?(): Promise<WatchlistListResult>;
25
+ }
26
+ /** ToolOutputDefinition.render(args, value):第一参是入参、第二参才是返回值。 */
27
+ declare function render(_args: unknown, value: unknown): Array<{
28
+ type: 'text';
29
+ text: string;
30
+ }>;
31
+ /** 成功回包。`limit` 只在宿主给了才带(那个数字归 `capital-watchlist` 所有,这里不抄一份)。 */
32
+ export declare function watchlistReceipt(rows: WatchlistRow[], limit: number | undefined, now: number): Record<string, unknown>;
33
+ export declare function watchlistToolDefinition(source: () => WatchlistServiceLike | undefined): {
34
+ name: string;
35
+ description: string;
36
+ parameters: object;
37
+ output: {
38
+ schema: object;
39
+ render: typeof render;
40
+ };
41
+ execute(): Promise<Record<string, unknown>>;
42
+ };
43
+ /**
44
+ * 注册 `get_watchlist`(主 Agent 的工具;`capital-generation-scope` 组里注册 = 主 Agent 与
45
+ * 全部 child 都看得见,所以通用 `subagent` 行必须把它 deny 掉,见 preset 的 agent.patch.yml)。
46
+ *
47
+ * 会话级数据(Dataset / profile / chart)走 workspace,而自选股是**宿主级用户资产**:
48
+ * 本工具不读 `{{cwd}}`、不碰 `~/.dsh/storages/` 那个文件,只调那一路唯一的宿主服务
49
+ * (设计文档 §2.4:不允许另建一份存储)。
50
+ */
51
+ export declare function registerWatchlistTool(ctx: Context): void;
52
+ export {};