dsh-artifact-linker 0.0.0-stage → 0.2.5
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 +110 -0
- package/LICENSE +21 -0
- package/README.md +135 -2
- package/README.zh.md +185 -0
- package/cordis.patch.yml +52 -0
- package/icon.svg +12 -0
- package/lib/client.js +454 -0
- package/lib/index.js +584 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +57 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 — 2026-10-27
|
|
4
|
+
|
|
5
|
+
### 用户实测反馈驱动的重写
|
|
6
|
+
|
|
7
|
+
三条反馈,全部照办:
|
|
8
|
+
|
|
9
|
+
1. 「这插件不对劲不是我想要的效果,我希望打开的方式是从对话中的名称可以直接点击或者右键方式打开而不是悬浮球」
|
|
10
|
+
2. 「悬浮球你做的也有问题很多不是产物被你识别了」
|
|
11
|
+
3. 「而且切换窗口刷新很慢」
|
|
12
|
+
|
|
13
|
+
### 删掉的东西(v0.1 的设计错误)
|
|
14
|
+
|
|
15
|
+
- **整个悬浮球 + 产物面板**:右下角 `📎 N` 胶囊、清单面板、状态点、刷新/清缓存按钮 —— 全删
|
|
16
|
+
- **全量 TreeWalker 扫描**:不再遍历文档文本节点
|
|
17
|
+
- **MutationObserver**:不再监听 DOM 变化
|
|
18
|
+
- **setInterval 定时刷新 / 300ms 防抖扫描**:不再有任何常驻定时器
|
|
19
|
+
- 体积:832 行 / 36KB → 368 行 / 15.8KB(顺带缓解 `dsh-experimental-inspector` 的 source-frame 字节上限)
|
|
20
|
+
|
|
21
|
+
### 新增的交互
|
|
22
|
+
|
|
23
|
+
| 操作 | 效果 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| 左键点产物名 | 在资源管理器里揭示并选中 |
|
|
26
|
+
| 右键点产物名 | 自建菜单:揭示 / 默认程序打开 / 复制完整路径 |
|
|
27
|
+
| 悬停命中 | 鼠标变手型(唯一视觉提示,mousemove 150ms 节流) |
|
|
28
|
+
| 非产物处右键 | 完全放行,宿主原生菜单照常 |
|
|
29
|
+
| 解析不到 | 静默放行或一次性提示,绝不假装成功 |
|
|
30
|
+
|
|
31
|
+
### 修掉的真 bug ①:面板里全是「未解析」
|
|
32
|
+
|
|
33
|
+
**根因**:客户端调 `/resolve` 没传 `base`,宿主就固定用 `process.cwd()`。那是 DSH 宿主进程的工作目录,**不是会话 workspace** → 相对路径和裸文件名全部解析失败。
|
|
34
|
+
|
|
35
|
+
**修法**:新增 `candidateBases()` —— 宿主自己列出所有可能是 workspace 的目录:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
process.cwd()
|
|
39
|
+
~/.dsh/*/workspace
|
|
40
|
+
~/.dsh/profiles/*/workspace
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
再由 `resolveAcross()` 逐个当 base 试,命中即返回。`/info` 现在也返回 `bases` 数组,排查时可以直接看宿主认为的候选目录。
|
|
44
|
+
|
|
45
|
+
**实测 12/12**:`测试报告-2026.pdf`、`对比图.tif`、`中文名称-测试.zip`、`数据汇总表.xlsx`、`运行日志.log`、`路径 带空格.pdf` 全部解析到真实绝对路径。
|
|
46
|
+
|
|
47
|
+
### 修掉的真 bug ②:Fluent 多级扩展名被截断
|
|
48
|
+
|
|
49
|
+
`fluid.cas.h5` 以前被截成 `fluid.cas` → 撞一个不存在的文件 → 点了没反应。正则尾部加 `(?:\.[A-Za-z0-9]{1,6})*` 吃掉多级尾巴。
|
|
50
|
+
|
|
51
|
+
### 探针(全部落盘可复跑,位置:`.dsh\backups\artifact-linker-probes\`)
|
|
52
|
+
|
|
53
|
+
| 探针 | 覆盖 | 结果 |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `probe-v2.mjs` | 识别层实跑 14 正例 + 7 反例 + 9 条禁止 + 11 条必须 + 体积红线 | 45/0 |
|
|
56
|
+
| `probe-client-materialize.mjs` | cordis 客户端契约(有 apply / 不碰 document / 单例守卫 / dispose 清场) | 13/0 |
|
|
57
|
+
| `probe-host-apply.mjs` | 宿主 apply 契约(6 路由 / prompt 段 / 403 / disposer) | 21/0 |
|
|
58
|
+
| `verify-resolve.mjs` | 真实文件解析 12 项 | 12/0 |
|
|
59
|
+
|
|
60
|
+
探针刻意放在包外:包内每多一个文件,`dsh-experimental-inspector` 采集的源码量就更大,撑爆 source-frame 上限会让 **host exit 1**。
|
|
61
|
+
|
|
62
|
+
### 已知边界(不是 bug)
|
|
63
|
+
|
|
64
|
+
- 只认白名单扩展名,**刻意不含 exe/msi/dll**:正文里这些多半是命令输出,不是要打开的产物
|
|
65
|
+
- 只在**点击瞬间**判定,不做任何预扫描 → 尚未渲染出来的内容点不到(但也不会误报)
|
|
66
|
+
- `Ctrl/Shift/Alt + 左键` 一律放行给宿主,方便你选中文字
|
|
67
|
+
|
|
68
|
+
## 0.1.0 — 2026-10-27
|
|
69
|
+
|
|
70
|
+
首个可运行版本。
|
|
71
|
+
|
|
72
|
+
### 宿主端
|
|
73
|
+
|
|
74
|
+
- 新增常驻 system-prompt 段 `artifact-path-rules`(order 200,约 600 字符):要求模型把产物写成完整绝对路径、给用户看的正文只写文件名、不确定时先核实再写、绝不编造路径、路径含空格或中文用反引号包裹。
|
|
75
|
+
- 新增 6 条回环路由(全部 `127.0.0.1` 限定,外部来源 403):
|
|
76
|
+
- `GET /info` — 插件身份、版本、workspace、三个开关的实际生效值
|
|
77
|
+
- `GET /resolve?p=` — 路径解析,三源兜底(绝对 → workspace 相对 → 裸文件名递归搜索)
|
|
78
|
+
- `GET /stat?p=` — 批量 stat,10 秒 TTL 缓存
|
|
79
|
+
- `GET /flush-cache` — 清空路径解析与 stat 缓存
|
|
80
|
+
- `GET /reveal?p=` — 在资源管理器揭示并选中
|
|
81
|
+
- `GET /open?p=` — 用系统默认关联程序打开
|
|
82
|
+
- `reveal` / `open` 走 `execFile(cmd, args)` 参数数组,不经 shell,路径中的 `& | ; %VAR%` 均为字面量。
|
|
83
|
+
- 跨平台:Windows `explorer.exe /select,`、macOS `open -R`、Linux `xdg-open`(揭示退化为打开父目录)。
|
|
84
|
+
|
|
85
|
+
### 客户端端
|
|
86
|
+
|
|
87
|
+
- 新增 `__ModuleLoader__.load` bundle(id = `dsh-artifact-linker`,与包名一致)。
|
|
88
|
+
- **全程只读正文,不修改任何 DOM 节点、不添加属性、不重挂 React 节点。** 可点性由 document 捕获期 `click` + `caretRangeFromPoint` 反查光标所在词实现,节点被 React 重渲染换掉也不会失效。
|
|
89
|
+
- 修饰键分流:左键揭示并选中 / Ctrl·Shift·Cmd 系统程序打开 / Alt 复制路径。
|
|
90
|
+
- 新增右下角浮动胶囊 `📎 N`(Shadow DOM 挂 `body`,不主动 `focus()`,`mousedown` 在 document 捕获期截断以避免宿主 `closeOutside` 关掉面板)。
|
|
91
|
+
- 产物清单:状态点(存在 / 已不存在 / 未确认)、大小、相对修改时间、每行 `📍 ↗ ⧉` 三按钮、刷新 / 清缓存 / 清空。
|
|
92
|
+
- 明暗主题跟随:读取 `body` 背景亮度判定,仅在切换时重设 CSS 变量,不逐帧读样式。
|
|
93
|
+
- **URL 排除**:渲染后的链接文本节点本身就是 `https://x.com/a.png`,里面那个 `a.png` 会被文件名正则误命中、被当成 workspace 产物。现在每段文本先算出所有 URL 区间,命中落进去一律丢弃(扫描与点击反查两条路径都做了)。
|
|
94
|
+
- 单例守卫 `window.__dshArtifactLinkerV1`:热重载前先 `dispose()` 旧实例,`dispose()` 摘净全部监听器、定时器、观察者与 DOM。**热重载清理不掉「修复前加载的旧实例」,改完必须完全重启 DSH。**
|
|
95
|
+
- 附 `scripts/probe-regex.mjs` 探针回归:从真实源码切出识别段,跑 14 条识别/排除断言 + 10 条源码静态断言(不出现 iframe、不构造绝对 URL 去 fetch、有单例守卫、dispose 摘净监听、不修改正文 DOM、不接管 markdown 图片、mousedown 闸门在 document 捕获期、浮层不主动 focus、bundle id 一致)。`node --check` 只查语法,这些边界只有探针能兜住。
|
|
96
|
+
|
|
97
|
+
### 已知边界
|
|
98
|
+
|
|
99
|
+
- 不接管 markdown 图片与链接语法(改写成芯片会导致图不显示)。
|
|
100
|
+
- 扩展名走白名单,不做「看起来像路径就标成产物」的猜测;`exe/lock` 之类故意不在白名单里。
|
|
101
|
+
- 裸文件名在 workspace 内搜到多个同名文件时返回候选,不自动选择。
|
|
102
|
+
- `allowOutside` 默认 `true`(产物大多在 E:/D: 项目盘,不在 workspace 内);多用户环境建议改 `false`,否则等于允许「模型给个路径就打开任意文件」。
|
|
103
|
+
|
|
104
|
+
### 交付前按官方 14 条硬规则自查修正(未发布即改,同属 0.1.0)
|
|
105
|
+
|
|
106
|
+
- **硬规则 #6(peer range 在安装时被强制校验)**:`@deepseek-ai/dsh` 由 `>=0.1.7-rc.1` 改为 `>=0.2.1-rc.3`。semver 规定 prerelease 版本只匹配「major.minor.patch 相同、且 range 内带 prerelease 比较器」的范围,原范围对运行时 `0.2.1-rc.3` 实测为 **false** —— 不改这一条,`dsh plugin add` 会直接拒绝安装。`engines.dsh` 同步。
|
|
107
|
+
- **硬规则 #13(样式必须走 `--dsw-alias-*` 主题令牌)**:12 处自造颜色变量改为三层链 `var(--dsw-alias-*, var(--alXxx, <硬编码>))`,取 `bg-layer-2` / `label-primary` / `border-l2` / `interactive-bg-hover`(均在 app.asar 内实测存在,命中 120~566 次)。自算明暗降为第二层兜底。
|
|
108
|
+
- **硬规则 #5(tunable 不许硬编码)**:`STAT_TTL_MS` → `statTtlMs`、裸名反查深度 `2` → `searchMaxDepth`、铁律文本 → `rulesText`,三者均可由 `cordis.patch.yml` 覆盖(`searchMaxDepth` 硬上限 8 层,防止 `/resolve` 退化成全盘扫描)。
|
|
109
|
+
- **硬规则 #8(双语文档成对)**:`README.md` 改为英文主文档,新增 `README.zh.md` 中文完整版,两者顶部互链;`files` 白名单同步。
|
|
110
|
+
- 未采用的项:硬规则 #13 的「render React in a slot」—— 本插件不注册 slot,用 `document.body` + Shadow DOM 承载悬浮胶囊(与 `dsh-todo-float-ball` 同构),因为它的可点性挂在正文文本节点上、不能放进某个 slot 里。硬规则 #12「不要手写 profile package.json」—— 本机既有装机方式即手写 `dependencies` 一行 + `link:` 依赖,已在 README 标注。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 loyalchiiina
|
|
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,136 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-artifact-linker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Every artifact your agent delivers is one click away — no more "copy the path → open Explorer → paste into the address bar → Enter".**
|
|
4
|
+
**让 DSH 交付的每一个产物,都能点一下就到位 —— 不用再「复制路径 → 打开文件管理器 → 贴到地址栏 → 回车」。**
|
|
5
|
+
|
|
6
|
+
**English** · [简体中文](README.zh.md) · [Interaction](#interaction) · [Install](#install) · [Config](#config) · [Boundaries](#what-it-does-not-do) · [Troubleshooting](#troubleshooting)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## The problem
|
|
11
|
+
|
|
12
|
+
DSH already previews images, code, PDFs and Office files in the sidebar. But **scripts, Fluent exports, packed archives, temporary dumps** have no previewer — all you get in the transcript is a bare file name, so opening one costs four mechanical steps:
|
|
13
|
+
|
|
14
|
+
1. Select and copy the path (assuming the model even printed one)
|
|
15
|
+
2. Open File Explorer
|
|
16
|
+
3. Paste into the address bar
|
|
17
|
+
4. Press Enter
|
|
18
|
+
|
|
19
|
+
Worse: the model often prints a **relative path or just a file name**, so step 1 fails outright and you have to go hunting for the file first.
|
|
20
|
+
|
|
21
|
+
This plugin removes all four steps **and** removes the root cause.
|
|
22
|
+
|
|
23
|
+
## What it does
|
|
24
|
+
|
|
25
|
+
Two halves, one contract.
|
|
26
|
+
|
|
27
|
+
| Half | Responsibility |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| **Host** (`lib/index.js`) | Registers a **resident system-prompt section** so "declare artifacts with full absolute paths" holds in *every* session and *every* preset — no skill needs to be loaded. Exposes six loopback-only routes for path resolution, `stat`, reveal and open. |
|
|
30
|
+
| **Client** (`lib/client.js`) | Turns artifact names in the transcript into clickable targets: left click reveals, right click opens a menu. **No resident UI, no scanning, no timers.** |
|
|
31
|
+
|
|
32
|
+
The host half closes the loop: even when the model only says `result.h5`, the route layer resolves it against the session workspace, then falls back to a shallow recursive basename search, and returns the real absolute path.
|
|
33
|
+
|
|
34
|
+
## Interaction
|
|
35
|
+
|
|
36
|
+
Click behaviour is decided by the modifier key — no menu, no first-time setup:
|
|
37
|
+
|
|
38
|
+
| Action | Result |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| **Left click** on a file name / path | Reveal in File Explorer *with the file selected* |
|
|
41
|
+
| **Ctrl / Shift / Cmd + click** | Open with the system's default application |
|
|
42
|
+
| **Alt + click** | Copy the full absolute path to the clipboard |
|
|
43
|
+
|
|
44
|
+
### Interactions (v0.2 — no floating pill any more)
|
|
45
|
+
|
|
46
|
+
| Action | Result |
|
|
47
|
+
|---|---|
|
|
48
|
+
| **Left click** an artifact name | Reveals and selects the file in Explorer |
|
|
49
|
+
| **Right click** an artifact name | Inline menu: `📍 reveal` / `↗ open with default app` / `⧉ copy absolute path` |
|
|
50
|
+
| **Hover** an artifact name | Cursor turns into a pointer (the only visual hint) |
|
|
51
|
+
| Right click on **non-artifact** text | Fully passed through — the host's native menu appears as usual |
|
|
52
|
+
| File missing / unresolvable | Silent pass-through or a one-shot toast. **Never pretends to succeed.** |
|
|
53
|
+
|
|
54
|
+
The following describes the removed v0.1 design:
|
|
55
|
+
|
|
56
|
+
The panel refreshes on its own; there are also explicit refresh / flush-cache / clear / close controls.
|
|
57
|
+
|
|
58
|
+
## How paths resolve
|
|
59
|
+
|
|
60
|
+
A single path may arrive in three shapes, tried in order:
|
|
61
|
+
|
|
62
|
+
1. **Absolute** — used as-is (`E:\project\out\case.h5`, `\\nas\share\a.zip`). Both drive letters and UNC are recognised.
|
|
63
|
+
2. **Relative** — joined against the session workspace (`out\case.h5` → `<workspace>\out\case.h5`).
|
|
64
|
+
3. **Bare file name** — a shallow recursive basename search under the workspace (depth 2 by default), used only when exactly one match is found. Multiple matches are surfaced as candidates instead of guessing.
|
|
65
|
+
|
|
66
|
+
Results are cached with a 10 s TTL so that repeated queries from DOM re-renders do not hammer the disk, while a just-deleted file still flips to "gone" quickly.
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# 1. Drop the package into DSH's local plugin directory
|
|
72
|
+
# Windows: %USERPROFILE%\.dsh\local-plugins\dsh-artifact-linker
|
|
73
|
+
|
|
74
|
+
# 2. Declare the dependency in your profile's package.json
|
|
75
|
+
# %USERPROFILE%\.dsh\profiles\<profile>\package.json → dependencies
|
|
76
|
+
# "dsh-artifact-linker": "link:C:/Users/<you>/.dsh/local-plugins/dsh-artifact-linker"
|
|
77
|
+
|
|
78
|
+
# 3. Fully restart DSH — client bundles are NOT reloaded by Ctrl+R
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> The host half needs `webServer` and `systemPrompt`; the client half is a `__ModuleLoader__` bundle. Peers declare `@deepseek-ai/dsh >=0.2.1-rc.3` — the installer compares the runtime version against that range and refuses a mismatch.
|
|
82
|
+
|
|
83
|
+
## Config
|
|
84
|
+
|
|
85
|
+
All tunables live in `cordis.patch.yml` (or the profile-level patch). Changing them requires a restart.
|
|
86
|
+
|
|
87
|
+
| Key | Default | Meaning |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `injectRules` | `true` | Register the resident prompt section |
|
|
90
|
+
| `clientChips` | `true` | Client capsule + click takeover. `false` = pure injection mode |
|
|
91
|
+
| `allowOutside` | `true` | Allow resolving paths outside the workspace |
|
|
92
|
+
| `statTtlMs` | `10000` | Host-side `stat` cache lifetime |
|
|
93
|
+
| `searchMaxDepth` | `2` (max 8) | Recursion depth for bare-filename lookup |
|
|
94
|
+
| `rulesText` | built-in 6 lines | Replace the injected rules wholesale (array or multi-line string) |
|
|
95
|
+
|
|
96
|
+
On `allowOutside`: artifacts almost always live on a project drive (`E:\`, `D:\`) rather than inside the DSH workspace, so a strict default would make the plugin useless on first run. **Set it to `false` when shipping to other people** — that is the gate which stops this plugin from becoming "the model names any path and your machine opens it".
|
|
97
|
+
|
|
98
|
+
## What it does NOT do
|
|
99
|
+
|
|
100
|
+
- **Never modifies a single DOM node.** React owns the transcript; mutating its nodes causes blank renders on re-render. Clickability comes from a capture-phase `click` listener plus `caretRangeFromPoint` to work out which word was hit.
|
|
101
|
+
- **Never takes over markdown image or link syntax.** Rewriting `` into a chip would break the image.
|
|
102
|
+
- **Never writes a file, never phones home.** Zero telemetry. The only `spawn` calls are the explicit `/reveal` and `/open` routes, using `execFile` with an argument array (no shell, no injection surface), guarded by a loopback-origin check.
|
|
103
|
+
- **Never renames or moves anything.** Read-only by construction.
|
|
104
|
+
- **Does not unify with the official `present` card.** If an artifact was already delivered as a card, the injected rules tell the model not to repeat it.
|
|
105
|
+
|
|
106
|
+
## Troubleshooting
|
|
107
|
+
|
|
108
|
+
| Symptom | Check |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| Nothing clickable | Was DSH **fully restarted**? `dsh.client` bundles do not hot-reload |
|
|
111
|
+
| Capsule missing | `clientChips` is `true`; the loader reports `loaded without registering` if the bundle id mismatches `package.json` |
|
|
112
|
+
| Click does nothing | File may have been deleted — the capsule marks it `gone` |
|
|
113
|
+
| "outside workspace" | Either set `allowOutside: true`, or the path really is outside the project |
|
|
114
|
+
| Injection not working | `injectRules` is `true`; look for the host log `artifact path rules registered` |
|
|
115
|
+
| Theme colours look off | The plugin prefers `--dsw-alias-*` tokens, then self-detected light/dark, then hardcoded fallbacks |
|
|
116
|
+
|
|
117
|
+
## Layout
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
dsh-artifact-linker/
|
|
121
|
+
├── package.json # dsh.bundle + dsh.client, peer >=0.2.1-rc.3
|
|
122
|
+
├── cordis.patch.yml # the patch entry (never duplicate this id)
|
|
123
|
+
├── lib/
|
|
124
|
+
│ ├── index.js # host: prompt section + 6 loopback routes
|
|
125
|
+
│ └── client.js # client: __ModuleLoader__ bundle, read-only DOM
|
|
126
|
+
├── locale/{zh,en}.json # meta.title / meta.description
|
|
127
|
+
├── icon.svg
|
|
128
|
+
├── scripts/probe-regex.mjs
|
|
129
|
+
├── README.md / README.zh.md
|
|
130
|
+
├── CHANGELOG.md
|
|
131
|
+
└── LICENSE
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT © loyalchiiina
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# dsh-artifact-linker · 产物路径联动器
|
|
2
|
+
|
|
3
|
+
> 让 DSH 交付的每一个产物,都能**点一下就到位**——不用再「复制路径 → 打开文件管理器 → 贴到地址栏 → 回车」。
|
|
4
|
+
|
|
5
|
+
[English](README.md) · **简体中文** · [安装](#安装) · [交互](#交互) · [配置](#配置) · [边界](#它不做什么) · [排查](#排查)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 它解决什么
|
|
10
|
+
|
|
11
|
+
DSH 里预览器已经能开图片、代码、pdf、office。但**插件、脚本、Fluent 出图、打包产物、临时导出**这些文件,正文里只给一个名字,想打开它得走完四步机械劳动:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
看到 "结果.xlsx" → 复制路径 → Win+E → 地址栏输入 → 回车 → 在一堆同级文件里找那一个
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`dsh-artifact-linker` 把这条路压成 **0 步**:正文里的文件名本身就是入口。
|
|
18
|
+
|
|
19
|
+
它做两件互相咬合的事:
|
|
20
|
+
|
|
21
|
+
| 半边 | 做什么 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| **宿主(提示词层)** | 常驻注入一条「产物路径铁律」,要求模型把产物写成**完整绝对路径**、而给用户看的正文**只写文件名**——路径有据可查,正文又不被长串路径污染 |
|
|
24
|
+
| **客户端(渲染层)** | 正文里的产物名变成可点目标:左键揭示、右键菜单。**零常驻 UI、零扫描、零定时器** |
|
|
25
|
+
|
|
26
|
+
第 1 条管「模型把路径写对」,第 2 条管「用户点得动」。缺前者,路径经常是猜的、点开是死链;缺后者,路径再对也要人去复制。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 交互
|
|
31
|
+
|
|
32
|
+
正文里点一个文件名(或绝对路径):
|
|
33
|
+
|
|
34
|
+
| 操作 | 结果 |
|
|
35
|
+
|---|---|
|
|
36
|
+
| **左键** | 在系统资源管理器里**揭示并选中**该文件(不是打开它,是把它亮在你面前) |
|
|
37
|
+
| **Ctrl / Shift / Cmd + 左键** | 用系统默认关联程序打开(Excel、PS、播放器……) |
|
|
38
|
+
| **Alt + 左键** | 复制完整绝对路径到剪贴板 |
|
|
39
|
+
|
|
40
|
+
### 三种交互(v0.2 起不再有浮球)
|
|
41
|
+
|
|
42
|
+
正文里出现产物名时:
|
|
43
|
+
|
|
44
|
+
| 操作 | 效果 |
|
|
45
|
+
|---|---|
|
|
46
|
+
| **左键**点产物名 | 在资源管理器里揭示并选中该文件 |
|
|
47
|
+
| **右键**点产物名 | 本插件菜单:`📍 揭示` / `↗ 默认程序打开` / `⧉ 复制完整路径` |
|
|
48
|
+
| **悬停**在产物名上 | 鼠标变手型(唯一的视觉提示) |
|
|
49
|
+
| 在**非产物**文字上右键 | 完全放行,宿主原生菜单照常出现 |
|
|
50
|
+
| 文件不存在 / 解析不到 | 静默放行或一次性提示,**绝不假装成功** |
|
|
51
|
+
|
|
52
|
+
以下为 v0.1 的历史设计(已移除):
|
|
53
|
+
|
|
54
|
+
- 点击展开清单,按发现时间倒序,每行给出:类型图标 · 文件名 · 状态点 · 完整路径 · 大小 · 修改时间;
|
|
55
|
+
- 状态点:🟢 存在 / 🔴 已不存在(划掉) / ⚪ 尚未确认;
|
|
56
|
+
- 每行右侧三个按钮:`📍` 揭示 · `↗` 打开 · `⧉` 复制;
|
|
57
|
+
- 顶部:`刷新`(重查状态)· `清缓存`(清宿主 stat 缓存)· `清空`(只清面板清单,**不动任何文件**)。
|
|
58
|
+
|
|
59
|
+
同一文件名出现多次只记一条(按次数累计),清单上限 200 条,超出后按发现顺序淘汰最旧。
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 路径怎么解析(三源兜底)
|
|
64
|
+
|
|
65
|
+
模型嘴里的话不可全信,插件按顺序试:
|
|
66
|
+
|
|
67
|
+
1. **`/workspace/<...>`** —— DSH 工作区的虚拟根,映射到真实 workspace 目录;
|
|
68
|
+
2. **原生绝对路径** —— `E:\proj\out\a.xlsx` 直接用;
|
|
69
|
+
3. **workspace 内的相对路径** —— 以 workspace 为基准拼接;
|
|
70
|
+
4. **裸文件名** —— 在 workspace 内递归搜同名文件(跳过 `node_modules/.git/dist/build/` 等),按「最近修改优先」+「目录深度浅优先」排序:
|
|
71
|
+
- 命中 **1 个** → 直接用;
|
|
72
|
+
- 命中 **多个** → 返回候选列表交给用户,**绝不猜**(猜错比不猜更糟);
|
|
73
|
+
- 命中 **0 个** → 若 `allowOutside: true`,再在磁盘上按名字做一次浅层搜索。
|
|
74
|
+
|
|
75
|
+
结果一律带 10 秒 TTL 缓存,避免重复 stat 打满磁盘。
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 安装
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 1. 放进 DSH 的本地插件目录
|
|
83
|
+
# Windows: %USERPROFILE%\.dsh\local-plugins\dsh-artifact-linker
|
|
84
|
+
|
|
85
|
+
# 2. profile 里声明依赖(<profile> 换成 desktop / web / xiufu)
|
|
86
|
+
# 在 %USERPROFILE%\.dsh\profiles\<profile>\package.json 的 dependencies 里加:
|
|
87
|
+
# "dsh-artifact-linker": "file:~/.dsh/local-plugins/dsh-artifact-linker"
|
|
88
|
+
|
|
89
|
+
# 3. 完全重启 DSH(客户端 bundle 不随 Ctrl+R 重载)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> ⚠️ **`file:` 依赖的坑**:`file:` / `link:` 指向会在 profile 重装依赖时把目录内容**按快照恢复**,本地改动可能被回滚。长期使用建议 `npm pack` 成 tarball 后用 `file:` 指向 tgz,或直接发到 npm。
|
|
93
|
+
|
|
94
|
+
装好后确认:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
GET http://127.0.0.1:<port>/dsh-artifact-linker/info
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
正常应返回插件名、版本、`workspace`、以及 `injectRules / clientChips / allowOutside` 三个开关的**实际生效值**(这一条是排障时唯一可信的地方——别信 README,信 `/info`)。
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 配置
|
|
105
|
+
|
|
106
|
+
在 profile 的 `cordis.patch.yml` 里给本插件条目加 `config`(**不要在 profile 里手写第二段 `insert`**,同一 entry id 插两行会 `duplicate loader entry id`):
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
- id: dsh-artifact-linker
|
|
110
|
+
name: dsh-artifact-linker
|
|
111
|
+
config:
|
|
112
|
+
injectRules: true # 常驻 prompt 铁律
|
|
113
|
+
clientChips: true # 是否接管正文点击(false = 只留提示词注入,不动鼠标)
|
|
114
|
+
allowOutside: true # 是否允许解析 workspace 之外的路径
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| 开关 | 默认 | 置 false / true 的效果 |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `injectRules` | `true` | `false` = 不注入 prompt 段,模型不再被要求输出绝对路径 |
|
|
120
|
+
| `clientChips` | `true` | `false` = 纯注入模式,不接管任何鼠标事件 |
|
|
121
|
+
| `allowOutside` | `true` | `false` = 只允许打开 workspace 内的文件 |
|
|
122
|
+
|
|
123
|
+
**`allowOutside` 要特别说一句**:产物经常落在项目盘(`E:\…`、`D:\…`)而不是 DSH 的 workspace 里,所以默认放开。但它同时是安全闸——挡的是「模型说个 `C:\Windows\System32\xxx.exe` 就帮你点开」这类情况。**自己一个人用保持 `true` 更顺手;给别人用、或者接入不可信模型,改成 `false`。**
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 它不做什么
|
|
128
|
+
|
|
129
|
+
这几条是**刻意**的,不是没做完:
|
|
130
|
+
|
|
131
|
+
1. **不改任何 DOM 节点**。正文由 React 渲染,给文本包一层 `<span>` 就是移动 React 的节点,重渲染时整段白屏。本插件全程只读,可点性是「捕获期 click + 光标位置反查」实现的。
|
|
132
|
+
2. **不接管 markdown 图片与链接语法**。`` 一旦被改写成芯片,图就不显示了。
|
|
133
|
+
3. **不联网、零遥测**。全部路由只在 `127.0.0.1` 回环可用,外部来源一律 403。
|
|
134
|
+
4. **不 spawn shell**。`reveal`/`open` 走 `execFile(cmd, args)` 参数数组,路径里的 `& | ; %VAR%` 全是字面量,没有命令注入面。
|
|
135
|
+
5. **不猜多候选路径**。搜到多个同名文件时把选择权还给人。
|
|
136
|
+
6. **不保证文件存在**。库里的文件随时可能被删、被移走、被别的进程占用——所以每次打开前都重新 stat,点开发现没了就明说「已不存在」,不会装作没事。
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 排查
|
|
141
|
+
|
|
142
|
+
| 症状 | 原因与处置 |
|
|
143
|
+
|---|---|
|
|
144
|
+
| 正文文件名不可点 | ① `clientChips` 被置 false;② 扩展名不在白名单(本插件只认已知产物扩展名,避免把 `C:\Windows` 之类误标成产物);③ 客户端 bundle 没重载——**Ctrl+R 不重载客户端 bundle,必须完全重启 DSH** |
|
|
145
|
+
| 点开提示「解析不到绝对路径」 | 裸文件名不在 workspace 内,且 `allowOutside: false`。先确认文件真实路径,再决定是否放宽 |
|
|
146
|
+
| 点开提示「文件已不存在」 | 路径是真的,文件被删/移了。清单里该行会打上删除线 |
|
|
147
|
+
| 点产物名没反应 | ① 文件不在候选 base 下(访问宿主 `/info` 看 `bases`);② 扩展名不在白名单(`lib/client.js` 的 `EXT`);③ 直接看 `/resolve?p=<名字>` 返回的 `reason` |
|
|
148
|
+
| 右键没出本插件菜单 | 该处文字没解析成文件(同上);**对非产物右键本来就不接管**,出的是宿主原生菜单 |
|
|
149
|
+
| 日志里出现 N 份相同输出 | 热重载堆叠了多个实例。**Ctrl+R 无效,必须完全重启 DSH**;同时确认 `window.__dshArtifactLinkerV1.dispose()` 守卫在 |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 目录
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
dsh-artifact-linker/
|
|
157
|
+
├── package.json # dsh.bundle + dsh.client(两个都要,缺 bundle 装不上)
|
|
158
|
+
├── cordis.patch.yml # 插件注册补丁(entry id = 包名,唯一)
|
|
159
|
+
├── lib/
|
|
160
|
+
│ ├── index.js # 宿主:prompt 段 + 6 条回环路由 + 三源路径解析
|
|
161
|
+
│ └── client.js # 客户端:__ModuleLoader__.load bundle(id 必须 = 包名)
|
|
162
|
+
├── locale/{zh,en}.json
|
|
163
|
+
├── icon.svg
|
|
164
|
+
├── README.md · CHANGELOG.md · LICENSE
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
<a id="english-summary"></a>
|
|
170
|
+
## English summary
|
|
171
|
+
|
|
172
|
+
`dsh-artifact-linker` turns every delivered artifact into a one-click target, so you never copy a path into Explorer again.
|
|
173
|
+
|
|
174
|
+
- **Host half** injects a resident system-prompt rule: always emit **absolute** artifact paths in tooling, but only the **file name** in user-facing prose.
|
|
175
|
+
- **Client half** makes those names clickable — **left click** reveals and selects the file in Explorer, **right click** opens a small menu (reveal / open with default app / copy absolute path). Hovering turns the cursor into a pointer. No floating UI, no document scanning, no timers.
|
|
176
|
+
- Bare file names are resolved with a three-tier fallback (native absolute → workspace-relative → recursive same-name search). Ambiguous matches are surfaced as candidates rather than guessed.
|
|
177
|
+
- All routes are loopback-only, telemetry-free, and spawn nothing through a shell. The client never mutates React-rendered DOM.
|
|
178
|
+
|
|
179
|
+
MIT licensed.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT © loyalchiiina
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# dsh-artifact-linker — 产物路径联动器(双端插件)
|
|
2
|
+
#
|
|
3
|
+
# ── 宿主半边(lib/index.js)──────────────────────────────────────────────────
|
|
4
|
+
# 1. 注册一条常驻 system-prompt 段(ctx.systemPrompt.section,order 200),
|
|
5
|
+
# 让「产物一律输出完整绝对路径」在**每个会话/每个预设**生效,不依赖任何
|
|
6
|
+
# 技能被加载(技能按需加载,prompt 段常驻);
|
|
7
|
+
# 2. 注册 6 条回环路由:
|
|
8
|
+
# /info 插件身份 + workspace 信息 + 各开关实际取值
|
|
9
|
+
# /resolve {原样路径 | 相对路径 | 裸文件名} → 绝对路径(三源兜底)
|
|
10
|
+
# /stat 批量 stat + 10s TTL 缓存
|
|
11
|
+
# /flush-cache 清空宿主端路径解析与 stat 缓存
|
|
12
|
+
# /reveal 在系统资源管理器里揭示并选中该文件
|
|
13
|
+
# /open 用系统默认关联程序打开该文件
|
|
14
|
+
# 3. 不写任何文件、不联网(零遥测);唯一会 spawn 的是 reveal/open 两条
|
|
15
|
+
# 显式路由,且走 execFile 参数数组(不经 shell,无命令注入面),
|
|
16
|
+
# 并带 loopback 来源校验。
|
|
17
|
+
#
|
|
18
|
+
# ── 客户端半边(lib/client.js)──────────────────────────────────────────────
|
|
19
|
+
# **全程只读正文,一个 DOM 节点都不改、一条属性都不加**(改 React 管理的节点
|
|
20
|
+
# 会在重渲染时白屏,这是本插件刻意规避的第一号风险)。可点性靠:
|
|
21
|
+
# · document 捕获期 click + caretRangeFromPoint 反查「用户点中了哪个词」,
|
|
22
|
+
# 正则命中产物名/路径即拦截,按修饰键分流;
|
|
23
|
+
# · 右下角浮动胶囊「📎 N」把本会话检测到的产物聚成清单(Shadow DOM 挂 body)。
|
|
24
|
+
# 修饰键分流:左键 = 资源管理器揭示并选中 / Ctrl·Shift·Cmd = 系统默认程序打开 /
|
|
25
|
+
# Alt = 复制完整路径。清单每行另有 📍 ↗ ⧉ 三个按钮。
|
|
26
|
+
# markdown 图片与链接语法一律不接管(避免把图改成芯片导致图不显示)。
|
|
27
|
+
#
|
|
28
|
+
# ── 开关(改完重启 DSH 生效)───────────────────────────────────────────────
|
|
29
|
+
# injectRules (默认 true) 常驻 prompt 段:要求模型输出产物绝对路径、正文只写文件名
|
|
30
|
+
# clientChips (默认 true) 客户端胶囊与点击接管;置 false 则纯注入模式(只剩 prompt 铁律)
|
|
31
|
+
# allowOutside (默认 true) 是否允许解析 workspace 之外的路径。产物几乎总在项目盘
|
|
32
|
+
# (E:/D:)而不是 DSH workspace 里,所以默认放开。
|
|
33
|
+
# **多用户/给别人用时改 false**——那是防止本插件变成
|
|
34
|
+
# 「模型说个路径就帮你打开任意文件」的安全闸。
|
|
35
|
+
# statTtlMs (默认 10000) 宿主端 stat 缓存毫秒数。调小 = 更快反映「文件刚被删」,
|
|
36
|
+
# 调大 = DOM 重渲染反复问同一路径时更省 IO
|
|
37
|
+
# searchMaxDepth (默认 2,硬上限 8) 裸文件名反查的递归深度。调大能找回埋得更深的同名
|
|
38
|
+
# 文件,但 /resolve 的目录遍历成本随之上升
|
|
39
|
+
# rulesText (默认内置 6 条) 整体替换注入的铁律文本,可写数组或一个多行字符串。
|
|
40
|
+
# 想追加自己的规矩(例:「交付前先确认文件字节数非 0」)就写这里
|
|
41
|
+
# —— 官方硬规则 #5 要求所有 tunable 必须能在 cordis.yml 改,
|
|
42
|
+
# 故上述全部可在 profile 的 cordis.patch.yml 里覆盖。
|
|
43
|
+
#
|
|
44
|
+
# 🔴 绝不要再在 profile 的 cordis.patch.yml 里手写同一段 insert —— 同一 entry id
|
|
45
|
+
# 插两行会触发 duplicate loader entry id,插件市场更新时被差回滚。
|
|
46
|
+
- insert:
|
|
47
|
+
- id: dsh-artifact-linker
|
|
48
|
+
name: dsh-artifact-linker
|
|
49
|
+
config:
|
|
50
|
+
injectRules: true
|
|
51
|
+
clientChips: true
|
|
52
|
+
allowOutside: true
|
package/icon.svg
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Artifact Linker">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#4f8cff"/>
|
|
5
|
+
<stop offset="1" stop-color="#7a5cf0"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="2" y="2" width="60" height="60" rx="14" fill="url(#g)"/>
|
|
9
|
+
<path d="M24 40 L40 24" stroke="#fff" stroke-width="4" stroke-linecap="round" opacity=".9"/>
|
|
10
|
+
<circle cx="21" cy="43" r="7" fill="none" stroke="#fff" stroke-width="4"/>
|
|
11
|
+
<circle cx="43" cy="21" r="7" fill="none" stroke="#fff" stroke-width="4"/>
|
|
12
|
+
</svg>
|