dsh-links-panel 0.0.0-stage → 0.77.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The dsh-links-panel authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,161 @@
1
- # Temporary Holding Version
1
+ # dsh-links-panel
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A **Reference Links** tab for the right sidebar of [DeepSeek Harness](https://github.com/brickhu/dsh-plugins) (DSH).
4
+ It collects every URL that appears in the current conversation into one list — with pins,
5
+ turn counts, real site icons, manual renaming, hiding, search and a keyboard shortcut —
6
+ so you never have to scroll back through a long chat to find that link again.
7
+
8
+ - Design notes: [DESIGN.md](DESIGN.md)
9
+ - 中文说明: [README.zh.md](README.zh.md)
10
+ - Requires DSH `>=0.2.1-rc.3 <0.3.0` (declared in `package.json` → `engines.dsh`)
11
+ - No build step: the client half is a self-contained lazy-CJS bundle
12
+
13
+ ---
14
+
15
+ ## What it does
16
+
17
+ | Capability | Behaviour |
18
+ |---|---|
19
+ | **Collect** | Every `http(s)` URL from user and assistant messages (plain URLs and Markdown links), plus the titles/URLs carried by successful `web_search` / `web_fetch` results. |
20
+ | **Ignore noise** | URLs that only appear in `bash`, `read`, `edit`, `write`, `grep`… results, or in tool *arguments*, are never collected. Static assets, private hosts, IP literals, placeholder domains and API endpoints are filtered out. |
21
+ | **Live** | The panel is event-driven: a new `web_fetch` result appends its URL while the agent is still streaming, and a background tracker keeps collecting even when the tab is closed. |
22
+ | **Titles** | Titles come **only from the conversation** — Markdown labels, `web_search` result titles, the page title line inside a successful `web_fetch` result — or from your own rename. Otherwise the URL itself is shown. No remote title probing ever happens. |
23
+ | **Turns** | Each row shows how many **turns** mentioned the URL. Click the badge to open a dialog listing every turn, with relative timestamps (`5d ago`, `just now`) and “click to view” on hover, jumping straight to that turn. |
24
+ | **Pins** | Pin links to a separate “Pinned” section at the top. Pins are global and survive restarts. |
25
+ | **Hidden** | Hide a single link or the whole host (`a.baidu.com`, never `baidu.com`). Hidden links stay expandable and restorable. |
26
+ | **Icons** | A global favicon library keyed by host / registrable domain. Hosts without an entry are probed **one host at a time** through the Host process, then cached forever. Deterministic hash icons are the final fallback, and monochrome icons are inverted when they would disappear on the current light/dark theme. |
27
+ | **Search** | Search title or domain. While searching, grouping and collapsible sections are replaced by a flat list headed `Search results (N)`. |
28
+ | **Keyboard** | `⌘⇧L` on macOS, `Ctrl+Shift+L` elsewhere. The shortcut works from the conversation and is displayed on the guide card. |
29
+
30
+ ## Installation
31
+
32
+ ### From npm (recommended)
33
+
34
+ 1. Open DSH → right sidebar → **Plugins**.
35
+ 2. Install the bundle by name: `dsh-links-panel` (or `npm:dsh-links-panel`).
36
+ 3. Select the bundle for your profile and restart DSH.
37
+ 4. Open a conversation, expand the right sidebar, click **+** → **Reference Links**.
38
+
39
+ ### Local development / linking this repository
40
+
41
+ The desktop profile lives at `~/.dsh/profiles/<profile>` (default `desktop`) and is owned
42
+ by the Electron app, so use the bundled installer instead of editing it by hand:
43
+
44
+ ```bash
45
+ # from the repository root
46
+ node references/scripts/install.mjs # symlink + dependency + bundle patch
47
+ node references/scripts/install.mjs --uninstall # restore the pre-install profile
48
+ ```
49
+
50
+ The script backs up `package.json`, `pnpm-lock.yaml` and `cordis.patch.yml` to
51
+ `~/.dsh/profiles/desktop/.dsh-links-backup/` on first install; re-running it only
52
+ updates the symlink (useful after moving the repository).
53
+
54
+ ### Restarting
55
+
56
+ DSH loads plugin halves at app launch and caches the client bundle. After changing this
57
+ plugin **quit the app completely** (`Cmd+Q`, the Dock icon must disappear) and start it
58
+ again. The build stamp in the panel footer (e.g. `v0.77.0`) tells you which build is
59
+ actually running.
60
+
61
+ ## Using the panel
62
+
63
+ - **Sections** — `Pinned (N)`, `All links (N)`, `Hidden (N)`. Section headers are
64
+ collapsible; hover a header for the chevron. Counts are omitted while searching.
65
+ - **Rows** — click opens the link in the built-in browser, `⌘`/`Ctrl`+click in the system
66
+ browser. Long titles are truncated with a gradient mask and shown in full by a marquee
67
+ on hover.
68
+ - **Row menu (⋮)** — `Copy link` · `Open in built-in browser` · `Open in system browser` ·
69
+ `Edit title…` · `Hide this link` · `Hide all from <host>` · `Pin this link`.
70
+ - **Edit title…** — inline dialog with `Leave empty to restore the automatic title`;
71
+ custom titles persist and win over conversation titles.
72
+ - **Footer** — live status, collected link count, `Rescan` (rebuilds this conversation's
73
+ list from history; pins and the global icon library are kept) and the build stamp.
74
+ The footer is hidden when the conversation has no links yet; the empty state then shows
75
+ a centred explanation and its own `Rescan` button.
76
+ - **Empty/hidden states** are fully localized (English / 中文 follow the Harness language).
77
+
78
+ ## Architecture
79
+
80
+ ```
81
+ references/
82
+ package.json # dsh.bundle.patch + dsh.client(platform: web), engines.dsh
83
+ cordis.patch.yml # Loader insert: id dsh-links, name dsh-links-panel
84
+ lib/index.js # Host half: favicon route, serialized outbound requests
85
+ lib/client.js # Browser half: tab type, scanning, aggregation, render
86
+ scripts/install.mjs # install / uninstall into a DSH profile
87
+ test/run.mjs # offline tests (VM + fake React, no DSH required)
88
+ locale/ # en/zh manifests for the DSH plugin page
89
+ ```
90
+
91
+ ### Host half (`lib/index.js`)
92
+
93
+ - Registers `GET /dsh-links/favicon?url=…`, which fetches a favicon through
94
+ `curl` (respecting the desktop system proxy) and returns it as a data URL
95
+ (64 KB cap; PNG / GIF / JPEG / WebP / ICO / SVG sniffing).
96
+ - Every outbound request is serialized through one promise chain — the plugin never
97
+ issues parallel network requests.
98
+
99
+ ### Browser half (`lib/client.js`)
100
+
101
+ - Lazy-CJS module: `window.__ModuleLoader__.load({ id, factory })`, only `react` is
102
+ required, so the bundle is self-contained.
103
+ - Scans the session event stream (`assistant/message`, `user/message`, and results of
104
+ web-facing tools only), aggregates rows per seq with a cache, and merges newest titles.
105
+ - Registers the sidebar tab, the guide entry, the pane title and the shortcut.
106
+ - Runs a background tracker over the sessions service so rows keep accumulating while
107
+ the panel is closed.
108
+
109
+ ## Data stored in the browser
110
+
111
+ | Key | Contents |
112
+ |---|---|
113
+ | `dsh-links.links.v3:<sessionId>` | Per-conversation rows + scan cursor |
114
+ | `dsh-links.pinned.v1` | Pinned URLs |
115
+ | `dsh-links.customtitles.v1` | User-renamed titles (URL → title) |
116
+ | `dsh-links.favicons.v1` | Global favicon library (host/domain → data URL) |
117
+ | `dsh-links.blocked-hosts.v1` | Globally hidden hosts |
118
+ | `dsh-links.ignored.v1:<sessionId>` | Per-conversation hidden links |
119
+
120
+ Everything is defensive: storage failures (private mode, quota) only disable persistence,
121
+ never the panel.
122
+
123
+ ## Development
124
+
125
+ ```bash
126
+ node test/run.mjs # offline test suite (no DSH needed)
127
+ ```
128
+
129
+ The tests cover URL normalization and filtering, event extraction (including tool-result
130
+ titles), aggregation/turn counting, per-seq caching, tab registration, rendering,
131
+ Markdown export, hide/restore semantics, localization dictionaries and the Host half's
132
+ helpers.
133
+
134
+ To iterate on the UI, link the repository with `scripts/install.mjs`, edit
135
+ `lib/client.js`, then fully restart DSH and confirm the footer build stamp.
136
+
137
+ ## Limitations
138
+
139
+ - Titles are only as good as the conversation: a bare URL that was never fetched,
140
+ labelled or renamed shows as the URL.
141
+ - `web_fetch` refuses binary content (`application/pdf` and friends), so PDF links never
142
+ receive a page title from the fetch pipeline.
143
+ - No remote title scraping and no LLM calls — by design.
144
+ - Icons are fetched once per host and cached; a site that changes its favicon keeps the
145
+ cached one until the browser storage is cleared.
146
+
147
+ ## Publishing
148
+
149
+ ```bash
150
+ cd references
151
+ npm pack --dry-run # inspect the package contents
152
+ npm version patch # or minor / major
153
+ npm publish --access public
154
+ ```
155
+
156
+ Keep `exports` (`"."`, `"./client"`, `"./locale/*.json"`, `"./icon"`, `"./package.json"`),
157
+ `dsh.bundle.patch`, `dsh.client.inject`, `files` and `engines.dsh` intact.
158
+
159
+ ## License
160
+
161
+ MIT — see [LICENSE](LICENSE).
package/README.zh.md ADDED
@@ -0,0 +1,153 @@
1
+ # dsh-links-panel
2
+
3
+ DSH 右栏「链接」面板:把当前对话里出现过的全部 http(s) URL 汇总成清单,**按根域名
4
+ 分节**,行内显示站点图标 + 页面标题(取不到标题时直接显示 URL)、出现次数;分类
5
+ (资料 / 代码 / 工具 / 媒体,一条链接可带多个)作为筛选标签切换;支持搜索、排序、在右栏浏览器打开、
6
+ 回到对话原文、复制成 Markdown 引用清单、忽略噪音链接。
7
+
8
+ - 设计说明:[DESIGN.md](DESIGN.md)
9
+ - 运行版本:DeepSeek Harness 桌面版 **0.2.1-rc.3**(`package.json` 的 `engines.dsh`)
10
+ - 没有构建步骤;host 半只做一件事:替浏览器读页面标题(跨域限制)
11
+
12
+ ## 结构
13
+
14
+ ```
15
+ references/ # 本插件工程(宏仓库里的一个子目录)
16
+ package.json # dsh.bundle.patch + dsh.client(platform:web)
17
+ cordis.patch.yml # - insert: id: dsh-links, name: 'dsh-links-panel'
18
+ lib/index.js # host 半:GET /dsh-links/meta,用 ctx.web 解析页面 <title>
19
+ lib/client.js # 浏览器半:lazy-CJS bundle,列表 / 分组 / 标题 / 操作
20
+ scripts/install.mjs # 装/卸到 ~/.dsh/profiles/<profile>
21
+ test/run.mjs # 离线测试(VM + 假 React,不需要 DSH 在跑)
22
+ DESIGN.md # 产品设计
23
+ ```
24
+
25
+ `lib/client.js` 用 lazy-CJS 格式加载,执行时只注册工厂:
26
+
27
+ ```js
28
+ window.__ModuleLoader__.load({
29
+ id: 'dsh-links-panel',
30
+ factory: (require) => { /* ... */ return { apply, inject } },
31
+ })
32
+ ```
33
+
34
+ `require` 只能取平台基线模块(这里只用 `react`),所以 bundle 必须自包含。
35
+
36
+ ## 安装(本机 Desktop profile)
37
+
38
+ 桌面版的 profile 在 `~/.dsh/profiles/desktop`,由 Electron 独占管理,`dsh` CLI
39
+ 不能直接操作。用仓库里的脚本做和插件管理器等价的三处写入:
40
+
41
+ ```bash
42
+ # 在仓库根目录
43
+ node references/scripts/install.mjs # 建立软链 + 写 dependency + 选入 bundle
44
+ node references/scripts/install.mjs --uninstall # 恢复安装前的 profile 文件
45
+ ```
46
+
47
+ 也可以进插件目录后 `node scripts/install.mjs`。脚本会把 `package.json` /
48
+ `pnpm-lock.yaml` / `cordis.patch.yml` 首次安装前备份到
49
+ `~/.dsh/profiles/desktop/.dsh-links-backup/`,`--uninstall` 时还原;重复安装只会更新
50
+ 软链指向(换仓库位置后重跑一次即可)。
51
+
52
+ 生效方式:
53
+
54
+ - 桌面版启用了 HMR 时,profile manifest 变化会触发重组,右栏「+」里直接出现
55
+ 「链接」,无需重启;
56
+ - 若没有生效,重启 DeepSeek Harness(重启后会话历史仍在)。
57
+
58
+ ## 使用
59
+
60
+ 1. 打开一个会话,展开右栏(会话头部右上角的展开按钮)。
61
+ 2. 右栏顶部「+」→ guide 页 → **链接**。
62
+ 3. 点一行在右栏浏览器打开;`Cmd+点击` 走系统浏览器;`⌖` 回到对话原文。
63
+
64
+ 清单操作:
65
+
66
+ - **标题**:以页面 `<title>` 为准(Host 抓取);抓不到就用足够的捕获标题兜底,再没有
67
+ 就整行显示 URL。打不开的页面(404/410、域名失效、超时、403、非 HTML)不进列表。
68
+ - **行菜单**:点行尾 `⋮` →「在右栏打开 / 在系统浏览器打开 / 复制链接 / 定位到对话
69
+ 原文 / 忽略这条链接 / 忽略 example.com」。点行本身等于「在右栏打开」。
70
+ - **排列**:列表是平的(无分节头),同一根域名(`docs.acme.dev` 归到 `acme.dev`)的行相邻排列。
71
+ - **标签**:`资料 / 代码 / 工具 / 媒体`,一个链接可以同时有多个(CodePen = 代码 + 工具、
72
+ Figma = 工具 + 媒体、`.ipynb` = 资料 + 代码);行尾色点表示标签,顶部 chips 按标签筛选。
73
+ 「被引用」筛选已移除。
74
+ - **复制**:底部「复制」把当前筛选结果复制成 Markdown(按三类分组),可直接粘进
75
+ 笔记或分享。
76
+ - **忽略**:被忽略的条目在底部「已忽略 N」里可以逐条恢复。忽略表按会话存在
77
+ `localStorage`。
78
+
79
+ 面板底部出现「加载更早」,说明当前只统计了已加载的最近一窗历史,点一下会
80
+ 翻一页并自动重扫。
81
+
82
+ ## 解码约定(给维护者)
83
+
84
+ - URL 语法与截断:`URL_RE` + `trimTrailing`(处理 markdown `](…)`、句末标点、
85
+ 中文标点、不平衡右括号)。
86
+ - 归一化:去 `www.` / 追踪参数 / 锚点 / 尾斜杠,host 小写、路径保大小写。
87
+ - 跳过私网与本地地址;纯数字 host(如 `http://127` 的缩写)也丢弃。
88
+ - 静态资源(`.png/.svg/.ico/.jpg/.webp/.woff/.ttf/.css/.map` 等)不算参考,直接丢弃。
89
+ - 只接受合法主机名,最后一节须像 TLD(字母 ≥2 或 `xn--`);`https://x$`、`https://a.b`、`https://foo.1` 直接排除。
90
+ - 不接受 IP 字面量(v4/v6,公网也排除)。
91
+ - 排除占位域名:注册域名首段是 `acme`/`example`/`foo`/`bar`/`baz`/`qux`/`demo`/`sample`/`placeholder`/`dummy`/`test`(含 `sub.acme.co.uk`、`foo.com`)。
92
+ - 排除 API / fetch 端点:`api.*`、首段 `api`/`graphql`/`rest`/`rpc`/`openapi`/`swagger`/`jsapi`/`fetch`、`/v2/` 版本路径、`.json`/`.jsonl`/`.ndjson` 数据文件。
93
+ - 不可达的行不展示:Host 探测 404/410 或 DNS/连接失败即隐藏;403、超时、5xx 保留(浏览器可能能打开)。
94
+ - 跳过保留域名:`example.com/.net/.org` 及其子域,`.example/.test/.invalid/.localhost` 后缀。
95
+ - 事件抽取按类型分支;工具名要从 `data.message.toolCallId`(或
96
+ `data.message.source.callId`)反查 `tool/call` 表,`web_search` 只收 `Sources:`
97
+ 之后,`web_fetch` 只收首行 `Fetched <url>`,避免把抓回整页的导航链接算成参考。
98
+ - 窗口条目类型是 `'event'`(另有 `'transient'` 临时行);抽取结果按 `seq` 缓存,
99
+ `tool/result` 在工具名未知前不缓存,等 call 进窗口后重算一次。
100
+ - 标题与图标:每次 tab 重新可见就用 `IntersectionObserver` 触发一轮刷新(Host 路由带 `refresh=1`,绕过缓存),图标优先取页面 `<link rel="icon">`,回退 `/favicon.ico`、再回退字母块;刷新期间保留旧值、失败不覆盖。
101
+ - 标题:以页面 `<title>` 为准;捕获文本只在足够像标题时兜底(CJK ≥2 字 / 含空格
102
+ 且 ≥4 字 / ≥8 字),否则显示 URL。可用性:Host 返回 `dead`(404/410、DNS、拒绝
103
+ 连接)或 `ok:false`(超时、403、非 HTML)的行不展示。标题抓取走 host 路由
104
+ `GET /dsh-links/meta`(`ctx.web.fetch` + `<title>` 解析),客户端每次挂载最多问
105
+ 24 个、每批 8 个,结果记 `localStorage`(`dsh-links.titles.v1`)。
106
+ - 根域名:`rootDomain` 去 `www.` 后取后两段,内置 `co.uk` / `com.cn` 等少量多段后缀表。
107
+ - 忽略表:`hostIgnored` 做"裸域名覆盖子域名",`withIgnore` 加宽时吸收被覆盖的窄
108
+ 条目,`restoreOne` 恢复单行时给同域兄弟补 link 级条目;写 `localStorage` 全部包
109
+ try/catch。
110
+ - Markdown 导出:`markdownOf` 只做纯字符串拼装,标题里的 `[` `]` 会被去掉,避免
111
+ 破坏链接语法。
112
+
113
+ ## 测试
114
+
115
+ ```bash
116
+ node test/run.mjs
117
+ ```
118
+
119
+ 断言覆盖:URL 归一化/去重、三类判定、围栏代码、两类工具结果(含 callId 位置)、
120
+ 事件聚合与计数、per-seq 缓存、tab 注册参数、首屏渲染与行渲染、Markdown 导出与转义、
121
+ 忽略表增删(含子域名覆盖、幂等、单行恢复)、标题接受规则,以及 host 半的 `<title>`
122
+ 解析、实体解码、URL 校验与批量请求解析。
123
+
124
+ ## 发布(npm)
125
+
126
+ 这个插件是一个普通的 DSH bundle 包,发布就是打出 npm 包:
127
+
128
+ ```bash
129
+ cd references
130
+ npm pack --dry-run # 先确认打包内容(会列出 files 里的全部文件)
131
+ npm version patch # 或 minor / major
132
+ npm publish --access public
133
+ ```
134
+
135
+ 发布前检查:
136
+
137
+ - `package.json` 里不要有 `private: true`(已移除),`name` 用 `dsh-links-panel`;
138
+ - `exports` 必须保留 `"."`、`"./client"`、`"./locale/*.json"`、`"./icon"`、`"./package.json"`;
139
+ - `dsh.bundle.patch` 指向 `cordis.patch.yml`,里面的 `name` 与包名一致;
140
+ - `files` 覆盖运行时全部文件(lib、cordis.patch.yml、locale、icon、README、LICENSE);
141
+ - `engines.dsh` 与你验证过的 Harness 版本一致;`dsh.client.inject` 声明 UI 依赖。
142
+
143
+ 用户安装:
144
+
145
+ 1. 打开 DSH 右栏 → **Plugins** 页 → 安装 bundle,填包名 `dsh-links-panel`(或 `npm:...` / 本地目录);
146
+ 2. 或者在某个 profile 目录里执行 `dsh plugin --profile desktop add dsh-links-panel`,再在 Plugins 页选中该 bundle;
147
+ 3. 重启后打开会话,右栏出现「应用链接 / Reference Links」tab。
148
+
149
+ 本地开发安装(当前仓库目录直接链接进 desktop profile):
150
+
151
+ ```bash
152
+ node references/scripts/install.mjs
153
+ ```
@@ -0,0 +1,5 @@
1
+ # 以 bundle 形式把这个插件插进 profile 的 Loader 树:
2
+ # 一条 insert 让 host 侧加载 lib/index.js、client 侧加载 lib/client.js。
3
+ - insert:
4
+ - id: dsh-links
5
+ name: 'dsh-links-panel'
package/icon.svg ADDED
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 48 48">
2
+ <path d="M0 0h48v48H0z" fill="none" />
3
+ <path fill="#1976D2" d="M38 13h-3c-5.5 0-10 4.5-10 10s4.5 10 10 10h3c5.5 0 10-4.5 10-10s-4.5-10-10-10m0 16h-3c-3.3 0-6-2.7-6-6s2.7-6 6-6h3c3.3 0 6 2.7 6 6s-2.7 6-6 6M13 13h-3C4.5 13 0 17.5 0 23s4.5 10 10 10h3c5.5 0 10-4.5 10-10s-4.5-10-10-10m0 16h-3c-3.3 0-6-2.7-6-6s2.7-6 6-6h3c3.3 0 6 2.7 6 6s-2.7 6-6 6" />
4
+ <path fill="#42A5F5" d="M33 21H15c-1.1 0-2 .9-2 2s.9 2 2 2h18c1.1 0 2-.9 2-2s-.9-2-2-2" />
5
+ </svg>