dsh-files-native 0.2.0 → 0.2.1

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.
Files changed (3) hide show
  1. package/README.md +20 -7
  2. package/lib/client.js +152 -138
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -11,6 +11,24 @@
11
11
 
12
12
  </div>
13
13
 
14
+ ## ⭐ 欢迎点星收藏
15
+
16
+ 如果附件轨帮到了你,欢迎到 [GitHub 仓库](https://github.com/meyaomiao/dsh-files-native) 点个 Star ⭐,让更多 DSH 用户看到它。问题与建议请提 Issue。
17
+
18
+ ## 📋 兼容性
19
+
20
+ | 插件版本 | 状态 | 对应 DSH |
21
+ |---|---|---|
22
+ | **0.2.x**(当前主线,含 0.2.1) | ✅ | **0.1.5-rc.1 / 0.1.5-rc.2**(及之后的 0.1.5 线) |
23
+ | 0.1.x | 🔧 维护态(仅修 bug) | DSH ≤ 0.1.2-rc.1(仍兼容 0.1.1-rc.2 / 0.1.2-alpha.4) |
24
+
25
+ ### 本次升级功能变化
26
+
27
+ - **官方已有的交给官方**:回复尾的「产物」卡不再由本插件绘制,改用 DSH 官方交付文件卡(0.1.5-rc.2 起排版/图标更紧)。用户气泡旁的附件卡仍由本插件画。
28
+ - **粘贴让路**:纯官方四类图(PNG / JPEG / WebP / GIF)交给官方草稿图;其它文件仍走本插件轨。
29
+ - **不依赖 better-sidebar**:模块 `inject` 只有 `slots`。有侧栏则气泡卡走预览,没有则新页签打开。
30
+ - 不要和 `dsh-file-fix` / `dsh-file-upload` / `dsh-paste-to-path` 叠装。
31
+
14
32
  运行时插件 id 仍是 `file-native`(上传路由 `/plugins/file-native/*`)。npm / GitHub 仓库名是 `dsh-files-native`。
15
33
 
16
34
  - **图片**(PNG / JPEG / WebP / GIF)走官方草稿图栏(64px 缩略图,点击灯箱),发送后进模型视觉上下文。
@@ -19,7 +37,7 @@
19
37
  - **气泡附件卡**:消息发出后,文件卡渲染在用户气泡上方(与图片同列右对齐),注入提示行自动隐藏。
20
38
  - **点击打开**:点气泡附件卡——装了 [better-sidebar](https://github.com/用户/dsh-better-sidebar) 走侧栏预览;未装则新页签打开(文本/图片/PDF 页签内展示,二进制自动下载)。
21
39
  - 发送后文件卡贴在用户气泡栈(与官方图同一列),作业回复不会插在提问和文件中间。
22
- - 发送成功即清输入框待发轨。回复结尾只画「产物」chip。
40
+ - 发送成功即清输入框待发轨。**回复尾产物卡让给官方。**
23
41
 
24
42
  不要和 `dsh-file-fix` / `dsh-file-upload` / `dsh-paste-to-path` 叠装。
25
43
 
@@ -29,7 +47,7 @@
29
47
  |---|---|
30
48
  | ![空输入框](screenshots/01-empty.png) | ![待发混排](screenshots/02-rail.png) |
31
49
 
32
- | 拖入窗口任意位置 | 发送后:图与文件卡跟用户气泡,产物在回复尾 |
50
+ | 拖入窗口任意位置 | 发送后:图与文件卡跟用户气泡(产物走官方卡) |
33
51
  |---|---|
34
52
  | ![拖入遮罩](screenshots/03-overlay.png) | ![发送后](screenshots/04-sent.png) |
35
53
 
@@ -82,9 +100,4 @@ dsh plugin --profile web add .
82
100
  - 上传路由仅 loopback。
83
101
  - 刷新会丢掉「已上传未发送」的文件卡(磁盘文件仍在 `.dsh-uploads/`)。
84
102
 
85
- ## 兼容性
86
-
87
- - DeepSeek Harness `0.1.2-rc.1`(仍兼容 `0.1.1-rc.2` 与 `0.1.2-alpha.4`,web profile)
88
- - 不要和 `dsh-file-fix` / `dsh-file-upload` / `dsh-paste-to-path` 叠装
89
-
90
103
  改仓库前先读 [CONTRIBUTING.md](./CONTRIBUTING.md)(Issue → 分支 → Draft PR)。思考原则见 [AI-ISSUE-WORKFLOW.md](./AI-ISSUE-WORKFLOW.md)。插件硬约束见 [AGENTS.md](./AGENTS.md)。
package/lib/client.js CHANGED
@@ -28,7 +28,7 @@ __export(client_exports, {
28
28
  name: () => name
29
29
  });
30
30
  module.exports = __toCommonJS(client_exports);
31
- var import_react8 = require("react");
31
+ var import_react7 = require("react");
32
32
 
33
33
  // src/rail.tsx
34
34
  var import_react2 = require("react");
@@ -70,11 +70,30 @@ function splitIntake(files) {
70
70
  }
71
71
  return { images, others };
72
72
  }
73
+ function planPaste(files) {
74
+ const nativeImages = [];
75
+ const cards = [];
76
+ for (const file of files) {
77
+ if (isNativeImage(file)) nativeImages.push(file);
78
+ else cards.push(file);
79
+ }
80
+ if (cards.length === 0 && nativeImages.length > 0) return { action: "yield" };
81
+ return { action: "take", cards, nativeImages };
82
+ }
83
+ function extractPasteText(raw, files) {
84
+ const text = raw.replace(/\uFFFC/g, "").trim();
85
+ if (text === "") return "";
86
+ const names = files.map((file) => file.name.replace(/^.*[/\\]/, "")).filter((name2) => name2.length >= 4);
87
+ if (names.length === 0) return text;
88
+ const tokens = text.split(/\s+/).filter(Boolean);
89
+ const referenced = (token) => names.some((name2) => token.toLowerCase().includes(name2.toLowerCase()));
90
+ const meaningful = tokens.filter((token) => !referenced(token));
91
+ return meaningful.length === 0 ? "" : text;
92
+ }
73
93
 
74
94
  // src/store.ts
75
95
  var items = [];
76
96
  var sentBySession = /* @__PURE__ */ new Map();
77
- var sentByTurn = /* @__PURE__ */ new Map();
78
97
  var listeners = /* @__PURE__ */ new Set();
79
98
  var generation = 0;
80
99
  function emit() {
@@ -127,12 +146,6 @@ function archiveSent(sessionId) {
127
146
  if (files.length > 0) emit();
128
147
  return files;
129
148
  }
130
- function lastSent(sessionId) {
131
- return sentBySession.get(sessionId) ?? [];
132
- }
133
- function filesForTurn(sessionId, turn) {
134
- return sentByTurn.get(`${sessionId}:${turn}`) ?? lastSent(sessionId);
135
- }
136
149
 
137
150
  // src/styles.ts
138
151
  var FR_CSS = `
@@ -676,6 +689,86 @@ function ComposerRail(props) {
676
689
  }, host !== null && host.isConnected ? (0, import_react_dom2.createPortal)(rail, host) : null);
677
690
  }
678
691
 
692
+ // src/vision.ts
693
+ function currentModelLabel() {
694
+ const buttons = document.querySelectorAll("button[aria-label]");
695
+ for (const button of buttons) {
696
+ const label = button.getAttribute("aria-label") ?? "";
697
+ if (/选择模型|select model|current model/i.test(label)) return label;
698
+ }
699
+ return "";
700
+ }
701
+ function insertComposerText(text) {
702
+ const active = document.activeElement;
703
+ const el = active instanceof HTMLTextAreaElement || active instanceof HTMLInputElement ? active : document.querySelector("[data-composer-card] textarea");
704
+ if (!(el instanceof HTMLTextAreaElement) && !(el instanceof HTMLInputElement)) return;
705
+ el.focus();
706
+ let inserted = false;
707
+ try {
708
+ inserted = document.execCommand("insertText", false, text);
709
+ } catch {
710
+ inserted = false;
711
+ }
712
+ if (inserted) return;
713
+ const proto = el instanceof HTMLTextAreaElement ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype;
714
+ const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
715
+ setter?.call(el, `${el.value}${text}`);
716
+ el.dispatchEvent(new Event("input", { bubbles: true }));
717
+ }
718
+ function pasteAccompanyingText(text) {
719
+ insertComposerText(text);
720
+ }
721
+ var modlensRouteAvailable = true;
722
+ async function probeModlensTakeover() {
723
+ if (!modlensRouteAvailable) return false;
724
+ const label = currentModelLabel();
725
+ try {
726
+ const res = await fetch(`/modlens/paste?model=${encodeURIComponent(label)}`, {
727
+ signal: AbortSignal.timeout(2e3)
728
+ });
729
+ if (res.status === 404) {
730
+ modlensRouteAvailable = false;
731
+ return false;
732
+ }
733
+ if (!res.ok) return false;
734
+ const body = await res.json();
735
+ return body.takeover === true;
736
+ } catch {
737
+ return false;
738
+ }
739
+ }
740
+ async function uploadModlensPaste(file) {
741
+ const res = await fetch("/modlens/paste", { method: "POST", body: await file.arrayBuffer() });
742
+ if (!res.ok) return void 0;
743
+ const body = await res.json();
744
+ return typeof body.path === "string" && body.path !== "" ? body.path : void 0;
745
+ }
746
+ async function deliverImages(sessionId, files) {
747
+ if (files.length === 0) return;
748
+ const takeover = await probeModlensTakeover();
749
+ if (!takeover) {
750
+ addImages(sessionId, files);
751
+ return;
752
+ }
753
+ const paths = [];
754
+ const failed = [];
755
+ for (const file of files) {
756
+ try {
757
+ const path = await uploadModlensPaste(file);
758
+ if (path !== void 0) paths.push(path);
759
+ else failed.push(file);
760
+ } catch {
761
+ failed.push(file);
762
+ }
763
+ }
764
+ if (paths.length === 0) {
765
+ addImages(sessionId, files);
766
+ return;
767
+ }
768
+ if (failed.length > 0) addImages(sessionId, failed);
769
+ insertComposerText(`${paths.join(" ")} `);
770
+ }
771
+
679
772
  // src/picker.tsx
680
773
  function PaperclipButton(props) {
681
774
  ensureStyles();
@@ -689,7 +782,9 @@ function PaperclipButton(props) {
689
782
  event.currentTarget.value = "";
690
783
  const sid = String(props.sessionId || currentSessionId());
691
784
  if (files.length === 0) return;
692
- intakeFiles(sid, files, (images) => addImages(sid, images));
785
+ intakeFiles(sid, files, (images) => {
786
+ void deliverImages(sid, images);
787
+ });
693
788
  };
694
789
  return (0, import_react5.createElement)(
695
790
  "span",
@@ -713,97 +808,15 @@ function PaperclipButton(props) {
713
808
  );
714
809
  }
715
810
 
716
- // src/tail.tsx
717
- var import_react6 = require("react");
718
- function basename(path) {
719
- const at = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
720
- return at === -1 ? path : path.slice(at + 1);
721
- }
722
- function ChipRow({ label, items: items2, open }) {
723
- if (items2.length === 0) return null;
724
- const shown = items2.slice(0, 6);
725
- const hidden = items2.length - shown.length;
726
- return (0, import_react6.createElement)(
727
- "div",
728
- { className: "fr-tail" },
729
- (0, import_react6.createElement)("span", { className: "fr-tail-label" }, label),
730
- (0, import_react6.createElement)(
731
- "div",
732
- { className: "fr-tail-row" },
733
- ...shown.map((item) => (0, import_react6.createElement)("button", {
734
- key: item.path,
735
- type: "button",
736
- className: "fr-chip",
737
- title: item.path,
738
- onClick: () => open(item.path)
739
- }, item.name)),
740
- hidden > 0 ? (0, import_react6.createElement)("span", { className: "fr-more" }, `+ ${hidden} \u4E2A\u6587\u4EF6`) : null
741
- )
742
- );
743
- }
744
- function UploadedTail(props) {
745
- ensureStyles();
746
- const sessionId = String(props.sessionId ?? "");
747
- const [uploaded, setUploaded] = (0, import_react6.useState)(
748
- props.matched?.uploaded ?? (props.turn !== void 0 ? filesForTurn(sessionId, props.turn) : lastSent(sessionId))
749
- );
750
- (0, import_react6.useEffect)(() => {
751
- if (props.matched?.uploaded) {
752
- setUploaded(props.matched.uploaded);
753
- return;
754
- }
755
- let cancelled = false;
756
- void fetch(`/plugins/file-native/last?sessionId=${encodeURIComponent(sessionId)}`).then((res) => res.json()).then((body) => {
757
- if (!cancelled && Array.isArray(body.files) && body.files.length > 0) setUploaded(body.files);
758
- }).catch(() => {
759
- });
760
- return () => {
761
- cancelled = true;
762
- };
763
- }, [sessionId, props.matched, props.turn]);
764
- const produced = props.matched?.produced ?? [];
765
- if (uploaded.length === 0 && produced.length === 0) return null;
766
- const open = props.openFile ?? ((path) => {
767
- window.open(path, "_blank");
768
- });
769
- return (0, import_react6.createElement)(
770
- "div",
771
- { "data-file-native-tail": true },
772
- (0, import_react6.createElement)(ChipRow, {
773
- label: "\u9644\u4EF6",
774
- items: uploaded.map((file) => ({ name: file.name, path: file.relPath })),
775
- open
776
- }),
777
- (0, import_react6.createElement)(ChipRow, {
778
- label: "\u4EA7\u7269",
779
- items: produced.map((path) => ({ name: basename(path), path })),
780
- open
781
- })
782
- );
783
- }
784
- function producedPathsOf(owner2) {
785
- const data = owner2.turn?.data?.get?.("deliverables");
786
- const seq = owner2.seq ?? Number.POSITIVE_INFINITY;
787
- if (!data?.produced) return [];
788
- const paths = [];
789
- const seen = /* @__PURE__ */ new Set();
790
- for (const item of data.produced) {
791
- if (item.seq > seq || seen.has(item.path)) continue;
792
- seen.add(item.path);
793
- paths.push(item.path);
794
- }
795
- return paths;
796
- }
797
-
798
811
  // src/context.tsx
799
- var import_react7 = require("react");
812
+ var import_react6 = require("react");
800
813
  var import_react_dom3 = require("react-dom");
801
814
  function isFileNoticeRow(el) {
802
815
  const text = el.textContent ?? "";
803
816
  return text.includes("\u{1F4CE} \u9644\u4EF6") && text.includes("\u4E2A\u6587\u4EF6");
804
817
  }
805
818
  function FileCard({ file, onOpen }) {
806
- return (0, import_react7.createElement)(
819
+ return (0, import_react6.createElement)(
807
820
  "button",
808
821
  {
809
822
  type: "button",
@@ -811,12 +824,12 @@ function FileCard({ file, onOpen }) {
811
824
  title: file.relPath,
812
825
  onClick: () => onOpen?.(file)
813
826
  },
814
- (0, import_react7.createElement)("span", { className: "fr-ext" }, extOf(file.name)),
815
- (0, import_react7.createElement)(
827
+ (0, import_react6.createElement)("span", { className: "fr-ext" }, extOf(file.name)),
828
+ (0, import_react6.createElement)(
816
829
  "span",
817
830
  { className: "fr-copy" },
818
- (0, import_react7.createElement)("span", { className: "fr-name" }, file.name),
819
- file.size > 0 ? (0, import_react7.createElement)("span", { className: "fr-meta" }, formatSize(file.size)) : null
831
+ (0, import_react6.createElement)("span", { className: "fr-name" }, file.name),
832
+ file.size > 0 ? (0, import_react6.createElement)("span", { className: "fr-meta" }, formatSize(file.size)) : null
820
833
  )
821
834
  );
822
835
  }
@@ -859,11 +872,11 @@ function placeHostInRow(notice) {
859
872
  }
860
873
  function MessageCards(props) {
861
874
  ensureStyles();
862
- const [hosted, setHosted] = (0, import_react7.useState)([]);
863
- const sessionKey = (0, import_react7.useRef)("");
864
- const lastCount = (0, import_react7.useRef)(-1);
865
- const cachedFiles = (0, import_react7.useRef)(null);
866
- (0, import_react7.useEffect)(() => {
875
+ const [hosted, setHosted] = (0, import_react6.useState)([]);
876
+ const sessionKey = (0, import_react6.useRef)("");
877
+ const lastCount = (0, import_react6.useRef)(-1);
878
+ const cachedFiles = (0, import_react6.useRef)(null);
879
+ (0, import_react6.useEffect)(() => {
867
880
  if (sessionKey.current !== (props.sessionId ?? "")) {
868
881
  sessionKey.current = props.sessionId ?? "";
869
882
  lastCount.current = -1;
@@ -923,14 +936,14 @@ function MessageCards(props) {
923
936
  };
924
937
  }, [props.sessionId]);
925
938
  if (hosted.length === 0) return null;
926
- return (0, import_react7.createElement)(
939
+ return (0, import_react6.createElement)(
927
940
  "span",
928
941
  { "aria-hidden": true },
929
942
  ...hosted.map((item, i) => (0, import_react_dom3.createPortal)(
930
- (0, import_react7.createElement)(
943
+ (0, import_react6.createElement)(
931
944
  "div",
932
945
  { className: "fr-msg-row-inner" },
933
- ...item.files.map((file) => (0, import_react7.createElement)(FileCard, { key: file.relPath, file, onOpen: props.onOpen }))
946
+ ...item.files.map((file) => (0, import_react6.createElement)(FileCard, { key: file.relPath, file, onOpen: props.onOpen }))
934
947
  ),
935
948
  item.host,
936
949
  `file-native-cards-${i}`
@@ -1006,9 +1019,16 @@ function installFileIntercept(handlers) {
1006
1019
  const fromItems = Array.from(event.clipboardData?.items ?? []).filter((item) => item.kind === "file").map((item) => item.getAsFile()).filter((file) => file !== null);
1007
1020
  const files = fromList.length > 0 ? fromList : fromItems;
1008
1021
  if (files.length === 0) return;
1022
+ const plan = planPaste(files);
1023
+ if (plan.action === "yield") return;
1009
1024
  event.preventDefault();
1010
- event.stopPropagation();
1011
- handlers.onFiles(files);
1025
+ event.stopImmediatePropagation();
1026
+ if (plan.cards.length > 0) handlers.onFiles(plan.cards);
1027
+ if (plan.nativeImages.length > 0) handlers.onImages?.(plan.nativeImages);
1028
+ if (handlers.onText !== void 0) {
1029
+ const kept = extractPasteText(event.clipboardData?.getData("text/plain") ?? "", files);
1030
+ if (kept !== "") handlers.onText(kept);
1031
+ }
1012
1032
  };
1013
1033
  document.addEventListener("dragenter", onDragEnter, true);
1014
1034
  document.addEventListener("dragover", onDragOver, true);
@@ -1028,13 +1048,13 @@ function installFileIntercept(handlers) {
1028
1048
 
1029
1049
  // src/client.ts
1030
1050
  var name = "file-native";
1031
- var inject = ["slots", "betterSidebar"];
1051
+ var inject = ["slots"];
1032
1052
  function RailSlot(props) {
1033
1053
  ensureStyles();
1034
1054
  const sessionId = resolveSessionId(props);
1035
1055
  rememberSessionId(sessionId);
1036
- const tokenRef = (0, import_react8.useRef)(0);
1037
- (0, import_react8.useLayoutEffect)(() => {
1056
+ const tokenRef = (0, import_react7.useRef)(0);
1057
+ (0, import_react7.useLayoutEffect)(() => {
1038
1058
  tokenRef.current = setLiveOwner({
1039
1059
  attachments: props.attachments,
1040
1060
  canAcceptDrop: props.canAcceptDrop,
@@ -1044,7 +1064,7 @@ function RailSlot(props) {
1044
1064
  dropLimits: props.dropLimits
1045
1065
  });
1046
1066
  });
1047
- (0, import_react8.useLayoutEffect)(() => () => clearLiveOwner(tokenRef.current), []);
1067
+ (0, import_react7.useLayoutEffect)(() => () => clearLiveOwner(tokenRef.current), []);
1048
1068
  return null;
1049
1069
  }
1050
1070
  var chatCardOpener;
@@ -1055,7 +1075,7 @@ function PickerSlot(props) {
1055
1075
  const inputActions = props.inputActions;
1056
1076
  const submitAction = inputActions?.submit;
1057
1077
  const kitSig = `inputActions=${props.inputActions ? "y" : "n"} useSession=${props.useSession ? "y" : "n"}`;
1058
- (0, import_react8.useEffect)(() => {
1078
+ (0, import_react7.useEffect)(() => {
1059
1079
  console.info(`[file-native] kit: ${kitSig}`);
1060
1080
  }, [kitSig]);
1061
1081
  const submitWithFiles = (ev, sid) => {
@@ -1071,7 +1091,7 @@ function PickerSlot(props) {
1071
1091
  void fetch(`/plugins/file-native/arm?sessionId=${encodeURIComponent(sid)}`, { method: "POST" }).catch(() => {
1072
1092
  }).then(() => submitFn());
1073
1093
  };
1074
- (0, import_react8.useEffect)(() => {
1094
+ (0, import_react7.useEffect)(() => {
1075
1095
  const onKeyDown = (ev) => {
1076
1096
  if (ev.key !== "Enter" || ev.shiftKey || ev.isComposing || ev.altKey || ev.ctrlKey || ev.metaKey) return;
1077
1097
  if (running) return;
@@ -1082,7 +1102,7 @@ function PickerSlot(props) {
1082
1102
  document.addEventListener("keydown", onKeyDown, true);
1083
1103
  return () => document.removeEventListener("keydown", onKeyDown, true);
1084
1104
  }, [running, inputActions, sessionId]);
1085
- (0, import_react8.useEffect)(() => {
1105
+ (0, import_react7.useEffect)(() => {
1086
1106
  const onClick = (ev) => {
1087
1107
  if (running) return;
1088
1108
  const button = ev.target instanceof Element ? ev.target.closest("button") : null;
@@ -1095,34 +1115,28 @@ function PickerSlot(props) {
1095
1115
  document.addEventListener("click", onClick, true);
1096
1116
  return () => document.removeEventListener("click", onClick, true);
1097
1117
  }, [running, inputActions, sessionId]);
1098
- return (0, import_react8.createElement)(
1118
+ return (0, import_react7.createElement)(
1099
1119
  "span",
1100
1120
  null,
1101
- (0, import_react8.createElement)(PaperclipButton, { sessionId }),
1102
- (0, import_react8.createElement)(MessageCards, { sessionId, onOpen: (file) => chatCardOpener?.(sessionId || currentSessionId(), file.relPath, file.name) })
1121
+ (0, import_react7.createElement)(PaperclipButton, { sessionId }),
1122
+ (0, import_react7.createElement)(MessageCards, { sessionId, onOpen: (file) => chatCardOpener?.(sessionId || currentSessionId(), file.relPath, file.name) })
1103
1123
  );
1104
1124
  }
1105
- function TailSlot(props) {
1106
- return (0, import_react8.createElement)(UploadedTail, {
1107
- sessionId: props.sessionId,
1108
- openFile: props.openFile,
1109
- matched: props.matched ?? null,
1110
- turn: props.turn?.turn
1111
- });
1112
- }
1113
- function selectTail(owner2) {
1114
- const produced = producedPathsOf(owner2);
1115
- if (produced.length === 0) return null;
1116
- return { uploaded: [], produced };
1117
- }
1118
1125
  function installLiveIntercept() {
1119
1126
  return installFileIntercept({
1120
1127
  canAccept: () => true,
1121
1128
  onDepth: (depth) => setDragDepth(depth),
1122
1129
  onFiles: (files) => {
1123
1130
  const sessionId = currentSessionId();
1124
- intakeFiles(sessionId, files, (images) => addImages(sessionId, images));
1125
- }
1131
+ intakeFiles(sessionId, files, (images) => {
1132
+ void deliverImages(sessionId, images);
1133
+ });
1134
+ },
1135
+ onImages: (files) => {
1136
+ const sessionId = currentSessionId();
1137
+ void deliverImages(sessionId, files);
1138
+ },
1139
+ onText: (text) => pasteAccompanyingText(text)
1126
1140
  });
1127
1141
  }
1128
1142
  var INSTANCE = (() => {
@@ -1134,7 +1148,12 @@ var INSTANCE = (() => {
1134
1148
  })();
1135
1149
  function apply(ctx) {
1136
1150
  ctx.effect(() => installLiveIntercept(), "file-native: document intercept");
1137
- const sidebar = ctx.betterSidebar;
1151
+ let sidebar;
1152
+ try {
1153
+ sidebar = ctx.betterSidebar;
1154
+ } catch {
1155
+ sidebar = void 0;
1156
+ }
1138
1157
  if (sidebar !== void 0 && sidebar.features.includes("openFile")) {
1139
1158
  chatCardOpener = (sid, relPath, name2) => sidebar.openFile({ sessionId: sid }, relPath, name2);
1140
1159
  } else {
@@ -1164,11 +1183,6 @@ function apply(ctx) {
1164
1183
  order: 0,
1165
1184
  priority: base
1166
1185
  }, PickerSlot));
1167
- safeInject("conversation.chat.turnTail", () => ctx.slots.register({
1168
- name: "conversation.chat.turnTail",
1169
- select: selectTail,
1170
- priority: base
1171
- }, TailSlot));
1172
1186
  console.info(`[file-native] client loaded (instance #${INSTANCE})`);
1173
1187
  }
1174
1188
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-files-native",
3
3
  "description": "DSH 插件:接近原生质感的附件上传——拖入/粘贴/回形针,图片缩略图与文件卡片混排在官方附件栏",
4
- "version": "0.2.0",
4
+ "version": "0.2.1",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {