@v587d/capital-generation 2.14.1 → 2.15.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@
7
7
  - **第三位(patch)**:不破坏既有工具 / 配置 / 会话的新增与修复(如 2.1.1、2.1.2)。
8
8
  - **第二位(minor)**:有需要用户知晓的行为变更,且段内附迁移说明(如 2.1.0 的图表呈现通道重做、2.2.0 的数据源与检索来源扩容、2.4.0 的**必须换宿主版本**、2.9.0 的选择器收敛与质检前置——本插件自己的工具与数据契约没变,所以只抬第二位)。
9
9
 
10
+ ## [2.15.0] - 2026-10-31
11
+
12
+ 宿主基线从 `0.2.1-rc.3` 抬到 `0.2.1-rc.5`;同时修掉互动易问答列表的时间列解析——站点在提问与
13
+ 回复里混用绝对时间与相对时间,旧实现只认三种写法,遇到新写法整页失败。
14
+
15
+ ### Fixed
16
+
17
+ - **互动易(`sseinfo_qa`)时间列认全站点写法**:绝对时间(`2026年09月18日`、`2026-09-18`、
18
+ `2026/09/18`)、`今天` / `昨天` / `前天`、`N秒|分钟|小时|天前`、`MM-DD HH:MM`(跨年回退)、
19
+ 只有 `HH:MM`、`刚刚` / `刚才` 都按北京时间归一成 `YYYY-MM-DD HH:MM`;非法日期、非法时刻与
20
+ 超长相对量返回 `undefined`,由整页解析响亮失败,不再把格式漂移变成一列空值或错日期。
21
+
22
+ ### Changed
23
+
24
+ - **`peerDependencies` 的 `dsh-skill` / `dsh-skill-filesystem` 改成精确 pin `0.2.1-rc.5`**:
25
+ 这两颗是运行时值导入(`FileSystemSkillProvider`),与宿主同版才不会有"插件带 rc.5 的 provider
26
+ 接 rc.3 的 skill 服务"这种跨版本组合。`devDependencies` 与 lockfile 同步到 rc.5。
27
+ - README 基线徽章、CONTRIBUTING、CI 安装版本、preset 维护文档同步到 rc.5。
28
+
29
+ ### Migration
30
+
31
+ - **本次必须换宿主版本**:先 `dsh --version` 确认为 `0.2.1-rc.5`,再安装或更新本插件。在 rc.3 上
32
+ 安装本版会被宿主的插件兼容预检直接拒绝;反过来,若先升宿主而插件仍是 2.14.1,启动时该插件行
33
+ 会被禁用(stderr 一行 `disabling profile plugin ...`,界面上模式与工具整体消失),执行
34
+ `dsh plugin --profile <profile> update @v587d/capital-generation` 后重启即可恢复。
35
+ - 升级宿主或插件后都需**重启宿主再新建会话**:已在内存里的旧客户端 bundle 与新宿主不混用。
36
+ - 互动易时间列修复无需迁移:已存 Dataset 的列值不变,下一次 `sseinfo_qa` 取数按新解析写入。
37
+
10
38
  ## [2.14.1] - 2026-10-30
11
39
 
12
40
  投研自动化两处交互收尾:每周多选菜单去掉冗余的「完成」项,新建任务弹窗收窄到 600px。
package/README.md CHANGED
@@ -13,12 +13,12 @@
13
13
  <a href="https://github.com/deepseek-ai"><img src="https://img.shields.io/badge/DeepSeek_Harness-plugin-blue" alt="DSH Plugin"></a>
14
14
  <a href="https://github.com/deepseek-ai"><img src="https://img.shields.io/badge/DeepSeek_Harness-web-orange" alt="DSH Web"></a>
15
15
  <a href="https://github.com/deepseek-ai"><img src="https://img.shields.io/badge/DeepSeek_Harness-desktop-brightgreen" alt="DSH Desktop · 已实机验证"></a>
16
- <a href="https://github.com/deepseek-ai"><img src="https://img.shields.io/badge/DSH%20Baseline-0.2.1--rc.3-blue" alt="DSH@0.2.1-rc.3"></a>
16
+ <a href="https://github.com/deepseek-ai"><img src="https://img.shields.io/badge/DSH%20Baseline-0.2.1--rc.5-blue" alt="DSH@0.2.1-rc.5"></a>
17
17
  </p>
18
18
  <p align="center">
19
19
  <a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin"></a>
20
20
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-green" alt="License"></a>
21
- <a href="https://github.com/v587d/capital-generation/releases"><img src="https://img.shields.io/badge/version-2.14.1-9cf" alt="Version"></a>
21
+ <a href="https://github.com/v587d/capital-generation/releases"><img src="https://img.shields.io/badge/version-2.15.0-9cf" alt="Version"></a>
22
22
  <a href="https://www.npmjs.com/package/@v587d/capital-generation"><img src="https://img.shields.io/npm/v/%40v587d%2Fcapital-generation" alt="npm version"></a>
23
23
  <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered-by-dsh"></a>
24
24
  </p>
@@ -39,7 +39,7 @@ Capital Generation 是运行在 [DeepSeek Harness(DSH)](https://github.com/d
39
39
  ## 快速上手
40
40
 
41
41
  > [!NOTE]
42
- > 最新版本已适配 DSH`@0.2.1-rc.3`。
42
+ > 最新版本已适配 DSH`@0.2.1-rc.5`。
43
43
 
44
44
 
45
45
  0. **前置**
@@ -110,7 +110,7 @@ Capital Generation 是运行在 [DeepSeek Harness(DSH)](https://github.com/d
110
110
 
111
111
  在新的自动模式会话中使用以下指令。将方括号内容换成自己的标的、公告或持仓;证券代码写完整市场后缀,例如 `600519.SH`。每条指令单独发送,便于观察结果。
112
112
 
113
- [样例:指南针、同花顺复盘分析报告(20261023)](docs/sample/指南针-同花顺-复盘分析报告.md)
113
+ [样例:指南针Q3暴跌探因(20261031)](docs/sample/指南针Q3暴跌探因.md)
114
114
 
115
115
  <details>
116
116
  <summary>展开示例指令</summary>
@@ -201,7 +201,7 @@ Capital Generation 是运行在 [DeepSeek Harness(DSH)](https://github.com/d
201
201
  - **安装后找不到模式?** 确认插件出现在「插件」→ 已安装列表;Web / CLI profile 安装后要重启该 profile,并在新会话选择模式。选择器里应看到自动模式、行情专家、资讯专员;分析师和绘图专员是自动模式的内部角色,不会出现在这里。
202
202
  - **配置在哪?** 打开「插件」→ 已安装的 **Capital Generation**,页面顶部就是「数据源 / 精选 Skills / 包含的组件」三格,五把密钥都在第一格;不在 DSH 的普通「设置」页面,也不用点进任何一行组件。保存密钥后新建会话再试。
203
203
  - **升级到 2.9.0 后,分析师和绘图专员去哪了?** 这一版把它们收进自动模式内部,不再出现在选择器与预设管理里,避免误开一个没有取数入口的空会话。旧会话仍能按原身份恢复;新任务直接用自动模式提出即可。
204
- - **桌面端升级后仍是旧版?** 桌面端安装时会固定所解析的版本,不会自动升级。卸载后重新安装,再到插件详情查看版本号;如需指定版本,可在地址末尾加 `#v2.14.1` 或 `@2.14.1`。
204
+ - **桌面端升级后仍是旧版?** 桌面端安装时会固定所解析的版本,不会自动升级。卸载后重新安装,再到插件详情查看版本号;如需指定版本,可在地址末尾加 `#v2.15.0` 或 `@2.15.0`。
205
205
  - **刚发布的新版本装不到?** 桌面端取包时会跳过**刚发布不久**的版本(宿主侧包管理器的发布冷却策略,实测会静默装成上一个版本),国内镜像的同步也可能滞后一段时间。急着用最新代码,改用仓库地址安装最直接。
206
206
  - **网页抓取失败?** 配置卡片中的「允许启动本地提取网页内容」默认开启:AnySearch 提取失败时可尝试本机直连。关闭后,本机直连的具名来源查询也不可用;请检查该开关和网络连接。
207
207
  - **Wind 那部分会消耗积分?** 会。公告与新闻检索、宏观 / 行业 / 汇率指标、按日期区间取的历史 K 线都按次消耗你在 Wind 侧的额度;额度与条款以 Wind 为准,插件不做用量封顶。没配 `WIND_API_KEY` 时这几类不可用,其余功能照常。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/capital-automation",
3
- "version": "2.14.1",
3
+ "version": "2.15.0",
4
4
  "description": "CapGen 投研自动化:独立 host bundle(固定 capital_automation 域、单 scheduler owner、/capital-automation 路由与浏览器页面)。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/capital-charts",
3
- "version": "2.14.1",
3
+ "version": "2.15.0",
4
4
  "description": "自动模式的图表前端组件(浏览器半边 + 序列旁路路由):由 @v587d/capital-generation 随包携带并作为 host 平面行挂载。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/config",
3
- "version": "2.14.1",
3
+ "version": "2.15.0",
4
4
  "description": "自动模式的取数凭据与偏好配置行:卡片坐在 @v587d/capital-generation 插件主页的「配置」段,点这一行本身没有表单。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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.14.1";
6
+ export declare const LOCAL_FETCH_CLIENT_VERSION = "2.15.0";
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.14.1';
7
+ export const LOCAL_FETCH_CLIENT_VERSION = '2.15.0';
8
8
  export const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
9
9
  export class LocalFetchError extends Error {
10
10
  code;
@@ -87,8 +87,10 @@ export declare function resolveSseCompanyUid(transport: Requester, code: string,
87
87
  /** Split Q/A on the reply block class; per-item id markers differ between the two list kinds. */
88
88
  export declare function parseSseFeed(text: string): SourceItem[];
89
89
  /**
90
- * Normalize to Beijing "YYYY-MM-DD HH:MM" (never host local time). All three observed
91
- * forms (X分钟前 / 昨天 HH:MM / absolute) must parse or one row fails the page.
90
+ * Normalize a feed label to Beijing "YYYY-MM-DD HH:MM"; unknown shapes return undefined so
91
+ * the caller fails the page instead of carrying a blank column. The site mixes absolute
92
+ * question times with relative reply times ("昨天 18:22"): every form must parse or one row
93
+ * fails the page. Relative forms use Beijing wall clock, never host local time.
92
94
  */
93
95
  export declare function sseTime(text: string, now?: Date): string | undefined;
94
96
  export declare function sseinfoQa(transport: Requester, input: {
@@ -473,31 +473,87 @@ export function parseSseFeed(text) {
473
473
  }
474
474
  return items;
475
475
  }
476
+ const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
477
+ const ONE_DAY_MS = 86_400_000;
478
+ const SSE_DAY_WORD_DAYS = { 今天: 0, 昨天: 1, 前天: 2 };
479
+ const SSE_ELAPSED_MS = { 秒: 1_000, 分钟: 60_000, 小时: 3_600_000, 天: ONE_DAY_MS };
480
+ /** Beijing wall clock carried in UTC fields, so UTC getters read as Beijing local time. */
481
+ function beijingClock(now) {
482
+ return new Date(now.getTime() + BEIJING_OFFSET_MS);
483
+ }
484
+ function beijingStamp(at) {
485
+ return new Date(at.getTime() + BEIJING_OFFSET_MS).toISOString().slice(0, 16).replace('T', ' ');
486
+ }
487
+ function pad2(value) {
488
+ return String(value).padStart(2, '0');
489
+ }
490
+ /** "HH:MM" from numeric fields; undefined means the label is not a real clock time. */
491
+ function clockText(hour, minute) {
492
+ return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59 ? `${pad2(hour)}:${pad2(minute)}` : undefined;
493
+ }
494
+ function realDate(year, month, day) {
495
+ if (month < 1 || month > 12 || day < 1)
496
+ return false;
497
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
498
+ const lengths = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
499
+ return day <= (lengths[month - 1] ?? 0);
500
+ }
476
501
  /**
477
- * Normalize to Beijing "YYYY-MM-DD HH:MM" (never host local time). All three observed
478
- * forms (X分钟前 / 昨天 HH:MM / absolute) must parse or one row fails the page.
502
+ * Normalize a feed label to Beijing "YYYY-MM-DD HH:MM"; unknown shapes return undefined so
503
+ * the caller fails the page instead of carrying a blank column. The site mixes absolute
504
+ * question times with relative reply times ("昨天 18:22"): every form must parse or one row
505
+ * fails the page. Relative forms use Beijing wall clock, never host local time.
479
506
  */
480
507
  export function sseTime(text, now = new Date()) {
481
- const absolute = /(\d{4})年(\d{2})月(\d{2})日\s*(\d{2}:\d{2})/u.exec(text);
508
+ const label = text.trim();
509
+ const absolute = /(\d{4})\s*[年\-/]\s*(\d{1,2})\s*[月\-/]\s*(\d{1,2})\s*日?\s*(\d{1,2}):(\d{2})/u.exec(label);
482
510
  if (absolute !== null) {
483
- return `${absolute[1]}-${absolute[2]}-${absolute[3]} ${absolute[4]}`;
484
- }
485
- const beijing = new Date(now.getTime() + 8 * 3600 * 1000);
486
- const stamp = () => beijing.toISOString().slice(0, 16).replace('T', ' ');
487
- const relative = /(今天|昨天)\s*(\d{2}:\d{2})/u.exec(text);
488
- if (relative !== null) {
489
- if (relative[1] === '昨天')
490
- beijing.setUTCDate(beijing.getUTCDate() - 1);
491
- return `${beijing.toISOString().slice(0, 10)} ${relative[2]}`;
511
+ const [year, month, day] = [Number(absolute[1]), Number(absolute[2]), Number(absolute[3])];
512
+ const time = clockText(Number(absolute[4]), Number(absolute[5]));
513
+ return time === undefined || !realDate(year, month, day)
514
+ ? undefined
515
+ : `${absolute[1]}-${pad2(month)}-${pad2(day)} ${time}`;
516
+ }
517
+ const wall = beijingClock(now);
518
+ const dayWord = /^(今天|昨天|前天)\s*(\d{1,2}):(\d{2})$/u.exec(label);
519
+ if (dayWord !== null) {
520
+ const time = clockText(Number(dayWord[2]), Number(dayWord[3]));
521
+ if (time === undefined)
522
+ return undefined;
523
+ wall.setUTCDate(wall.getUTCDate() - SSE_DAY_WORD_DAYS[dayWord[1]]);
524
+ return `${wall.toISOString().slice(0, 10)} ${time}`;
525
+ }
526
+ // Month-day without a year: more than a day ahead means last year ("12-31" seen in January).
527
+ const monthDay = /^(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2})$/u.exec(label);
528
+ if (monthDay !== null) {
529
+ const [month, day] = [Number(monthDay[1]), Number(monthDay[2])];
530
+ const time = clockText(Number(monthDay[3]), Number(monthDay[4]));
531
+ if (time === undefined || !realDate(wall.getUTCFullYear(), month, day))
532
+ return undefined;
533
+ let year = wall.getUTCFullYear();
534
+ const candidate = Date.UTC(year, month - 1, day, Number(monthDay[3]), Number(monthDay[4]));
535
+ if (candidate - wall.getTime() > ONE_DAY_MS) {
536
+ if (!realDate(year - 1, month, day))
537
+ return undefined;
538
+ year -= 1;
539
+ }
540
+ return `${String(year)}-${pad2(month)}-${pad2(day)} ${time}`;
492
541
  }
493
- const elapsed = /(\d+)\s*(分钟|小时)前/u.exec(text);
542
+ const elapsed = /^(\d+)\s*(秒|分钟|小时|天)前$/u.exec(label);
494
543
  if (elapsed !== null) {
495
- const amount = Number(elapsed[1]) * (elapsed[2] === '小时' ? 60 : 1);
496
- beijing.setUTCMinutes(beijing.getUTCMinutes() - amount);
497
- return stamp();
544
+ const when = new Date(now.getTime() - Number(elapsed[1]) * SSE_ELAPSED_MS[elapsed[2]]);
545
+ return Number.isNaN(when.getTime()) ? undefined : beijingStamp(when);
498
546
  }
499
547
  // 「刚刚」has no minute precision: use the current minute; otherwise fail (no guessing).
500
- return /^刚刚$/u.test(text.trim()) ? stamp() : undefined;
548
+ if (label === '刚刚' || label === '刚才')
549
+ return beijingStamp(now);
550
+ // Clock only means today in Beijing.
551
+ const clockOnly = /^(\d{1,2}):(\d{2})$/u.exec(label);
552
+ if (clockOnly !== null) {
553
+ const time = clockText(Number(clockOnly[1]), Number(clockOnly[2]));
554
+ return time === undefined ? undefined : `${wall.toISOString().slice(0, 10)} ${time}`;
555
+ }
556
+ return undefined;
501
557
  }
502
558
  export async function sseinfoQa(transport, input) {
503
559
  const type = SSE_KIND[input.kind];
@@ -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.14.1";
38
+ export declare const WIND_CLIENT_VERSION = "2.15.0";
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.14.1';
22
+ export const WIND_CLIENT_VERSION = '2.15.0';
23
23
  function errorText(error) {
24
24
  return error instanceof Error ? error.message : String(error);
25
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/capital-generation",
3
- "version": "2.14.1",
3
+ "version": "2.15.0",
4
4
  "description": "Capital Generation:面向中国散户的 AI 投资助手插件(自动模式 Agent Preset)",
5
5
  "icon": "assets/cg-icon.png",
6
6
  "author": "v587d",
@@ -162,14 +162,14 @@
162
162
  },
163
163
  "peerDependencies": {
164
164
  "@deepseek-ai/cordis": "^4.0.2",
165
- "@deepseek-ai/dsh-skill": "0.2.1-rc.3",
166
- "@deepseek-ai/dsh-skill-filesystem": "0.2.1-rc.3"
165
+ "@deepseek-ai/dsh-skill": "0.2.1-rc.5",
166
+ "@deepseek-ai/dsh-skill-filesystem": "0.2.1-rc.5"
167
167
  },
168
168
  "devDependencies": {
169
169
  "@deepseek-ai/cordis": "^4.0.2",
170
- "@deepseek-ai/dsh-skill": "^0.2.1-rc.3",
171
- "@deepseek-ai/dsh-skill-filesystem": "^0.2.1-rc.3",
172
- "@deepseek-ai/dsh-system-prompt": "^0.2.1-rc.3",
170
+ "@deepseek-ai/dsh-skill": "^0.2.1-rc.5",
171
+ "@deepseek-ai/dsh-skill-filesystem": "^0.2.1-rc.5",
172
+ "@deepseek-ai/dsh-system-prompt": "^0.2.1-rc.5",
173
173
  "@deepseek-ai/schemastery": "^3.18.4",
174
174
  "esbuild": "^0.28.2",
175
175
  "js-yaml": "^5.4.1",
@@ -23,7 +23,7 @@
23
23
 
24
24
  ## 2. 人设(persona)承载与官方语义
25
25
 
26
- 实测 `dsh 0.2.1-rc.3`(`test/persona.test.mjs` 对着本机 dsh 核对字段名),两层载体注册的是
26
+ 实测 `dsh 0.2.1-rc.5`(`test/persona.test.mjs` 对着本机 dsh 核对字段名),两层载体注册的是
27
27
  **同一个 system-prompt 节名**:
28
28
 
29
29
  | 载体 | 位置 | 字段 | 生效范围 |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/watchlist",
3
- "version": "2.14.1",
3
+ "version": "2.15.0",
4
4
  "description": "自动模式的用户自选股(宿主级清单 + Fuyao 报价快照 + /capital-watchlist 路由 + 输入框菜单弹窗):由 @v587d/capital-generation 随包携带并作为 host 平面行挂载。",
5
5
  "license": "MIT",
6
6
  "type": "module",