# PRD — dsh-widget-center(DSH 桌面 Widget Center 插件) - 版本:v0.11.0(2026-09-05;v0.11 变更:**notes 类型落地**(`config.lines` 多行文本、 可建多个实例、桌面窗 30s 轮询跟随设置);**settings UI 两层化**——实例卡片列表只放 摘要与快捷操作,点进卡片进入详情页编辑全部配置,新增添加/删除实例入口; **conversationTool 实例化**——market_quote 开关从插件级移入 shares 实例 config (用户 2026-09-05 裁决:该开关服务 shares 数据,属于 shares widget 的设置), 旧顶层布尔读取时自动迁移;**dashboard fetch 路径修复**——页面 API base 从 location 推导,修复 `no route: GET /widget-center/dashboard/api/quotes`(相对路径 `api/` 在 `/dashboard/` 页内被浏览器拼错层级)。v0.10 变更:**更名 + 多实例架构**——插件 更名 `dsh-widget-center`(原 `dsh-plugin-shares-widget`),升级为 Widget Center:管理 N 个桌面 widget **实例**,每实例有 type(shares/notes)/名称/窗口几何/类型专属配置, 在设置页以 **instance card** 呈现。v0.9.4 变更:widget 实体升级为原生 macOS 桌面独立 窗——NSPanel + WKWebView(Swift 单文件,swiftc 编译,独立进程,无 dock 图标、悬浮层、 跟随所有空间),chrome `--app` 降级为备用入口;设置页 = 纯配置页,不展示行情数据。 v0.9.3 变更:意图修正——widget 是桌面独立窗而非 DSH 侧栏面板) - 作者:Salieri(基于 2026-09-04 与用户的调研对话定稿;2026-09-05 多次按用户纠偏修订) - 状态:v0.11 已实现(notes + 两层设置 UI + conversationTool 实例化 + fetch 修复) - 对标产品:macOS 桌面小组件;Kimi Work Dashboard / Live Widget(Moonshot AI) --- ## 1. 背景与动机 Kimi Work(月之暗面桌面 Agent)提供 Dashboard 特性:对话生成可交互 widget、保存在跨会话 Dashboard、可 Pin 成桌面独立窗口;Live Widget 绑定 Widget Task(定时/事件触发)自动刷新。 其股票 dashboard 的数据面**没有第一方授权行情源**——官方帮助中心明确 widgets "connect to local data or external plugins for continuous updates",本质是每次任务运行时由 agent 现抓 公开行情接口/搜索,分钟级快照。 DSH 侧 2026-09-04 实测结论(本 PRD 数据层的实证基础): | 数据源 | 覆盖市场 | 连通性 | 坑位 | |---|---|---|---| | 腾讯 `qt.gtimg.cn/q=` | A股/ETF/指数/港股/美股 | ✅ 直连 | GBK 编码需 iconv;返回 `var v_xx="..."` 文本格式 | | 东方财富 `push2.eastmoney.com` | A股/ETF/板块/资金流 | ✅ 直连 | JSON,字段为 f2/f3/f12 缩写码 | | 新浪 `hq.sinajs.cn` | 指数/期货/外汇/贵金属 | ✅ 直连 | **必须带 Referer: https://finance.sina.com.cn**,否则 403 | | Yahoo Finance `query1.finance.yahoo.com` | 美股/全球/期货/加密 | ✅ 需代理 | 直连返回 HTML 反爬页;走 socks5 需 UA | | Binance `api.binance.com` | 加密货币 | ✅ 需代理 | 直连超时 | 代理为实例级配置(默认关闭);URL 按本机代理端口填写,常见本地端口示例:`socks5h://127.0.0.1:7897`。 **定位**:本插件把「数据层 + 渲染层」做成 DSH 原生能力,比 Kimi 的 LLM-per-refresh 模型 更便宜(host 进程常驻抓取,零 token)、更快(秒级轮询)、更可控(自选品种 + 多源 fallback)。 ## 2. 目标与非目标 ### 目标(v1,v0.9.3 修正) 1. **G1 桌面行情 Widget**:独立桌面小窗(对标 macOS widget / Kimi Pin-to-Desktop), chrome `--app` 无边框窗承载 `/dashboard` 页,秒级自动刷新,显示价格/涨跌幅/交易时段/ 数据时间戳;不依赖 DSH GUI 会话开着。 2. **G2 Widget 窗入口**:设置页提供「打开 Widget 窗」(一键弹出预览窗)与「复制 chrome `--app` 命令」两个入口。 3. **G3 偏好设置页**:在 **DSH 设置 UI 侧栏**贡献「行情 Widget」偏好页(`settings.section` slot,additive),控制**功能启停、刷新频率、数据源优先级、代理、watchlist 管理、主题、 窗口尺寸**;设置持久化、热生效、重启保留。 4. **G4 多源数据层**:5 个 provider 适配器 + 优先级 fallback + 交易时段感知节流 + 磁盘缓存。 5. **G5 模型工具(可选开关)**:注册 `market_quote` 工具,让任意会话能拉标准化行情 (供 automation/子代理复用)。 ### 非目标(P2 减法,v1 明确不做) - ❌ **DSH 侧栏/会话区内嵌任何行情 UI**(v0.9.3 明确否决,widget 只活在桌面窗) - ❌ K 线/分时图(v2 候选;v1 仅列表 + 可选 sparkline) - ❌ 逐笔 tick / Level-2 / 下单交易 / 价格告警推送(v2+) - ❌ 自建 automation 任务(client 轮询已覆盖刷新;automation 仅在文档提示可组合) - ❌ Electron 原生桌面壳(浏览器 app 窗已达标,不引入运行时) - ❌ 自研行情源或付费数据接入 ## 3. 用户故事 - **US1(盯盘)**:用户盯中证1000 网格仓位——桌面上常驻一个 380×460 的行情小窗, 显示 sh000852 + 159845/560010/159679 实时价与日内涨跌,10s 自动刷新,无需任何操作; DSH GUI 关着也照常刷新。 - **US2(开 Widget)**:想加个桌面积木——打开 DSH 设置 UI 侧栏的「行情 Widget」页, 点「打开 Widget 窗」立即弹出(或复制 `chrome --app` 命令得到真正的无边框窗)。 - **US3(改偏好)**:嫌刷新太快/想加腾讯控股——在设置页加品种、把间隔改成 30s、关掉 Yahoo 源,保存即生效(已开着的 Widget 窗下一轮轮询自动跟随),重启 DSH 后仍在。 - **US4(临时停用)**:不想看到 Widget——设置页关总开关,host 停止回源抓取,Widget 窗 显示「已停用」;再打开恢复。完全卸载走宿主 `dsh plugin remove`(文档说明两级开关语义)。 - **US5(会话内问价)**:对 agent 说「现在 159845 多少」——agent 调 `market_quote` 工具 返回标准化报价并引用数据时间戳。 ## 4. 架构总览 ```mermaid flowchart LR subgraph host["Host half (src/index.js, 常驻进程)"] CFG[SettingsService
storages/widget-center/settings.json] Q[QuoteService
节流+缓存+交易时段] P1[Provider: tencent] P2[Provider: eastmoney] P3[Provider: sina] P4[Provider: yahoo→proxy] P5[Provider: binance→proxy] API[HTTP API
webServer.register /widget-center/*] TOOL[market_quote 工具
tools.register] CFG --> Q --> P1 & P2 & P3 & P4 & P5 Q --> API Q --> TOOL end subgraph client["Client half (client/client.js, GUI 页面)"] SEC[设置 UI 偏好页
settings.section slot
「行情 Widget」] end WIDGET[桌面行情 Widget
native NSPanel /widget-center/dashboard/:id] API <-->|同源 fetch| SEC API <-->|同源 fetch| WIDGET SEC -.->|打开窗 / 复制命令| WIDGET ``` **关键架构决策(ADR 摘要)** - **ADR-1 刷新走 Widget 窗/页面轮询 host API,不走 LLM/automation**。Kimi 用 Widget Task (每轮 LLM 会话)刷新;我们用 host 常驻进程抓取 + 缓存,页面 `setInterval` 同源 fetch。 零 token、秒级、断 GUI 仍可用。 - **ADR-2 桌面 Widget = host 提供的完整 HTML 页面路由**(`GET /widget-center/dashboard/:id`,v0.10 起按实例), 非 file:// 独立文件。同源于 host API → 无 CORS/CSP 问题;`chrome --app=` 得到 无边框小窗,即 Mac-widget 本体。 - **ADR-3(v0.9.3)偏好设置页挂 DSH 设置 UI `settings.section` slot**(list,additive, replaceRisk=none):client 半 `ctx.slots.inject("settings.section", () => ctx.slots.register({name, id:"widget-center", order:20, label}, PageComponent))`, 页面自动出现在设置面板侧栏导航(general=0 / models=10 / plugins=15 之后)。 **不向 DSH 侧栏注入任何 DOM**——v0.9.2 的 shell.overlay 面板方案经用户 2026-09-05 裁决废弃:widget 的本体是桌面窗,侧栏内嵌面板是对 widget 概念的误解。 - **ADR-4 两级启停语义**: - *插件级*:宿主 `cordis.patch.yml` / bundles 移除(需重启,属身份层,文档说明即可); - *功能级*:settings `enabled=false`(热生效:面板卸载、桌面窗页显示已停用、host 停止 回源抓取、工具返回明确报错)。设置界面控制的是功能级。 - **ADR-5 设置持久化在 host 侧** `~/.dsh/storages/widget-center/settings.json`,client 不落 localStorage(多窗口一致性);写入采用 temp+rename 原子写。 - **ADR-6(v0.10,v0.11 修订)多实例模型**:settings v2 = `{version, instances[]}`; 每实例 `{id, type, name, enabled, autoStart, window{x,y,w,h}, config}`,config 校验按 type 分派(`normalizeSharesConfig` / `normalizeNotesConfig`)。二进制共享一个 (同一 Swift app,URL/位置按实例参数化),pidfile 按实例隔离(`widget-.pid`), QuoteService 每 shares 实例一个。v1 单 widget 设置由 `migrateV1` 一次性迁移为 `shares-1` 实例(旧目录只读保留,旧 widget 进程被终止)。 `market_quote` 工具服务第一个 enabled 且 config.conversationTool 开启的 shares 实例; **`conversationTool` 是 shares 实例级开关(v0.11 起;原插件级裁决被用户推翻: 该开关服务 shares 数据,属于 shares widget 的设置。读取含旧顶层布尔的 settings 时 自动迁移到第一个 shares 实例)。** ## 5. 功能需求 ### FR-1 数据层(host) - **FR-1.1 符号规范**(全插件统一): `{cn-sh}:sh000852`、`{cn-etf}:sz159845`、`{hk}:hk00700`、`{us}:usAAPL`、 `{index-cn}:sh000852`、`{fx}:fx_susdcny`、`{futures}:nf_…`、`{crypto}:binance:USDCUSDT`。 watchlist 存规范符号,各 provider 内部做映射。 - **FR-1.2 标准化 Quote 模型**: ```ts interface Quote { symbol: string; // 规范符号 market: 'cn'|'hk'|'us'|'fx'|'crypto'|'futures'; name: string; // UTF-8 中文名(GBK 已转码) price: number; prevClose: number; open?: number; high?: number; low?: number; change: number; changePct: number; // %,已乘 100 保留 2 位 volume?: number; amount?: number; currency: 'CNY'|'HKD'|'USD'|'USDT'; ts: number; // 行情时间戳(ms,交易所本地时间换算) tradingPhase: 'pre'|'open'|'break'|'post'|'closed'; source: 'tencent'|'eastmoney'|'sina'|'yahoo'|'binance'; stale: boolean; // 缓存回退时 true } ``` - **FR-1.3 Provider 接口**:每个 provider 实现 `supports(symbol) → bool`、`fetchQuotes(symbols[]) → Quote[]`;解析器必须是纯函数 (原始响应文本 → Quote[]),便于用录制 fixture 做单测。 - **FR-1.4 Fallback 链**:按 settings.providerPriority 顺序尝试;单 provider 失败(超时 8s、 非 2xx、解析异常)自动降级到下一个;全部失败返回 stale 缓存 + `stale:true`。 - **FR-1.5 节流与交易时段**:host 内 QuoteService 按 symbol 缓存 TTL 回源—— `tradingPhase=open` 时 `ttl=openTtlSec`(默认 15s),非 open 用 `closedTtlSec`(默认 300s); `POST /api/refresh` 强制回源。交易时段表按市场硬编码(cn/hk/us 含午休与节假日占位表, 节假日 v1 仅 weekend 判定,文档注明局限)。 - **FR-1.6 代理**:settings.proxy 仅作用于标记 `needsProxy` 的 provider(yahoo/binance); 实现 = Node undici `ProxyAgent`(socks5 需 `socks-proxy-agent` 依赖,见 §9 依赖裁决)。 v0.12 发布修订:`proxy.enabled` 默认 `false`(原 `true` 是开发机环境默认;发布版 不应默认假设本机有 socks 服务),URL 保留为示例值,用户按实例开启。 - **FR-1.7 崩溃面**:任何 provider 异常不得击穿 host 进程;全部 catch 后降级 + `console.warn('[widget-center] …')` 带前缀日志。 ### FR-2 桌面行情 Widget(v0.9.4,主交互面) - **FR-2.1 本体 = 原生 macOS 桌面独立小窗**:`src/widget/WidgetApp.swift`(NSPanel borderless + nonactivatingPanel + WKWebView),由 host 懒编译(swiftc,产物缓存于 `storages/widget-center/SharesWidget`(实例共享))并 **detached 独立进程**运行——无 dock 图标、 悬浮于普通窗口之上、跟随所有桌面空间、圆角;**任意位置可拖动**(webview 层接管 mouseDown→performDrag,dashboard 页纯展示无 DOM 交互故无冲突);**自定义右键菜单** (刷新行情 / 置顶开关 / 重置尺寸 / 退出 Widget),替代 WKWebView 默认菜单; 关窗即退出进程。这是对标 macOS 桌面小组件的 desktop standalone 形态。 - **FR-2.2** 内容 = host `/dashboard` 页(WKWebView 加载):名称、规范符号、现价、 涨跌幅(默认 A 股红涨绿跌,`upsideGreen` 切美式)、数据时间戳、stale ⚠ 标记、 host 离线/已停用态。 - **FR-2.3** 轮询:页面内 `setInterval(refreshIntervalSec)` fetch `/api/quotes`; `document.hidden` 时暂停。widget 进程独立于 DSH host 存活(standalone),host 重启后 widget 重连即继续刷新。 - **FR-2.4** 生命周期由 host API 管理:`POST /api/widget/show`(懒编译 + 替换旧实例)、 `POST /api/widget/hide`(SIGTERM + 清 pidfile)、`GET /api/widget/status`(pidfile 探活)。 - **FR-2.5** 备用入口:chrome `--app` 启动命令(swiftc 不可用时唯一可用;是浏览器窗 而非原生窗,如实标注为 fallback)。 ### FR-3 设置 UI 偏好页(client 渲染 + host 持久化;v0.11 = 纯配置,v0.11.1 = 三视图) - **FR-3.1** 入口:DSH 设置 UI 侧栏导航「Widget Center」页(`settings.section` slot, id `widget-center`,order 20;additive,不动任何原生 UI)。 - **FR-3.2** **纯配置页,不渲染任何行情数据**(数据只活在 widget 上,v0.9.4 明确)。 **三视图结构(v0.11.1,用户裁决:添加入口按实例抽象而非按类型铺按钮、 保存只属于实例详情、返回按钮属页级导航不混入卡片)**: - *视图一 · 实例卡片列表*:每实例一张紧凑卡——类型徽标 / 名称 / 运行状态点 / enabled 徽标 / 一行摘要(shares:自选数+轮询+首选源;notes:行数)+ 「在桌面显示 / 隐藏」快捷操作 + 「配置 ›」进入详情;卡片下方唯一 「+ 新增 Widget 实例」按钮;列表页无保存栏(列表恒等于已落盘状态)。 - *视图二 · 类型选择页*:点新增进入——列出已注册类型(host `WIDGET_TYPES` 的 client 镜像注册表),点选即以该类型默认配置落盘创建,并跳入该实例详情。 - *视图三 · 实例详情页*:「‹ 返回列表」独立导航行 + 名称编辑 + 通用设置(窗口宽高 / autostart / enabled)+ 类型专属配置(shares:watchlist / 刷新节流 / 数据源优先级 / 代理 / 外观 / 会话工具开关;notes:多行文本 textarea)+「删除此实例」(两步确认; 仅存 1 实例时禁用)。 - **FR-3.3** 控件与校验:见 §6 设置项规格;watchlist 管理 = 增删行 + 上下移;品种输入 支持规范符号直填,v1 不做代码搜索。 - **FR-3.4** 配置修改保存即 `PUT /api/instances` 全量提交(保存栏仅在实例详情页, v0.11.1 起创建与删除同样即时持久化,两步确认防误删);host 校验后落盘并热应用 (QuoteService 重建 provider 链、运行中的 widget 下一轮轮询自动跟随,notes 窗 30s 轮询跟随;尺寸/主题改动需「重新显示 Widget」应用); 保存成功页内提示,失败显示 host 400 原因。 - **FR-3.5** 冲突语义:last-write-wins(多窗口同时改设置的冲突不做合并,文档注明)。 - **FR-3.6** **类型创作工坊(v0.12,Type Studio)**:类型选择页底部「+ 新建 Widget 类型」 → `POST /api/type-studio` → host 经 `agents.create` 创建一个**不启动**的会话 (`agentPreset: 'cordis'`「创造模式」,缺失时回退 standard;cwd = `~/.dsh/.agent-presets`, 不挂任何 workspace;无任何 user/assistant 消息)+ `session/title` 钉「Widget 类型创作 HH:MM」 标题 + dispose 释放 live agent(session 持久留存,与 automation 会话同生命周期)。 client 随即 `sessions.open` 打开该会话并把 **issue-template 式抽象提示词**预填进 composer (固定上下文 + `{{占位}}`,具体需求由用户补全后手动发送才启动)。composer 草稿是客户端 持久化 store,预填跨刷新保留;已输入草稿不被覆盖。host 会话服务不可用 → 503 原文页内展示。 ### FR-4 Widget 生命周期入口(v0.9.4) - **FR-4.1** 设置页「在桌面显示 Widget」→ `POST /api/widget/show`:host 懒编译 Swift (二进制缺失或源码更新时 swiftc 重编),detached spawn 独立进程,pidfile 记账; URL 按 `Host` 头动态构造(端口无关,R7),尺寸/主题来自已保存设置。 - **FR-4.2** 设置页「隐藏 Widget」→ `POST /api/widget/hide`:SIGTERM + 清 pidfile。 - **FR-4.3** chrome `--app` 备用命令(复制到终端执行): `open -na "Google Chrome" --args --app="http://127.0.0.1:3080/widget-center/dashboard/shares-1" --window-size=380,460`。 ### FR-5 模型工具(可选) - **FR-5.1** `market_quote(symbols: string[])`:返回 Quote[](JSON);服务第一个 enabled 且 `config.conversationTool` 开启的 shares 实例(v0.11 起开关在实例 config), 否则返回明确错误文案并说明原因(不是 unknown tool——工具注册本身保留,execute 内 检查开关,避免 unattended 会话拿到幽灵工具)。 - **FR-5.2** 工具 description 写明:延迟为免费快照级(非逐笔)、时间戳必须随答引用 (防模型编造实时性)。 ## 6. 设置项规格(settings.json schema,v2 instances 模型) ```jsonc { "version": 2, "instances": [ { "id": "shares-1", // 小写 [a-z0-9-],唯一 "type": "shares", // 类型:shares | notes(v0.11 起 notes 可用) "name": "行情", // 卡片显示名(≤24 字符) "enabled": true, // 实例开关(数据抓取 + show 准入) "autoStart": true, // DSH 启动自动显示 "window": { "x": 120, "y": 240, "w": 380, "h": 460 }, "config": { /* 类型专属,见下表 */ } } ] } ``` **shares 类型 config**: | key | 类型 | 默认值 | 说明 | |---|---|---|---| | `conversationTool` | bool | `true` | 放行 market_quote 给模型会话(v0.11 起实例级;旧 v0.10 顶层布尔读取时自动迁移到第一个 shares 实例) | | `watchlist` | `Array<{symbol, group?, note?}>` | 预填 5 条* | 自选列表,有序可分组 | | `refreshIntervalSec` | int 5–600 | `10` | 页面轮询间隔 | | `openTtlSec` / `closedTtlSec` | int | `15` / `300` | host 回源节流(交易/非交易) | | `providerPriority` | string[] | `["tencent","eastmoney","sina","yahoo","binance"]` | fallback 顺序 | | `proxy.enabled` / `proxy.url` | bool / string | `false` / `socks5h://127.0.0.1:7897`(示例) | 仅作用于 needsProxy provider | | `theme` | `follow`\|`light`\|`dark` | `follow` | 桌面窗主题 | | `upsideGreen` | bool | `false` | 涨跌配色(默认 A 股红涨;true 切美式绿涨) | | `showSparkline` | bool | `false` | 行尾 30 点日内迷你线(数据源支持时) | | `extendedHours` | bool | `false` | 美股盘前盘后价显示 | \* 默认 watchlist 预填(网格策略场景):`sh000852`、`sz159845`、`sh560010`、 `sz159679`、`fx_susdcny`(Q2 已确认)。校验语义:显式传非法值 → 400 + 原因;键缺失或值 undefined → 回落默认(对全量替换 PUT 的部分字段友好)。**新建实例**(设置页添加入口) 的 shares config 为默认模板但 `watchlist: []`(新实例不预设品种)。 **notes 类型(v0.11 落地)**:`config = { lines: string[] }`——多行文本行(逐行原样 保留,编辑侧以换行 split/join);上限 50 行 × 每行 500 字符(防粘贴炸窗);默认 `lines: []`(空态显示引导文案)。允许多个 notes 实例并存(`notes-1`、`notes-2`…), 每实例一个独立桌面窗;窗口 30s 轮询 `/api/instances` 跟随设置,改文案无需重新显示。 ## 7. HTTP API 契约(host,全部挂 `/widget-center/` 前缀) | 路由 | 方法 | 说明 | |---|---|---| | `/api/instances` | GET | `{instances[]}`,实例附运行态 `widget{running,pid}`(v0.11 起无顶层 conversationTool) | | `/api/instances` | PUT | 全量替换(校验失败 400 + 原因;成功热应用:QuoteService 重建;兼容接受 v0.10 顶层 conversationTool 并迁移) | | `/api/instances/:id/show` | POST | 懒编译 + detached spawn 实例窗(enabled=false → 400;shares/notes 通用) | | `/api/instances/:id/hide` | POST | SIGTERM + 清该实例 pidfile | | `/api/quotes?symbols=a,b` | GET | 标准化 Quote[](默认 = 第一个 enabled shares 实例的 watchlist) | | `/api/health` | GET | `{providers, cacheSize, instances[]摘要}` | | `/dashboard/:id` | GET | 实例桌面窗完整 HTML(按 type 分派:shares 行情页 / notes 便签页) | 约定:client/桌面窗只同源 fetch;响应一律 JSON envelope `{ok:true,data}` / `{ok:false,error}`; 无鉴权(本机回环端口,与宿主 GUI 同安全边界)。v1 的 `/shares-widget/*` 路由随 rename 退役,不做兼容层(widget 窗全部由新路由重建)。 ## 8. 目录结构与里程碑 ``` dsh-widget-center/ ├── PRD.md # 本文件 ├── package.json # type:module; main:src/index.js; │ # dsh.bundle.patch:"./cordis.patch.yml"(loader 硬门槛) ├── cordis.patch.yml # - {id: widget-center, name: 'dsh-widget-center', config: {}} ├── src/index.js # host half: name + apply(ctx, config) │ ├── settings.js quotes.js providers/{tencent,eastmoney,sina,yahoo,binance}.js ├── client/client.js # __ModuleLoader__.load({id, factory}):面板 + 设置弹层 └── test/ # node --test;providers 用 fixtures/ 录制响应 ``` | 里程碑 | 内容 | 验收标准(全部可跑命令验证) | |---|---|---| | **M1 数据层** | providers ×5 + QuoteService + API | `node --test` 全绿(fixture 驱动);真机 curl 六品种(sh000852/sz159845/hk00700/usAAPL/fx_susdcny/binance:USDCUSDT)返回合法 Quote;断 sina Referer 模拟失败 → fallback 生效 | | **M2 GUI 面板** | client 面板 + 轮询 | GUI 实测:面板出现、10s 刷新、`document.hidden` 暂停、卸载后 DOM/timer 零残留(console 探针) | | **M3 设置** | 设置弹层 + 持久化热应用 | 改间隔立即生效;关 enabled 面板消失且 `/api/health` enabled:false;重启 DSH 设置保留;非法 settings PUT 返回 400 | | **M4 桌面窗 + 工具** | dashboard 页 + `market_quote` | chrome --app 打开自动刷新 ≥30min 无报错;会话内调工具返回带 ts 的 Quote;关闭 conversationTool 后返回明确错误文案 | 每个里程碑走 `git-finish-work`(分支沿用 `dev` 直接提交(历史惯例) → 合并 `dev`)。 ## 9. 依赖与实现纪律裁决 - **依赖**:仅新增 `socks-proxy-agent`(socks5 必须,undici ProxyAgent 只支持 http/https)。 其余全部 Node 内置(fetch/undici/zod 若宿主不可复用则内联轻量校验)。**不引入** axios/chart 库(sparkline 手写 SVG polyline ≤30 行)。 - **vendor 冻结**:client 不包装/不改写任何宿主 React 组件(冻结 Monkey-patch 静默失效); 全部自建 DOM + textContent。 - **file: 硬链接**:开发期每次改源码后 `cp` 同步进 `~/.dsh/profiles/web/node_modules/dsh-widget-center/`(新建文件需 `ln -f`), 验证法 = diff 双路径 + curl boot manifest。 - **安装前**:跑 loader 复刻预检(模拟 loadProfile 校验 bundle patch 字段/文件存在)—— sidebar-views 0.2.1 崩溃事故的直接教训;装后 `plugin_health --profile web`。 - **GBK 转码**:腾讯/新浪响应用 `iconv-lite`?否——Node 内置 `TextDecoder('gbk')` (Node ≥18 支持完整 ICU)即可,零依赖。 - **升级自检注册**:本插件为独立 bundles 包(非宿主 patch),不受 pnpm 重装冲 patch 影响; 但需在 `dsh-plugin-upgrade-recovery` 的 known-patches 文档登记「存在性检查」一行。 ## 10. 风险与已知坑位(实现前必读) | # | 风险 | 缓解 | |---|---|---| | R1 | loader 硬门槛:bundle patch 缺失 → 整个 GUI 崩溃循环 | 复刻预检 + M1 起就用真实 bundle 装测试,不留到最后 | | R2 | 新浪 Referer / Yahoo 直连反爬 / GBK | 已实测解法固化进 provider(§1 表) | | R3 | 免费接口限频/改版 | TTL 节流 + fallback + 解析器纯函数化(改 fixture 即可修) | | R4 | MutationObserver 自持永动 | FR-2.4 签名 diff 纪律 | | R5 | `defineTool` this 丢失 | 闭包捕获 service 引用 | | R6 | 交易时段节假日误判(Holiday 表缺失) | stale 标记 + 文档注明 v1 仅 weekend;TTL 按相位自适应兜底 | | R7 | `10.0.0.1` 以外的宿主端口/路径前缀变化 | 所有 URL 从 `location` 推导,零硬编码端口 | | R8 | Yahoo/币安经代理失败(代理未开) | provider 标记 needsProxy,失败降级并在面板显示该源 offline | ## 11. 开放问题(开工前需用户裁决) - **Q1 v1 范围**:`market_quote` 工具(G5)进 v1 还是砍到 v2?(倾向:进,成本低、 automation/子代理立刻受益) - **Q2 默认 watchlist**:按 §6 脚注预填中证1000 三 ETF + 指数 + USDCNY,还是留空? - **Q3 侧栏挂点**:面板放侧栏顶部(当前 ADR-3)还是底部(不挤压会话列表)? - **Q4 命名 — 已定案**(2026-09-04,用户两轮裁决):**`dsh-plugin-shares-widget`**。 演进:v0.9 `market-widget`(弃:与 DSH 插件市场 dsh market 歧义)→ v0.9.1 `quote-widget`(弃:读感不佳)→ v0.9.2 `shares-widget`(shares = 股票/股份,简短无歧义; 候选 `share-market-widget` 被否——market 歧义回归,且 share 独立读作「共享」)。 连带命名空间:cordis id `shares-widget`、HTTP 前缀 `/shares-widget/*`、 storages `shares-widget/`、日志前缀 `[shares-widget]`、分支沿用 `dev` 直接提交(历史惯例)。 模型工具名保留 `market_quote`(金融动作语义,工具语境与插件市场无冲突)。 - **Q5 v0.11 批次 — 已定案**(2026-09-05,用户):① settings 卡片设计不纯——顶部 quota(market_quote/conversationTool)配置移入 shares 实例设置(该开关服务 shares 数据,属 shares widget 的一部分);② dashboard 报 `no route: GET /widget-center/dashboard/api/quotes`——页面相对路径 `api/` 修复;③ 设置 UI 卡片臃肿 ——两层化:卡片只放摘要与快捷操作,点进卡片进详情编辑(FR-3.2);④ notes widget 正式落地并在 settings 增加卡片与添加/删除实例入口(FR-3.2 / §6 notes config)。 - **Q6 Type Studio 管道 — 已定案**(2026-09-06,实测裁决):FR-5 的 host 路由 `POST /api/type-studio` 退役。两个实测硬约束:① host `agents.create` 后 client `sessions.open` 立即 select 报 `unknown session`(client list 快照未含新会话的 竞态);② 无 workspace 会话的 composer 只读(「选择一个工作区开始」占位, readOnly:true——v0.12 仅验证过 disabled:false,漏了 readOnly)。改为纯 client 管道:`connectWorkspace(当前/最近工作区)` → `sessions.create({workspaceId, agentPreset:'cordis',缺失回退 standard})`(create 自带 list 同步投影,立即可 open)→ `binding().session.rename` 钉题 → composer 预填。预填定位改结构式 (可见/可写/空值/非本插件 textarea)——placeholder 措辞随 workspace 状态与 版本变化(消息 / 选择一个工作区开始 / 描述你想要构建的内容),语义定位不可依赖。 入口从类型选择页底上调至列表页,与「新增 Widget 实例」并排(同层级动作)。 ## 12. 参考 - Kimi 官方:kimi.com/help/kimi-work/widgets · /dashboard · kimi.ai/zh-hans/resources/kimi-work-dashboard - 本会话实测记录:2026-09-04 六端点连通性(§1 表)与 socks5 代理验证 - 插件编写面经验:工作区 memory `dsh-plugin-authoring.md`、`sidebar-views-plugin.md`、 `dsh-sidebar-workspaces-slot.md`(loader 门槛/硬链接/frozen vendor/overlay 宿主全有先例)