dsh-sidenote 0.3.4 → 0.4.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.
Files changed (39) hide show
  1. package/README.md +14 -16
  2. package/lib/client.js +940 -208
  3. package/lib/index.js +180 -2
  4. package/lib/types/client/annotate/send.d.ts +11 -0
  5. package/lib/types/client/chat/cards.d.ts +3 -3
  6. package/lib/types/client/host/contracts.d.ts +75 -2
  7. package/lib/types/client/host/labels.d.ts +13 -0
  8. package/lib/types/client/index.d.ts +6 -5
  9. package/lib/types/client/locales.d.ts +10 -0
  10. package/lib/types/client/reflow.d.ts +6 -0
  11. package/lib/types/client/sidechat/lifecycle.d.ts +10 -2
  12. package/lib/types/client/sidechat/metaStore.d.ts +73 -0
  13. package/lib/types/client/sidechat/native-tab.d.ts +32 -0
  14. package/lib/types/client/sidechat/native.d.ts +15 -0
  15. package/lib/types/client/sidechat/open.d.ts +17 -6
  16. package/lib/types/index.d.ts +36 -5
  17. package/lib/types/trust-fence.d.ts +24 -0
  18. package/package.json +28 -4
  19. package/src/client/annotate/send.ts +126 -27
  20. package/src/client/chat/ToolCard.tsx +8 -8
  21. package/src/client/chat/cards.ts +3 -3
  22. package/src/client/host/contracts.ts +81 -1
  23. package/src/client/host/labels.ts +16 -0
  24. package/src/client/index.tsx +12 -6
  25. package/src/client/locales.ts +20 -0
  26. package/src/client/reflow.ts +52 -0
  27. package/src/client/sidechat/SideChatPanel.tsx +18 -7
  28. package/src/client/sidechat/index.tsx +54 -35
  29. package/src/client/sidechat/lifecycle.ts +49 -7
  30. package/src/client/sidechat/metaStore.ts +197 -0
  31. package/src/client/sidechat/native-tab.tsx +249 -0
  32. package/src/client/sidechat/native.ts +39 -1
  33. package/src/client/sidechat/notify.ts +29 -8
  34. package/src/client/sidechat/open.ts +161 -24
  35. package/src/client/sidechat/referenceSource.ts +41 -7
  36. package/src/client/sidechat/sidechat.module.css +11 -0
  37. package/src/client/sidechat/slash.ts +18 -6
  38. package/src/index.ts +194 -6
  39. package/src/trust-fence.ts +84 -0
package/lib/client.js CHANGED
@@ -21,13 +21,44 @@ window.__ModuleLoader__.load({
21
21
  return root ?? fallback;
22
22
  }
23
23
  /**
24
+ * betterSidebar 服务的唯一安全读取面(对抗性审查 B-1):cordis 对「未
25
+ * provide 且未 inject」的服务**属性访问抛错**(`cannot get property without
26
+ * inject`),`?.` 也挡不住;`ctx.get` 返回 undefined 不抛。optional peer
27
+ * 后 betterSidebar 不在 inject 清单——一切消费点必须走这里。
28
+ */
29
+ function betterSidebarOf(ctx) {
30
+ try {
31
+ const viaGet = ctx.get("betterSidebar");
32
+ if (viaGet !== void 0 && viaGet !== null) return viaGet;
33
+ } catch {}
34
+ try {
35
+ return ctx.betterSidebar;
36
+ } catch {
37
+ return;
38
+ }
39
+ }
40
+ /**
41
+ * 直连腿判定(WI-05):tab 直连注册进原生右栏的条件。
42
+ * - betterSidebar ≥ 0.19 ⇒ 宿主必 ≥ 0.1.5(BS 0.19 只支持 0.1.5)⇒ 直连;
43
+ * - betterSidebar 整体缺席(optional peer 后的合法形态)⇒ 只能直连;
44
+ * 无 BS 且无原生面的非法环境(宿主 < 0.1.5)下注册静默不到、侧聊缺席,
45
+ * 其余功能(注释/回流搭车)存活——静默缺席优于崩。
46
+ */
47
+ function directNativeLeg(ctx) {
48
+ const bs = betterSidebarOf(ctx);
49
+ if (bs === void 0 || bs === null) return true;
50
+ return nativeSidebarHost(ctx);
51
+ }
52
+ /**
24
53
  * True when the host keeps sidebar tabs outside the legacy layout store
25
54
  * (better-sidebar >= 0.19). Version parse is best-effort: an unparsable
26
55
  * version reports false, which keeps the pre-0.19 behaviour.
27
56
  */
28
57
  function nativeSidebarHost(ctx) {
29
58
  try {
30
- const [major = 0, minor = 0] = ctx.betterSidebar.version.split(".").map((part) => Number.parseInt(part, 10));
59
+ const version = betterSidebarOf(ctx)?.version;
60
+ if (version === void 0) return false;
61
+ const [major = 0, minor = 0] = version.split(".").map((part) => Number.parseInt(part, 10));
31
62
  return major > 0 || minor >= NATIVE_SIDEBAR_MINOR;
32
63
  } catch {
33
64
  return false;
@@ -143,6 +174,16 @@ window.__ModuleLoader__.load({
143
174
  tabBaseTitle: "Side",
144
175
  codeCopy: "Copy",
145
176
  codeCopied: "Copied",
177
+ jsonCopyValue: "Copy value",
178
+ jsonCopyJson: "Copy JSON",
179
+ jsonCopyPath: "Copy path",
180
+ jsonCopyPretty: "Copy pretty JSON",
181
+ jsonCopyCompact: "Copy compact JSON",
182
+ jsonCopyFailed: "Copy failed",
183
+ jsonCollapse: "Collapse node",
184
+ jsonExpand: "Expand node",
185
+ jsonTreeLabel: "JSON payload",
186
+ sideChatCrashed: "Side chat panel crashed: ",
146
187
  forkErrorTitle: "Couldn't create the side chat",
147
188
  forkErrorHint: "The main session needs at least one completed turn to fork from. Close this tab with the × on the tab.",
148
189
  missingTitle: "Session no longer exists",
@@ -277,6 +318,16 @@ window.__ModuleLoader__.load({
277
318
  tabBaseTitle: "侧边",
278
319
  codeCopy: "复制",
279
320
  codeCopied: "已复制",
321
+ jsonCopyValue: "复制值",
322
+ jsonCopyJson: "复制 JSON",
323
+ jsonCopyPath: "复制路径",
324
+ jsonCopyPretty: "复制格式化 JSON",
325
+ jsonCopyCompact: "复制紧凑 JSON",
326
+ jsonCopyFailed: "复制失败",
327
+ jsonCollapse: "折叠节点",
328
+ jsonExpand: "展开节点",
329
+ jsonTreeLabel: "JSON 数据",
330
+ sideChatCrashed: "侧边聊天面板崩溃了:",
280
331
  forkErrorTitle: "无法创建侧边聊天",
281
332
  forkErrorHint: "主会话需要至少一轮已完成的对话才能 fork。点击标签上的 × 可关闭此标签页。",
282
333
  missingTitle: "会话已不存在",
@@ -781,28 +832,238 @@ window.__ModuleLoader__.load({
781
832
  };
782
833
  }
783
834
  //#endregion
835
+ //#region src/client/sidechat/metaStore.ts
836
+ const KEY_PREFIX$1 = "dsh-sidenote:side-meta:v1:";
837
+ /**
838
+ * 本页面生命周期 id。**挂在 window 上**(审查 m3):模块级铸造会在插件
839
+ * HMR/重载时换模块实例 → 新 runId 触发孤键清扫误删本页活记录。window
840
+ * 级持有跨模块重载稳定;页面刷新(真正的清扫时机)window 重建。
841
+ */
842
+ const SIDENOTE_RUN_ID = (() => {
843
+ if (typeof window !== "undefined") {
844
+ const w = window;
845
+ if (w.__dshSidenoteRunId === void 0) w.__dshSidenoteRunId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `run-${Date.now()}-${Math.random().toString(36).slice(2)}`;
846
+ return w.__dshSidenoteRunId;
847
+ }
848
+ return `run-ssr-${Math.random().toString(36).slice(2)}`;
849
+ })();
850
+ function keyOf(sessionId, tabId) {
851
+ return `${KEY_PREFIX$1}${sessionId}:${tabId}`;
852
+ }
853
+ function revive$1(value) {
854
+ if (typeof value !== "object" || value === null) return null;
855
+ const r = value;
856
+ if (typeof r.sessionId !== "string" || r.sessionId === "") return null;
857
+ if (typeof r.tabId !== "string" || r.tabId === "") return null;
858
+ if (typeof r.number !== "number") return null;
859
+ return {
860
+ tabId: r.tabId,
861
+ sessionId: r.sessionId,
862
+ ...typeof r.childId === "string" ? { childId: r.childId } : {},
863
+ ...typeof r.parentSessionId === "string" ? { parentSessionId: r.parentSessionId } : {},
864
+ ...typeof r.pendingDraft === "string" ? { pendingDraft: r.pendingDraft } : {},
865
+ ...typeof r.boundarySeq === "number" && Number.isInteger(r.boundarySeq) ? { boundarySeq: r.boundarySeq } : {},
866
+ number: r.number,
867
+ createdAt: typeof r.createdAt === "number" ? r.createdAt : 0,
868
+ ...typeof r.runId === "string" ? { runId: r.runId } : {}
869
+ };
870
+ }
871
+ function storage() {
872
+ return typeof localStorage === "undefined" ? null : localStorage;
873
+ }
874
+ let version = 0;
875
+ const listeners$1 = /* @__PURE__ */ new Set();
876
+ function notify() {
877
+ version += 1;
878
+ for (const fn of [...listeners$1]) fn();
879
+ }
880
+ /** useSyncExternalStore 三件套。 */
881
+ const sideChatMetaStore = {
882
+ getSnapshot: () => version,
883
+ subscribe(fn) {
884
+ listeners$1.add(fn);
885
+ return () => {
886
+ listeners$1.delete(fn);
887
+ };
888
+ }
889
+ };
890
+ /** 渲染期静默写后的补通知(面板 reconcile 用——render 期写不能 notify)。 */
891
+ function notifySideChatMetaListeners() {
892
+ notify();
893
+ }
894
+ function readSideChatMeta(sessionId, tabId) {
895
+ const store = storage();
896
+ if (store === null) return void 0;
897
+ try {
898
+ const raw = store.getItem(keyOf(sessionId, tabId));
899
+ if (raw === null) return void 0;
900
+ return revive$1(JSON.parse(raw)) ?? void 0;
901
+ } catch {
902
+ return;
903
+ }
904
+ }
905
+ /** 写/合并一个 tab 的 meta(整体替换语义由调用方组合:先读再写)。 */
906
+ function writeSideChatMeta(record, options) {
907
+ const store = storage();
908
+ if (store === null) return;
909
+ try {
910
+ store.setItem(keyOf(record.sessionId, record.tabId), JSON.stringify(record));
911
+ if (options?.silent !== true) notify();
912
+ } catch {}
913
+ }
914
+ function dropSideChatMeta(sessionId, tabId) {
915
+ const store = storage();
916
+ if (store === null) return;
917
+ try {
918
+ store.removeItem(keyOf(sessionId, tabId));
919
+ notify();
920
+ } catch {}
921
+ }
922
+ /** 枚举一个主会话的全部 meta 记录(按创建序升序)。 */
923
+ function sideChatMetasOf(sessionId) {
924
+ return sideChatMetasAll().filter((r) => r.sessionId === sessionId).sort((a, b) => a.createdAt - b.createdAt);
925
+ }
926
+ /** 枚举全部存活记录(跨会话;完成通知等全局监听用)。 */
927
+ function sideChatMetasAll() {
928
+ const store = storage();
929
+ if (store === null) return [];
930
+ try {
931
+ const out = [];
932
+ for (let i = 0; i < store.length; i += 1) {
933
+ const key = store.key(i);
934
+ if (typeof key !== "string" || !key.startsWith(KEY_PREFIX$1)) continue;
935
+ const record = revive$1(JSON.parse(store.getItem(key) ?? "null"));
936
+ if (record !== null) out.push(record);
937
+ }
938
+ return out;
939
+ } catch {
940
+ return [];
941
+ }
942
+ }
943
+ /**
944
+ * 下一个实例编号:当前会话存活记录的最大编号 + 1。
945
+ * 单实例期调用方保证同会话同时至多一条存活记录(held 规则),
946
+ * 此函数为 Delivery_02 的多实例预留正确语义。
947
+ */
948
+ function nextSideChatNumber(sessionId) {
949
+ const existing = sideChatMetasOf(sessionId);
950
+ return existing.length === 0 ? 1 : Math.max(...existing.map((r) => r.number)) + 1;
951
+ }
952
+ /**
953
+ * 孤键清扫:删掉 runId 与本页面生命周期不匹配的记录(上一页面周期的
954
+ * 残留——原生布局刷新即空,这些键指向的 tab 已不存在)。插件 apply 时
955
+ * 调用一次。返回清扫数。
956
+ */
957
+ function sweepOrphanedSideChatMeta() {
958
+ const store = storage();
959
+ if (store === null) return 0;
960
+ try {
961
+ const staleKeys = [];
962
+ for (let i = 0; i < store.length; i += 1) {
963
+ const key = store.key(i);
964
+ if (typeof key !== "string" || !key.startsWith(KEY_PREFIX$1)) continue;
965
+ const record = revive$1(JSON.parse(store.getItem(key) ?? "null"));
966
+ if (record === null || record.runId !== SIDENOTE_RUN_ID) staleKeys.push(key);
967
+ }
968
+ for (const key of staleKeys) store.removeItem(key);
969
+ if (staleKeys.length > 0) notify();
970
+ return staleKeys.length;
971
+ } catch {
972
+ return 0;
973
+ }
974
+ }
975
+ //#endregion
784
976
  //#region src/client/sidechat/open.ts
977
+ /** 直连腿的原生右栏面(off-face 探测;无面 = 不在直连腿或面缺席降级)。 */
978
+ function sidebarRightOf(ctx) {
979
+ try {
980
+ const face = ctx.get("sidebarRight");
981
+ return typeof face?.openTab === "function" ? face : void 0;
982
+ } catch {
983
+ return;
984
+ }
985
+ }
986
+ /** 直连腿判定:directNativeLeg(BS ≥0.19 或 BS 缺席)+ 原生面已就位。 */
987
+ function directLeg(ctx) {
988
+ return directNativeLeg(ctx) && sidebarRightOf(ctx) !== void 0;
989
+ }
990
+ /** 直连腿:向既存 tab 追加待投递草稿(面板 pendingDraft effect 消费)。 */
991
+ function appendNativePendingDraft(sessionId, tabId, draftText) {
992
+ const meta = readSideChatMeta(sessionId, tabId);
993
+ if (meta === void 0) return;
994
+ writeSideChatMeta({
995
+ ...meta,
996
+ pendingDraft: appendDraftText(meta.pendingDraft ?? "", draftText)
997
+ });
998
+ }
785
999
  /**
786
1000
  * 从最新快照读一个 Tab(meta 合并写入前的读取面;布局即注册表)。
787
- * better-sidebar >= 0.19: the open tab lives in the native right sidebar, not
788
- * in the layout snapshot — fall back to the live-panel registry.
1001
+ * 直连腿:metaStore 是 meta 权威(原生布局无 meta 面)。
789
1002
  */
790
- function readTab(ctx, tabId) {
791
- return collectTabs(ctx.betterSidebar.getSnapshot().state).find((candidate) => candidate.id === tabId) ?? nativeTabShell(tabId, "dsh-sidenote:side");
1003
+ function readTab(ctx, sessionId, tabId) {
1004
+ const direct = readSideChatMeta(sessionId, tabId);
1005
+ if (direct !== void 0) {
1006
+ const live = liveSideChatsOf(sessionId).find((entry) => entry.tabId === tabId);
1007
+ return {
1008
+ id: tabId,
1009
+ type: SIDE_TAB_TYPE,
1010
+ title: live?.readTitle() ?? (direct.number <= 1 ? t("tabBaseTitle") : `${t("tabBaseTitle")} ${direct.number}`),
1011
+ meta: direct
1012
+ };
1013
+ }
1014
+ const legacySnap = betterSidebarOf(ctx)?.getSnapshot();
1015
+ return collectTabs(legacySnap?.state).find((candidate) => candidate.id === tabId) ?? nativeTabShell(tabId, "dsh-sidenote:side");
792
1016
  }
793
1017
  /**
794
1018
  * 为 sessionId 新建或聚焦一个侧边聊天 Tab;draftText 给出时写入其草稿。
795
1019
  * @returns false = 未能打开(调用方保持自己的流程)。
796
1020
  */
797
1021
  function openOrFocusSideChat(ctx, sessionId, draftText) {
1022
+ if (directLeg(ctx)) return openOrFocusDirect(ctx, sessionId, draftText);
1023
+ return openOrFocusLegacy(ctx, sessionId, draftText);
1024
+ }
1025
+ /** 直连腿:聚焦既有(三级数据源)或铸造新开。 */
1026
+ function openOrFocusDirect(ctx, sessionId, draftText) {
1027
+ try {
1028
+ if (ctx.sessions.list.getSnapshot().current !== sessionId) return false;
1029
+ const hasDraft = draftText !== void 0 && draftText !== "";
1030
+ const live = lastLiveSideChat(sessionId);
1031
+ if (live !== void 0) {
1032
+ if (hasDraft) live.seedDraft(draftText);
1033
+ if (!focusNativeTab(ctx, live.tabId)) sidebarRightOf(ctx)?.focus(live.tabId);
1034
+ return true;
1035
+ }
1036
+ const opening = sideChatOpening(sessionId);
1037
+ if (opening !== void 0) {
1038
+ if (hasDraft) opening.drafts.push(draftText);
1039
+ return true;
1040
+ }
1041
+ const metas = sideChatMetasOf(sessionId);
1042
+ const target = metas[metas.length - 1];
1043
+ if (target !== void 0 && hasDraft) appendNativePendingDraft(sessionId, target.tabId, draftText);
1044
+ sidebarRightOf(ctx).openTab(SIDE_TAB_TYPE, { params: {
1045
+ parentSessionId: sessionId,
1046
+ ...hasDraft && target === void 0 ? { pendingDraft: draftText } : {}
1047
+ } });
1048
+ markSideChatOpening(sessionId);
1049
+ return true;
1050
+ } catch (error) {
1051
+ console.warn("[dsh-sidenote] 打开侧边聊天失败:", error);
1052
+ return false;
1053
+ }
1054
+ }
1055
+ /** legacy 腿:布局快照即注册表的原始路径(BS < 0.19)。 */
1056
+ function openOrFocusLegacy(ctx, sessionId, draftText) {
1057
+ const bs = betterSidebarOf(ctx);
1058
+ if (bs === void 0) return false;
798
1059
  try {
799
- const snapshot = ctx.betterSidebar.getSnapshot();
1060
+ const snapshot = bs.getSnapshot();
800
1061
  if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
801
1062
  const existing = collectSideTabs(snapshot.state);
802
1063
  const live = lastLiveSideChat(sessionId);
803
1064
  if (live !== void 0) {
804
1065
  if (draftText !== void 0 && draftText !== "") live.seedDraft(draftText);
805
- if (!focusNativeTab(ctx, live.tabId)) ctx.betterSidebar.activateTab(live.tabId, { sessionId });
1066
+ if (!focusNativeTab(ctx, live.tabId)) bs.activateTab(live.tabId, { sessionId });
806
1067
  return true;
807
1068
  }
808
1069
  if (existing.length > 0) {
@@ -811,12 +1072,12 @@ window.__ModuleLoader__.load({
811
1072
  const meta = parseSideChatMeta(target.meta);
812
1073
  const input = meta.childId === void 0 ? null : resolveSessionInput(ctx, meta.childId);
813
1074
  if (input !== null) input.setDraft(appendDraftText(readInputDraft(input), draftText));
814
- else ctx.betterSidebar.updateTab(target.id, { meta: {
1075
+ else bs.updateTab(target.id, { meta: {
815
1076
  ...meta,
816
1077
  pendingDraft: appendDraftText(meta.pendingDraft ?? "", draftText)
817
1078
  } });
818
1079
  }
819
- ctx.betterSidebar.activateTab(target.id, { sessionId });
1080
+ bs.activateTab(target.id, { sessionId });
820
1081
  return true;
821
1082
  }
822
1083
  const opening = sideChatOpening(sessionId);
@@ -831,28 +1092,35 @@ window.__ModuleLoader__.load({
831
1092
  }
832
1093
  }
833
1094
  /**
834
- * 无条件新建一个侧边聊天 Tab(/side「新建侧边聊天」选项的语义——既有实例
835
- * 在弹层里另有聚焦项,「新建」必须真的新建,否则标签与行为不符)。
1095
+ * 无条件新建一个侧边聊天 Tab(/side「新建侧边聊天」选项的语义)。
1096
+ * 直连腿(单实例期):折叠为「聚焦既有 + 草稿正确投递」(= openOrFocus;
1097
+ * 多实例随 Delivery_02 的 multiple 放开)。
836
1098
  * @returns false = 未能创建(调用方保持自己的流程)。
837
1099
  */
838
1100
  function createSideChat(ctx, sessionId, draftText) {
1101
+ if (directLeg(ctx)) return openOrFocusDirect(ctx, sessionId, draftText);
1102
+ return createSideChatLegacy(ctx, sessionId, draftText);
1103
+ }
1104
+ function createSideChatLegacy(ctx, sessionId, draftText) {
1105
+ const bs = betterSidebarOf(ctx);
1106
+ if (bs === void 0) return false;
839
1107
  try {
840
- const snapshot = ctx.betterSidebar.getSnapshot();
1108
+ const snapshot = bs.getSnapshot();
841
1109
  if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
842
- if (!ctx.betterSidebar.isTabEnabled("dsh-sidenote:side")) return false;
1110
+ if (!bs.isTabEnabled("dsh-sidenote:side")) return false;
843
1111
  const before = new Set(collectTabs(snapshot.state).map((tab) => tab.id));
844
1112
  const seed = draftText !== void 0 && draftText !== "" ? {
845
1113
  type: SIDE_TAB_TYPE,
846
1114
  meta: { pendingDraft: draftText }
847
1115
  } : { type: SIDE_TAB_TYPE };
848
- ctx.betterSidebar.openTab(seed, { sessionId });
849
- const created = collectSideTabs(ctx.betterSidebar.getSnapshot().state).find((tab) => !before.has(tab.id));
1116
+ bs.openTab(seed, { sessionId });
1117
+ const created = collectSideTabs(bs.getSnapshot().state).find((tab) => !before.has(tab.id));
850
1118
  if (created === void 0) {
851
1119
  if (!nativeSidebarHost(ctx)) return false;
852
1120
  markSideChatOpening(sessionId);
853
1121
  return true;
854
1122
  }
855
- if (draftText !== void 0 && draftText !== "") ctx.betterSidebar.updateTab(created.id, { meta: { pendingDraft: draftText } });
1123
+ if (draftText !== void 0 && draftText !== "") bs.updateTab(created.id, { meta: { pendingDraft: draftText } });
856
1124
  return true;
857
1125
  } catch (error) {
858
1126
  console.warn("[dsh-sidenote] 新建侧边聊天失败:", error);
@@ -866,8 +1134,17 @@ window.__ModuleLoader__.load({
866
1134
  */
867
1135
  function sideChatTargetTitle(ctx, sessionId) {
868
1136
  try {
869
- const snapshot = ctx.betterSidebar.getSnapshot();
870
- if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return void 0;
1137
+ if (directLeg(ctx)) {
1138
+ if (ctx.sessions.list.getSnapshot().current !== sessionId) return void 0;
1139
+ const live = lastLiveSideChat(sessionId);
1140
+ if (live !== void 0) return live.readTitle();
1141
+ const metas = sideChatMetasOf(sessionId);
1142
+ const last = metas[metas.length - 1];
1143
+ if (last !== void 0) return last.number <= 1 ? t("tabBaseTitle") : `${t("tabBaseTitle")} ${last.number}`;
1144
+ return t("tabBaseTitle");
1145
+ }
1146
+ const snapshot = betterSidebarOf(ctx)?.getSnapshot();
1147
+ if (snapshot === void 0 || snapshot.sessionId !== sessionId || snapshot.state === void 0) return void 0;
871
1148
  const existing = collectSideTabs(snapshot.state);
872
1149
  if (existing.length > 0) return existing[existing.length - 1].title;
873
1150
  const live = lastLiveSideChat(sessionId);
@@ -884,28 +1161,43 @@ window.__ModuleLoader__.load({
884
1161
  * 有现成错误态兜着)。
885
1162
  */
886
1163
  function reopenSideChat(ctx, sessionId, childId, title) {
1164
+ if (directLeg(ctx)) try {
1165
+ if (ctx.sessions.list.getSnapshot().current !== sessionId) return false;
1166
+ if (sideChatMetasOf(sessionId).length > 0) return false;
1167
+ sidebarRightOf(ctx).openTab(SIDE_TAB_TYPE, { params: {
1168
+ parentSessionId: sessionId,
1169
+ childId
1170
+ } });
1171
+ markSideChatOpening(sessionId);
1172
+ return true;
1173
+ } catch (error) {
1174
+ console.warn("[dsh-sidenote] 重开侧边聊天失败:", error);
1175
+ return false;
1176
+ }
1177
+ const bs = betterSidebarOf(ctx);
1178
+ if (bs === void 0) return false;
887
1179
  try {
888
- const snapshot = ctx.betterSidebar.getSnapshot();
1180
+ const snapshot = bs.getSnapshot();
889
1181
  if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
890
- if (!ctx.betterSidebar.isTabEnabled("dsh-sidenote:side")) return false;
1182
+ if (!bs.isTabEnabled("dsh-sidenote:side")) return false;
891
1183
  if (nativeSidebarHost(ctx) && liveSideChatsOf(sessionId).length > 0) return false;
892
1184
  const before = new Set(collectTabs(snapshot.state).map((tab) => tab.id));
893
1185
  const meta = {
894
1186
  childId,
895
1187
  parentSessionId: sessionId
896
1188
  };
897
- ctx.betterSidebar.openTab({
1189
+ bs.openTab({
898
1190
  type: SIDE_TAB_TYPE,
899
1191
  ...title !== void 0 ? { title } : {},
900
1192
  meta
901
1193
  }, { sessionId });
902
- const created = collectSideTabs(ctx.betterSidebar.getSnapshot().state).find((tab) => !before.has(tab.id));
1194
+ const created = collectSideTabs(bs.getSnapshot().state).find((tab) => !before.has(tab.id));
903
1195
  if (created === void 0) {
904
1196
  if (!nativeSidebarHost(ctx)) return false;
905
1197
  markSideChatOpening(sessionId);
906
1198
  return true;
907
1199
  }
908
- ctx.betterSidebar.updateTab(created.id, {
1200
+ bs.updateTab(created.id, {
909
1201
  ...title !== void 0 ? { title } : {},
910
1202
  meta
911
1203
  });
@@ -936,7 +1228,7 @@ window.__ModuleLoader__.load({
936
1228
  } catch {}
937
1229
  const forked = await ctx.sessions.fork({ sessionId: parentSessionId });
938
1230
  const owner = rootContext(ctx);
939
- updateTabMeta(ctx, tabId, (current) => ({
1231
+ updateTabMeta(ctx, parentSessionId, tabId, (current) => ({
940
1232
  ...current,
941
1233
  childId: forked,
942
1234
  parentSessionId,
@@ -956,10 +1248,49 @@ window.__ModuleLoader__.load({
956
1248
  }
957
1249
  return forked;
958
1250
  }
959
- /** Tab meta 读-改-写(布局持久化寄存处);tab 已消失时 no-op。 */
960
- function updateTabMeta(ctx, tabId, mutate) {
961
- const current = parseSideChatMeta(readTab(ctx, tabId)?.meta);
962
- ctx.betterSidebar.updateTab(tabId, { meta: mutate(current) });
1251
+ /**
1252
+ * Tab meta 读-改-写。双腿:直连原生腿的 meta 在自有 metaStore(原生无
1253
+ * updateTab 面,布局 memory-only);legacy 腿在布局快照 + betterSidebar.updateTab。
1254
+ * 分腿判据统一为 directNativeLeg(对抗性审查 m1:判据单点化——metaStore
1255
+ * 有无记录不能再当判据,否则记录缺失时 childId 登记会静默落到 no-op 的
1256
+ * legacy 面)。直连腿记录缺失 = 异常态(reconcile 应先已建):warn 并 no-op。
1257
+ */
1258
+ function updateTabMeta(ctx, sessionId, tabId, mutate) {
1259
+ if (directNativeLeg(ctx)) {
1260
+ const direct = readSideChatMeta(sessionId, tabId);
1261
+ if (direct === void 0) {
1262
+ console.warn("[dsh-sidenote] meta 记录缺失(reconcile 应先已建),本次 meta 写入跳过:", tabId);
1263
+ return;
1264
+ }
1265
+ const next = mutate(parseSideChatMeta(direct));
1266
+ writeSideChatMeta({
1267
+ tabId,
1268
+ sessionId: direct.sessionId,
1269
+ ...next.childId !== void 0 ? { childId: next.childId } : {},
1270
+ ...next.parentSessionId !== void 0 ? { parentSessionId: next.parentSessionId } : {},
1271
+ ...next.pendingDraft !== void 0 ? { pendingDraft: next.pendingDraft } : {},
1272
+ ...next.boundarySeq !== void 0 ? { boundarySeq: next.boundarySeq } : {},
1273
+ number: direct.number,
1274
+ createdAt: direct.createdAt,
1275
+ ...direct.runId !== void 0 ? { runId: direct.runId } : {}
1276
+ });
1277
+ return;
1278
+ }
1279
+ const bs = betterSidebarOf(ctx);
1280
+ if (bs === void 0) return;
1281
+ const current = parseSideChatMeta(readTab(ctx, sessionId, tabId)?.meta);
1282
+ bs.updateTab(tabId, { meta: mutate(current) });
1283
+ }
1284
+ /** 关闭侧聊 tab:直连腿走 ISidebarRight.close,legacy 走 betterSidebar.closeTab。 */
1285
+ function closeSideTab(ctx, tabId, sessionId) {
1286
+ if (directNativeLeg(ctx)) {
1287
+ const face = sidebarRightOf(ctx);
1288
+ if (face !== void 0) {
1289
+ face.close(tabId);
1290
+ return;
1291
+ }
1292
+ }
1293
+ betterSidebarOf(ctx)?.closeTab(tabId, { sessionId });
963
1294
  }
964
1295
  /**
965
1296
  * 程序化入口(/side、bridge 划选提问)打开 Tab 时面板可能处于折叠态——
@@ -1202,6 +1533,49 @@ window.__ModuleLoader__.load({
1202
1533
  * 持久化:localStorage 按会话键(dsh-sidenote:reflow:v1:<sessionId>),
1203
1534
  * 与注释 store 同款纪律(刷新不丢、容错 revive、空删键)。
1204
1535
  */
1536
+ /**
1537
+ * reflow 注入通道(Workitem_06):优先经宿主路由 /sidenote/reflow 以
1538
+ * agent.inject 落父会话日志(plugin-source 独立事件,context row 折叠);
1539
+ * 路由缺席/父会话无 live agent/超时 → 调用方回落搭车形态(发送拦截器
1540
+ * 序列化前缀)。逐个 item 投递;成功项由调用方从 store 移除(不等发送
1541
+ * 确认——注入已入 inbox,chip 消失表达「已注入」)。
1542
+ *
1543
+ * 有界等待:注入尝试绝不能拖住发送手势——300ms 超时即回落搭车。
1544
+ */
1545
+ const REFLOW_ROUTE_TIMEOUT_MS = 300;
1546
+ /** 尝试经宿主路由注入一批回流项;任何失败都降级为「全未注入」。 */
1547
+ async function tryInjectReflows(parentSessionId, items) {
1548
+ if (items.length === 0) return { injectedIds: /* @__PURE__ */ new Set() };
1549
+ const empty = { injectedIds: /* @__PURE__ */ new Set() };
1550
+ try {
1551
+ const controller = new AbortController();
1552
+ const timer = setTimeout(() => {
1553
+ controller.abort();
1554
+ }, REFLOW_ROUTE_TIMEOUT_MS);
1555
+ let res;
1556
+ try {
1557
+ res = await fetch("/sidenote/reflow", {
1558
+ method: "POST",
1559
+ headers: { "content-type": "application/json" },
1560
+ body: JSON.stringify({
1561
+ parentSessionId,
1562
+ items: items.map((item) => ({
1563
+ text: `以下是用户从侧边对话带回的结论(参考上下文):\n\n${buildReflowBlock(item)}`,
1564
+ sideTitle: item.sideTitle
1565
+ }))
1566
+ }),
1567
+ signal: controller.signal
1568
+ });
1569
+ } finally {
1570
+ clearTimeout(timer);
1571
+ }
1572
+ if (!res.ok) return empty;
1573
+ if ((await res.json()).ok !== true) return empty;
1574
+ return { injectedIds: new Set(items.map((item) => item.id)) };
1575
+ } catch {
1576
+ return empty;
1577
+ }
1578
+ }
1205
1579
  const STORAGE_PREFIX$1 = "dsh-sidenote:reflow:v1:";
1206
1580
  /**
1207
1581
  * 回流块序列化(XML 形态,与注释协议同族):
@@ -2169,9 +2543,24 @@ window.__ModuleLoader__.load({
2169
2543
  markdown: markdownChrome()
2170
2544
  };
2171
2545
  }
2546
+ /** JsonTreeLabels(0.1.5 必填:label + labels 全套)。 */
2547
+ function jsonTreeLabels() {
2548
+ return {
2549
+ copyValue: t("jsonCopyValue"),
2550
+ copyJson: t("jsonCopyJson"),
2551
+ copyPath: t("jsonCopyPath"),
2552
+ copyPrettyJson: t("jsonCopyPretty"),
2553
+ copyCompactJson: t("jsonCopyCompact"),
2554
+ copied: t("codeCopied"),
2555
+ copyFailed: t("jsonCopyFailed"),
2556
+ collapseNode: t("jsonCollapse"),
2557
+ expandNode: t("jsonExpand"),
2558
+ copyButtonTitle: (action) => action
2559
+ };
2560
+ }
2172
2561
  //#endregion
2173
2562
  //#region \0dsh-css:/home/runner/work/dsh-sidenote/dsh-sidenote/src/client/sidechat/sidechat.module.css.mjs
2174
- const css$1 = "._5KEEsG_root{height:100%;color:var(--dsw-alias-label-primary);flex-direction:column;font-size:13px;display:flex;position:relative}._5KEEsG_body{flex-direction:column;flex:1;padding:12px 10px;display:flex;overflow-y:auto}._5KEEsG_transcript{flex-direction:column;gap:10px;display:flex}._5KEEsG_empty,._5KEEsG_stateScreen{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:24px 16px;display:flex}._5KEEsG_emptyIcon{color:var(--dsw-alias-label-tertiary)}._5KEEsG_emptyTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600}._5KEEsG_emptyText{color:var(--dsw-alias-label-tertiary);max-width:240px;font-size:12px;line-height:1.6}._5KEEsG_stateDetail{color:var(--dsw-alias-state-error-primary);word-break:break-word;max-width:260px;font-size:12px;line-height:1.6}._5KEEsG_userRow{justify-content:flex-end;display:flex}._5KEEsG_userBubble{background:var(--dsw-specific-bubble,#edf3fe);max-width:88%;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:16px;padding:8px 12px;font-size:13px;line-height:1.55}._5KEEsG_assistantRow{justify-content:flex-start;display:flex;position:relative}._5KEEsG_rowActions{opacity:0;z-index:2;transition:opacity .12s ease-out;position:absolute;top:-8px;right:0}._5KEEsG_assistantRow:hover ._5KEEsG_rowActions{opacity:1}._5KEEsG_reflowButton{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-bg-layer-1,#fff);width:22px;height:22px;color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;border-radius:999px;place-items:center;padding:0;display:grid;box-shadow:0 2px 8px #00000014}._5KEEsG_reflowButton:hover{color:var(--dsw-alias-state-business-primary,#2f88ff);border-color:var(--dsw-alias-state-business-primary,#2f88ff)}._5KEEsG_assistantBody{word-break:break-word;max-width:100%;font-size:13px;line-height:1.6}._5KEEsG_reasoning{border-left:3px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);margin-bottom:6px;padding-left:8px;font-size:12px}._5KEEsG_reasoning>summary{cursor:pointer;user-select:none}._5KEEsG_reasoningBody{white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-tertiary);margin-top:2px;padding-bottom:2px;padding-left:22px}._5KEEsG_streamingHint{color:var(--dsw-alias-label-dimmed)}._5KEEsG_toolCard{font-size:12px}._5KEEsG_toolHead{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:12px;display:flex}._5KEEsG_toolError{color:var(--dsw-alias-state-error-primary)}._5KEEsG_toolRunning{color:var(--dsw-alias-label-tertiary)}._5KEEsG_toolBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;max-height:160px;padding:2px 0 2px 22px;font-size:12px;overflow-y:auto}._5KEEsG_errorRow{color:var(--dsw-alias-state-error-primary);white-space:pre-wrap;padding:4px 2px;font-size:12px}._5KEEsG_noticeRow{color:var(--dsw-alias-label-dimmed);text-align:center;padding:2px 0;font-size:12px}._5KEEsG_composer{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:6px;padding:8px 10px;display:flex;position:relative}._5KEEsG_composerCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:14px;flex-direction:column;gap:2px;padding:4px 6px 6px;transition:border-color .12s;display:flex}._5KEEsG_composerCard:focus-within{border-color:var(--dsw-alias-state-business-primary)}._5KEEsG_input{resize:none;width:100%;min-height:36px;max-height:160px;color:var(--dsw-alias-label-primary);font:inherit;box-sizing:border-box;background:0 0;border:none;padding:6px 6px 2px}._5KEEsG_input:focus{outline:none}._5KEEsG_composerFoot{justify-content:space-between;align-items:center;gap:8px;padding:0 2px;display:flex}._5KEEsG_modelLabel{color:var(--dsw-alias-label-dimmed);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._5KEEsG_sendButton,._5KEEsG_stopButton{appearance:none;background:var(--dsw-alias-button-info-fill);color:#fff;cursor:pointer;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;width:28px;height:28px;padding:0;display:inline-flex}._5KEEsG_sendButton:disabled{opacity:.45;cursor:default}._5KEEsG_headerSide{border:1px solid var(--dsw-alias-border-l1,#0000001a);color:var(--dsw-alias-label-secondary,#3f454d);cursor:pointer;user-select:none;background:0 0;border-radius:999px;align-items:center;gap:5px;padding:3px 10px;font-size:12px;line-height:18px;display:inline-flex}._5KEEsG_headerSide:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2329)}._5KEEsG_sentChipRow{align-items:center;gap:6px;margin-right:8px;display:inline-flex}._5KEEsG_sentChip{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-secondary,#3f454d);user-select:none;border-radius:999px;align-items:center;gap:4px;padding:1px 8px;font-size:11px;line-height:16px;display:inline-flex}._5KEEsG_flowRow{font-size:12px}._5KEEsG_rowPreview{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-dimmed);flex:1;margin-left:8px;font-size:12px;overflow:hidden}._5KEEsG_flowRowError{color:var(--dsw-alias-state-error-primary)}._5KEEsG_todoBody{flex-direction:column;gap:3px;padding:2px 0 4px 22px;display:flex}._5KEEsG_todoItem{align-items:baseline;gap:7px;min-width:0;font-size:12px;line-height:1.5;display:flex}._5KEEsG_todoDot{background:var(--dsw-alias-label-dimmed);border-radius:50%;flex:none;width:6px;height:6px;transform:translateY(-1px)}._5KEEsG_todoDot[data-status=completed]{background:var(--dsw-alias-state-success-primary,#34a853)}._5KEEsG_todoDot[data-status=in_progress]{background:var(--dsw-alias-state-business-primary)}._5KEEsG_todoText[data-status=completed]{color:var(--dsw-alias-label-dimmed)}._5KEEsG_toolGenericBody{flex-direction:column;gap:6px;padding:4px 0 4px 22px;display:flex}._5KEEsG_toolBodyText{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.55;font-family:var(--dsw-alias-font-mono,ui-monospace, monospace);margin:0}._5KEEsG_toolLocations{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-all;font-size:11px}._5KEEsG_inheritedBody{border-top:1px dashed var(--dsw-alias-border-l2);opacity:.92;padding:6px 0 2px}._5KEEsG_densityRow{justify-content:flex-end;align-items:center;padding:2px 2px 4px;display:flex}._5KEEsG_densityToggle{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:2px 6px;font-size:11px}._5KEEsG_densityToggle:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_jumpBottom{z-index:5;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:999px;padding:3px 10px;font-size:11px;position:absolute;bottom:96px;right:12px;box-shadow:0 2px 8px #0000001f}._5KEEsG_jumpBottom:hover{background:var(--dsw-alias-bg-layer-2)}._5KEEsG_modelSelect{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:6px;align-items:center;gap:3px;max-width:180px;padding:3px 6px;font-size:12px;display:inline-flex;overflow:hidden}._5KEEsG_modelSelect>span:first-child{text-overflow:ellipsis;overflow:hidden}._5KEEsG_modelSelect>svg{color:var(--dsw-alias-label-dimmed);flex:none}._5KEEsG_modelSelect:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_composerChips{align-items:center;gap:4px;min-width:0;display:inline-flex;overflow:hidden}._5KEEsG_slashMenu{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);z-index:20;border-radius:10px;max-height:260px;margin-bottom:6px;padding:4px;position:absolute;bottom:100%;left:8px;right:8px;overflow-y:auto;box-shadow:0 8px 24px #00000024}._5KEEsG_slashGroupTitle{color:var(--dsw-alias-label-tertiary);text-transform:none;padding:4px 8px 2px;font-size:11px}._5KEEsG_slashItem,._5KEEsG_slashItemActive{text-align:left;cursor:pointer;width:100%;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:6px;align-items:baseline;gap:8px;padding:6px 8px;font-size:13px;display:flex}._5KEEsG_slashItemActive,._5KEEsG_slashItem:hover{background:var(--dsw-alias-interactive-bg-hover)}._5KEEsG_slashItemName{flex:none;font-weight:500}._5KEEsG_slashItemDetail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._5KEEsG_attachRail{flex-wrap:wrap;gap:6px;padding:2px 0;display:flex}._5KEEsG_attachThumb{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;width:44px;height:44px;position:relative;overflow:hidden}._5KEEsG_attachImg{object-fit:cover;width:100%;height:100%;display:block}._5KEEsG_attachRemove{color:#fff;cursor:pointer;background:#0000008c;border:none;border-radius:999px;place-items:center;width:16px;height:16px;padding:0;font-size:11px;line-height:1;display:grid;position:absolute;top:1px;right:1px}._5KEEsG_attachButton{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:4px;display:inline-flex}._5KEEsG_attachButton:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_pendingCard{border:1px solid var(--dsw-alias-border-l2);border-left:3px solid var(--dsw-alias-state-warning-primary,#e8a13a);background:var(--dsw-alias-bg-layer-2);border-radius:10px;flex-direction:column;gap:6px;margin:0 10px 6px;padding:10px 12px;font-size:12px;display:flex}._5KEEsG_pendingHead{color:var(--dsw-alias-state-warning-primary,#e8a13a);align-items:center;gap:6px;display:flex}._5KEEsG_pendingTitle{color:var(--dsw-alias-label-primary);font-weight:600}._5KEEsG_pendingDesc{color:var(--dsw-alias-label-secondary)}._5KEEsG_pendingReason{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word}._5KEEsG_pendingQuestion{flex-direction:column;gap:4px;padding:4px 0;display:flex}._5KEEsG_pendingQHeader{color:var(--dsw-alias-label-primary);font-weight:600}._5KEEsG_pendingQText{color:var(--dsw-alias-label-secondary)}._5KEEsG_pendingOption{text-align:left;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:6px;flex-direction:column;align-items:flex-start;gap:2px;padding:5px 8px;font-size:12px;display:flex}._5KEEsG_pendingOption:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d)}._5KEEsG_pendingOptionActive{text-align:left;border:1px solid var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex-direction:column;align-items:flex-start;gap:2px;padding:5px 8px;font-size:12px;display:flex}._5KEEsG_pendingOptionDesc{color:var(--dsw-alias-label-dimmed);font-size:11px}._5KEEsG_pendingCustom{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);font-size:12px;font:inherit;background:0 0;border-radius:6px;padding:5px 8px}._5KEEsG_pendingCustom:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}._5KEEsG_pendingActions{justify-content:flex-end;gap:8px;margin-top:2px;display:flex}._5KEEsG_pendingBtnGhost{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:6px;padding:4px 12px;font-size:12px}._5KEEsG_pendingBtnPrimary{background:var(--dsw-alias-button-info-fill);color:#fff;cursor:pointer;border:none;border-radius:6px;padding:4px 12px;font-size:12px}._5KEEsG_pendingBtnGhost:disabled,._5KEEsG_pendingBtnPrimary:disabled{opacity:.5;cursor:default}._5KEEsG_pendingBar{border:1px solid var(--dsw-alias-state-warning-primary,#d4a017);background:var(--dsw-alias-bg-layer-2);width:calc(100% - 16px);color:var(--dsw-alias-state-warning-primary,#d4a017);text-align:left;cursor:pointer;border-radius:8px;margin:0 8px 6px;padding:6px 10px;font-size:12px;display:block}._5KEEsG_pendingBar:hover{background:var(--dsw-alias-bg-layer-3)}._5KEEsG_writeNotice{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);border-radius:8px;align-items:center;gap:8px;margin:0 8px 6px;padding:6px 10px;font-size:12px;display:flex}._5KEEsG_writeNoticeClose{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;margin-left:auto;padding:0 4px;font-size:13px}._5KEEsG_parentStrip{border:none;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:left;align-items:center;gap:6px;padding:5px 12px;font-size:11px;display:flex}._5KEEsG_parentStrip:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}._5KEEsG_dotRunning,._5KEEsG_dotPending,._5KEEsG_dotIdle{border-radius:999px;flex:none;width:7px;height:7px}._5KEEsG_dotRunning{background:var(--dsw-alias-state-business-primary,#2f88ff);animation:1.2s ease-in-out infinite _5KEEsG_sidechat-pulse}._5KEEsG_dotPending{background:var(--dsw-alias-state-warning-primary,#d4a017)}._5KEEsG_dotIdle{background:var(--dsw-alias-label-dimmed)}@keyframes _5KEEsG_sidechat-pulse{50%{opacity:.45}}._5KEEsG_actionGroup{gap:2px;margin-right:auto;display:inline-flex}";
2563
+ const css$1 = "._5KEEsG_nativeHost{flex-direction:column;height:100%;min-height:0;display:flex}._5KEEsG_root{height:100%;color:var(--dsw-alias-label-primary);flex-direction:column;font-size:13px;display:flex;position:relative}._5KEEsG_body{flex-direction:column;flex:1;padding:12px 10px;display:flex;overflow-y:auto}._5KEEsG_transcript{flex-direction:column;gap:10px;display:flex}._5KEEsG_empty,._5KEEsG_stateScreen{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:24px 16px;display:flex}._5KEEsG_emptyIcon{color:var(--dsw-alias-label-tertiary)}._5KEEsG_emptyTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600}._5KEEsG_emptyText{color:var(--dsw-alias-label-tertiary);max-width:240px;font-size:12px;line-height:1.6}._5KEEsG_stateDetail{color:var(--dsw-alias-state-error-primary);word-break:break-word;max-width:260px;font-size:12px;line-height:1.6}._5KEEsG_userRow{justify-content:flex-end;display:flex}._5KEEsG_userBubble{background:var(--dsw-specific-bubble,#edf3fe);max-width:88%;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:16px;padding:8px 12px;font-size:13px;line-height:1.55}._5KEEsG_assistantRow{justify-content:flex-start;display:flex;position:relative}._5KEEsG_rowActions{opacity:0;z-index:2;transition:opacity .12s ease-out;position:absolute;top:-8px;right:0}._5KEEsG_assistantRow:hover ._5KEEsG_rowActions{opacity:1}._5KEEsG_reflowButton{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-bg-layer-1,#fff);width:22px;height:22px;color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;border-radius:999px;place-items:center;padding:0;display:grid;box-shadow:0 2px 8px #00000014}._5KEEsG_reflowButton:hover{color:var(--dsw-alias-state-business-primary,#2f88ff);border-color:var(--dsw-alias-state-business-primary,#2f88ff)}._5KEEsG_assistantBody{word-break:break-word;max-width:100%;font-size:13px;line-height:1.6}._5KEEsG_reasoning{border-left:3px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);margin-bottom:6px;padding-left:8px;font-size:12px}._5KEEsG_reasoning>summary{cursor:pointer;user-select:none}._5KEEsG_reasoningBody{white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-tertiary);margin-top:2px;padding-bottom:2px;padding-left:22px}._5KEEsG_streamingHint{color:var(--dsw-alias-label-dimmed)}._5KEEsG_toolCard{font-size:12px}._5KEEsG_toolHead{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:12px;display:flex}._5KEEsG_toolError{color:var(--dsw-alias-state-error-primary)}._5KEEsG_toolRunning{color:var(--dsw-alias-label-tertiary)}._5KEEsG_toolBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;max-height:160px;padding:2px 0 2px 22px;font-size:12px;overflow-y:auto}._5KEEsG_errorRow{color:var(--dsw-alias-state-error-primary);white-space:pre-wrap;padding:4px 2px;font-size:12px}._5KEEsG_noticeRow{color:var(--dsw-alias-label-dimmed);text-align:center;padding:2px 0;font-size:12px}._5KEEsG_composer{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:6px;padding:8px 10px;display:flex;position:relative}._5KEEsG_composerCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:14px;flex-direction:column;gap:2px;padding:4px 6px 6px;transition:border-color .12s;display:flex}._5KEEsG_composerCard:focus-within{border-color:var(--dsw-alias-state-business-primary)}._5KEEsG_input{resize:none;width:100%;min-height:36px;max-height:160px;color:var(--dsw-alias-label-primary);font:inherit;box-sizing:border-box;background:0 0;border:none;padding:6px 6px 2px}._5KEEsG_input:focus{outline:none}._5KEEsG_composerFoot{justify-content:space-between;align-items:center;gap:8px;padding:0 2px;display:flex}._5KEEsG_modelLabel{color:var(--dsw-alias-label-dimmed);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._5KEEsG_sendButton,._5KEEsG_stopButton{appearance:none;background:var(--dsw-alias-button-info-fill);color:#fff;cursor:pointer;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;width:28px;height:28px;padding:0;display:inline-flex}._5KEEsG_sendButton:disabled{opacity:.45;cursor:default}._5KEEsG_headerSide{border:1px solid var(--dsw-alias-border-l1,#0000001a);color:var(--dsw-alias-label-secondary,#3f454d);cursor:pointer;user-select:none;background:0 0;border-radius:999px;align-items:center;gap:5px;padding:3px 10px;font-size:12px;line-height:18px;display:inline-flex}._5KEEsG_headerSide:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2329)}._5KEEsG_sentChipRow{align-items:center;gap:6px;margin-right:8px;display:inline-flex}._5KEEsG_sentChip{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-secondary,#3f454d);user-select:none;border-radius:999px;align-items:center;gap:4px;padding:1px 8px;font-size:11px;line-height:16px;display:inline-flex}._5KEEsG_flowRow{font-size:12px}._5KEEsG_rowPreview{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-dimmed);flex:1;margin-left:8px;font-size:12px;overflow:hidden}._5KEEsG_flowRowError{color:var(--dsw-alias-state-error-primary)}._5KEEsG_todoBody{flex-direction:column;gap:3px;padding:2px 0 4px 22px;display:flex}._5KEEsG_todoItem{align-items:baseline;gap:7px;min-width:0;font-size:12px;line-height:1.5;display:flex}._5KEEsG_todoDot{background:var(--dsw-alias-label-dimmed);border-radius:50%;flex:none;width:6px;height:6px;transform:translateY(-1px)}._5KEEsG_todoDot[data-status=completed]{background:var(--dsw-alias-state-success-primary,#34a853)}._5KEEsG_todoDot[data-status=in_progress]{background:var(--dsw-alias-state-business-primary)}._5KEEsG_todoText[data-status=completed]{color:var(--dsw-alias-label-dimmed)}._5KEEsG_toolGenericBody{flex-direction:column;gap:6px;padding:4px 0 4px 22px;display:flex}._5KEEsG_toolBodyText{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.55;font-family:var(--dsw-alias-font-mono,ui-monospace, monospace);margin:0}._5KEEsG_toolLocations{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-all;font-size:11px}._5KEEsG_inheritedBody{border-top:1px dashed var(--dsw-alias-border-l2);opacity:.92;padding:6px 0 2px}._5KEEsG_densityRow{justify-content:flex-end;align-items:center;padding:2px 2px 4px;display:flex}._5KEEsG_densityToggle{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:2px 6px;font-size:11px}._5KEEsG_densityToggle:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_jumpBottom{z-index:5;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:999px;padding:3px 10px;font-size:11px;position:absolute;bottom:96px;right:12px;box-shadow:0 2px 8px #0000001f}._5KEEsG_jumpBottom:hover{background:var(--dsw-alias-bg-layer-2)}._5KEEsG_modelSelect{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:6px;align-items:center;gap:3px;max-width:180px;padding:3px 6px;font-size:12px;display:inline-flex;overflow:hidden}._5KEEsG_modelSelect>span:first-child{text-overflow:ellipsis;overflow:hidden}._5KEEsG_modelSelect>svg{color:var(--dsw-alias-label-dimmed);flex:none}._5KEEsG_modelSelect:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_composerChips{align-items:center;gap:4px;min-width:0;display:inline-flex;overflow:hidden}._5KEEsG_slashMenu{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);z-index:20;border-radius:10px;max-height:260px;margin-bottom:6px;padding:4px;position:absolute;bottom:100%;left:8px;right:8px;overflow-y:auto;box-shadow:0 8px 24px #00000024}._5KEEsG_slashGroupTitle{color:var(--dsw-alias-label-tertiary);text-transform:none;padding:4px 8px 2px;font-size:11px}._5KEEsG_slashItem,._5KEEsG_slashItemActive{text-align:left;cursor:pointer;width:100%;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:6px;align-items:baseline;gap:8px;padding:6px 8px;font-size:13px;display:flex}._5KEEsG_slashItemActive,._5KEEsG_slashItem:hover{background:var(--dsw-alias-interactive-bg-hover)}._5KEEsG_slashItemName{flex:none;font-weight:500}._5KEEsG_slashItemDetail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._5KEEsG_attachRail{flex-wrap:wrap;gap:6px;padding:2px 0;display:flex}._5KEEsG_attachThumb{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;width:44px;height:44px;position:relative;overflow:hidden}._5KEEsG_attachImg{object-fit:cover;width:100%;height:100%;display:block}._5KEEsG_attachRemove{color:#fff;cursor:pointer;background:#0000008c;border:none;border-radius:999px;place-items:center;width:16px;height:16px;padding:0;font-size:11px;line-height:1;display:grid;position:absolute;top:1px;right:1px}._5KEEsG_attachButton{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:4px;display:inline-flex}._5KEEsG_attachButton:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_pendingCard{border:1px solid var(--dsw-alias-border-l2);border-left:3px solid var(--dsw-alias-state-warning-primary,#e8a13a);background:var(--dsw-alias-bg-layer-2);border-radius:10px;flex-direction:column;gap:6px;margin:0 10px 6px;padding:10px 12px;font-size:12px;display:flex}._5KEEsG_pendingHead{color:var(--dsw-alias-state-warning-primary,#e8a13a);align-items:center;gap:6px;display:flex}._5KEEsG_pendingTitle{color:var(--dsw-alias-label-primary);font-weight:600}._5KEEsG_pendingDesc{color:var(--dsw-alias-label-secondary)}._5KEEsG_pendingReason{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word}._5KEEsG_pendingQuestion{flex-direction:column;gap:4px;padding:4px 0;display:flex}._5KEEsG_pendingQHeader{color:var(--dsw-alias-label-primary);font-weight:600}._5KEEsG_pendingQText{color:var(--dsw-alias-label-secondary)}._5KEEsG_pendingOption{text-align:left;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:6px;flex-direction:column;align-items:flex-start;gap:2px;padding:5px 8px;font-size:12px;display:flex}._5KEEsG_pendingOption:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d)}._5KEEsG_pendingOptionActive{text-align:left;border:1px solid var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex-direction:column;align-items:flex-start;gap:2px;padding:5px 8px;font-size:12px;display:flex}._5KEEsG_pendingOptionDesc{color:var(--dsw-alias-label-dimmed);font-size:11px}._5KEEsG_pendingCustom{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);font-size:12px;font:inherit;background:0 0;border-radius:6px;padding:5px 8px}._5KEEsG_pendingCustom:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}._5KEEsG_pendingActions{justify-content:flex-end;gap:8px;margin-top:2px;display:flex}._5KEEsG_pendingBtnGhost{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:6px;padding:4px 12px;font-size:12px}._5KEEsG_pendingBtnPrimary{background:var(--dsw-alias-button-info-fill);color:#fff;cursor:pointer;border:none;border-radius:6px;padding:4px 12px;font-size:12px}._5KEEsG_pendingBtnGhost:disabled,._5KEEsG_pendingBtnPrimary:disabled{opacity:.5;cursor:default}._5KEEsG_pendingBar{border:1px solid var(--dsw-alias-state-warning-primary,#d4a017);background:var(--dsw-alias-bg-layer-2);width:calc(100% - 16px);color:var(--dsw-alias-state-warning-primary,#d4a017);text-align:left;cursor:pointer;border-radius:8px;margin:0 8px 6px;padding:6px 10px;font-size:12px;display:block}._5KEEsG_pendingBar:hover{background:var(--dsw-alias-bg-layer-3)}._5KEEsG_writeNotice{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);border-radius:8px;align-items:center;gap:8px;margin:0 8px 6px;padding:6px 10px;font-size:12px;display:flex}._5KEEsG_writeNoticeClose{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;margin-left:auto;padding:0 4px;font-size:13px}._5KEEsG_parentStrip{border:none;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:left;align-items:center;gap:6px;padding:5px 12px;font-size:11px;display:flex}._5KEEsG_parentStrip:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}._5KEEsG_dotRunning,._5KEEsG_dotPending,._5KEEsG_dotIdle{border-radius:999px;flex:none;width:7px;height:7px}._5KEEsG_dotRunning{background:var(--dsw-alias-state-business-primary,#2f88ff);animation:1.2s ease-in-out infinite _5KEEsG_sidechat-pulse}._5KEEsG_dotPending{background:var(--dsw-alias-state-warning-primary,#d4a017)}._5KEEsG_dotIdle{background:var(--dsw-alias-label-dimmed)}@keyframes _5KEEsG_sidechat-pulse{50%{opacity:.45}}._5KEEsG_actionGroup{gap:2px;margin-right:auto;display:inline-flex}";
2175
2564
  const tagId$1 = "dsh-sidenote/sidechat.module.css";
2176
2565
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
2177
2566
  const tag = document.createElement("style");
@@ -2181,92 +2570,93 @@ window.__ModuleLoader__.load({
2181
2570
  document.head.appendChild(tag);
2182
2571
  }
2183
2572
  var sidechat_module_css_default = {
2184
- "pendingBar": "_5KEEsG_pendingBar",
2573
+ "slashItemName": "_5KEEsG_slashItemName",
2574
+ "slashGroupTitle": "_5KEEsG_slashGroupTitle",
2575
+ "toolCard": "_5KEEsG_toolCard",
2576
+ "pendingOptionDesc": "_5KEEsG_pendingOptionDesc",
2185
2577
  "composerCard": "_5KEEsG_composerCard",
2186
- "attachRail": "_5KEEsG_attachRail",
2187
- "reasoningBody": "_5KEEsG_reasoningBody",
2188
2578
  "empty": "_5KEEsG_empty",
2189
- "dotRunning": "_5KEEsG_dotRunning",
2190
- "toolBody": "_5KEEsG_toolBody",
2191
- "attachImg": "_5KEEsG_attachImg",
2192
- "slashItem": "_5KEEsG_slashItem",
2193
- "stateDetail": "_5KEEsG_stateDetail",
2194
- "sentChipRow": "_5KEEsG_sentChipRow",
2195
- "userBubble": "_5KEEsG_userBubble",
2579
+ "pendingBtnPrimary": "_5KEEsG_pendingBtnPrimary",
2580
+ "pendingCustom": "_5KEEsG_pendingCustom",
2581
+ "userRow": "_5KEEsG_userRow",
2582
+ "assistantRow": "_5KEEsG_assistantRow",
2583
+ "pendingBtnGhost": "_5KEEsG_pendingBtnGhost",
2584
+ "pendingHead": "_5KEEsG_pendingHead",
2585
+ "attachRail": "_5KEEsG_attachRail",
2586
+ "toolHead": "_5KEEsG_toolHead",
2587
+ "toolError": "_5KEEsG_toolError",
2588
+ "body": "_5KEEsG_body",
2589
+ "rowActions": "_5KEEsG_rowActions",
2196
2590
  "rowPreview": "_5KEEsG_rowPreview",
2591
+ "flowRowError": "_5KEEsG_flowRowError",
2592
+ "attachRemove": "_5KEEsG_attachRemove",
2593
+ "pendingDesc": "_5KEEsG_pendingDesc",
2594
+ "pendingActions": "_5KEEsG_pendingActions",
2595
+ "pendingQHeader": "_5KEEsG_pendingQHeader",
2596
+ "parentStrip": "_5KEEsG_parentStrip",
2597
+ "pendingTitle": "_5KEEsG_pendingTitle",
2598
+ "toolBodyText": "_5KEEsG_toolBodyText",
2599
+ "errorRow": "_5KEEsG_errorRow",
2600
+ "todoItem": "_5KEEsG_todoItem",
2601
+ "userBubble": "_5KEEsG_userBubble",
2197
2602
  "reflowButton": "_5KEEsG_reflowButton",
2198
- "toolLocations": "_5KEEsG_toolLocations",
2603
+ "toolBody": "_5KEEsG_toolBody",
2604
+ "attachButton": "_5KEEsG_attachButton",
2605
+ "toolRunning": "_5KEEsG_toolRunning",
2199
2606
  "toolGenericBody": "_5KEEsG_toolGenericBody",
2200
- "jumpBottom": "_5KEEsG_jumpBottom",
2201
- "streamingHint": "_5KEEsG_streamingHint",
2202
- "pendingTitle": "_5KEEsG_pendingTitle",
2607
+ "emptyIcon": "_5KEEsG_emptyIcon",
2608
+ "attachThumb": "_5KEEsG_attachThumb",
2609
+ "assistantBody": "_5KEEsG_assistantBody",
2610
+ "pendingReason": "_5KEEsG_pendingReason",
2611
+ "pendingCard": "_5KEEsG_pendingCard",
2612
+ "slashItem": "_5KEEsG_slashItem",
2613
+ "dotPending": "_5KEEsG_dotPending",
2614
+ "toolLocations": "_5KEEsG_toolLocations",
2615
+ "todoDot": "_5KEEsG_todoDot",
2616
+ "pendingBar": "_5KEEsG_pendingBar",
2617
+ "dotIdle": "_5KEEsG_dotIdle",
2203
2618
  "inheritedBody": "_5KEEsG_inheritedBody",
2204
- "pendingQHeader": "_5KEEsG_pendingQHeader",
2619
+ "slashItemActive": "_5KEEsG_slashItemActive",
2205
2620
  "pendingOption": "_5KEEsG_pendingOption",
2206
2621
  "pendingOptionActive": "_5KEEsG_pendingOptionActive",
2207
- "rowActions": "_5KEEsG_rowActions",
2208
- "stateScreen": "_5KEEsG_stateScreen",
2209
- "pendingActions": "_5KEEsG_pendingActions",
2210
- "pendingBtnGhost": "_5KEEsG_pendingBtnGhost",
2211
- "actionGroup": "_5KEEsG_actionGroup",
2212
- "toolHead": "_5KEEsG_toolHead",
2213
- "toolBodyText": "_5KEEsG_toolBodyText",
2214
- "sidechat-pulse": "_5KEEsG_sidechat-pulse",
2215
- "assistantBody": "_5KEEsG_assistantBody",
2216
- "todoDot": "_5KEEsG_todoDot",
2217
2622
  "todoBody": "_5KEEsG_todoBody",
2218
- "toolError": "_5KEEsG_toolError",
2219
- "reasoning": "_5KEEsG_reasoning",
2220
- "attachThumb": "_5KEEsG_attachThumb",
2221
- "attachButton": "_5KEEsG_attachButton",
2222
- "densityToggle": "_5KEEsG_densityToggle",
2223
- "composerFoot": "_5KEEsG_composerFoot",
2224
- "stopButton": "_5KEEsG_stopButton",
2225
- "slashItemActive": "_5KEEsG_slashItemActive",
2226
- "slashMenu": "_5KEEsG_slashMenu",
2227
- "writeNotice": "_5KEEsG_writeNotice",
2623
+ "attachImg": "_5KEEsG_attachImg",
2624
+ "stateScreen": "_5KEEsG_stateScreen",
2625
+ "emptyText": "_5KEEsG_emptyText",
2228
2626
  "input": "_5KEEsG_input",
2627
+ "reasoningBody": "_5KEEsG_reasoningBody",
2628
+ "modelLabel": "_5KEEsG_modelLabel",
2229
2629
  "densityRow": "_5KEEsG_densityRow",
2230
- "composerChips": "_5KEEsG_composerChips",
2231
- "sentChip": "_5KEEsG_sentChip",
2232
- "pendingQuestion": "_5KEEsG_pendingQuestion",
2233
- "todoText": "_5KEEsG_todoText",
2234
- "pendingReason": "_5KEEsG_pendingReason",
2235
- "pendingBtnPrimary": "_5KEEsG_pendingBtnPrimary",
2236
- "dotIdle": "_5KEEsG_dotIdle",
2237
- "assistantRow": "_5KEEsG_assistantRow",
2238
- "flowRow": "_5KEEsG_flowRow",
2239
- "userRow": "_5KEEsG_userRow",
2240
- "toolCard": "_5KEEsG_toolCard",
2241
- "errorRow": "_5KEEsG_errorRow",
2630
+ "densityToggle": "_5KEEsG_densityToggle",
2631
+ "transcript": "_5KEEsG_transcript",
2242
2632
  "noticeRow": "_5KEEsG_noticeRow",
2633
+ "slashMenu": "_5KEEsG_slashMenu",
2634
+ "pendingQText": "_5KEEsG_pendingQText",
2243
2635
  "slashItemDetail": "_5KEEsG_slashItemDetail",
2244
- "transcript": "_5KEEsG_transcript",
2245
- "toolRunning": "_5KEEsG_toolRunning",
2246
- "emptyIcon": "_5KEEsG_emptyIcon",
2247
- "pendingCustom": "_5KEEsG_pendingCustom",
2248
- "headerSide": "_5KEEsG_headerSide",
2249
- "parentStrip": "_5KEEsG_parentStrip",
2636
+ "dotRunning": "_5KEEsG_dotRunning",
2637
+ "sidechat-pulse": "_5KEEsG_sidechat-pulse",
2638
+ "composer": "_5KEEsG_composer",
2639
+ "sentChip": "_5KEEsG_sentChip",
2640
+ "reasoning": "_5KEEsG_reasoning",
2250
2641
  "modelSelect": "_5KEEsG_modelSelect",
2251
- "pendingHead": "_5KEEsG_pendingHead",
2252
- "root": "_5KEEsG_root",
2253
- "flowRowError": "_5KEEsG_flowRowError",
2642
+ "stateDetail": "_5KEEsG_stateDetail",
2643
+ "stopButton": "_5KEEsG_stopButton",
2644
+ "nativeHost": "_5KEEsG_nativeHost",
2645
+ "writeNotice": "_5KEEsG_writeNotice",
2254
2646
  "writeNoticeClose": "_5KEEsG_writeNoticeClose",
2255
- "slashGroupTitle": "_5KEEsG_slashGroupTitle",
2256
- "sendButton": "_5KEEsG_sendButton",
2257
- "slashItemName": "_5KEEsG_slashItemName",
2258
- "body": "_5KEEsG_body",
2259
- "emptyText": "_5KEEsG_emptyText",
2260
- "composer": "_5KEEsG_composer",
2647
+ "headerSide": "_5KEEsG_headerSide",
2648
+ "flowRow": "_5KEEsG_flowRow",
2649
+ "actionGroup": "_5KEEsG_actionGroup",
2650
+ "todoText": "_5KEEsG_todoText",
2651
+ "root": "_5KEEsG_root",
2261
2652
  "emptyTitle": "_5KEEsG_emptyTitle",
2262
- "attachRemove": "_5KEEsG_attachRemove",
2263
- "dotPending": "_5KEEsG_dotPending",
2264
- "todoItem": "_5KEEsG_todoItem",
2265
- "pendingQText": "_5KEEsG_pendingQText",
2266
- "pendingOptionDesc": "_5KEEsG_pendingOptionDesc",
2267
- "pendingDesc": "_5KEEsG_pendingDesc",
2268
- "modelLabel": "_5KEEsG_modelLabel",
2269
- "pendingCard": "_5KEEsG_pendingCard"
2653
+ "streamingHint": "_5KEEsG_streamingHint",
2654
+ "composerFoot": "_5KEEsG_composerFoot",
2655
+ "sentChipRow": "_5KEEsG_sentChipRow",
2656
+ "jumpBottom": "_5KEEsG_jumpBottom",
2657
+ "pendingQuestion": "_5KEEsG_pendingQuestion",
2658
+ "composerChips": "_5KEEsG_composerChips",
2659
+ "sendButton": "_5KEEsG_sendButton"
2270
2660
  };
2271
2661
  //#endregion
2272
2662
  //#region src/client/chat/ToolCard.tsx
@@ -2403,7 +2793,11 @@ window.__ModuleLoader__.load({
2403
2793
  m.rawInput !== void 0 && (typeof m.rawInput === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
2404
2794
  className: sidechat_module_css_default.toolBodyText,
2405
2795
  children: m.rawInput
2406
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonTree, { data: m.rawInput })),
2796
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonTree, {
2797
+ label: t("jsonTreeLabel"),
2798
+ labels: jsonTreeLabels(),
2799
+ data: m.rawInput
2800
+ })),
2407
2801
  m.locations !== void 0 && m.locations.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2408
2802
  className: sidechat_module_css_default.toolLocations,
2409
2803
  children: m.locations.map((l) => `${l.path}${l.line !== void 0 ? `:${l.line}` : ""}`).join("\n")
@@ -3647,6 +4041,11 @@ window.__ModuleLoader__.load({
3647
4041
  const childId = meta.childId;
3648
4042
  const [forkError, setForkError] = (0, react.useState)(null);
3649
4043
  const forkStarted = (0, react.useRef)(false);
4044
+ const prevChildId = (0, react.useRef)(childId);
4045
+ (0, react.useEffect)(() => {
4046
+ if (prevChildId.current !== void 0 && childId === void 0) forkStarted.current = false;
4047
+ prevChildId.current = childId;
4048
+ }, [childId]);
3650
4049
  const { store } = props;
3651
4050
  (0, react.useEffect)(() => {
3652
4051
  ensurePanelOpen(store);
@@ -3719,7 +4118,7 @@ window.__ModuleLoader__.load({
3719
4118
  (0, react.useEffect)(() => {
3720
4119
  if (pendingDraft === void 0 || pendingDraft === "" || phase !== "chat") return;
3721
4120
  composer.appendDraft(pendingDraft);
3722
- updateTabMeta(ctx, tab.id, clearPendingDraft);
4121
+ updateTabMeta(ctx, scope.sessionId, tab.id, clearPendingDraft);
3723
4122
  requestAnimationFrame(() => {
3724
4123
  rootRef.current?.querySelector("textarea")?.focus();
3725
4124
  });
@@ -3739,10 +4138,10 @@ window.__ModuleLoader__.load({
3739
4138
  titleRef.current = tab.title;
3740
4139
  (0, react.useEffect)(() => {
3741
4140
  const metaNow = parseSideChatMeta(metaRef.current);
3742
- if (nativeSidebarHost(ctx) && metaNow.childId !== void 0) dropClosedSideChat(scope.sessionId, metaNow.childId);
4141
+ if (directNativeLeg(ctx) && metaNow.childId !== void 0) dropClosedSideChat(scope.sessionId, metaNow.childId);
3743
4142
  const dispose = registerLiveSideChat(scope.sessionId, tab.id, (text) => {
3744
4143
  if (phaseRef.current !== "chat") {
3745
- updateTabMeta(ctx, tab.id, (m) => ({
4144
+ updateTabMeta(ctx, scope.sessionId, tab.id, (m) => ({
3746
4145
  ...m,
3747
4146
  pendingDraft: appendDraftText(m.pendingDraft ?? "", text)
3748
4147
  }));
@@ -3755,7 +4154,7 @@ window.__ModuleLoader__.load({
3755
4154
  }, () => metaRef.current, () => titleRef.current);
3756
4155
  return () => {
3757
4156
  dispose();
3758
- if (nativeSidebarHost(ctx)) {
4157
+ if (directNativeLeg(ctx)) {
3759
4158
  const m = parseSideChatMeta(metaRef.current);
3760
4159
  if (m.childId !== void 0 && m.parentSessionId !== void 0) recordClosedSideChat(m.parentSessionId, {
3761
4160
  childId: m.childId,
@@ -3779,7 +4178,7 @@ window.__ModuleLoader__.load({
3779
4178
  increaseTitle: true
3780
4179
  });
3781
4180
  ctx.sessions.open(promoted);
3782
- ctx.betterSidebar.closeTab(tab.id, { sessionId: scope.sessionId });
4181
+ closeSideTab(ctx, tab.id, scope.sessionId);
3783
4182
  showToast(t("promoteDone"));
3784
4183
  } catch (error) {
3785
4184
  console.warn("[dsh-sidenote] 保存为正式会话失败:", error);
@@ -3940,6 +4339,216 @@ window.__ModuleLoader__.load({
3940
4339
  });
3941
4340
  }
3942
4341
  //#endregion
4342
+ //#region src/client/sidechat/native-tab.tsx
4343
+ /**
4344
+ * 直连注册层(Workitem_02):侧聊 tab 类型直连 DSH 原生右栏(≥ 0.1.5-rc.1)。
4345
+ *
4346
+ * better-sidebar 0.19+ 把右栏让渡给原生 sidebarRight 后只剩纯转发——本模块
4347
+ * 绕开转发层,把侧聊类型直接注册进原生面:
4348
+ *
4349
+ * - 类型注册必须**等服务**(ctx.inject(['sidebarRightTabs']))而非等槽声明:
4350
+ * 原生栏先声明 sidebar.right.pane.tab 槽、约 3 秒后才 provide 注册表服务,
4351
+ * 等槽触发会永久静默不注册(better-sidebar native/index.ts 的真机教训)。
4352
+ * - 面板体挂 sidebar.right.pane.tab keyed 槽(key = 注册实现 id);
4353
+ * 芯片活标题挂 sidebar.right.pane.tab.title 槽(编号读 metaStore)。
4354
+ * - meta 无原生面(原生布局 memory-only):全部走自有 metaStore;
4355
+ * openTab → 面板挂载的窗口期内,初始值(parentSessionId/pendingDraft/恢复
4356
+ * 用的 childId)经 openTab params 随宿主管道投递,面板挂载时落进 store。
4357
+ *
4358
+ * 单实例语义(Delivery_01):held 规则下同 pane 同 kind 去重,「新建」由
4359
+ * 编排层折叠为聚焦既有(open.ts),注册层不感知;多实例等 Delivery_02
4360
+ * 的 multiple(0.1.6+)放开。
4361
+ */
4362
+ /** 原生注册表里的实现 id(槽 key;kind 沿用 SIDE_TAB_TYPE 保持自文档化)。 */
4363
+ const NATIVE_IMPL_ID = "dsh-sidenote:side-chat";
4364
+ function parseOpenParams(params) {
4365
+ if (typeof params !== "object" || params === null) return void 0;
4366
+ const p = params;
4367
+ if (typeof p.parentSessionId !== "string" || p.parentSessionId === "") return void 0;
4368
+ return {
4369
+ parentSessionId: p.parentSessionId,
4370
+ ...typeof p.pendingDraft === "string" && p.pendingDraft !== "" ? { pendingDraft: p.pendingDraft } : {},
4371
+ ...typeof p.childId === "string" && p.childId !== "" ? { childId: p.childId } : {}
4372
+ };
4373
+ }
4374
+ /**
4375
+ * 渲染期把 params 的初始值幂等落进 metaStore(静默写,不 notify——渲染期
4376
+ * notify 会让订阅者(title 槽)触发 React「渲染中更新」警告;由调用方在
4377
+ * effect 里补 notify)。必须在渲染期完成:子组件 SideChatPanel 的 fork
4378
+ * effect 先于本组件的 effect 执行(React effect 自底向上),reopen 场景
4379
+ * 若在 effect 里登记,子面板会先按「无 childId」错误地重新 fork 出孤儿会话。
4380
+ * @returns true = 本次新建了记录(调用方 effect 里补 notify)。
4381
+ */
4382
+ function reconcileMeta(record, sessionId) {
4383
+ const existing = readSideChatMeta(sessionId, record.id);
4384
+ if (existing !== void 0) return {
4385
+ created: false,
4386
+ meta: existing
4387
+ };
4388
+ const params = parseOpenParams(record.navigation.params);
4389
+ const meta = {
4390
+ tabId: record.id,
4391
+ sessionId,
4392
+ ...params?.childId !== void 0 ? { childId: params.childId } : {},
4393
+ parentSessionId: params?.parentSessionId ?? sessionId,
4394
+ ...params?.pendingDraft !== void 0 ? { pendingDraft: params.pendingDraft } : {},
4395
+ number: nextSideChatNumber(sessionId),
4396
+ createdAt: Date.now(),
4397
+ runId: SIDENOTE_RUN_ID
4398
+ };
4399
+ writeSideChatMeta(meta, { silent: true });
4400
+ return {
4401
+ created: true,
4402
+ meta
4403
+ };
4404
+ }
4405
+ /** 芯片标题:metaStore 的编号兑现(「侧边」/「侧边 N」)。 */
4406
+ function titleOf(sessionId, tabId) {
4407
+ const meta = readSideChatMeta(sessionId, tabId);
4408
+ if (meta === void 0 || meta.number <= 1) return t("tabBaseTitle");
4409
+ return `${t("tabBaseTitle")} ${meta.number}`;
4410
+ }
4411
+ /** 面板体外壳:把 useTabInfo(可能对未提交 record throw)也包进错误边界。 */
4412
+ function NativeSideChatBody(props) {
4413
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NativeTabBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NativeSideChatBodyInner, { ...props }) });
4414
+ }
4415
+ /** 面板体:原生 tab → SideChatPanel 的 TabComponentProps 合成适配。 */
4416
+ function NativeSideChatBodyInner(props) {
4417
+ const { ctx, sessionId, reflow, useTabInfo } = props;
4418
+ const nativeTab = useTabInfo().tab;
4419
+ const { created, meta: reconciledMeta } = reconcileMeta(nativeTab, sessionId);
4420
+ const createdRef = (0, react.useRef)(false);
4421
+ if (created) createdRef.current = true;
4422
+ (0, react.useEffect)(() => {
4423
+ if (createdRef.current) {
4424
+ createdRef.current = false;
4425
+ notifySideChatMetaListeners();
4426
+ }
4427
+ }, []);
4428
+ const cwd = useSessionCwd(ctx, sessionId);
4429
+ const scope = (0, react.useMemo)(() => ({
4430
+ sessionId,
4431
+ ...cwd !== void 0 ? { cwd } : {}
4432
+ }), [sessionId, cwd]);
4433
+ (0, react.useSyncExternalStore)(sideChatMetaStore.subscribe, sideChatMetaStore.getSnapshot);
4434
+ const meta = readSideChatMeta(sessionId, nativeTab.id) ?? reconciledMeta;
4435
+ const tabSignal = nativeTab.signal;
4436
+ (0, react.useEffect)(() => {
4437
+ const onAbort = () => {
4438
+ dropSideChatMeta(sessionId, nativeTab.id);
4439
+ };
4440
+ if (tabSignal.aborted) {
4441
+ onAbort();
4442
+ return;
4443
+ }
4444
+ tabSignal.addEventListener("abort", onAbort, { once: true });
4445
+ return () => {
4446
+ tabSignal.removeEventListener("abort", onAbort);
4447
+ };
4448
+ }, [
4449
+ nativeTab.id,
4450
+ tabSignal,
4451
+ sessionId
4452
+ ]);
4453
+ const tab = {
4454
+ id: nativeTab.id,
4455
+ type: SIDE_TAB_TYPE,
4456
+ title: titleOf(sessionId, nativeTab.id),
4457
+ ...meta === void 0 ? {} : { meta }
4458
+ };
4459
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4460
+ className: sidechat_module_css_default.nativeHost,
4461
+ "data-dsh-sidenote-native-host": "",
4462
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SideChatPanel, {
4463
+ ctx,
4464
+ store: void 0,
4465
+ scope,
4466
+ tab,
4467
+ visible: nativeTab.visible,
4468
+ reflow
4469
+ })
4470
+ });
4471
+ }
4472
+ /** 极简错误边界:渲染期 throw 兜成错误态,不逃到原生 seat(直连后不再有
4473
+ * better-sidebar 的 RenderBoundary 包裹)。 */
4474
+ var NativeTabBoundary = class extends react.Component {
4475
+ state = { error: null };
4476
+ static getDerivedStateFromError(error) {
4477
+ return { error: error instanceof Error ? error.message : String(error) };
4478
+ }
4479
+ componentDidCatch(error) {
4480
+ console.warn("[dsh-sidenote] 侧聊面板渲染崩溃:", error);
4481
+ }
4482
+ render() {
4483
+ if (this.state.error !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4484
+ style: {
4485
+ padding: 16,
4486
+ fontSize: 13
4487
+ },
4488
+ children: [t("sideChatCrashed"), this.state.error]
4489
+ });
4490
+ return this.props.children;
4491
+ }
4492
+ };
4493
+ /** 芯片活标题:metaStore 订阅 + 编号标题。 */
4494
+ function NativeSideChatTitle(props) {
4495
+ const { useTabInfo } = props;
4496
+ const info = useTabInfo();
4497
+ (0, react.useSyncExternalStore)(sideChatMetaStore.subscribe, sideChatMetaStore.getSnapshot);
4498
+ return titleOf(props.sessionId, info.tab.id);
4499
+ }
4500
+ /** 会话工作区根(better-sidebar useSessionCwd 同款)。 */
4501
+ function useSessionCwd(ctx, sessionId) {
4502
+ return (0, react.useSyncExternalStore)((0, react.useMemo)(() => (listener) => ctx.sessions.list.subscribe(listener), [ctx]), () => {
4503
+ return (ctx.sessions.list.getSnapshot().byId?.[sessionId])?.cwd;
4504
+ });
4505
+ }
4506
+ /**
4507
+ * 直连注册:类型 + body/title 槽。返回 disposer(插件 fiber 撤销时清场)。
4508
+ * 探测与分腿在调用方(sidechat/index.tsx);本函数只在 sidebarRightTabs
4509
+ * 存在时被调用。
4510
+ */
4511
+ function registerNativeSideChatTab(ctx, reflow) {
4512
+ ctx.inject(["sidebarRightTabs"], (injected) => {
4513
+ const tabs = injected.get("sidebarRightTabs");
4514
+ if (tabs === void 0) return;
4515
+ const disposeType = tabs.register({
4516
+ id: NATIVE_IMPL_ID,
4517
+ kind: SIDE_TAB_TYPE,
4518
+ priority: "extension",
4519
+ title: () => t("tabBaseTitle"),
4520
+ guide: [{
4521
+ order: 60,
4522
+ title: () => t("menuTitle"),
4523
+ icon: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutline16, { size: props.size ?? 16 })
4524
+ }]
4525
+ });
4526
+ const disposeBody = ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
4527
+ name: "sidebar.right.pane.tab",
4528
+ key: NATIVE_IMPL_ID,
4529
+ inject: (sessionId) => ({
4530
+ ctx,
4531
+ sessionId,
4532
+ reflow
4533
+ })
4534
+ }, NativeSideChatBody));
4535
+ const disposeTitle = ctx.slots.inject("sidebar.right.pane.tab.title", () => ctx.slots.register({
4536
+ name: "sidebar.right.pane.tab.title",
4537
+ key: NATIVE_IMPL_ID,
4538
+ inject: (sessionId) => ({
4539
+ ctx,
4540
+ sessionId,
4541
+ reflow
4542
+ })
4543
+ }, NativeSideChatTitle));
4544
+ return () => {
4545
+ disposeBody();
4546
+ disposeTitle();
4547
+ disposeType();
4548
+ };
4549
+ });
4550
+ }
4551
+ //#endregion
3943
4552
  //#region src/client/sidechat/header.tsx
3944
4553
  /**
3945
4554
  * 顶栏「侧边」入口(Delivery_02 Workitem_02 发现性):注册进宿主的
@@ -3996,21 +4605,32 @@ window.__ModuleLoader__.load({
3996
4605
  kind: "popupSelect",
3997
4606
  options: (session) => {
3998
4607
  const options = [];
3999
- const native = nativeSidebarHost(ctx);
4608
+ const native = directNativeLeg(ctx);
4000
4609
  const lives = native ? liveSideChatsOf(session.sessionId) : [];
4610
+ const liveIds = new Set(lives.map((live) => live.tabId));
4611
+ const unloaded = native ? sideChatMetasOf(session.sessionId).filter((meta) => !liveIds.has(meta.tabId)) : [];
4001
4612
  if (!native || lives.length === 0) options.push({
4002
4613
  id: "new",
4003
4614
  label: t("cmdNew"),
4004
4615
  detail: t("cmdNewDetail")
4005
4616
  });
4006
- if (native) for (const live of lives) options.push({
4007
- id: `focus:${live.tabId}`,
4008
- label: t("cmdFocus", { title: live.readTitle() }),
4009
- detail: t("cmdFocusDetail")
4010
- });
4011
- else {
4012
- const snapshot = ctx.betterSidebar.getSnapshot();
4013
- if (snapshot.sessionId === session.sessionId && snapshot.state !== void 0) for (const tab of collectSideTabs(snapshot.state)) options.push({
4617
+ if (native) {
4618
+ for (const live of lives) options.push({
4619
+ id: `focus:${live.tabId}`,
4620
+ label: t("cmdFocus", { title: live.readTitle() }),
4621
+ detail: t("cmdFocusDetail")
4622
+ });
4623
+ for (const meta of unloaded) {
4624
+ const title = meta.number <= 1 ? t("tabBaseTitle") : `${t("tabBaseTitle")} ${meta.number}`;
4625
+ options.push({
4626
+ id: `focus:${meta.tabId}`,
4627
+ label: t("cmdFocus", { title }),
4628
+ detail: t("cmdFocusDetail")
4629
+ });
4630
+ }
4631
+ } else {
4632
+ const snapshot = betterSidebarOf(ctx)?.getSnapshot();
4633
+ if (snapshot !== void 0 && snapshot.sessionId === session.sessionId && snapshot.state !== void 0) for (const tab of collectSideTabs(snapshot.state)) options.push({
4014
4634
  id: `focus:${tab.id}`,
4015
4635
  label: t("cmdFocus", { title: tab.title }),
4016
4636
  detail: t("cmdFocusDetail")
@@ -4030,7 +4650,7 @@ window.__ModuleLoader__.load({
4030
4650
  }
4031
4651
  if (option.id.startsWith("focus:")) {
4032
4652
  const tabId = option.id.slice(6);
4033
- if (!focusNativeTab(ctx, tabId)) ctx.betterSidebar.activateTab(tabId, { sessionId: session.sessionId });
4653
+ if (!focusNativeTab(ctx, tabId)) betterSidebarOf(ctx)?.activateTab(tabId, { sessionId: session.sessionId });
4034
4654
  return;
4035
4655
  }
4036
4656
  if (option.id.startsWith("reopen:")) {
@@ -4074,7 +4694,11 @@ window.__ModuleLoader__.load({
4074
4694
  }
4075
4695
  return `<side-chat-reference source="${(() => {
4076
4696
  try {
4077
- for (const tab of collectSideTabs(ctx.betterSidebar.getSnapshot().state)) if (parseSideChatMeta(tab.meta).childId === childId) return tab.title;
4697
+ if (directNativeLeg(ctx)) {
4698
+ const meta = sideChatMetasAll().find((m) => m.childId === childId);
4699
+ if (meta !== void 0) return meta.number <= 1 ? t("tabBaseTitle") : `${t("tabBaseTitle")} ${meta.number}`;
4700
+ }
4701
+ for (const tab of collectSideTabs(betterSidebarOf(ctx)?.getSnapshot().state)) if (parseSideChatMeta(tab.meta).childId === childId) return tab.title;
4078
4702
  } catch {}
4079
4703
  return childId;
4080
4704
  })()}" note="用户 @ 引用的侧边聊天内容(问答成对)">\n${parts.join("\n")}\n</side-chat-reference>`;
@@ -4095,9 +4719,14 @@ window.__ModuleLoader__.load({
4095
4719
  order: 50,
4096
4720
  candidates: (session) => {
4097
4721
  try {
4098
- const snapshot = ctx.betterSidebar.getSnapshot();
4099
4722
  if (session?.sessionId === void 0) return Promise.resolve([]);
4100
- if (snapshot.sessionId !== session.sessionId || snapshot.state === void 0) return Promise.resolve([]);
4723
+ if (directNativeLeg(ctx)) return Promise.resolve(sideChatMetasOf(session.sessionId).filter((meta) => meta.childId !== void 0).map((meta) => ({
4724
+ name: meta.number <= 1 ? t("tabBaseTitle") : `${t("tabBaseTitle")} ${meta.number}`,
4725
+ description: "侧边聊天",
4726
+ icon: "💬"
4727
+ })));
4728
+ const snapshot = betterSidebarOf(ctx)?.getSnapshot();
4729
+ if (snapshot === void 0 || snapshot.sessionId !== session.sessionId || snapshot.state === void 0) return Promise.resolve([]);
4101
4730
  return Promise.resolve(collectSideTabs(snapshot.state).map((tab) => ({
4102
4731
  name: tab.title,
4103
4732
  description: "侧边聊天",
@@ -4108,10 +4737,20 @@ window.__ModuleLoader__.load({
4108
4737
  }
4109
4738
  },
4110
4739
  onPick: (pick, session) => {
4111
- const snapshot = ctx.betterSidebar.getSnapshot();
4112
4740
  if (session?.sessionId === void 0) return void 0;
4113
- if (snapshot.sessionId !== session.sessionId || snapshot.state === void 0) return void 0;
4114
- const tab = collectSideTabs(snapshot.state).find((t) => t.title === pick.candidate.name);
4741
+ if (directNativeLeg(ctx)) {
4742
+ const meta = sideChatMetasOf(session.sessionId).filter((m) => m.childId !== void 0).find((m) => (m.number <= 1 ? t("tabBaseTitle") : `${t("tabBaseTitle")} ${m.number}`) === pick.candidate.name);
4743
+ if (meta?.childId === void 0) return void 0;
4744
+ return { insert: {
4745
+ source: SOURCE,
4746
+ ref: meta.childId,
4747
+ label: pick.candidate.name,
4748
+ clipboardText: `@${pick.candidate.name}`
4749
+ } };
4750
+ }
4751
+ const snapshot = betterSidebarOf(ctx)?.getSnapshot();
4752
+ if (snapshot === void 0 || snapshot.sessionId !== session.sessionId || snapshot.state === void 0) return void 0;
4753
+ const tab = collectSideTabs(snapshot.state).find((tab) => tab.title === pick.candidate.name);
4115
4754
  const childId = tab === void 0 ? void 0 : parseSideChatMeta(tab.meta).childId;
4116
4755
  if (childId === void 0) return void 0;
4117
4756
  return { insert: {
@@ -4144,24 +4783,40 @@ window.__ModuleLoader__.load({
4144
4783
  function registerCompletionNotify(ctx) {
4145
4784
  ctx.effect(() => {
4146
4785
  const watchers = /* @__PURE__ */ new Map();
4786
+ const collectAlive = () => {
4787
+ if (directNativeLeg(ctx)) return sideChatMetasAll().flatMap((meta) => {
4788
+ if (meta.childId === void 0) return [];
4789
+ const title = meta.number <= 1 ? t("tabBaseTitle") : `${t("tabBaseTitle")} ${meta.number}`;
4790
+ return [{
4791
+ childId: meta.childId,
4792
+ title
4793
+ }];
4794
+ });
4795
+ const snapshot = betterSidebarOf(ctx)?.getSnapshot();
4796
+ if (snapshot?.state === void 0) return [];
4797
+ return collectSideTabs(snapshot.state).flatMap((tab) => {
4798
+ const childId = parseSideChatMeta(tab.meta).childId;
4799
+ return childId === void 0 ? [] : [{
4800
+ childId,
4801
+ title: tab.title
4802
+ }];
4803
+ });
4804
+ };
4147
4805
  const rescan = () => {
4148
4806
  let tabs = [];
4149
4807
  try {
4150
- tabs = collectSideTabs(ctx.betterSidebar.getSnapshot());
4808
+ tabs = collectAlive();
4151
4809
  } catch {
4152
4810
  return;
4153
4811
  }
4154
4812
  const alive = /* @__PURE__ */ new Set();
4155
- for (const tab of tabs) {
4156
- const childId = parseSideChatMeta(tab.meta).childId;
4157
- if (childId === void 0) continue;
4813
+ for (const { childId, title } of tabs) {
4158
4814
  alive.add(childId);
4159
4815
  if (watchers.has(childId)) continue;
4160
4816
  try {
4161
4817
  const session = ctx.sessions.binding(childId)?.session;
4162
4818
  if (session === void 0) continue;
4163
4819
  openSessionWindow(session);
4164
- const title = tab.title;
4165
4820
  const watcher = {
4166
4821
  unsub: () => {},
4167
4822
  wasRunning: false,
@@ -4181,7 +4836,7 @@ window.__ModuleLoader__.load({
4181
4836
  watchers.delete(id);
4182
4837
  }
4183
4838
  };
4184
- const offState = ctx.betterSidebar.subscribeState(rescan);
4839
+ const offState = directNativeLeg(ctx) ? sideChatMetaStore.subscribe(rescan) : betterSidebarOf(ctx)?.subscribeState(rescan) ?? (() => {});
4185
4840
  rescan();
4186
4841
  return () => {
4187
4842
  offState();
@@ -4210,38 +4865,47 @@ window.__ModuleLoader__.load({
4210
4865
  * 斜杠命令(spike,popupSelect 形态,不可行时降级为只有 Tab 入口)。
4211
4866
  */
4212
4867
  function registerSideChat(ctx, reflow) {
4213
- ctx.effect(() => ctx.betterSidebar.registerTab({
4214
- id: SIDE_TAB_TYPE,
4215
- title: () => t("menuTitle"),
4216
- icon: (size) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutline16, { size }),
4217
- order: 60,
4218
- available: (availableCtx, scope) => canForkFrom(availableCtx, scope.sessionId),
4219
- createTab: (state) => {
4220
- const titles = collectSideTabs(state).map((tab) => tab.title);
4221
- if (nativeSidebarHost(ctx)) {
4222
- const current = ctx.sessions.list.getSnapshot().current;
4223
- if (current !== void 0) for (const live of liveSideChatsOf(current)) titles.push(live.readTitle());
4224
- }
4225
- return { tab: {
4226
- id: mintSideTabId(),
4227
- type: SIDE_TAB_TYPE,
4228
- title: sideTabTitle(titles)
4229
- } };
4230
- },
4231
- onClose: (closedTab) => {
4232
- const meta = parseSideChatMeta(closedTab.meta);
4233
- if (meta.childId !== void 0 && meta.parentSessionId !== void 0) recordClosedSideChat(meta.parentSessionId, {
4234
- childId: meta.childId,
4235
- parentSessionId: meta.parentSessionId,
4236
- title: closedTab.title,
4237
- closedAt: Date.now()
4238
- });
4239
- },
4240
- component: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SideChatPanel, {
4241
- ...props,
4242
- reflow
4243
- })
4244
- }), "dsh-sidenote: side chat tab");
4868
+ registerNativeSideChatTab(ctx, reflow);
4869
+ ctx.inject(["betterSidebar"], (injected) => {
4870
+ const bs = injected.get("betterSidebar");
4871
+ if (bs === void 0) return;
4872
+ try {
4873
+ const [major = 0, minor = 0] = bs.version.split(".").map((part) => Number.parseInt(part, 10));
4874
+ if (major > 0 || minor >= 19) return;
4875
+ } catch {}
4876
+ ctx.effect(() => bs.registerTab({
4877
+ id: SIDE_TAB_TYPE,
4878
+ title: () => t("menuTitle"),
4879
+ icon: (size) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutline16, { size }),
4880
+ order: 60,
4881
+ available: (availableCtx, scope) => canForkFrom(availableCtx, scope.sessionId),
4882
+ createTab: (state) => {
4883
+ const titles = collectSideTabs(state).map((tab) => tab.title);
4884
+ if (nativeSidebarHost(ctx)) {
4885
+ const current = ctx.sessions.list.getSnapshot().current;
4886
+ if (current !== void 0) for (const live of liveSideChatsOf(current)) titles.push(live.readTitle());
4887
+ }
4888
+ return { tab: {
4889
+ id: mintSideTabId(),
4890
+ type: SIDE_TAB_TYPE,
4891
+ title: sideTabTitle(titles)
4892
+ } };
4893
+ },
4894
+ onClose: (closedTab) => {
4895
+ const meta = parseSideChatMeta(closedTab.meta);
4896
+ if (meta.childId !== void 0 && meta.parentSessionId !== void 0) recordClosedSideChat(meta.parentSessionId, {
4897
+ childId: meta.childId,
4898
+ parentSessionId: meta.parentSessionId,
4899
+ title: closedTab.title,
4900
+ closedAt: Date.now()
4901
+ });
4902
+ },
4903
+ component: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SideChatPanel, {
4904
+ ...props,
4905
+ reflow
4906
+ })
4907
+ }), "dsh-sidenote: side chat tab");
4908
+ });
4245
4909
  ctx.effect(() => {
4246
4910
  const impl = {
4247
4911
  askInSideChat: (sessionId, draftText) => openOrFocusSideChat(ctx, sessionId, draftText),
@@ -4709,42 +5373,42 @@ window.__ModuleLoader__.load({
4709
5373
  document.head.appendChild(tag);
4710
5374
  }
4711
5375
  var annotate_module_css_default = {
4712
- "badgeSent": "_SVViG_badgeSent",
4713
- "chipClearAll": "_SVViG_chipClearAll",
5376
+ "editorTarget": "_SVViG_editorTarget",
5377
+ "badge": "_SVViG_badge",
4714
5378
  "highlight": "_SVViG_highlight",
4715
- "chipRemove": "_SVViG_chipRemove",
4716
- "dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
4717
- "editorNew": "_SVViG_editorNew",
4718
- "chipRow": "_SVViG_chipRow",
4719
5379
  "editorTextarea": "_SVViG_editorTextarea",
4720
- "editorAsk": "_SVViG_editorAsk",
4721
- "chipNote": "_SVViG_chipNote",
4722
- "chip": "_SVViG_chip",
4723
- "chipText": "_SVViG_chipText",
5380
+ "deleteButton": "_SVViG_deleteButton",
4724
5381
  "chipPanel": "_SVViG_chipPanel",
4725
- "editorError": "_SVViG_editorError",
4726
- "toolbarBelow": "_SVViG_toolbarBelow",
5382
+ "chipText": "_SVViG_chipText",
5383
+ "sentChip": "_SVViG_sentChip",
5384
+ "chip": "_SVViG_chip",
5385
+ "chipNumber": "_SVViG_chipNumber",
4727
5386
  "sentCardQuote": "_SVViG_sentCardQuote",
4728
- "hint": "_SVViG_hint",
5387
+ "editorAsk": "_SVViG_editorAsk",
5388
+ "saveButton": "_SVViG_saveButton",
4729
5389
  "sentChipRow": "_SVViG_sentChipRow",
4730
- "deleteButton": "_SVViG_deleteButton",
5390
+ "hint": "_SVViG_hint",
5391
+ "editorSpacer": "_SVViG_editorSpacer",
5392
+ "editorAskRow": "_SVViG_editorAskRow",
5393
+ "chipClearAll": "_SVViG_chipClearAll",
5394
+ "chipWrap": "_SVViG_chipWrap",
5395
+ "chipRow": "_SVViG_chipRow",
5396
+ "sentCardTitle": "_SVViG_sentCardTitle",
5397
+ "toolbarBelow": "_SVViG_toolbarBelow",
4731
5398
  "sentCardNote": "_SVViG_sentCardNote",
4732
- "confirmButton": "_SVViG_confirmButton",
5399
+ "editorInput": "_SVViG_editorInput",
4733
5400
  "cancelButton": "_SVViG_cancelButton",
5401
+ "badgeSent": "_SVViG_badgeSent",
5402
+ "editorEdit": "_SVViG_editorEdit",
5403
+ "editorNew": "_SVViG_editorNew",
5404
+ "chipRemove": "_SVViG_chipRemove",
4734
5405
  "editorFooter": "_SVViG_editorFooter",
4735
- "sentCardTitle": "_SVViG_sentCardTitle",
4736
- "badge": "_SVViG_badge",
4737
- "editorTarget": "_SVViG_editorTarget",
4738
- "chipNumber": "_SVViG_chipNumber",
4739
- "editorAskRow": "_SVViG_editorAskRow",
4740
- "editorSpacer": "_SVViG_editorSpacer",
4741
5406
  "hintClose": "_SVViG_hintClose",
4742
- "editorInput": "_SVViG_editorInput",
4743
- "saveButton": "_SVViG_saveButton",
5407
+ "chipNote": "_SVViG_chipNote",
5408
+ "confirmButton": "_SVViG_confirmButton",
5409
+ "dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
4744
5410
  "toolbarButton": "_SVViG_toolbarButton",
4745
- "editorEdit": "_SVViG_editorEdit",
4746
- "chipWrap": "_SVViG_chipWrap",
4747
- "sentChip": "_SVViG_sentChip",
5411
+ "editorError": "_SVViG_editorError",
4748
5412
  "toolbar": "_SVViG_toolbar"
4749
5413
  };
4750
5414
  //#endregion
@@ -5584,6 +6248,23 @@ window.__ModuleLoader__.load({
5584
6248
  function installSendInterceptor(ctx, store, reflow) {
5585
6249
  /** 重入/连按护栏:事务进行中吞掉命中识别面的 Enter/点击(不重复驱动)。 */
5586
6250
  let committing = false;
6251
+ /**
6252
+ * 手势尾随 click 的一次性吞并。pointerdown 提交后,同一次点击的 click 仍会
6253
+ * 到达,而此时宿主主按钮已按 `primaryStops = running && empty` 变身「停止」
6254
+ * ——放行它会立刻 `stop()` 掉刚发起的这一轮(表现:assistant/attempt 空流、
6255
+ * 缺 turn/end、且没有任何错误提示)。只吞这一发,并带兜底超时。
6256
+ */
6257
+ let swallowNextClick = false;
6258
+ let swallowTimer = 0;
6259
+ const clearSwallow = () => {
6260
+ swallowNextClick = false;
6261
+ window.clearTimeout(swallowTimer);
6262
+ };
6263
+ const armSwallow = () => {
6264
+ swallowNextClick = true;
6265
+ window.clearTimeout(swallowTimer);
6266
+ swallowTimer = window.setTimeout(clearSwallow, 2e3);
6267
+ };
5587
6268
  const currentSessionId = () => {
5588
6269
  try {
5589
6270
  return ctx.sessions.list.getSnapshot().current ?? "";
@@ -5591,37 +6272,36 @@ window.__ModuleLoader__.load({
5591
6272
  return "";
5592
6273
  }
5593
6274
  };
5594
- const hijack = () => {
5595
- const sessionId = currentSessionId();
5596
- if (sessionId === "") return false;
5597
- const active = store.listActive(sessionId);
5598
- const reflows = reflow.list(sessionId);
5599
- if (active.length === 0 && reflows.length === 0) return false;
5600
- const input = resolveInput(ctx, sessionId);
5601
- if (input === void 0) return false;
5602
- const snap = input.state.getSnapshot();
5603
- const draft = snap.draft;
5604
- if (draft.trimStart().startsWith("/")) return false;
5605
- if (snap.phase !== "plain") return false;
5606
- const seat = composerSeat();
5607
- if (seat === null) return false;
5608
- if (seat.querySelector("[role=\"listbox\"]") !== null) return false;
5609
- if (seat.querySelector("[aria-expanded=\"true\"]") !== null) return false;
6275
+ /**
6276
+ * 异步提交(Workitem_06):inject 尝试(有界等待,见 tryInjectReflows)
6277
+ * 在拼稿前——成功项以 plugin-source 事件进父会话日志(不进消息前缀),
6278
+ * 且立即从 reflow store 移除(chip 消失表达「已注入」);失败/超时项
6279
+ * 照常搭车(拼进前缀)。正文取提交瞬间的最新草稿(注入等待窗口内的
6280
+ * 用户输入不丢)。
6281
+ */
6282
+ const commitAsync = async (sessionId, input, draftAtHijack, reflows, active) => {
6283
+ const { injectedIds } = await tryInjectReflows(sessionId, reflows);
6284
+ for (const id of injectedIds) reflow.remove(id);
6285
+ const hitchhikers = reflows.filter((item) => !injectedIds.has(item.id));
5610
6286
  const parts = [];
5611
- for (const item of reflows) parts.push(buildReflowBlock(item));
6287
+ for (const item of hitchhikers) parts.push(buildReflowBlock(item));
5612
6288
  if (active.length > 0) parts.push(buildProtocolBlock(active));
5613
- const body = draft.trim();
6289
+ const draftBeforeCompose = input.state.getSnapshot().draft;
6290
+ const body = draftBeforeCompose.trim();
5614
6291
  const full = [...parts, ...body === "" ? [] : [body]].join("\n\n");
5615
6292
  const sentIds = active.map((a) => a.id);
6293
+ if (full === "") {
6294
+ committing = false;
6295
+ return;
6296
+ }
5616
6297
  input.setDraft(full);
5617
- committing = true;
5618
6298
  try {
5619
6299
  input.submit("queue");
5620
6300
  } catch (error) {
5621
6301
  console.warn("[dsh-sidenote] 提交失败,回滚草稿:", error);
5622
- if (input.state.getSnapshot().draft.startsWith(full)) input.setDraft(draft);
6302
+ if (input.state.getSnapshot().draft.startsWith(full)) input.setDraft(draftAtHijack);
5623
6303
  committing = false;
5624
- return true;
6304
+ return;
5625
6305
  }
5626
6306
  const off = input.state.subscribe(() => {
5627
6307
  const state = input.state.getSnapshot();
@@ -5634,12 +6314,34 @@ window.__ModuleLoader__.load({
5634
6314
  reflow.clearSession(sessionId);
5635
6315
  return;
5636
6316
  }
5637
- if (state.draft.startsWith(full)) input.setDraft(draft);
6317
+ if (state.draft.startsWith(full)) input.setDraft(draftBeforeCompose);
5638
6318
  });
5639
6319
  const watchdog = window.setTimeout(() => {
5640
6320
  off();
5641
6321
  committing = false;
5642
6322
  }, 15e3);
6323
+ };
6324
+ const hijack = () => {
6325
+ const sessionId = currentSessionId();
6326
+ if (sessionId === "") return false;
6327
+ const active = store.listActive(sessionId);
6328
+ const reflows = reflow.list(sessionId);
6329
+ if (active.length === 0 && reflows.length === 0) return false;
6330
+ const input = resolveInput(ctx, sessionId);
6331
+ if (input === void 0) return false;
6332
+ const snap = input.state.getSnapshot();
6333
+ const draft = snap.draft;
6334
+ if (draft.trimStart().startsWith("/")) return false;
6335
+ if (snap.phase !== "plain") return false;
6336
+ const seat = composerSeat();
6337
+ if (seat === null) return false;
6338
+ if (seat.querySelector("[role=\"listbox\"]") !== null) return false;
6339
+ if (seat.querySelector("[aria-expanded=\"true\"]") !== null) return false;
6340
+ committing = true;
6341
+ commitAsync(sessionId, input, draft, reflows, active).catch((error) => {
6342
+ console.warn("[dsh-sidenote] 提交编排失败:", error);
6343
+ committing = false;
6344
+ });
5643
6345
  return true;
5644
6346
  };
5645
6347
  const onKeyDown = (event) => {
@@ -5663,6 +6365,12 @@ window.__ModuleLoader__.load({
5663
6365
  const target = event.target;
5664
6366
  if (!(target instanceof Element)) return;
5665
6367
  if (target.closest("[data-composer-seat]") === null) return;
6368
+ if (swallowNextClick) {
6369
+ clearSwallow();
6370
+ event.preventDefault();
6371
+ event.stopImmediatePropagation();
6372
+ return;
6373
+ }
5666
6374
  const button = target.closest("button");
5667
6375
  if (button === null) return;
5668
6376
  if (committing) {
@@ -5675,11 +6383,35 @@ window.__ModuleLoader__.load({
5675
6383
  event.preventDefault();
5676
6384
  event.stopImmediatePropagation();
5677
6385
  };
6386
+ /**
6387
+ * 空草稿补位:只接管「因空草稿而 disabled」的主按钮。enabled 时立即放行,
6388
+ * 正常路径仍由上面的 click 拦截负责 —— 二者以 button.disabled 互斥。
6389
+ */
6390
+ const onPointerDown = (event) => {
6391
+ if (event.button !== 0) return;
6392
+ const target = event.target;
6393
+ if (!(target instanceof Element)) return;
6394
+ const button = target.closest("button");
6395
+ if (!(button instanceof HTMLButtonElement)) return;
6396
+ if (!button.disabled) return;
6397
+ if (button !== findSendButtonInCard()) return;
6398
+ if (committing) return;
6399
+ const sessionId = currentSessionId();
6400
+ if (sessionId === "") return;
6401
+ const input = resolveInput(ctx, sessionId);
6402
+ if (input === void 0 || input.state.getSnapshot().draft.trim() !== "") return;
6403
+ if (!hijack()) return;
6404
+ armSwallow();
6405
+ event.preventDefault();
6406
+ event.stopImmediatePropagation();
6407
+ };
5678
6408
  document.addEventListener("keydown", onKeyDown, true);
5679
6409
  document.addEventListener("click", onClick, true);
6410
+ document.addEventListener("pointerdown", onPointerDown, true);
5680
6411
  return () => {
5681
6412
  document.removeEventListener("keydown", onKeyDown, true);
5682
6413
  document.removeEventListener("click", onClick, true);
6414
+ document.removeEventListener("pointerdown", onPointerDown, true);
5683
6415
  };
5684
6416
  }
5685
6417
  //#endregion
@@ -5849,7 +6581,6 @@ window.__ModuleLoader__.load({
5849
6581
  //#endregion
5850
6582
  //#region src/client/index.tsx
5851
6583
  const inject = [
5852
- "betterSidebar",
5853
6584
  "sessions",
5854
6585
  "workspaces",
5855
6586
  "slots",
@@ -5865,6 +6596,7 @@ window.__ModuleLoader__.load({
5865
6596
  }, "dsh-sidenote: native bridge");
5866
6597
  attachLocale(ctx.locale);
5867
6598
  probeHost(ctx);
6599
+ sweepOrphanedSideChatMeta();
5868
6600
  const reflow = createReflowStore();
5869
6601
  registerSideChat(ctx, reflow);
5870
6602
  registerAnnotations(ctx, reflow);