dsh-artifact-linker 0.4.4 → 0.4.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.5 — 2026-11-07
4
+
5
+ ### 修复:宿主渲染的「文件芯片 / 交付卡片」右键只出官方菜单
6
+
7
+ **现象**:聊天里由宿主渲染的文件芯片(markdown 文件链接、present 交付卡片,例如表格里的蓝色 `追光.mp3` 芯片),右键只弹出宿主官方菜单(在侧边栏打开 / 在浮窗中打开 / 在文件夹显示 / 用 XX 打开 / 复制文件路径 / 另存为…),插件的 8 项菜单不出现;而正文里的纯文本绝对路径(如 `…\lib\client.js`)右键正常。
8
+
9
+ **根因**:宿主把文件渲染成 `<button class="…fileMention …fileLink" title="<路径>">`(markdown 文件链接)与 `<div data-presented-file><button class="cardPreview" title="<绝对路径>">`(present 卡片),右键由宿主 `ctx.contextMenus` 的官方 `open-in-app.file` provider 接管;这些节点正文里没有「含路径的纯文本节点」,`hitAt` 只能看到裸文件名、`/resolve` 失败 → 放行 → 只出官方菜单。
10
+
11
+ **修复**:在 `contextmenu` 捕获期先识别宿主文件芯片/交付卡片,从其 `title` 读取真实路径(相对路径按当前会话 cwd 补成绝对路径),随即 `preventDefault()+stopPropagation()` 拦下官方菜单并弹出插件自己的完整 8 项菜单;纯文本路径仍走原 `caretRangeFromPoint` 逻辑。同时 `click` 对芯片直接让路给宿主,避免左键双开。
12
+
13
+ **验证**:`node --check lib/client.js` 通过、无 BOM;DOM-stub 探针 `probe_chip_menu.cjs` 8/8(绝对路径芯片拦截、相对路径按 cwd 补全、普通文本放行)。
14
+
15
+ **生效方式**:本次只改客户端 bundle `lib/client.js` → **Ctrl+Shift+R 硬刷新**即可(Ctrl+R 不重新读盘)。
16
+
3
17
  ## 0.4.4 — 2026-11-05
4
18
 
5
19
  ### 对齐官方交付卡片:左键改为侧栏预览,右键 8 项菜单
package/lib/client.js CHANGED
@@ -29,7 +29,7 @@ window.__ModuleLoader__.load({
29
29
  var exports = module.exports;
30
30
 
31
31
  var GUARD = "__dshArtifactLinkerV1";
32
- var VERSION = "0.4.4";
32
+ var VERSION = "0.4.5";
33
33
  // 路由前缀必须与宿主的 ROUTE_PREFIX 一致;永远根相对,绝不拼宿主 origin
34
34
  var ROUTE = "/dsh-artifact-linker";
35
35
 
@@ -290,6 +290,66 @@ window.__ModuleLoader__.load({
290
290
  return null;
291
291
  }
292
292
 
293
+ // ── 宿主「文件芯片 / 交付卡片」路径读取(2026-11-07 修复)──────────────────
294
+ // 背景:宿主把 markdown 文件链接渲染成
295
+ // <button class="…fileMention …fileLink" title="<路径>">,
296
+ // 把 present 交付卡片渲染成
297
+ // <div data-presented-file><button class="cardPreview" title="<绝对路径>">。
298
+ // 这两类节点的右键由宿主 contextMenus 的官方 provider 接管,正文里**没有**能
299
+ // 被 caretRangeFromPoint 命中的「含路径文本节点」→ 老版 hitAt 只能看到裸文件名、
300
+ // 解析失败 → 放行 → 用户只看到官方默认菜单(本次病根)。这里直接从 title 取真路径。
301
+ var FILE_LINK_SELECTOR = 'button[class*="fileMention"],button[class*="fileLink"]';
302
+ function chipPathAt(target) {
303
+ try {
304
+ var el = null;
305
+ if (target && target.closest) {
306
+ el = target.closest(FILE_LINK_SELECTOR);
307
+ if (!el) {
308
+ var card = target.closest("[data-presented-file]");
309
+ if (card && card.querySelector) el = card.querySelector("button[title]");
310
+ }
311
+ }
312
+ if (!el) return null;
313
+ var t = el.getAttribute && el.getAttribute("title");
314
+ if (!t) return null;
315
+ t = String(t).trim();
316
+ if (!t || t.length > 1024 || /[\r\n]/.test(t)) return null;
317
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(t)) return null; // 排除 http(s):// 等 URL 协议头
318
+ if (looksAbsoluteClient(t)) return t;
319
+ if (/\.[A-Za-z0-9]{1,12}$/.test(t)) return t; // 相对路径,须带扩展名
320
+ return null;
321
+ } catch (e) { return null; }
322
+ }
323
+
324
+ // 当前会话工作区根目录(把芯片上的相对路径补成绝对路径;拿不到就返回空串)
325
+ function currentCwd() {
326
+ try {
327
+ if (!rootCtx || typeof rootCtx.get !== "function") return "";
328
+ var sessions = rootCtx.get("sessions");
329
+ if (!sessions || !sessions.list || typeof sessions.list.getSnapshot !== "function") return "";
330
+ var snap = sessions.list.getSnapshot();
331
+ var byId = (snap && snap.byId) || {};
332
+ var sid = null;
333
+ var sidebar = rootCtx.get("sidebarRight");
334
+ var mounted = sidebar && sidebar.mounted;
335
+ if (mounted && typeof mounted.get === "function") {
336
+ var m = mounted.get();
337
+ if (m && m.sessionId) sid = m.sessionId;
338
+ } else if (mounted && mounted.sessionId) {
339
+ sid = mounted.sessionId;
340
+ }
341
+ if (!sid) { for (var k in byId) { if (byId[k] && byId[k].cwd) { sid = k; break; } } }
342
+ return sid && byId[sid] ? String(byId[sid].cwd || "") : "";
343
+ } catch (e) { return ""; }
344
+ }
345
+ function joinCwd(p) {
346
+ if (looksAbsoluteClient(p)) return p;
347
+ var cwd = currentCwd();
348
+ if (!cwd) return p;
349
+ var sep = cwd.indexOf("\\") >= 0 ? "\\" : "/";
350
+ return cwd.replace(/[/\\]+$/, "") + sep + String(p).replace(/^(?:\.?[/\\])+/, "");
351
+ }
352
+
293
353
  // ── 提示与菜单 ──────────────────────────────────────────────────────────
294
354
  function toast(msg) {
295
355
  if (!document || !document.body) return;
@@ -704,6 +764,8 @@ window.__ModuleLoader__.load({
704
764
  if (disposed) return;
705
765
  if (ev.button !== 0) return;
706
766
  if (menuEl && menuEl.contains && menuEl.contains(ev.target)) return;
767
+ // 宿主文件芯片 / 交付卡片:左键交给宿主自己的 openFile / onPreview,避免双开
768
+ if (chipPathAt(ev.target) !== null) return;
707
769
  var hit = hitAt(ev.clientX, ev.clientY);
708
770
  if (!hit) return;
709
771
  // 官方 present 交付卡片本身已内置「点卡片 → 侧边栏预览」,交给官方自己开,避免双开
@@ -772,6 +834,21 @@ window.__ModuleLoader__.load({
772
834
  ev.stopPropagation();
773
835
  return;
774
836
  }
837
+ // ① 宿主渲染的文件芯片 / 交付卡片:直接从 title 取真实路径,弹自己的完整菜单,
838
+ // 并在捕获期拦下事件、阻止宿主 contextMenus 的官方菜单(这是 2026-11-07 的修复点)。
839
+ var chipRaw = chipPathAt(ev.target);
840
+ if (chipRaw !== null) {
841
+ var chipAbs = joinCwd(chipRaw);
842
+ if (looksAbsoluteClient(chipAbs)) {
843
+ ev.preventDefault();
844
+ ev.stopPropagation();
845
+ showMenu(ev.clientX, ev.clientY, chipAbs);
846
+ return;
847
+ }
848
+ // 相对路径且拿不到会话 cwd → 补不成绝对路径,不抢,交给宿主官方菜单兜底
849
+ }
850
+
851
+ // ② 正文里的纯文本路径:沿用原有 caretRangeFromPoint 命中逻辑
775
852
  var hit = hitAt(ev.clientX, ev.clientY);
776
853
  if (!hit) return; // 非产物处右键 → 放行宿主原生菜单
777
854
  var c = cached(hit.raw);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-artifact-linker",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "DSH 产物路径联动器:常驻注入「产物路径铁律」让模型输出完整绝对路径,同时把聊天正文里的文件名/路径渲染成一键可点的产物芯片——左键在右侧边栏预览(对齐官方交付卡片,绝对路径 0 次解析直通,侧栏认领不了自动退回系统默认程序),Ctrl/Shift/Cmd+左键用系统默认程序打开,Alt+左键复制绝对路径,右键 8 项菜单(在侧栏打开 / 在浮窗中打开 / 在文件夹中显示 / 用默认程序打开 / 另存为… / 复制文件 / 复制文件路径 / 添加到对话框)。宿主端提供回环只读路由与 /saveas /copyfile 两个文件搬运动作(Shell COM 揭示、WinForms 保存对话框、CF_HDROP 剪贴板),客户端零侵入、不改 React 节点、不接管 markdown 图片语法。解决「产物要打开得先复制路径再粘到文件管理器」的反复割裂。/ Turn declared artifact paths into one-click chips: resident system-prompt rules force absolute paths, the client half renders file names as clickable chips, left click previews in DSH's built-in sidebar (absolute paths resolve in 0 round-trips; unclaimed types fall back to the default app), Ctrl/Shift/Cmd+click opens with the system default app, Alt+click copies the absolute path, and the right-click menu offers eight actions (open in sidebar / open in floating window / reveal in Explorer / open with default app / save-as / copy-file / copy-path / insert-into-composer). Host routes are loopback-only; zero telemetry.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",