dsh-sidenote 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -2
- package/cordis.patch.yml +4 -3
- package/lib/client.js +1960 -1266
- package/lib/types/client/locales.d.ts +8 -1
- package/lib/types/client/protocol.d.ts +9 -0
- package/lib/types/client/sidechat/composer.d.ts +13 -2
- package/lib/types/client/sidechat/identity.d.ts +25 -0
- package/lib/types/client/sidechat/lifecycle.d.ts +57 -2
- package/lib/types/client/sidechat/metaStore.d.ts +14 -1
- package/lib/types/client/sidechat/model.d.ts +17 -1
- package/lib/types/client/sidechat/native-tab.d.ts +3 -1
- package/lib/types/client/sidechat/open.d.ts +6 -1
- package/lib/types/client/sidechat/recentClosed.d.ts +2 -0
- package/lib/types/client/sidechat/referenceSource.d.ts +2 -0
- package/lib/types/client/sidechat/rows.d.ts +10 -0
- package/lib/types/client/sidechat/slash.d.ts +2 -1
- package/lib/types/client/sidechat/snapshot.d.ts +43 -0
- package/package.json +2 -2
- package/src/client/annotate/overlay.tsx +2 -1
- package/src/client/locales.ts +16 -2
- package/src/client/protocol.ts +15 -0
- package/src/client/reflow.ts +2 -1
- package/src/client/sidechat/SideChatPanel.tsx +45 -6
- package/src/client/sidechat/composer.ts +20 -5
- package/src/client/sidechat/identity.ts +86 -0
- package/src/client/sidechat/index.tsx +1 -0
- package/src/client/sidechat/lifecycle.ts +366 -13
- package/src/client/sidechat/metaStore.ts +20 -2
- package/src/client/sidechat/model.ts +23 -2
- package/src/client/sidechat/native-tab.tsx +7 -4
- package/src/client/sidechat/notify.ts +13 -4
- package/src/client/sidechat/open.ts +14 -6
- package/src/client/sidechat/recentClosed.ts +2 -0
- package/src/client/sidechat/referenceSource.ts +60 -44
- package/src/client/sidechat/rows.tsx +58 -4
- package/src/client/sidechat/sidechat.module.css +17 -0
- package/src/client/sidechat/slash.ts +16 -5
- package/src/client/sidechat/snapshot.ts +141 -0
package/lib/client.js
CHANGED
|
@@ -241,7 +241,12 @@ window.__ModuleLoader__.load({
|
|
|
241
241
|
cmdFocus: "Focus \"{title}\"",
|
|
242
242
|
cmdFocusDetail: "Existing side chat",
|
|
243
243
|
cmdReopen: "Reopen \"{title}\"",
|
|
244
|
-
|
|
244
|
+
closedJustNow: "Closed just now",
|
|
245
|
+
closedMinutesAgo: "Closed {n} min ago",
|
|
246
|
+
closedHoursAgo: "Closed {n}h ago",
|
|
247
|
+
closedYesterday: "Closed yesterday",
|
|
248
|
+
closedOnDate: "Closed {month}/{day}",
|
|
249
|
+
closedOnDateYear: "Closed {year}/{month}/{day}",
|
|
245
250
|
addToConversation: "Add to conversation",
|
|
246
251
|
askInSideChat: "Ask in side chat",
|
|
247
252
|
toolbarAria: "Selection annotations",
|
|
@@ -290,6 +295,8 @@ window.__ModuleLoader__.load({
|
|
|
290
295
|
termExpand: "Show all ({n} hidden lines)",
|
|
291
296
|
termExpandAria: "Show all ({n} hidden lines)",
|
|
292
297
|
inheritedLabel: "Inherited from main session · up to the fork point · {n}",
|
|
298
|
+
snapshotLabel: "Mainline progress snapshot",
|
|
299
|
+
snapshotLabelAt: "Mainline progress snapshot · taken {at}",
|
|
293
300
|
collapseAll: "Collapse all",
|
|
294
301
|
expandAll: "Expand all",
|
|
295
302
|
jumpToLatest: "↓ Jump to latest",
|
|
@@ -385,7 +392,12 @@ window.__ModuleLoader__.load({
|
|
|
385
392
|
cmdFocus: "聚焦「{title}」",
|
|
386
393
|
cmdFocusDetail: "已存在的侧边聊天",
|
|
387
394
|
cmdReopen: "重开「{title}」",
|
|
388
|
-
|
|
395
|
+
closedJustNow: "刚刚关闭",
|
|
396
|
+
closedMinutesAgo: "{n} 分钟前关闭",
|
|
397
|
+
closedHoursAgo: "{n} 小时前关闭",
|
|
398
|
+
closedYesterday: "昨天关闭",
|
|
399
|
+
closedOnDate: "{month}月{day}日关闭",
|
|
400
|
+
closedOnDateYear: "{year}年{month}月{day}日关闭",
|
|
389
401
|
addToConversation: "添加到对话",
|
|
390
402
|
askInSideChat: "在侧边聊天中提问",
|
|
391
403
|
toolbarAria: "划选注释",
|
|
@@ -434,6 +446,8 @@ window.__ModuleLoader__.load({
|
|
|
434
446
|
termExpand: "展开全部(隐藏 {n} 行)",
|
|
435
447
|
termExpandAria: "展开全部(隐藏 {n} 行)",
|
|
436
448
|
inheritedLabel: "继承自主会话 · 截至 fork 点 · {n} 条",
|
|
449
|
+
snapshotLabel: "主线进展快照",
|
|
450
|
+
snapshotLabelAt: "主线进展快照 · 截于 {at}",
|
|
437
451
|
collapseAll: "全部折叠",
|
|
438
452
|
expandAll: "全部展开",
|
|
439
453
|
jumpToLatest: "↓ 跳到最新",
|
|
@@ -593,7 +607,12 @@ window.__ModuleLoader__.load({
|
|
|
593
607
|
if (typeof raw.childId === "string" && raw.childId !== "") out.childId = raw.childId;
|
|
594
608
|
if (typeof raw.parentSessionId === "string" && raw.parentSessionId !== "") out.parentSessionId = raw.parentSessionId;
|
|
595
609
|
if (typeof raw.pendingDraft === "string" && raw.pendingDraft !== "") out.pendingDraft = raw.pendingDraft;
|
|
596
|
-
if (typeof raw.boundarySeq === "number" && Number.
|
|
610
|
+
if (typeof raw.boundarySeq === "number" && Number.isFinite(raw.boundarySeq)) out.boundarySeq = raw.boundarySeq;
|
|
611
|
+
if (raw.forkedMidTurn === true) out.forkedMidTurn = true;
|
|
612
|
+
if (raw.snapshotOnly === true) out.snapshotOnly = true;
|
|
613
|
+
if (typeof raw.leakedPromptPrefix === "string" && raw.leakedPromptPrefix !== "") out.leakedPromptPrefix = raw.leakedPromptPrefix;
|
|
614
|
+
if (raw.inheritedPurged === true) out.inheritedPurged = true;
|
|
615
|
+
if (typeof raw.topic === "string" && raw.topic !== "") out.topic = raw.topic;
|
|
597
616
|
return out;
|
|
598
617
|
}
|
|
599
618
|
/** 清除 meta.pendingDraft(其余字段原样保留)。 */
|
|
@@ -751,8 +770,15 @@ window.__ModuleLoader__.load({
|
|
|
751
770
|
* 与主输入框同语义),submit 走机器事务(序列化/裁决/默认 sink 全在机器内)。
|
|
752
771
|
* 降级路径:草稿住本地 state,发送直连 session.prompt([...], 'queue'),
|
|
753
772
|
* 失败时回填草稿并报错。
|
|
773
|
+
*
|
|
774
|
+
* firstSendPrefix(Delivery_04 busy fork):首条消息发送瞬间现取的前缀块
|
|
775
|
+
* (主线进展快照)。拼进即消费(由调用方清标记)——机器路径的失败回填与
|
|
776
|
+
* 降级路径的重试都保留已拼文本,天然不会二次拼接。
|
|
777
|
+
*
|
|
778
|
+
* onSubmitText(Delivery_05 内容身份):每次 submit 拿到干净 body(不含
|
|
779
|
+
* 快照前缀),供面板捕获首条消息主题为 topic;是否首条的判定归调用方。
|
|
754
780
|
*/
|
|
755
|
-
function useComposer(ctx, session, childId) {
|
|
781
|
+
function useComposer(ctx, session, childId, options) {
|
|
756
782
|
const input = (0, react.useMemo)(() => childId === void 0 ? null : resolveSessionInput(ctx, childId), [ctx, childId]);
|
|
757
783
|
const machineState = (0, react.useSyncExternalStore)((0, react.useCallback)((notify) => input === null ? NOOP_UNSUBSCRIBE$1 : input.state.subscribe(notify), [input]), () => input === null ? null : input.state.getSnapshot());
|
|
758
784
|
const machineDraft = machineState?.draft ?? "";
|
|
@@ -778,9 +804,13 @@ window.__ModuleLoader__.load({
|
|
|
778
804
|
else input.setDraft(appendDraftText(readInputDraft(input), text));
|
|
779
805
|
}, [input]);
|
|
780
806
|
const submit = (0, react.useCallback)((mode) => {
|
|
781
|
-
const
|
|
782
|
-
if (
|
|
807
|
+
const body = draft.trim();
|
|
808
|
+
if (body === "") return;
|
|
809
|
+
options?.onSubmitText?.(body);
|
|
810
|
+
const prefix = options?.firstSendPrefix?.() ?? null;
|
|
811
|
+
const text = prefix === null ? body : `${prefix}\n\n${body}`;
|
|
783
812
|
if (input !== null) {
|
|
813
|
+
if (prefix !== null) input.setDraft(text);
|
|
784
814
|
input.submit(mode);
|
|
785
815
|
return;
|
|
786
816
|
}
|
|
@@ -802,7 +832,8 @@ window.__ModuleLoader__.load({
|
|
|
802
832
|
}, [
|
|
803
833
|
draft,
|
|
804
834
|
input,
|
|
805
|
-
session
|
|
835
|
+
session,
|
|
836
|
+
options
|
|
806
837
|
]);
|
|
807
838
|
return {
|
|
808
839
|
machine: input !== null,
|
|
@@ -862,7 +893,12 @@ window.__ModuleLoader__.load({
|
|
|
862
893
|
...typeof r.childId === "string" ? { childId: r.childId } : {},
|
|
863
894
|
...typeof r.parentSessionId === "string" ? { parentSessionId: r.parentSessionId } : {},
|
|
864
895
|
...typeof r.pendingDraft === "string" ? { pendingDraft: r.pendingDraft } : {},
|
|
865
|
-
...typeof r.boundarySeq === "number" && Number.
|
|
896
|
+
...typeof r.boundarySeq === "number" && Number.isFinite(r.boundarySeq) ? { boundarySeq: r.boundarySeq } : {},
|
|
897
|
+
...r.forkedMidTurn === true ? { forkedMidTurn: true } : {},
|
|
898
|
+
...r.snapshotOnly === true ? { snapshotOnly: true } : {},
|
|
899
|
+
...typeof r.leakedPromptPrefix === "string" ? { leakedPromptPrefix: r.leakedPromptPrefix } : {},
|
|
900
|
+
...r.inheritedPurged === true ? { inheritedPurged: true } : {},
|
|
901
|
+
...typeof r.topic === "string" && r.topic !== "" ? { topic: r.topic } : {},
|
|
866
902
|
number: r.number,
|
|
867
903
|
createdAt: typeof r.createdAt === "number" ? r.createdAt : 0,
|
|
868
904
|
...typeof r.runId === "string" ? { runId: r.runId } : {}
|
|
@@ -973,6 +1009,75 @@ window.__ModuleLoader__.load({
|
|
|
973
1009
|
}
|
|
974
1010
|
}
|
|
975
1011
|
//#endregion
|
|
1012
|
+
//#region src/client/sidechat/identity.ts
|
|
1013
|
+
/**
|
|
1014
|
+
* 侧边聊天的内容身份(Delivery_05):编号(「侧边 2」)是存活序——回答
|
|
1015
|
+
* 「现在打开着的是哪个」,聊天一关闭就失去指称;native 单实例下编号恒为
|
|
1016
|
+
* 1,身份随之整体塌方(关闭列表五行全叫「侧边」的实证)。
|
|
1017
|
+
*
|
|
1018
|
+
* 身份 = 首条用户消息摘要(topic),sticky——侧边聊天是单主题支线,
|
|
1019
|
+
* 首条消息即意图;只认首条,不随对话漂移。本模块是唯一计算源:
|
|
1020
|
+
* 芯片标题、通知、@引用、菜单聚焦/重开行、关闭列表全部从这里取。
|
|
1021
|
+
*
|
|
1022
|
+
* L0 纯函数,react-free。
|
|
1023
|
+
*/
|
|
1024
|
+
/** 标题里 topic 段的最大码点数(CJK 安全截断)。 */
|
|
1025
|
+
const TOPIC_MAX = 20;
|
|
1026
|
+
/**
|
|
1027
|
+
* 首条用户消息 → topic。折叠全部空白(含换行)为单空格,截 20 码点,
|
|
1028
|
+
* 超长加省略号;清洗后为空返回 undefined(调用方回退编号标题)。
|
|
1029
|
+
* 快照前缀(Delivery_04)在 composer 层就不进 body,这里无需剥离。
|
|
1030
|
+
* 划选提问的种子草稿是「> 引用行 + note 行」(buildSideChatQuote):
|
|
1031
|
+
* 引用行剥除(用户自己的话才是主题),机械占位行(noNote)丢弃;
|
|
1032
|
+
* 剥完为空(未补充直接发送)时退回引用内容——引用即主题。
|
|
1033
|
+
*/
|
|
1034
|
+
function topicOf(text) {
|
|
1035
|
+
const lines = text.split("\n");
|
|
1036
|
+
const clean = lines.filter((l) => !l.startsWith(">")).filter((l) => l.trim() !== t("noNote")).join(" ").replaceAll(/\s+/gu, " ").trim();
|
|
1037
|
+
if (clean !== "") return truncate(clean);
|
|
1038
|
+
return truncate(lines.filter((l) => l.startsWith(">")).map((l) => l.replace(/^> ?/u, "")).join(" "));
|
|
1039
|
+
}
|
|
1040
|
+
/** 空白折叠 + 20 码点截断;空 → undefined。 */
|
|
1041
|
+
function truncate(text) {
|
|
1042
|
+
const clean = text.replaceAll(/\s+/gu, " ").trim();
|
|
1043
|
+
if (clean === "") return void 0;
|
|
1044
|
+
const chars = Array.from(clean);
|
|
1045
|
+
return chars.length <= TOPIC_MAX ? clean : `${chars.slice(0, TOPIC_MAX).join("")}…`;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* 侧边聊天标题:topic 优先(「侧边 · {topic}」),缺席回退编号
|
|
1049
|
+
* (「侧边」/「侧边 N」——单实例期恒前者)。
|
|
1050
|
+
*/
|
|
1051
|
+
function sideChatTitleOf(meta) {
|
|
1052
|
+
if (meta?.topic !== void 0 && meta.topic !== "") return `${t("tabBaseTitle")} · ${meta.topic}`;
|
|
1053
|
+
if (meta === void 0 || meta.number <= 1) return t("tabBaseTitle");
|
|
1054
|
+
return `${t("tabBaseTitle")} ${meta.number}`;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* 关闭时刻的相对时间(重开行 detail):刚刚 / N 分钟前 / N 小时前 /
|
|
1058
|
+
* 昨天 / M月d日(跨年带年)。小时档只用于当天(日历日);跨过午夜即落
|
|
1059
|
+
* 「昨天」——日历语义比「15 小时前」更好认。手写五档,不引依赖。
|
|
1060
|
+
*/
|
|
1061
|
+
function formatClosedAt(closedAt, now = Date.now()) {
|
|
1062
|
+
const diff = now - closedAt;
|
|
1063
|
+
if (diff < 6e4) return t("closedJustNow");
|
|
1064
|
+
if (diff < 36e5) return t("closedMinutesAgo", { n: Math.floor(diff / 6e4) });
|
|
1065
|
+
const then = new Date(closedAt);
|
|
1066
|
+
const today = new Date(now);
|
|
1067
|
+
if (then.getFullYear() === today.getFullYear() && then.getMonth() === today.getMonth() && then.getDate() === today.getDate()) return t("closedHoursAgo", { n: Math.floor(diff / 36e5) });
|
|
1068
|
+
const yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1);
|
|
1069
|
+
if (then.getFullYear() === yesterday.getFullYear() && then.getMonth() === yesterday.getMonth() && then.getDate() === yesterday.getDate()) return t("closedYesterday");
|
|
1070
|
+
if (then.getFullYear() === today.getFullYear()) return t("closedOnDate", {
|
|
1071
|
+
month: then.getMonth() + 1,
|
|
1072
|
+
day: then.getDate()
|
|
1073
|
+
});
|
|
1074
|
+
return t("closedOnDateYear", {
|
|
1075
|
+
year: then.getFullYear(),
|
|
1076
|
+
month: then.getMonth() + 1,
|
|
1077
|
+
day: then.getDate()
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
//#endregion
|
|
976
1081
|
//#region src/client/sidechat/open.ts
|
|
977
1082
|
/** 直连腿的原生右栏面(off-face 探测;无面 = 不在直连腿或面缺席降级)。 */
|
|
978
1083
|
function sidebarRightOf(ctx) {
|
|
@@ -1007,7 +1112,7 @@ window.__ModuleLoader__.load({
|
|
|
1007
1112
|
return {
|
|
1008
1113
|
id: tabId,
|
|
1009
1114
|
type: SIDE_TAB_TYPE,
|
|
1010
|
-
title: live?.readTitle() ?? (direct
|
|
1115
|
+
title: live?.readTitle() ?? sideChatTitleOf(direct),
|
|
1011
1116
|
meta: direct
|
|
1012
1117
|
};
|
|
1013
1118
|
}
|
|
@@ -1140,7 +1245,7 @@ window.__ModuleLoader__.load({
|
|
|
1140
1245
|
if (live !== void 0) return live.readTitle();
|
|
1141
1246
|
const metas = sideChatMetasOf(sessionId);
|
|
1142
1247
|
const last = metas[metas.length - 1];
|
|
1143
|
-
if (last !== void 0) return last
|
|
1248
|
+
if (last !== void 0) return sideChatTitleOf(last);
|
|
1144
1249
|
return t("tabBaseTitle");
|
|
1145
1250
|
}
|
|
1146
1251
|
const snapshot = betterSidebarOf(ctx)?.getSnapshot();
|
|
@@ -1159,14 +1264,17 @@ window.__ModuleLoader__.load({
|
|
|
1159
1264
|
* parentSessionId——面板挂载后走绑定恢复路径(不重新 fork)。
|
|
1160
1265
|
* 会话本体必须仍 archived 在列(若被清理,面板落「会话已不存在」态——
|
|
1161
1266
|
* 有现成错误态兜着)。
|
|
1267
|
+
* opts.topic(Delivery_05):内容身份随恢复走——否则重开后 reconcile
|
|
1268
|
+
* 铸造的是无 topic 的新记录,身份丢一次关闭就回不来了。
|
|
1162
1269
|
*/
|
|
1163
|
-
function reopenSideChat(ctx, sessionId, childId,
|
|
1270
|
+
function reopenSideChat(ctx, sessionId, childId, opts) {
|
|
1164
1271
|
if (directLeg(ctx)) try {
|
|
1165
1272
|
if (ctx.sessions.list.getSnapshot().current !== sessionId) return false;
|
|
1166
1273
|
if (sideChatMetasOf(sessionId).length > 0) return false;
|
|
1167
1274
|
sidebarRightOf(ctx).openTab(SIDE_TAB_TYPE, { params: {
|
|
1168
1275
|
parentSessionId: sessionId,
|
|
1169
|
-
childId
|
|
1276
|
+
childId,
|
|
1277
|
+
...opts?.topic !== void 0 ? { topic: opts.topic } : {}
|
|
1170
1278
|
} });
|
|
1171
1279
|
markSideChatOpening(sessionId);
|
|
1172
1280
|
return true;
|
|
@@ -1184,8 +1292,13 @@ window.__ModuleLoader__.load({
|
|
|
1184
1292
|
const before = new Set(collectTabs(snapshot.state).map((tab) => tab.id));
|
|
1185
1293
|
const meta = {
|
|
1186
1294
|
childId,
|
|
1187
|
-
parentSessionId: sessionId
|
|
1295
|
+
parentSessionId: sessionId,
|
|
1296
|
+
...opts?.topic !== void 0 ? { topic: opts.topic } : {}
|
|
1188
1297
|
};
|
|
1298
|
+
const title = opts?.title ?? (opts?.topic !== void 0 ? sideChatTitleOf({
|
|
1299
|
+
number: 1,
|
|
1300
|
+
topic: opts.topic
|
|
1301
|
+
}) : void 0);
|
|
1189
1302
|
bs.openTab({
|
|
1190
1303
|
type: SIDE_TAB_TYPE,
|
|
1191
1304
|
...title !== void 0 ? { title } : {},
|
|
@@ -1208,1223 +1321,1662 @@ window.__ModuleLoader__.load({
|
|
|
1208
1321
|
}
|
|
1209
1322
|
}
|
|
1210
1323
|
//#endregion
|
|
1211
|
-
//#region src/client/
|
|
1212
|
-
/**
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
let boundarySeq;
|
|
1220
|
-
try {
|
|
1221
|
-
const snap = chatSourceOf(ctx, ctx.sessions.binding(parentSessionId))?.getLegacy();
|
|
1222
|
-
let max = -1;
|
|
1223
|
-
for (const node of snap?.nodes ?? []) {
|
|
1224
|
-
const seq = node.seq;
|
|
1225
|
-
if (typeof seq === "number" && seq > max) max = seq;
|
|
1226
|
-
}
|
|
1227
|
-
if (max >= 0) boundarySeq = max;
|
|
1228
|
-
} catch {}
|
|
1229
|
-
const forked = await ctx.sessions.fork({ sessionId: parentSessionId });
|
|
1230
|
-
const owner = rootContext(ctx);
|
|
1231
|
-
updateTabMeta(ctx, parentSessionId, tabId, (current) => ({
|
|
1232
|
-
...current,
|
|
1233
|
-
childId: forked,
|
|
1234
|
-
parentSessionId,
|
|
1235
|
-
...boundarySeq !== void 0 ? { boundarySeq } : {}
|
|
1236
|
-
}));
|
|
1237
|
-
try {
|
|
1238
|
-
await owner.workspaces.archiveSession(forked);
|
|
1239
|
-
} catch (error) {
|
|
1240
|
-
console.warn("[dsh-sidenote] 归档侧边会话失败(会话列表可能短暂可见):", error);
|
|
1241
|
-
}
|
|
1242
|
-
try {
|
|
1243
|
-
const selection = await readModelSelection(owner, parentSessionId);
|
|
1244
|
-
if (selection === void 0) console.warn("[dsh-sidenote] 同步主会话模型跳过:主机模型 API 不可用");
|
|
1245
|
-
else if (!await writeModelSelection(owner, forked, selection)) console.warn("[dsh-sidenote] 同步主会话模型失败(子会话用默认模型)");
|
|
1246
|
-
} catch (error) {
|
|
1247
|
-
console.warn("[dsh-sidenote] 同步主会话模型失败(子会话用默认模型):", error);
|
|
1248
|
-
}
|
|
1249
|
-
return forked;
|
|
1250
|
-
}
|
|
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;
|
|
1324
|
+
//#region src/client/chat/cards.ts
|
|
1325
|
+
/** ContentBlock[] → 纯文本(transcript.ts 同款的极简版,避免互依赖)。 */
|
|
1326
|
+
function blocksText$1(content) {
|
|
1327
|
+
if (!Array.isArray(content)) return void 0;
|
|
1328
|
+
const parts = [];
|
|
1329
|
+
for (const block of content) {
|
|
1330
|
+
const b = block;
|
|
1331
|
+
if (b?.type === "text" && typeof b.text === "string") parts.push(b.text);
|
|
1278
1332
|
}
|
|
1279
|
-
|
|
1280
|
-
if (bs === void 0) return;
|
|
1281
|
-
const current = parseSideChatMeta(readTab(ctx, sessionId, tabId)?.meta);
|
|
1282
|
-
bs.updateTab(tabId, { meta: mutate(current) });
|
|
1333
|
+
return parts.length === 0 ? void 0 : parts.join("\n");
|
|
1283
1334
|
}
|
|
1284
|
-
/**
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
betterSidebarOf(ctx)?.closeTab(tabId, { sessionId });
|
|
1335
|
+
/** 未知 card 值的 warn-once 登记(T3 纪律:降级不静默)。 */
|
|
1336
|
+
const warnedCards = /* @__PURE__ */ new Set();
|
|
1337
|
+
function warnUnknownCard(value) {
|
|
1338
|
+
if (warnedCards.has(value)) return;
|
|
1339
|
+
warnedCards.add(value);
|
|
1340
|
+
console.warn(`[dsh-sidenote] 未知工具卡种类 "${value}"——降级为 generic 卡(宿主可能扩展了 card union)`);
|
|
1294
1341
|
}
|
|
1295
|
-
/**
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
function ensurePanelOpen(store) {
|
|
1302
|
-
const mutable = store;
|
|
1303
|
-
try {
|
|
1304
|
-
mutable?.update?.((state) => {
|
|
1305
|
-
if (state.panelOpen === false) state.panelOpen = true;
|
|
1306
|
-
});
|
|
1307
|
-
} catch {}
|
|
1342
|
+
/** 相对 cwd 按会话工作区折成绝对路径(UI bridge 责任——presentation 注释明文)。 */
|
|
1343
|
+
function resolveCwd(cwd, cwdBase) {
|
|
1344
|
+
if (cwd === void 0) return cwdBase;
|
|
1345
|
+
if (cwd.startsWith("/") || /^[A-Za-z]:[\\/]/.test(cwd)) return cwd;
|
|
1346
|
+
if (cwdBase === void 0) return cwd;
|
|
1347
|
+
return `${cwdBase.replace(/\/+$/, "")}/${cwd}`;
|
|
1308
1348
|
}
|
|
1309
1349
|
/**
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
1312
|
-
* 运行时自动 open)。feature-check + 吞错,运行时若移除则降级为只发不收。
|
|
1350
|
+
* 展示态路径缩短:>3 段的绝对路径只保末 3 段(≈仓库相对形态,与主区
|
|
1351
|
+
* 「Read · dsh-sidenote/scripts/x.sh」同款)。窄栏标题位只够放语义尾部。
|
|
1313
1352
|
*/
|
|
1314
|
-
function
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
console.warn("[dsh-sidenote] 会话窗口打开失败:", error);
|
|
1319
|
-
});
|
|
1353
|
+
function shortPath(p) {
|
|
1354
|
+
if (!p.startsWith("/")) return p;
|
|
1355
|
+
const segs = p.split("/").filter((s) => s !== "");
|
|
1356
|
+
return segs.length > 3 ? segs.slice(-3).join("/") : p;
|
|
1320
1357
|
}
|
|
1321
1358
|
/**
|
|
1322
|
-
*
|
|
1323
|
-
*
|
|
1324
|
-
* is needed and the read is safe for any session id. Snapshot shape authority:
|
|
1325
|
-
* `@deepseek-ai/dsh-api-session-controller/lib/types/types.d.ts`
|
|
1326
|
-
* (`ModelSelectionProjection = { lastUsed, next }`,两者均可为 null——
|
|
1327
|
-
* `next` 缺省时按投影文档回落 `lastUsed`)。
|
|
1359
|
+
* 标题整形:「Tool /abs/path」→「Tool · 末3段」(主区同名同款);
|
|
1360
|
+
* 裸绝对路径标题 → 末 3 段;不含路径的标题原样。
|
|
1328
1361
|
*/
|
|
1329
|
-
function
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
}
|
|
1362
|
+
function displayTitle(title) {
|
|
1363
|
+
const m = /^([A-Za-z][\w-]*) (\/.+)$/.exec(title);
|
|
1364
|
+
const name = m?.[1];
|
|
1365
|
+
const path = m?.[2];
|
|
1366
|
+
if (name !== void 0 && path !== void 0) return `${name} · ${shortPath(path)}`;
|
|
1367
|
+
return title.startsWith("/") ? shortPath(title) : title;
|
|
1336
1368
|
}
|
|
1337
|
-
/**
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
}
|
|
1351
|
-
continue;
|
|
1352
|
-
}
|
|
1353
|
-
const face = candidate;
|
|
1354
|
-
if (typeof face?.selectModel === "function") return face;
|
|
1355
|
-
if (typeof face?.session?.selectModel === "function") return face.session;
|
|
1369
|
+
/** todo_write 入参形状校验(字段不齐即放弃 todo 卡)。两种数据源形状:
|
|
1370
|
+
* 0.1.1 wire:callView.rawInput = todos 数组本身(dsh-tool-todo presentCall 实证);
|
|
1371
|
+
* 0.1.2 argsRaw:{ todos: [...] }。 */
|
|
1372
|
+
function todoItemsOf(raw) {
|
|
1373
|
+
const list = Array.isArray(raw) ? raw : typeof raw === "object" && raw !== null ? raw.todos : void 0;
|
|
1374
|
+
if (!Array.isArray(list) || list.length === 0) return void 0;
|
|
1375
|
+
const items = [];
|
|
1376
|
+
for (const it of list) {
|
|
1377
|
+
const o = it;
|
|
1378
|
+
if (typeof o?.content !== "string" || typeof o?.status !== "string") return void 0;
|
|
1379
|
+
items.push({
|
|
1380
|
+
content: o.content,
|
|
1381
|
+
status: o.status
|
|
1382
|
+
});
|
|
1356
1383
|
}
|
|
1384
|
+
return items;
|
|
1357
1385
|
}
|
|
1358
|
-
/**
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1386
|
+
/** todo 卡标题:任务 · 非零状态组计数(主区「任务 · N 已完成 · …」同款)。 */
|
|
1387
|
+
function todoTitleOf(items) {
|
|
1388
|
+
let done = 0;
|
|
1389
|
+
let doing = 0;
|
|
1390
|
+
let pending = 0;
|
|
1391
|
+
for (const it of items) if (it.status === "completed") done += 1;
|
|
1392
|
+
else if (it.status === "in_progress") doing += 1;
|
|
1393
|
+
else pending += 1;
|
|
1394
|
+
const parts = [];
|
|
1395
|
+
if (done > 0) parts.push(t("todoDone", { n: done }));
|
|
1396
|
+
if (doing > 0) parts.push(t("todoDoing", { n: doing }));
|
|
1397
|
+
if (pending > 0) parts.push(t("todoPending", { n: pending }));
|
|
1398
|
+
return parts.length === 0 ? t("todoTitle") : `${t("todoTitle")} · ${parts.join(" · ")}`;
|
|
1370
1399
|
}
|
|
1371
1400
|
/**
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
* 属真实失败,不再落旧面重试。
|
|
1401
|
+
* callView + resultView → 一张卡的视图模型。三层判别全在这里;
|
|
1402
|
+
* 结果态优先(title 等字段 result 覆盖 call),缺省逐字段回退。
|
|
1375
1403
|
*/
|
|
1376
|
-
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
} catch {
|
|
1387
|
-
return false;
|
|
1388
|
-
}
|
|
1389
|
-
try {
|
|
1390
|
-
return (await ctx.connection.api.sessions.selectModel(request)).result.ok;
|
|
1391
|
-
} catch {
|
|
1392
|
-
return false;
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
/** 读子会话当前模型名(composer 模型标签用);任何失败回退 null(默认文案)。 */
|
|
1396
|
-
async function readModelName(ctx, sessionId) {
|
|
1397
|
-
return (await readModelSelection(ctx, sessionId))?.model ?? null;
|
|
1398
|
-
}
|
|
1399
|
-
/**
|
|
1400
|
-
* 0.1.2 优先:`uiConversation` 服务 → binding(会话 binding) → target('chat')
|
|
1401
|
-
* → 快照 .legacy 切片(0.1.2 把内容面从 Session 快照顶层拆到此处——
|
|
1402
|
-
* 根因与证据见 reports/ux-review/W00-fork-replay-012.md);0.1.1 回退
|
|
1403
|
-
* Session 快照顶层(nodes 直接在)。均 miss 返回 undefined(面板维持
|
|
1404
|
-
* 既有降级,不崩)。
|
|
1405
|
-
*/
|
|
1406
|
-
function chatSourceOf(ctx, binding) {
|
|
1407
|
-
const session = binding?.session;
|
|
1408
|
-
if (session === void 0) return void 0;
|
|
1409
|
-
try {
|
|
1410
|
-
const target = ctx.get("uiConversation")?.binding?.(binding)?.target?.("chat");
|
|
1411
|
-
if (target !== void 0 && typeof target.getSnapshot === "function" && typeof target.subscribe === "function") return {
|
|
1412
|
-
subscribe: (fn) => target.subscribe(fn),
|
|
1413
|
-
getLegacy: () => {
|
|
1414
|
-
return target.getSnapshot()?.legacy ?? null;
|
|
1415
|
-
}
|
|
1416
|
-
};
|
|
1417
|
-
} catch {}
|
|
1418
|
-
const snap = session.getSnapshot();
|
|
1419
|
-
if (snap !== null && Array.isArray(snap.nodes)) return {
|
|
1420
|
-
subscribe: (fn) => session.subscribe(fn),
|
|
1421
|
-
getLegacy: () => session.getSnapshot()
|
|
1422
|
-
};
|
|
1423
|
-
}
|
|
1424
|
-
/**
|
|
1425
|
-
* 拉会话模型目录:0.1.5 走 `remote.session.modelCatalog()`(目录是宿主级,
|
|
1426
|
-
* 菜单选中项所需的 current 由投影/旧面补上);旧面回退
|
|
1427
|
-
* `connection.api.sessions.models`。失败 null → 菜单保持只读标签态。
|
|
1428
|
-
*/
|
|
1429
|
-
async function listModels(ctx, sessionId) {
|
|
1430
|
-
const face = remoteSessionFace(ctx);
|
|
1431
|
-
if (typeof face?.modelCatalog === "function") try {
|
|
1432
|
-
const result = await face.modelCatalog();
|
|
1433
|
-
if (result.ok) {
|
|
1434
|
-
const catalog = result.value;
|
|
1404
|
+
function cardModelOf(input) {
|
|
1405
|
+
const { callView, resultView } = input;
|
|
1406
|
+
const call = callView ?? void 0;
|
|
1407
|
+
const result = resultView ?? void 0;
|
|
1408
|
+
const card = result?.card ?? call?.card ?? "generic";
|
|
1409
|
+
switch (card) {
|
|
1410
|
+
case "terminal": {
|
|
1411
|
+
const rc = result?.card === "terminal" ? result : void 0;
|
|
1412
|
+
const cc = call?.card === "terminal" ? call : void 0;
|
|
1413
|
+
const command = rc?.title ?? cc?.title ?? input.toolName;
|
|
1435
1414
|
return {
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
...
|
|
1415
|
+
kind: "terminal",
|
|
1416
|
+
title: cc?.description !== void 0 ? `${displayToolName(input.toolName)} · ${cc.description}` : command,
|
|
1417
|
+
command,
|
|
1418
|
+
...cc?.description !== void 0 ? { description: cc.description } : {},
|
|
1419
|
+
...resolveCwd(cc?.cwd, input.cwdBase) !== void 0 ? { cwd: resolveCwd(cc?.cwd, input.cwdBase) } : {},
|
|
1420
|
+
...rc?.output !== void 0 ? { output: rc.output } : {},
|
|
1421
|
+
...rc?.exitCode !== void 0 ? { exitCode: rc.exitCode } : {},
|
|
1422
|
+
...rc?.signal !== void 0 ? { signal: rc.signal } : {}
|
|
1440
1423
|
};
|
|
1441
1424
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
async function switchModel(ctx, sessionId, provider, model) {
|
|
1452
|
-
return await writeModelSelection(ctx, sessionId, {
|
|
1453
|
-
provider,
|
|
1454
|
-
model
|
|
1455
|
-
}) ? model : null;
|
|
1456
|
-
}
|
|
1457
|
-
//#endregion
|
|
1458
|
-
//#region src/client/host/probes.ts
|
|
1459
|
-
const PROBES = [
|
|
1460
|
-
{
|
|
1461
|
-
name: "Session.open()(非 staged 会话开窗)",
|
|
1462
|
-
check: (ctx) => {
|
|
1463
|
-
return typeof ctx.sessions?.binding === "function";
|
|
1464
|
-
},
|
|
1465
|
-
fallback: "侧边聊天消息流降级为只发不收"
|
|
1466
|
-
},
|
|
1467
|
-
{
|
|
1468
|
-
name: "conversation.input 输入机器",
|
|
1469
|
-
check: (ctx) => {
|
|
1470
|
-
return typeof (ctx.get?.("conversation"))?.input?.for === "function";
|
|
1471
|
-
},
|
|
1472
|
-
fallback: "侧边 composer 降级为本地草稿 + session.prompt"
|
|
1473
|
-
},
|
|
1474
|
-
{
|
|
1475
|
-
name: "commandUi(斜杠命令注册)",
|
|
1476
|
-
check: (ctx) => {
|
|
1477
|
-
return typeof (ctx.get?.("commandUi"))?.register === "function";
|
|
1478
|
-
},
|
|
1479
|
-
fallback: "/side 与「/侧边」命令不可用(菜单/顶栏入口仍在)"
|
|
1480
|
-
},
|
|
1481
|
-
{
|
|
1482
|
-
name: "ctx.modules.import(动态组件复用)",
|
|
1483
|
-
check: (ctx) => {
|
|
1484
|
-
return typeof ctx.modules?.import === "function";
|
|
1485
|
-
},
|
|
1486
|
-
fallback: "ProducedFiles 等动态复用件降级为自绘"
|
|
1487
|
-
},
|
|
1488
|
-
{
|
|
1489
|
-
name: "sidebarRight.focus(native tab 程序化聚焦)",
|
|
1490
|
-
check: (ctx) => {
|
|
1491
|
-
if (!nativeSidebarHost(ctx)) return true;
|
|
1492
|
-
return typeof (ctx.get?.("sidebarRight"))?.focus === "function";
|
|
1493
|
-
},
|
|
1494
|
-
fallback: "聚焦既有侧边聊天 tab 不可用(activateTab 在 native 面是空操作)"
|
|
1495
|
-
},
|
|
1496
|
-
{
|
|
1497
|
-
name: "会话模型面(remote.session / connection.api.sessions 双版本链)",
|
|
1498
|
-
check: (ctx) => {
|
|
1499
|
-
if (remoteSessionFace(ctx) !== void 0) return true;
|
|
1500
|
-
return typeof ctx.connection?.api?.sessions?.models === "function";
|
|
1501
|
-
},
|
|
1502
|
-
fallback: "fork 模型同步、模型标签与模型切换不可用(子会话用宿主默认模型)"
|
|
1503
|
-
}
|
|
1504
|
-
];
|
|
1505
|
-
/** 激活时跑一遍探测清单;缺失项逐项 warn-once。返回缺失项数(测试可断言)。 */
|
|
1506
|
-
function probeHost(ctx) {
|
|
1507
|
-
let missing = 0;
|
|
1508
|
-
for (const probe of PROBES) {
|
|
1509
|
-
let ok = false;
|
|
1510
|
-
try {
|
|
1511
|
-
ok = probe.check(ctx);
|
|
1512
|
-
} catch {
|
|
1513
|
-
ok = false;
|
|
1425
|
+
case "diff": {
|
|
1426
|
+
const rc = result?.card === "diff" ? result : void 0;
|
|
1427
|
+
const cc = call?.card === "diff" ? call : void 0;
|
|
1428
|
+
return {
|
|
1429
|
+
kind: "diff",
|
|
1430
|
+
title: displayTitle(rc?.title ?? cc?.title ?? input.toolName),
|
|
1431
|
+
diffs: rc?.diffs ?? cc?.diffs ?? [],
|
|
1432
|
+
...cc?.locations !== void 0 ? { locations: cc.locations } : {}
|
|
1433
|
+
};
|
|
1514
1434
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1435
|
+
case "search": {
|
|
1436
|
+
const rc = result?.card === "search" ? result : void 0;
|
|
1437
|
+
if (rc === void 0) return genericModel(input, call, result);
|
|
1438
|
+
if (rc.shape === "matches") return {
|
|
1439
|
+
kind: "search",
|
|
1440
|
+
title: rc.title ?? call?.title ?? input.toolName,
|
|
1441
|
+
shape: "matches",
|
|
1442
|
+
files: rc.files,
|
|
1443
|
+
truncated: rc.truncated,
|
|
1444
|
+
total: rc.total
|
|
1445
|
+
};
|
|
1446
|
+
if (rc.shape === "paths") return {
|
|
1447
|
+
kind: "search",
|
|
1448
|
+
title: rc.title ?? call?.title ?? input.toolName,
|
|
1449
|
+
shape: "paths",
|
|
1450
|
+
paths: rc.paths,
|
|
1451
|
+
truncated: rc.truncated,
|
|
1452
|
+
total: rc.total
|
|
1453
|
+
};
|
|
1454
|
+
warnUnknownCard(`search/${String(rc.shape)}`);
|
|
1455
|
+
return genericModel(input, call, result);
|
|
1456
|
+
}
|
|
1457
|
+
case "read": {
|
|
1458
|
+
const rc = result?.card === "read" ? result : void 0;
|
|
1459
|
+
if (rc === void 0) return genericModel(input, call, result);
|
|
1460
|
+
return {
|
|
1461
|
+
kind: "read",
|
|
1462
|
+
title: `Read · ${shortPath(rc.path)}`,
|
|
1463
|
+
path: rc.path,
|
|
1464
|
+
lines: rc.lines,
|
|
1465
|
+
totalLines: rc.totalLines,
|
|
1466
|
+
...rc.lang !== void 0 ? { lang: rc.lang } : {}
|
|
1467
|
+
};
|
|
1518
1468
|
}
|
|
1469
|
+
case "web": {
|
|
1470
|
+
const rc = result?.card === "web" ? result : void 0;
|
|
1471
|
+
if (rc === void 0) return genericModel(input, call, result);
|
|
1472
|
+
const base = {
|
|
1473
|
+
kind: "web",
|
|
1474
|
+
title: rc.title ?? call?.title ?? input.toolName
|
|
1475
|
+
};
|
|
1476
|
+
if (rc.kind === "search") {
|
|
1477
|
+
const rs = rc;
|
|
1478
|
+
return {
|
|
1479
|
+
...base,
|
|
1480
|
+
webKind: "search",
|
|
1481
|
+
sources: rs.sources,
|
|
1482
|
+
truncated: rs.truncated,
|
|
1483
|
+
...rs.answer !== void 0 ? { answer: rs.answer } : {}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
if (rc.kind === "fetch") {
|
|
1487
|
+
const rf = rc;
|
|
1488
|
+
return {
|
|
1489
|
+
...base,
|
|
1490
|
+
webKind: "fetch",
|
|
1491
|
+
url: rf.url,
|
|
1492
|
+
statusCode: rf.statusCode,
|
|
1493
|
+
truncated: rf.truncated
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
warnUnknownCard(`web/${String(rc.kind)}`);
|
|
1497
|
+
return genericModel(input, call, result);
|
|
1498
|
+
}
|
|
1499
|
+
case "generic": return genericModel(input, call, result);
|
|
1500
|
+
default:
|
|
1501
|
+
warnUnknownCard(String(card));
|
|
1502
|
+
return genericModel(input, call, result);
|
|
1519
1503
|
}
|
|
1520
|
-
return missing;
|
|
1521
1504
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1505
|
+
function genericModel(input, call, result) {
|
|
1506
|
+
const gc = call?.card === "generic" ? call : void 0;
|
|
1507
|
+
const gr = result?.card === "generic" ? result : void 0;
|
|
1508
|
+
if (input.toolName === "todo_write") {
|
|
1509
|
+
const items = todoItemsOf(gc?.rawInput);
|
|
1510
|
+
if (items !== void 0) return {
|
|
1511
|
+
kind: "todo",
|
|
1512
|
+
title: todoTitleOf(items),
|
|
1513
|
+
items
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
kind: "generic",
|
|
1518
|
+
title: displayTitle(gr?.title ?? gc?.title ?? input.toolName),
|
|
1519
|
+
icon: gc?.kind ?? "other",
|
|
1520
|
+
...gc?.rawInput !== void 0 ? { rawInput: gc.rawInput } : {},
|
|
1521
|
+
bodyText: blocksText$1(gr?.content) ?? blocksText$1(gc?.content) ?? input.rawText,
|
|
1522
|
+
...gc?.locations !== void 0 ? { locations: gc.locations } : {}
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
/** 工具机器名 → 展示名(主区「Bash · …」的同款首字母大写)。 */
|
|
1526
|
+
function displayToolName(name) {
|
|
1527
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
1528
|
+
}
|
|
1529
|
+
/** 结果正文尾标解析(宿主 parseExitStatus 的线格式)。 */
|
|
1530
|
+
function parseExitStatus(text) {
|
|
1531
|
+
const m = /\n\[(?:exit code: (\d+)|killed by signal: ([A-Z]+))\]\s*$/.exec(text);
|
|
1532
|
+
if (m === null) return { output: text };
|
|
1533
|
+
const output = text.slice(0, m.index).replace(/\n+$/, "");
|
|
1534
|
+
if (m[1] !== void 0) return {
|
|
1535
|
+
output,
|
|
1536
|
+
exitCode: Number(m[1])
|
|
1537
|
+
};
|
|
1538
|
+
return {
|
|
1539
|
+
output,
|
|
1540
|
+
signal: m[2]
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
/** FsReadMeta 形状+语义校验(readMetaFromMeta 的忠实子集)。 */
|
|
1544
|
+
function readMetaOf(meta) {
|
|
1545
|
+
if (typeof meta !== "object" || meta === null) return void 0;
|
|
1546
|
+
const m = meta;
|
|
1547
|
+
if (typeof m.path !== "string" || !Array.isArray(m.lines) || typeof m.totalLines !== "number") return void 0;
|
|
1548
|
+
if (!Number.isInteger(m.totalLines) || m.totalLines < 0) return void 0;
|
|
1549
|
+
let prev = 0;
|
|
1550
|
+
const lines = [];
|
|
1551
|
+
for (const raw of m.lines) {
|
|
1552
|
+
const l = raw;
|
|
1553
|
+
if (typeof l?.number !== "number" || typeof l?.text !== "string") return void 0;
|
|
1554
|
+
if (!Number.isInteger(l.number) || l.number <= prev || l.number > m.totalLines) return void 0;
|
|
1555
|
+
prev = l.number;
|
|
1556
|
+
lines.push({
|
|
1557
|
+
number: l.number,
|
|
1558
|
+
text: l.text
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
return {
|
|
1562
|
+
path: m.path,
|
|
1563
|
+
lines,
|
|
1564
|
+
totalLines: m.totalLines,
|
|
1565
|
+
...typeof m.lang === "string" ? { lang: m.lang } : {}
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1536
1568
|
/**
|
|
1537
|
-
*
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1541
|
-
* 确认——注入已入 inbox,chip 消失表达「已注入」)。
|
|
1542
|
-
*
|
|
1543
|
-
* 有界等待:注入尝试绝不能拖住发送手势——300ms 超时即回落搭车。
|
|
1569
|
+
* FsDiffMeta 校验(0.1.2 diff 卡,dsh-tool-fs computeHunkDiffs 产物的忠实子集)。
|
|
1570
|
+
* 返回值三态:{diffs 非空} / 'empty'(空数组——write create/同内容覆写是合法值,
|
|
1571
|
+
* 客户端回退 args 整文件 diff)/ undefined(malformed——降级)。
|
|
1572
|
+
* 逐项校验,一项违规整卡降级(官方 narrowDiffs 同款)。
|
|
1544
1573
|
*/
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
if (
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1574
|
+
function diffMetaOf(meta) {
|
|
1575
|
+
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return void 0;
|
|
1576
|
+
const m = meta;
|
|
1577
|
+
if (!Array.isArray(m.diffs)) return void 0;
|
|
1578
|
+
if (m.diffs.length === 0) return "empty";
|
|
1579
|
+
const diffs = [];
|
|
1580
|
+
for (const raw of m.diffs) {
|
|
1581
|
+
const d = raw;
|
|
1582
|
+
if (typeof d?.path !== "string") return void 0;
|
|
1583
|
+
if (d.oldText !== null && typeof d.oldText !== "string") return void 0;
|
|
1584
|
+
if (typeof d.newText !== "string") return void 0;
|
|
1585
|
+
diffs.push({
|
|
1586
|
+
path: d.path,
|
|
1587
|
+
oldText: d.oldText,
|
|
1588
|
+
newText: d.newText
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
return { diffs };
|
|
1592
|
+
}
|
|
1593
|
+
/** SearchMeta 校验(0.1.2 search 卡;truncated/total 是硬字段;空结果合法)。 */
|
|
1594
|
+
function searchMetaOf(meta) {
|
|
1595
|
+
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return void 0;
|
|
1596
|
+
const m = meta;
|
|
1597
|
+
if (typeof m.truncated !== "boolean") return void 0;
|
|
1598
|
+
if (typeof m.total !== "number" || !Number.isInteger(m.total) || m.total < 0) return void 0;
|
|
1599
|
+
if (m.shape === "matches") {
|
|
1600
|
+
if (!Array.isArray(m.files)) return void 0;
|
|
1601
|
+
const files = [];
|
|
1602
|
+
for (const rawF of m.files) {
|
|
1603
|
+
const f = rawF;
|
|
1604
|
+
if (typeof f?.path !== "string" || !Array.isArray(f.matches)) return void 0;
|
|
1605
|
+
const matches = [];
|
|
1606
|
+
for (const rawM of f.matches) {
|
|
1607
|
+
const mm = rawM;
|
|
1608
|
+
if (typeof mm?.lineNumber !== "number" || !Number.isInteger(mm.lineNumber) || mm.lineNumber < 1) return void 0;
|
|
1609
|
+
if (typeof mm.line !== "string") return void 0;
|
|
1610
|
+
matches.push({
|
|
1611
|
+
lineNumber: mm.lineNumber,
|
|
1612
|
+
line: mm.line
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
files.push({
|
|
1616
|
+
path: f.path,
|
|
1617
|
+
matches
|
|
1568
1618
|
});
|
|
1569
|
-
} finally {
|
|
1570
|
-
clearTimeout(timer);
|
|
1571
1619
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1620
|
+
return {
|
|
1621
|
+
shape: "matches",
|
|
1622
|
+
files,
|
|
1623
|
+
truncated: m.truncated,
|
|
1624
|
+
total: m.total
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
if (m.shape === "paths") {
|
|
1628
|
+
if (!Array.isArray(m.paths) || !m.paths.every((p) => typeof p === "string")) return void 0;
|
|
1629
|
+
return {
|
|
1630
|
+
shape: "paths",
|
|
1631
|
+
paths: m.paths,
|
|
1632
|
+
truncated: m.truncated,
|
|
1633
|
+
total: m.total
|
|
1634
|
+
};
|
|
1577
1635
|
}
|
|
1578
1636
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1637
|
+
/** WebSearchMeta 校验(sources 逐项 url 必填、title/snippet 可选;publishedAt 校验后丢弃)。 */
|
|
1638
|
+
function webSearchMetaOf(meta) {
|
|
1639
|
+
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return void 0;
|
|
1640
|
+
const m = meta;
|
|
1641
|
+
if (typeof m.truncated !== "boolean") return void 0;
|
|
1642
|
+
if (!Array.isArray(m.sources)) return void 0;
|
|
1643
|
+
const sources = [];
|
|
1644
|
+
for (const raw of m.sources) {
|
|
1645
|
+
const s = raw;
|
|
1646
|
+
if (typeof s?.url !== "string") return void 0;
|
|
1647
|
+
if (s.title !== void 0 && typeof s.title !== "string") return void 0;
|
|
1648
|
+
if (s.snippet !== void 0 && typeof s.snippet !== "string") return void 0;
|
|
1649
|
+
if (s.publishedAt !== void 0 && typeof s.publishedAt !== "string") return void 0;
|
|
1650
|
+
sources.push({
|
|
1651
|
+
url: s.url,
|
|
1652
|
+
...s.title !== void 0 ? { title: s.title } : {},
|
|
1653
|
+
...s.snippet !== void 0 ? { snippet: s.snippet } : {}
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
if (m.answer !== void 0 && typeof m.answer !== "string") return void 0;
|
|
1657
|
+
return {
|
|
1658
|
+
sources,
|
|
1659
|
+
truncated: m.truncated,
|
|
1660
|
+
...m.answer !== void 0 ? { answer: m.answer } : {}
|
|
1661
|
+
};
|
|
1597
1662
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
if (typeof
|
|
1663
|
+
/** WebFetchMeta 校验(url + 整数 statusCode + truncated 硬字段)。 */
|
|
1664
|
+
function webFetchMetaOf(meta) {
|
|
1665
|
+
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return void 0;
|
|
1666
|
+
const m = meta;
|
|
1667
|
+
if (typeof m.url !== "string") return void 0;
|
|
1668
|
+
if (typeof m.statusCode !== "number" || !Number.isInteger(m.statusCode)) return void 0;
|
|
1669
|
+
if (typeof m.truncated !== "boolean") return void 0;
|
|
1603
1670
|
return {
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
text: r.text,
|
|
1608
|
-
...typeof r.question === "string" && r.question.trim() !== "" ? { question: r.question } : {},
|
|
1609
|
-
createdAt: typeof r.createdAt === "number" ? r.createdAt : 0
|
|
1671
|
+
url: m.url,
|
|
1672
|
+
statusCode: m.statusCode,
|
|
1673
|
+
truncated: m.truncated
|
|
1610
1674
|
};
|
|
1611
1675
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1676
|
+
/** argsRaw(JSON 字符串)里尽力提取展示字段。 */
|
|
1677
|
+
function argsSummary(argsRaw) {
|
|
1678
|
+
if (argsRaw === void 0 || argsRaw === "") return {};
|
|
1679
|
+
try {
|
|
1680
|
+
const parsed = JSON.parse(argsRaw);
|
|
1681
|
+
if (typeof parsed !== "object" || parsed === null) return {};
|
|
1682
|
+
const a = parsed;
|
|
1683
|
+
return {
|
|
1684
|
+
...typeof a.command === "string" ? { command: a.command } : {},
|
|
1685
|
+
...typeof a.description === "string" ? { description: a.description } : {},
|
|
1686
|
+
...typeof a.file_path === "string" ? { path: a.file_path } : typeof a.path === "string" ? { path: a.path } : {},
|
|
1687
|
+
...Array.isArray(a.todos) ? { todos: a.todos } : {},
|
|
1688
|
+
...typeof a.content === "string" ? { content: a.content } : {},
|
|
1689
|
+
...typeof a.old_string === "string" ? { oldString: a.old_string } : {},
|
|
1690
|
+
...typeof a.new_string === "string" ? { newString: a.new_string } : {},
|
|
1691
|
+
...typeof a.pattern === "string" ? { pattern: a.pattern } : {},
|
|
1692
|
+
...typeof a.include === "string" ? { include: a.include } : {},
|
|
1693
|
+
...Array.isArray(a.queries) && a.queries.every((q) => typeof q === "string") ? { queries: a.queries } : {},
|
|
1694
|
+
...typeof a.url === "string" ? { url: a.url } : {}
|
|
1695
|
+
};
|
|
1696
|
+
} catch {
|
|
1697
|
+
return {};
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* 0.1.2 推导路径:节点原始字段 → ToolCardModel。与 cardModelOf(0.1.1 wire
|
|
1702
|
+
* 面)平级;选择点在 transcript 层(节点带不带 callView 一眼可分)。
|
|
1703
|
+
*/
|
|
1704
|
+
function cardModelFromNode(input) {
|
|
1705
|
+
const args = argsSummary(input.argsRaw);
|
|
1706
|
+
const name = input.name;
|
|
1707
|
+
if (name === "bash" || name === "bash-persistent" || name === "pwsh" || name === "pwsh-persistent") {
|
|
1708
|
+
const { output, exitCode, signal } = parseExitStatus(input.rawText);
|
|
1709
|
+
const command = args.command ?? name;
|
|
1710
|
+
return {
|
|
1711
|
+
kind: "terminal",
|
|
1712
|
+
title: args.description !== void 0 ? `${displayToolName(name)} · ${args.description}` : command,
|
|
1713
|
+
command,
|
|
1714
|
+
...args.description !== void 0 ? { description: args.description } : {},
|
|
1715
|
+
output,
|
|
1716
|
+
...exitCode !== void 0 ? { exitCode } : {},
|
|
1717
|
+
...signal !== void 0 ? { signal } : {}
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
if (name === "read") {
|
|
1721
|
+
const meta = readMetaOf(input.meta);
|
|
1722
|
+
if (meta !== void 0) return {
|
|
1723
|
+
kind: "read",
|
|
1724
|
+
title: `Read · ${shortPath(meta.path)}`,
|
|
1725
|
+
path: meta.path,
|
|
1726
|
+
lines: meta.lines,
|
|
1727
|
+
totalLines: meta.totalLines,
|
|
1728
|
+
...meta.lang !== void 0 ? { lang: meta.lang } : {}
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
if (name === "todo_write") {
|
|
1732
|
+
const items = todoItemsOf(args.todos);
|
|
1733
|
+
if (items !== void 0) return {
|
|
1734
|
+
kind: "todo",
|
|
1735
|
+
title: todoTitleOf(items),
|
|
1736
|
+
items
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
if ((name === "write" || name === "edit") && input.isError !== true) {
|
|
1740
|
+
const path = args.path;
|
|
1741
|
+
if (path !== void 0 && path.trim() !== "") {
|
|
1742
|
+
const dm = diffMetaOf(input.meta);
|
|
1743
|
+
if (dm !== void 0 && dm !== "empty") return {
|
|
1744
|
+
kind: "diff",
|
|
1745
|
+
title: displayTitle(`${name === "write" ? "Write" : "Edit"} ${path}`),
|
|
1746
|
+
diffs: dm.diffs,
|
|
1747
|
+
locations: [{ path }]
|
|
1748
|
+
};
|
|
1749
|
+
if (name === "write" && args.content !== void 0) return {
|
|
1750
|
+
kind: "diff",
|
|
1751
|
+
title: displayTitle(`Write ${path}`),
|
|
1752
|
+
diffs: [{
|
|
1753
|
+
path,
|
|
1754
|
+
oldText: null,
|
|
1755
|
+
newText: args.content
|
|
1756
|
+
}],
|
|
1757
|
+
locations: [{ path }]
|
|
1758
|
+
};
|
|
1633
1759
|
}
|
|
1634
|
-
} catch (error) {
|
|
1635
|
-
console.warn("[dsh-sidenote] 回流持久化读取失败(按空起步):", error);
|
|
1636
1760
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1761
|
+
if ((name === "grep" || name === "glob") && input.isError !== true) {
|
|
1762
|
+
const sm = searchMetaOf(input.meta);
|
|
1763
|
+
const wantShape = name === "grep" ? "matches" : "paths";
|
|
1764
|
+
if (sm !== void 0 && sm.shape === wantShape && args.pattern !== void 0) {
|
|
1765
|
+
const where = args.path !== void 0 && args.path.trim() !== "" ? ` in ${shortPath(args.path)}` : "";
|
|
1766
|
+
const inc = name === "grep" && args.include !== void 0 && args.include.trim() !== "" ? ` (${args.include})` : "";
|
|
1767
|
+
const title = `${name === "grep" ? "Grep" : "Glob"} ${args.pattern}${where}${inc}`;
|
|
1768
|
+
return sm.shape === "matches" ? {
|
|
1769
|
+
kind: "search",
|
|
1770
|
+
title,
|
|
1771
|
+
shape: "matches",
|
|
1772
|
+
files: sm.files,
|
|
1773
|
+
truncated: sm.truncated,
|
|
1774
|
+
total: sm.total
|
|
1775
|
+
} : {
|
|
1776
|
+
kind: "search",
|
|
1777
|
+
title,
|
|
1778
|
+
shape: "paths",
|
|
1779
|
+
paths: sm.paths,
|
|
1780
|
+
truncated: sm.truncated,
|
|
1781
|
+
total: sm.total
|
|
1782
|
+
};
|
|
1646
1783
|
}
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1784
|
+
}
|
|
1785
|
+
if (name === "web_search" && input.isError !== true) {
|
|
1786
|
+
const wm = webSearchMetaOf(input.meta);
|
|
1787
|
+
const queries = args.queries?.filter((q) => q.trim() !== "") ?? [];
|
|
1788
|
+
if (wm !== void 0 && queries.length > 0) return {
|
|
1789
|
+
kind: "web",
|
|
1790
|
+
title: queries.join(", "),
|
|
1791
|
+
webKind: "search",
|
|
1792
|
+
sources: wm.sources,
|
|
1793
|
+
truncated: wm.truncated,
|
|
1794
|
+
...wm.answer !== void 0 ? { answer: wm.answer } : {}
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
if (name === "web_fetch" && input.isError !== true) {
|
|
1798
|
+
const wm = webFetchMetaOf(input.meta);
|
|
1799
|
+
if (wm !== void 0 && args.url !== void 0 && args.url.trim() !== "") return {
|
|
1800
|
+
kind: "web",
|
|
1801
|
+
title: args.url,
|
|
1802
|
+
webKind: "fetch",
|
|
1803
|
+
url: wm.url,
|
|
1804
|
+
statusCode: wm.statusCode,
|
|
1805
|
+
truncated: wm.truncated
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1652
1808
|
return {
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
listeners.delete(fn);
|
|
1658
|
-
};
|
|
1659
|
-
},
|
|
1660
|
-
add(sessionId, sideTitle, text, question) {
|
|
1661
|
-
const item = {
|
|
1662
|
-
id: nextId,
|
|
1663
|
-
sessionId,
|
|
1664
|
-
sideTitle,
|
|
1665
|
-
text,
|
|
1666
|
-
...typeof question === "string" && question.trim() !== "" ? { question } : {},
|
|
1667
|
-
createdAt: now()
|
|
1668
|
-
};
|
|
1669
|
-
nextId += 1;
|
|
1670
|
-
items = [...items, item];
|
|
1671
|
-
persist(sessionId);
|
|
1672
|
-
notify();
|
|
1673
|
-
return item;
|
|
1674
|
-
},
|
|
1675
|
-
remove(id) {
|
|
1676
|
-
const target = items.find((i) => i.id === id);
|
|
1677
|
-
if (target === void 0) return;
|
|
1678
|
-
items = items.filter((i) => i.id !== id);
|
|
1679
|
-
persist(target.sessionId);
|
|
1680
|
-
notify();
|
|
1681
|
-
},
|
|
1682
|
-
clearSession(sessionId) {
|
|
1683
|
-
if (!items.some((i) => i.sessionId === sessionId)) return;
|
|
1684
|
-
items = items.filter((i) => i.sessionId !== sessionId);
|
|
1685
|
-
persist(sessionId);
|
|
1686
|
-
notify();
|
|
1687
|
-
},
|
|
1688
|
-
list: (sessionId) => items.filter((i) => i.sessionId === sessionId)
|
|
1809
|
+
kind: "generic",
|
|
1810
|
+
title: args.path !== void 0 ? `${name} · ${shortPath(args.path)}` : args.command ?? name,
|
|
1811
|
+
icon: KIND_BY_NAME[name] ?? "other",
|
|
1812
|
+
bodyText: input.rawText === "" ? void 0 : input.rawText
|
|
1689
1813
|
};
|
|
1690
1814
|
}
|
|
1815
|
+
/** 工具名 → 图标类别的静态映射(0.1.2 没有 callView.kind 可用)。
|
|
1816
|
+
* wire 名实证:str_replace_editor 是下划线(uitool client.js L381);
|
|
1817
|
+
* web_search 的 0.1.1 wire kind 是 'search'(presentSearchCall),
|
|
1818
|
+
* 'fetch' 是 web_fetch 的(presentFetchCall)。 */
|
|
1819
|
+
const KIND_BY_NAME = {
|
|
1820
|
+
read: "read",
|
|
1821
|
+
write: "edit",
|
|
1822
|
+
edit: "edit",
|
|
1823
|
+
str_replace_editor: "edit",
|
|
1824
|
+
bash: "execute",
|
|
1825
|
+
"bash-persistent": "execute",
|
|
1826
|
+
pwsh: "execute",
|
|
1827
|
+
"pwsh-persistent": "execute",
|
|
1828
|
+
grep: "search",
|
|
1829
|
+
glob: "search",
|
|
1830
|
+
web_search: "search",
|
|
1831
|
+
web_fetch: "fetch"
|
|
1832
|
+
};
|
|
1691
1833
|
//#endregion
|
|
1692
|
-
//#region src/client/
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1834
|
+
//#region src/client/chat/transcript.ts
|
|
1835
|
+
/** 工具结果正文截断上限(面板是窄栏,超长输出不撑爆 DOM)。 */
|
|
1836
|
+
const TOOL_TEXT_LIMIT = 4e3;
|
|
1837
|
+
function truncateText(text, max) {
|
|
1838
|
+
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
1839
|
+
}
|
|
1840
|
+
/** ContentBlock[] → 纯文本:text 块拼接;image 块降级占位;其余忽略。 */
|
|
1841
|
+
function contentTextOf(content) {
|
|
1842
|
+
if (!Array.isArray(content)) return "";
|
|
1699
1843
|
const parts = [];
|
|
1700
1844
|
for (const block of content) {
|
|
1845
|
+
if (typeof block !== "object" || block === null) continue;
|
|
1701
1846
|
const b = block;
|
|
1702
|
-
if (b
|
|
1847
|
+
if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
|
|
1848
|
+
else if (b.type === "image") parts.push(t("imagePlaceholder"));
|
|
1703
1849
|
}
|
|
1704
|
-
return parts.
|
|
1705
|
-
}
|
|
1706
|
-
/** 未知 card 值的 warn-once 登记(T3 纪律:降级不静默)。 */
|
|
1707
|
-
const warnedCards = /* @__PURE__ */ new Set();
|
|
1708
|
-
function warnUnknownCard(value) {
|
|
1709
|
-
if (warnedCards.has(value)) return;
|
|
1710
|
-
warnedCards.add(value);
|
|
1711
|
-
console.warn(`[dsh-sidenote] 未知工具卡种类 "${value}"——降级为 generic 卡(宿主可能扩展了 card union)`);
|
|
1712
|
-
}
|
|
1713
|
-
/** 相对 cwd 按会话工作区折成绝对路径(UI bridge 责任——presentation 注释明文)。 */
|
|
1714
|
-
function resolveCwd(cwd, cwdBase) {
|
|
1715
|
-
if (cwd === void 0) return cwdBase;
|
|
1716
|
-
if (cwd.startsWith("/") || /^[A-Za-z]:[\\/]/.test(cwd)) return cwd;
|
|
1717
|
-
if (cwdBase === void 0) return cwd;
|
|
1718
|
-
return `${cwdBase.replace(/\/+$/, "")}/${cwd}`;
|
|
1850
|
+
return parts.join("\n");
|
|
1719
1851
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
* 「Read · dsh-sidenote/scripts/x.sh」同款)。窄栏标题位只够放语义尾部。
|
|
1723
|
-
*/
|
|
1724
|
-
function shortPath(p) {
|
|
1725
|
-
if (!p.startsWith("/")) return p;
|
|
1726
|
-
const segs = p.split("/").filter((s) => s !== "");
|
|
1727
|
-
return segs.length > 3 ? segs.slice(-3).join("/") : p;
|
|
1852
|
+
function seqKey(prefix, node) {
|
|
1853
|
+
return `${prefix}:${typeof node.seq === "number" ? node.seq : "?"}`;
|
|
1728
1854
|
}
|
|
1729
|
-
/**
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
*/
|
|
1733
|
-
function displayTitle(title) {
|
|
1734
|
-
const m = /^([A-Za-z][\w-]*) (\/.+)$/.exec(title);
|
|
1735
|
-
const name = m?.[1];
|
|
1736
|
-
const path = m?.[2];
|
|
1737
|
-
if (name !== void 0 && path !== void 0) return `${name} · ${shortPath(path)}`;
|
|
1738
|
-
return title.startsWith("/") ? shortPath(title) : title;
|
|
1855
|
+
/** 节点 seq 随行(D1 折叠边界用;缺省不带)。 */
|
|
1856
|
+
function seqOf(node) {
|
|
1857
|
+
return typeof node.seq === "number" ? { seq: node.seq } : {};
|
|
1739
1858
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
if (
|
|
1750
|
-
items.push({
|
|
1751
|
-
content: o.content,
|
|
1752
|
-
status: o.status
|
|
1753
|
-
});
|
|
1859
|
+
function assistantParts(blocks) {
|
|
1860
|
+
const texts = [];
|
|
1861
|
+
const reasonings = [];
|
|
1862
|
+
let hasToolCall = false;
|
|
1863
|
+
if (Array.isArray(blocks)) for (const block of blocks) {
|
|
1864
|
+
if (typeof block !== "object" || block === null) continue;
|
|
1865
|
+
const b = block;
|
|
1866
|
+
if (b.kind === "text" && typeof b.text === "string") texts.push(b.text);
|
|
1867
|
+
else if (b.kind === "reasoning" && typeof b.text === "string") reasonings.push(b.text);
|
|
1868
|
+
else if (b.kind === "tool-call") hasToolCall = true;
|
|
1754
1869
|
}
|
|
1755
|
-
return
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
let doing = 0;
|
|
1761
|
-
let pending = 0;
|
|
1762
|
-
for (const it of items) if (it.status === "completed") done += 1;
|
|
1763
|
-
else if (it.status === "in_progress") doing += 1;
|
|
1764
|
-
else pending += 1;
|
|
1765
|
-
const parts = [];
|
|
1766
|
-
if (done > 0) parts.push(t("todoDone", { n: done }));
|
|
1767
|
-
if (doing > 0) parts.push(t("todoDoing", { n: doing }));
|
|
1768
|
-
if (pending > 0) parts.push(t("todoPending", { n: pending }));
|
|
1769
|
-
return parts.length === 0 ? t("todoTitle") : `${t("todoTitle")} · ${parts.join(" · ")}`;
|
|
1870
|
+
return {
|
|
1871
|
+
text: texts.join("\n\n"),
|
|
1872
|
+
reasoning: reasonings.join("\n\n"),
|
|
1873
|
+
hasToolCall
|
|
1874
|
+
};
|
|
1770
1875
|
}
|
|
1771
1876
|
/**
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1774
|
-
*/
|
|
1775
|
-
function
|
|
1776
|
-
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1877
|
+
* 单个 ConversationNode → ChatMessage;返回 null = 面板不渲染
|
|
1878
|
+
* (context 注入、未知面事件、已取消的重试等对读者无信息的节点)。
|
|
1879
|
+
*/
|
|
1880
|
+
function nodeToMessage(node) {
|
|
1881
|
+
if (typeof node !== "object" || node === null) return null;
|
|
1882
|
+
const n = node;
|
|
1883
|
+
switch (n.kind) {
|
|
1884
|
+
case "user": return {
|
|
1885
|
+
key: seqKey("u", n),
|
|
1886
|
+
...seqOf(n),
|
|
1887
|
+
role: "user",
|
|
1888
|
+
text: contentTextOf(n.content)
|
|
1889
|
+
};
|
|
1890
|
+
case "steering": return {
|
|
1891
|
+
key: seqKey("s", n),
|
|
1892
|
+
...seqOf(n),
|
|
1893
|
+
role: "user",
|
|
1894
|
+
text: contentTextOf(n.content)
|
|
1895
|
+
};
|
|
1896
|
+
case "assistant": {
|
|
1897
|
+
const { text, reasoning, hasToolCall } = assistantParts(n.blocks);
|
|
1898
|
+
if (text === "" && reasoning === "" && hasToolCall) return null;
|
|
1785
1899
|
return {
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
...
|
|
1791
|
-
...
|
|
1792
|
-
...rc?.exitCode !== void 0 ? { exitCode: rc.exitCode } : {},
|
|
1793
|
-
...rc?.signal !== void 0 ? { signal: rc.signal } : {}
|
|
1900
|
+
key: seqKey("a", n),
|
|
1901
|
+
...seqOf(n),
|
|
1902
|
+
role: "assistant",
|
|
1903
|
+
text,
|
|
1904
|
+
...reasoning !== "" ? { reasoning } : {},
|
|
1905
|
+
...n.interrupted === true ? { interrupted: true } : {}
|
|
1794
1906
|
};
|
|
1795
1907
|
}
|
|
1796
|
-
case "
|
|
1797
|
-
const
|
|
1798
|
-
const
|
|
1908
|
+
case "tool-result": {
|
|
1909
|
+
const call = n.call;
|
|
1910
|
+
const toolName = typeof call?.name === "string" ? call.name : typeof n.callId === "string" ? n.callId : t("toolFallback");
|
|
1911
|
+
const text = truncateText(contentTextOf(n.content), TOOL_TEXT_LIMIT);
|
|
1912
|
+
const card = (n.callView ?? n.resultView) != null ? cardModelOf({
|
|
1913
|
+
toolName,
|
|
1914
|
+
callView: n.callView,
|
|
1915
|
+
resultView: n.resultView,
|
|
1916
|
+
rawText: text
|
|
1917
|
+
}) : cardModelFromNode({
|
|
1918
|
+
name: toolName,
|
|
1919
|
+
argsRaw: typeof call?.argsRaw === "string" ? call.argsRaw : void 0,
|
|
1920
|
+
meta: n.meta,
|
|
1921
|
+
rawText: text,
|
|
1922
|
+
...n.isError === true ? { isError: true } : {}
|
|
1923
|
+
});
|
|
1799
1924
|
return {
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
const rc = result?.card === "search" ? result : void 0;
|
|
1808
|
-
if (rc === void 0) return genericModel(input, call, result);
|
|
1809
|
-
if (rc.shape === "matches") return {
|
|
1810
|
-
kind: "search",
|
|
1811
|
-
title: rc.title ?? call?.title ?? input.toolName,
|
|
1812
|
-
shape: "matches",
|
|
1813
|
-
files: rc.files,
|
|
1814
|
-
truncated: rc.truncated,
|
|
1815
|
-
total: rc.total
|
|
1816
|
-
};
|
|
1817
|
-
if (rc.shape === "paths") return {
|
|
1818
|
-
kind: "search",
|
|
1819
|
-
title: rc.title ?? call?.title ?? input.toolName,
|
|
1820
|
-
shape: "paths",
|
|
1821
|
-
paths: rc.paths,
|
|
1822
|
-
truncated: rc.truncated,
|
|
1823
|
-
total: rc.total
|
|
1925
|
+
key: seqKey("t", n),
|
|
1926
|
+
...seqOf(n),
|
|
1927
|
+
role: "tool",
|
|
1928
|
+
toolName,
|
|
1929
|
+
text,
|
|
1930
|
+
card,
|
|
1931
|
+
...n.isError === true ? { isError: true } : {}
|
|
1824
1932
|
};
|
|
1825
|
-
warnUnknownCard(`search/${String(rc.shape)}`);
|
|
1826
|
-
return genericModel(input, call, result);
|
|
1827
1933
|
}
|
|
1828
|
-
case "
|
|
1829
|
-
|
|
1830
|
-
|
|
1934
|
+
case "turn-error": return {
|
|
1935
|
+
key: seqKey("e", n),
|
|
1936
|
+
...seqOf(n),
|
|
1937
|
+
role: "error",
|
|
1938
|
+
text: typeof n.message === "string" ? n.message : t("unknownError")
|
|
1939
|
+
};
|
|
1940
|
+
case "model-retry":
|
|
1941
|
+
if (n.retryState === "cancelled") return null;
|
|
1831
1942
|
return {
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
totalLines: rc.totalLines,
|
|
1837
|
-
...rc.lang !== void 0 ? { lang: rc.lang } : {}
|
|
1943
|
+
key: seqKey("r", n),
|
|
1944
|
+
...seqOf(n),
|
|
1945
|
+
role: "notice",
|
|
1946
|
+
text: t(n.retryState === "started" ? "modelRetryStarted" : "modelRetryWaiting")
|
|
1838
1947
|
};
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1948
|
+
case "turn-max-tokens": return {
|
|
1949
|
+
key: seqKey("m", n),
|
|
1950
|
+
...seqOf(n),
|
|
1951
|
+
role: "notice",
|
|
1952
|
+
text: t("maxTokens")
|
|
1953
|
+
};
|
|
1954
|
+
case "command": {
|
|
1955
|
+
const name = typeof n.name === "string" && n.name !== "" ? n.name : t("commandNameFallback");
|
|
1956
|
+
const args = typeof n.args === "string" ? n.args.trimStart() : "";
|
|
1957
|
+
return {
|
|
1958
|
+
key: seqKey("c", n),
|
|
1959
|
+
...seqOf(n),
|
|
1960
|
+
role: "notice",
|
|
1961
|
+
text: t("runCommand", { cmd: `/${name}${args === "" ? "" : ` ${args}`}` })
|
|
1846
1962
|
};
|
|
1847
|
-
if (rc.kind === "search") {
|
|
1848
|
-
const rs = rc;
|
|
1849
|
-
return {
|
|
1850
|
-
...base,
|
|
1851
|
-
webKind: "search",
|
|
1852
|
-
sources: rs.sources,
|
|
1853
|
-
truncated: rs.truncated,
|
|
1854
|
-
...rs.answer !== void 0 ? { answer: rs.answer } : {}
|
|
1855
|
-
};
|
|
1856
|
-
}
|
|
1857
|
-
if (rc.kind === "fetch") {
|
|
1858
|
-
const rf = rc;
|
|
1859
|
-
return {
|
|
1860
|
-
...base,
|
|
1861
|
-
webKind: "fetch",
|
|
1862
|
-
url: rf.url,
|
|
1863
|
-
statusCode: rf.statusCode,
|
|
1864
|
-
truncated: rf.truncated
|
|
1865
|
-
};
|
|
1866
|
-
}
|
|
1867
|
-
warnUnknownCard(`web/${String(rc.kind)}`);
|
|
1868
|
-
return genericModel(input, call, result);
|
|
1869
1963
|
}
|
|
1870
|
-
case "
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
}
|
|
1876
|
-
function genericModel(input, call, result) {
|
|
1877
|
-
const gc = call?.card === "generic" ? call : void 0;
|
|
1878
|
-
const gr = result?.card === "generic" ? result : void 0;
|
|
1879
|
-
if (input.toolName === "todo_write") {
|
|
1880
|
-
const items = todoItemsOf(gc?.rawInput);
|
|
1881
|
-
if (items !== void 0) return {
|
|
1882
|
-
kind: "todo",
|
|
1883
|
-
title: todoTitleOf(items),
|
|
1884
|
-
items
|
|
1964
|
+
case "compaction": return {
|
|
1965
|
+
key: seqKey("k", n),
|
|
1966
|
+
...seqOf(n),
|
|
1967
|
+
role: "notice",
|
|
1968
|
+
text: t("compacted")
|
|
1885
1969
|
};
|
|
1970
|
+
default: return null;
|
|
1886
1971
|
}
|
|
1887
|
-
return {
|
|
1888
|
-
kind: "generic",
|
|
1889
|
-
title: displayTitle(gr?.title ?? gc?.title ?? input.toolName),
|
|
1890
|
-
icon: gc?.kind ?? "other",
|
|
1891
|
-
...gc?.rawInput !== void 0 ? { rawInput: gc.rawInput } : {},
|
|
1892
|
-
bodyText: blocksText(gr?.content) ?? blocksText(gc?.content) ?? input.rawText,
|
|
1893
|
-
...gc?.locations !== void 0 ? { locations: gc.locations } : {}
|
|
1894
|
-
};
|
|
1895
1972
|
}
|
|
1896
|
-
/**
|
|
1897
|
-
|
|
1898
|
-
|
|
1973
|
+
/**
|
|
1974
|
+
* ConversationSnapshot → 渲染消息列表:终态节点 + 在途项(按 turn/step 归并)。
|
|
1975
|
+
* 快照缺省(未绑定)时为空列表。
|
|
1976
|
+
*
|
|
1977
|
+
* 时序归并(WI-01):在途项不再恒追加尾部——真实交错是「partial 文本 → 它
|
|
1978
|
+
* 自己 step 发出的工具卡」:partial 与同 step 的 runningCall 并存时必须文本
|
|
1979
|
+
* 在前(原文早于调用),不同 step 按 (turn, step) 升序;并行多 call 保持
|
|
1980
|
+
* 宿主 dispatch 序(同 turn/step 内稳定)。终态节点自带 seq 全序,不参与
|
|
1981
|
+
* 归并(在途项永远属于当前 turn 的前沿,尾部插入点天然正确)。
|
|
1982
|
+
*/
|
|
1983
|
+
function transcriptOf(snapshot) {
|
|
1984
|
+
if (snapshot === void 0 || snapshot === null) return [];
|
|
1985
|
+
const out = [];
|
|
1986
|
+
for (const node of snapshot.nodes ?? []) {
|
|
1987
|
+
const message = nodeToMessage(node);
|
|
1988
|
+
if (message !== null) out.push(message);
|
|
1989
|
+
}
|
|
1990
|
+
const inflight = [];
|
|
1991
|
+
const partial = snapshot.partial;
|
|
1992
|
+
if (partial !== void 0 && partial !== null) {
|
|
1993
|
+
const { text, reasoning, hasToolCall } = assistantParts(partial.blocks);
|
|
1994
|
+
if (text !== "" || reasoning !== "" || !hasToolCall) inflight.push({
|
|
1995
|
+
turn: typeof partial.turn === "number" ? partial.turn : Number.MAX_SAFE_INTEGER,
|
|
1996
|
+
step: typeof partial.step === "number" ? partial.step : Number.MAX_SAFE_INTEGER,
|
|
1997
|
+
order: 0,
|
|
1998
|
+
message: {
|
|
1999
|
+
key: "partial",
|
|
2000
|
+
role: "assistant",
|
|
2001
|
+
text,
|
|
2002
|
+
...reasoning !== "" ? { reasoning } : {},
|
|
2003
|
+
streaming: true
|
|
2004
|
+
}
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
if (Array.isArray(snapshot.runningCalls)) for (const call of snapshot.runningCalls) {
|
|
2008
|
+
if (typeof call !== "object" || call === null) continue;
|
|
2009
|
+
const c = call;
|
|
2010
|
+
const toolName = typeof c.name === "string" ? c.name : t("toolFallback");
|
|
2011
|
+
inflight.push({
|
|
2012
|
+
turn: typeof c.turn === "number" ? c.turn : Number.MAX_SAFE_INTEGER,
|
|
2013
|
+
step: typeof c.step === "number" ? c.step : Number.MAX_SAFE_INTEGER,
|
|
2014
|
+
order: 1,
|
|
2015
|
+
message: {
|
|
2016
|
+
key: `rc:${typeof c.callId === "string" ? c.callId : "?"}`,
|
|
2017
|
+
role: "tool",
|
|
2018
|
+
toolName,
|
|
2019
|
+
text: "",
|
|
2020
|
+
streaming: true,
|
|
2021
|
+
card: c.callView != null ? cardModelOf({
|
|
2022
|
+
toolName,
|
|
2023
|
+
callView: c.callView,
|
|
2024
|
+
resultView: null
|
|
2025
|
+
}) : cardModelFromNode({
|
|
2026
|
+
name: toolName,
|
|
2027
|
+
argsRaw: typeof c.argsRaw === "string" ? c.argsRaw : void 0,
|
|
2028
|
+
rawText: ""
|
|
2029
|
+
})
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
inflight.sort((a, b) => a.turn - b.turn || a.step - b.step || a.order - b.order);
|
|
2034
|
+
for (const item of inflight) out.push(item.message);
|
|
2035
|
+
return out;
|
|
1899
2036
|
}
|
|
1900
|
-
/**
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
2037
|
+
/**
|
|
2038
|
+
* D1 父历史折叠的划分:seq <= boundarySeq 的消息为继承区(fork 时刻快照,
|
|
2039
|
+
* 折叠进指示卡),其余(含在途项——无 seq)为新鲜区。boundarySeq 缺省 =
|
|
2040
|
+
* 全新鲜(升级前老 Tab 的行为不变)。
|
|
2041
|
+
*/
|
|
2042
|
+
function partitionInherited(messages, boundarySeq) {
|
|
2043
|
+
if (boundarySeq === void 0) return {
|
|
2044
|
+
inherited: [],
|
|
2045
|
+
fresh: [...messages]
|
|
1908
2046
|
};
|
|
2047
|
+
const inherited = [];
|
|
2048
|
+
const fresh = [];
|
|
2049
|
+
for (const message of messages) if (message.seq !== void 0 && message.seq <= boundarySeq) inherited.push(message);
|
|
2050
|
+
else fresh.push(message);
|
|
1909
2051
|
return {
|
|
1910
|
-
|
|
1911
|
-
|
|
2052
|
+
inherited,
|
|
2053
|
+
fresh
|
|
1912
2054
|
};
|
|
1913
2055
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
if (typeof l?.number !== "number" || typeof l?.text !== "string") return void 0;
|
|
1925
|
-
if (!Number.isInteger(l.number) || l.number <= prev || l.number > m.totalLines) return void 0;
|
|
1926
|
-
prev = l.number;
|
|
1927
|
-
lines.push({
|
|
1928
|
-
number: l.number,
|
|
1929
|
-
text: l.text
|
|
1930
|
-
});
|
|
2056
|
+
//#endregion
|
|
2057
|
+
//#region src/client/sidechat/snapshot.ts
|
|
2058
|
+
function blocksText(blocks, kinds) {
|
|
2059
|
+
if (!Array.isArray(blocks)) return "";
|
|
2060
|
+
const parts = [];
|
|
2061
|
+
for (const block of blocks) {
|
|
2062
|
+
if (typeof block !== "object" || block === null) continue;
|
|
2063
|
+
const b = block;
|
|
2064
|
+
const kind = b.kind ?? b.type;
|
|
2065
|
+
if (kind !== void 0 && kinds.includes(kind) && typeof b.text === "string") parts.push(b.text);
|
|
1931
2066
|
}
|
|
1932
|
-
return
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
...typeof m.lang === "string" ? { lang: m.lang } : {}
|
|
1937
|
-
};
|
|
2067
|
+
return parts.join("\n");
|
|
2068
|
+
}
|
|
2069
|
+
function contentText(content) {
|
|
2070
|
+
return blocksText(content, ["text"]);
|
|
1938
2071
|
}
|
|
1939
2072
|
/**
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
*
|
|
1943
|
-
*
|
|
2073
|
+
* 投影 nodes → 当前 turn 的快照拍序列。纯函数(node 可测)。
|
|
2074
|
+
*
|
|
2075
|
+
* 当前 turn 起点 = 最后一个 user 节点(在飞 turn 的任务消息)。注意:压缩
|
|
2076
|
+
* 摘要在投影里也是 user 形态节点(<compacted-summary> 框定的替换消息)——
|
|
2077
|
+
* 若最后一个 user 节点是摘要,从它取起同样正确(摘要即主线模型此刻的上下文
|
|
2078
|
+
* 头部)。起点之后:assistant 拆思考/正文两拍,tool-result 成拍,其余节点
|
|
2079
|
+
* (context 注入、系统面)不入快照。
|
|
1944
2080
|
*/
|
|
1945
|
-
function
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
if (typeof d?.path !== "string") return void 0;
|
|
1954
|
-
if (d.oldText !== null && typeof d.oldText !== "string") return void 0;
|
|
1955
|
-
if (typeof d.newText !== "string") return void 0;
|
|
1956
|
-
diffs.push({
|
|
1957
|
-
path: d.path,
|
|
1958
|
-
oldText: d.oldText,
|
|
1959
|
-
newText: d.newText
|
|
1960
|
-
});
|
|
2081
|
+
function snapshotEntriesOf(nodes, partial) {
|
|
2082
|
+
let start = -1;
|
|
2083
|
+
for (let i = nodes.length - 1; i >= 0; i -= 1) {
|
|
2084
|
+
const n = nodes[i];
|
|
2085
|
+
if (n?.kind === "user" || n?.kind === "steering") {
|
|
2086
|
+
start = i;
|
|
2087
|
+
break;
|
|
2088
|
+
}
|
|
1961
2089
|
}
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
for (const rawF of m.files) {
|
|
1974
|
-
const f = rawF;
|
|
1975
|
-
if (typeof f?.path !== "string" || !Array.isArray(f.matches)) return void 0;
|
|
1976
|
-
const matches = [];
|
|
1977
|
-
for (const rawM of f.matches) {
|
|
1978
|
-
const mm = rawM;
|
|
1979
|
-
if (typeof mm?.lineNumber !== "number" || !Number.isInteger(mm.lineNumber) || mm.lineNumber < 1) return void 0;
|
|
1980
|
-
if (typeof mm.line !== "string") return void 0;
|
|
1981
|
-
matches.push({
|
|
1982
|
-
lineNumber: mm.lineNumber,
|
|
1983
|
-
line: mm.line
|
|
2090
|
+
if (start < 0) return [];
|
|
2091
|
+
const entries = [];
|
|
2092
|
+
for (const node of nodes.slice(start)) {
|
|
2093
|
+
const n = node;
|
|
2094
|
+
switch (n.kind) {
|
|
2095
|
+
case "user":
|
|
2096
|
+
case "steering": {
|
|
2097
|
+
const text = contentText(n.content);
|
|
2098
|
+
if (text !== "") entries.push({
|
|
2099
|
+
kind: "request",
|
|
2100
|
+
text
|
|
1984
2101
|
});
|
|
2102
|
+
break;
|
|
2103
|
+
}
|
|
2104
|
+
case "assistant": {
|
|
2105
|
+
const thinking = blocksText(n.blocks, ["reasoning"]);
|
|
2106
|
+
const reply = blocksText(n.blocks, ["text"]);
|
|
2107
|
+
if (thinking !== "") entries.push({
|
|
2108
|
+
kind: "thinking",
|
|
2109
|
+
text: thinking
|
|
2110
|
+
});
|
|
2111
|
+
if (reply !== "") entries.push({
|
|
2112
|
+
kind: "reply",
|
|
2113
|
+
text: reply
|
|
2114
|
+
});
|
|
2115
|
+
break;
|
|
2116
|
+
}
|
|
2117
|
+
case "tool-result": {
|
|
2118
|
+
const call = n.call;
|
|
2119
|
+
const name = typeof call?.name === "string" ? call.name : "tool";
|
|
2120
|
+
const args = typeof call?.argsRaw === "string" ? call.argsRaw : "";
|
|
2121
|
+
const result = contentText(n.content);
|
|
2122
|
+
entries.push({
|
|
2123
|
+
kind: "tool",
|
|
2124
|
+
toolName: name,
|
|
2125
|
+
text: args === "" ? result : `调用:${args}\n${result}`
|
|
2126
|
+
});
|
|
2127
|
+
break;
|
|
1985
2128
|
}
|
|
1986
|
-
files.push({
|
|
1987
|
-
path: f.path,
|
|
1988
|
-
matches
|
|
1989
|
-
});
|
|
1990
2129
|
}
|
|
1991
|
-
return {
|
|
1992
|
-
shape: "matches",
|
|
1993
|
-
files,
|
|
1994
|
-
truncated: m.truncated,
|
|
1995
|
-
total: m.total
|
|
1996
|
-
};
|
|
1997
2130
|
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2131
|
+
const streaming = typeof partial === "string" ? partial : contentText(partial?.blocks);
|
|
2132
|
+
if (streaming !== "") entries.push({
|
|
2133
|
+
kind: "streaming",
|
|
2134
|
+
text: streaming
|
|
2135
|
+
});
|
|
2136
|
+
return entries;
|
|
2137
|
+
}
|
|
2138
|
+
/** 拍序列 → 协议块文本(拼进首条侧边消息前缀的形态)。 */
|
|
2139
|
+
function serializeSnapshot(entries, takenAt) {
|
|
2140
|
+
const pad = (v) => String(v).padStart(2, "0");
|
|
2141
|
+
return `<mainline-progress-snapshot taken-at="${`${takenAt.getFullYear()}-${pad(takenAt.getMonth() + 1)}-${pad(takenAt.getDate())} ${pad(takenAt.getHours())}:${pad(takenAt.getMinutes())}`}">
|
|
2142
|
+
你是从主线会话 fork 出的侧边会话。主线有一个任务正在执行中——它在你 fork 时正在进行,所以以「已中断」形态出现在上面的历史末尾(那只是快照切点,主线本体仍在正常运行)。以下是主线该任务截至此刻的实时进展(与主线模型当前可见的上下文一致):
|
|
2143
|
+
|
|
2144
|
+
${entries.map((entry) => {
|
|
2145
|
+
switch (entry.kind) {
|
|
2146
|
+
case "request": return `<user-request>\n${entry.text}\n</user-request>`;
|
|
2147
|
+
case "reply": return `<reply>\n${entry.text}\n</reply>`;
|
|
2148
|
+
case "thinking": return `<thinking>\n${entry.text}\n</thinking>`;
|
|
2149
|
+
case "tool": return `<tool-call name="${entry.toolName ?? "tool"}">\n${entry.text}\n</tool-call>`;
|
|
2150
|
+
case "streaming": return `<streaming>\n${entry.text}\n(主线正在输出中)</streaming>`;
|
|
2151
|
+
}
|
|
2152
|
+
}).join("\n\n")}
|
|
2153
|
+
</mainline-progress-snapshot>`;
|
|
2154
|
+
}
|
|
2155
|
+
/** 协议块标记(WI-03 呈现层据此把快照从气泡提升到继承卡)。 */
|
|
2156
|
+
const SNAPSHOT_BLOCK_MARK = "<mainline-progress-snapshot";
|
|
2157
|
+
/**
|
|
2158
|
+
* 现取主线当前 turn 的进展快照块。无任何在飞内容(主线恰在两轮之间等)
|
|
2159
|
+
* 返回 null(调用方跳过拼接)。
|
|
2160
|
+
*/
|
|
2161
|
+
function buildMainlineSnapshot(ctx, parentSessionId, now = /* @__PURE__ */ new Date()) {
|
|
2162
|
+
try {
|
|
2163
|
+
const snap = chatSourceOf(ctx, ctx.sessions.binding(parentSessionId))?.getLegacy();
|
|
2164
|
+
if (snap === void 0 || snap === null) return null;
|
|
2165
|
+
const entries = snapshotEntriesOf(snap.nodes ?? [], snap.partial);
|
|
2166
|
+
if (entries.length === 0) return null;
|
|
2167
|
+
return serializeSnapshot(entries, now);
|
|
2168
|
+
} catch {
|
|
2169
|
+
return null;
|
|
2006
2170
|
}
|
|
2007
2171
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2172
|
+
//#endregion
|
|
2173
|
+
//#region src/client/sidechat/lifecycle.ts
|
|
2174
|
+
/**
|
|
2175
|
+
* 主线忙碌判定(busy-fork 路由)。2026-09-22 实证的宿主 fork 语义缺口:fork
|
|
2176
|
+
* 一个 turn 在飞的会话,会把在飞消息以「未认领的 inbox splice」形态遗传
|
|
2177
|
+
* 给子会话——子会话 boot 后把它当成自己的首个任务执行,用户真正发的侧边
|
|
2178
|
+
* 消息则排进不可见队列(用户视角 = 「发不了」+「主线最后一条消息被抄到
|
|
2179
|
+
* 侧边」)。busy 时改走 busy-fork 路径(fork 后监护中和遗传 turn,见
|
|
2180
|
+
* armInheritedTurnPurge)。
|
|
2181
|
+
*
|
|
2182
|
+
* busy = running(Session 快照顶层,0.1.1/0.1.2/0.1.5 同在)或 pending
|
|
2183
|
+
* 非空(审批/提问挂起:0.1.1 在 Session 快照顶层,0.1.2 起在
|
|
2184
|
+
* uiConversation.chat 的 legacy 切片——双面都读,同面板 R8 链)。
|
|
2185
|
+
* 探测失败一律按「空闲」放行(fork 错误面兜底),绝不因读不到状态而卡死。
|
|
2186
|
+
*/
|
|
2187
|
+
function sessionBusyOf(ctx, sessionId) {
|
|
2188
|
+
try {
|
|
2189
|
+
const binding = ctx.sessions.binding(sessionId);
|
|
2190
|
+
const snap = binding?.session?.getSnapshot?.();
|
|
2191
|
+
if (snap?.running === true) return true;
|
|
2192
|
+
if ((snap?.pending?.length ?? 0) > 0) return true;
|
|
2193
|
+
return ((chatSourceOf(ctx, binding)?.getLegacy())?.pending?.length ?? 0) > 0;
|
|
2194
|
+
} catch {
|
|
2195
|
+
return false;
|
|
2026
2196
|
}
|
|
2027
|
-
if (m.answer !== void 0 && typeof m.answer !== "string") return void 0;
|
|
2028
|
-
return {
|
|
2029
|
-
sources,
|
|
2030
|
-
truncated: m.truncated,
|
|
2031
|
-
...m.answer !== void 0 ? { answer: m.answer } : {}
|
|
2032
|
-
};
|
|
2033
2197
|
}
|
|
2034
|
-
/**
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
return
|
|
2042
|
-
url: m.url,
|
|
2043
|
-
statusCode: m.statusCode,
|
|
2044
|
-
truncated: m.truncated
|
|
2045
|
-
};
|
|
2198
|
+
/**
|
|
2199
|
+
* 监护中的子会话登记处:完成通知模块(notify.ts)据此抑制误报——遗传 turn
|
|
2200
|
+
* 被 cancel 的 running 翻转不是「侧边回复完成」。
|
|
2201
|
+
*/
|
|
2202
|
+
const purgingChildren = /* @__PURE__ */ new Set();
|
|
2203
|
+
/** 该子会话当前是否处于遗传 turn 监护/清除窗口。 */
|
|
2204
|
+
function isPurgingInheritedTurn(childId) {
|
|
2205
|
+
return purgingChildren.has(childId);
|
|
2046
2206
|
}
|
|
2047
|
-
/**
|
|
2048
|
-
function
|
|
2049
|
-
if (argsRaw === void 0 || argsRaw === "") return {};
|
|
2207
|
+
/** 读父会话当前最大节点 seq(折叠边界;读不到不阻断)。 */
|
|
2208
|
+
function readMaxNodeSeq(ctx, sessionId) {
|
|
2050
2209
|
try {
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2053
|
-
const
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
...Array.isArray(a.todos) ? { todos: a.todos } : {},
|
|
2059
|
-
...typeof a.content === "string" ? { content: a.content } : {},
|
|
2060
|
-
...typeof a.old_string === "string" ? { oldString: a.old_string } : {},
|
|
2061
|
-
...typeof a.new_string === "string" ? { newString: a.new_string } : {},
|
|
2062
|
-
...typeof a.pattern === "string" ? { pattern: a.pattern } : {},
|
|
2063
|
-
...typeof a.include === "string" ? { include: a.include } : {},
|
|
2064
|
-
...Array.isArray(a.queries) && a.queries.every((q) => typeof q === "string") ? { queries: a.queries } : {},
|
|
2065
|
-
...typeof a.url === "string" ? { url: a.url } : {}
|
|
2066
|
-
};
|
|
2210
|
+
const snap = chatSourceOf(ctx, ctx.sessions.binding(sessionId))?.getLegacy();
|
|
2211
|
+
let max = -1;
|
|
2212
|
+
for (const node of snap?.nodes ?? []) {
|
|
2213
|
+
const seq = node.seq;
|
|
2214
|
+
if (typeof seq === "number" && seq > max) max = seq;
|
|
2215
|
+
}
|
|
2216
|
+
return max >= 0 ? max : void 0;
|
|
2067
2217
|
} catch {
|
|
2068
|
-
return
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
/**
|
|
2222
|
+
* 读 fork 折叠边界 = 父会话「在飞 turn 之前」的最大节点 seq。
|
|
2223
|
+
* busy 时在飞 turn 的节点(最后一条 user/steering 起)不随 fork 继承,必须
|
|
2224
|
+
* 排除——否则边界会越过遗传消息在子会话里的 seq,监护把泄漏误判成「继承
|
|
2225
|
+
* 内容」而漏 cancel(2026-09-23 真机实证:sleep 240 在子会话里跑起来了)。
|
|
2226
|
+
*/
|
|
2227
|
+
function readInheritedBoundary(ctx, sessionId, busy) {
|
|
2228
|
+
if (!busy) return readMaxNodeSeq(ctx, sessionId);
|
|
2229
|
+
try {
|
|
2230
|
+
const nodes = chatSourceOf(ctx, ctx.sessions.binding(sessionId))?.getLegacy()?.nodes ?? [];
|
|
2231
|
+
let endExclusive = nodes.length;
|
|
2232
|
+
for (let i = nodes.length - 1; i >= 0; i -= 1) {
|
|
2233
|
+
const kind = nodes[i]?.kind;
|
|
2234
|
+
if (kind === "user" || kind === "steering") {
|
|
2235
|
+
endExclusive = i;
|
|
2236
|
+
break;
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
let max = -1;
|
|
2240
|
+
for (const node of nodes.slice(0, endExclusive)) {
|
|
2241
|
+
const seq = node.seq;
|
|
2242
|
+
if (typeof seq === "number" && seq > max) max = seq;
|
|
2243
|
+
}
|
|
2244
|
+
return max >= 0 ? max : void 0;
|
|
2245
|
+
} catch {
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
/** fork-unavailable 判定(主线没有任何已完成 turn)。 */
|
|
2250
|
+
function isForkUnavailable(error) {
|
|
2251
|
+
const text = error instanceof Error ? `${error.name} ${error.message}` : String(error);
|
|
2252
|
+
return /fork-unavailable|no completed turn/.test(text);
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* 读主线在飞 turn 的任务消息前缀(监护的判别依据):父投影最后一个
|
|
2256
|
+
* user/steering 节点即它。截 200 字符入 meta(够判别即可,meta 走
|
|
2257
|
+
* localStorage,不扛全文)。读不到 = undefined(监护退化为纯时序判别)。
|
|
2258
|
+
*/
|
|
2259
|
+
function readInflightUserPrefix(ctx, parentSessionId) {
|
|
2260
|
+
try {
|
|
2261
|
+
const nodes = chatSourceOf(ctx, ctx.sessions.binding(parentSessionId))?.getLegacy()?.nodes ?? [];
|
|
2262
|
+
for (let i = nodes.length - 1; i >= 0; i -= 1) {
|
|
2263
|
+
const n = nodes[i];
|
|
2264
|
+
if (n?.kind === "user" || n?.kind === "steering") {
|
|
2265
|
+
const text = contentTextOf(n.content);
|
|
2266
|
+
if (text !== "") return text.slice(0, 200);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
} catch {}
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* 零完成轮兜底:主线首轮在飞时 fork 不可用(fork-unavailable)——退化为
|
|
2273
|
+
* create 一个纯快照会话(无继承历史,全部上下文由 WI-02 的进展快照供给)。
|
|
2274
|
+
* sessions.create 是 off-face(client 契约面只有 fork/binding/scope/open),
|
|
2275
|
+
* 探测不到就抛错(面板转 fork-error 态)。
|
|
2276
|
+
*/
|
|
2277
|
+
async function createSideSession(ctx, parentSessionId) {
|
|
2278
|
+
const svc = ctx.sessions;
|
|
2279
|
+
if (typeof svc.create !== "function") throw new Error("宿主 sessions.create 面缺席,无法创建快照会话");
|
|
2280
|
+
let cwd;
|
|
2281
|
+
try {
|
|
2282
|
+
const summary = ctx.sessions.list.getSnapshot().byId?.[parentSessionId];
|
|
2283
|
+
cwd = typeof summary?.cwd === "string" ? summary.cwd : void 0;
|
|
2284
|
+
} catch {}
|
|
2285
|
+
return svc.create(cwd === void 0 ? {} : { cwd });
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* 遗传 turn 监护(busy fork 的核心中和手段):busy fork 的子会话日志带着
|
|
2289
|
+
* 主线在飞消息的未认领 inbox insert(宿主 fork cut 缺陷),boot 后会被认领
|
|
2290
|
+
* 执行。子会话 fork 后是休眠的(实测不 open 不 prompt 不跑)。
|
|
2291
|
+
*
|
|
2292
|
+
* 信号面(实证教训,勿回退):Session 控制快照的 running 位由管理器的
|
|
2293
|
+
* 列表/活动通道驱动,**非在屏会话不实时翻牌**——watch 它永远等不到 true。
|
|
2294
|
+
* 可靠信号是 chat 投影(openSessionWindow 打开的事件窗驱动):泄漏消息只在
|
|
2295
|
+
* 被认领(= 遗传 turn 开跑)后才落成 user 节点——**该节点在子会话投影中
|
|
2296
|
+
* 出现即 turn 已开跑**,此刻 cancel 恰落在首个 LLM 响应窗口内(≥1s),
|
|
2297
|
+
* 工具来不及执行。
|
|
2298
|
+
*
|
|
2299
|
+
* 判别(双门,防误伤):先过 seq 门——继承节点的 seq 与父会话原值一致
|
|
2300
|
+
* (fork 前缀逐字拷贝),只有 seq > forkBoundarySeq 的用户节点才是 fork 后
|
|
2301
|
+
* 新增的(继承历史里的用户消息不参与判别);再过文本门——expectedPrefix
|
|
2302
|
+
* 在场时节点的文本须命中 fork 时刻记下的在飞消息前缀,未命中(宿主已修好
|
|
2303
|
+
* cut 缺陷/用户自己的首个侧边消息)即解除,绝不误 cancel。前缀缺席
|
|
2304
|
+
* (读取失败)退化为纯 seq 门判别。
|
|
2305
|
+
*
|
|
2306
|
+
* 监护一次性:settle(cancel 完成/判别未命中/用户自己的首条消息带快照前缀)
|
|
2307
|
+
* 后解除并把 meta 标 inheritedPurged——面板重挂载(关 tab 重开)靠该标记
|
|
2308
|
+
* 跳过重复监护。cancel 后推进折叠边界覆盖 aborted turn(界面无感)。
|
|
2309
|
+
* **不设静置超时**:子会话休眠到用户首条消息才认领 inbox(真机实证),
|
|
2310
|
+
* 泄漏 turn 可能在任意晚的时刻开跑——超时撤防等于把洞留给慢用户。
|
|
2311
|
+
* 幂等:同一 childId 重复调用只监护一次(purgingChildren 守卫)。
|
|
2312
|
+
*/
|
|
2313
|
+
function armInheritedTurnPurge(ctx, childId, parentSessionId, tabId, expectedPrefix, forkBoundarySeq) {
|
|
2314
|
+
if (purgingChildren.has(childId)) return;
|
|
2315
|
+
purgingChildren.add(childId);
|
|
2316
|
+
let settled = false;
|
|
2317
|
+
const offs = [];
|
|
2318
|
+
const markPurged = () => {
|
|
2319
|
+
updateTabMeta(ctx, parentSessionId, tabId, (cur) => ({
|
|
2320
|
+
...cur,
|
|
2321
|
+
inheritedPurged: true
|
|
2322
|
+
}));
|
|
2323
|
+
};
|
|
2324
|
+
const disarm = () => {
|
|
2325
|
+
if (settled) return;
|
|
2326
|
+
settled = true;
|
|
2327
|
+
for (const off of offs) off();
|
|
2328
|
+
purgingChildren.delete(childId);
|
|
2329
|
+
markPurged();
|
|
2330
|
+
};
|
|
2331
|
+
/**
|
|
2332
|
+
* 子会话投影里「fork 后新增」的用户消息文本(无 = ''):seq ≤ fork 边界的
|
|
2333
|
+
* 节点全是继承历史(seq 随 fork 前缀逐字拷贝),直接排除。
|
|
2334
|
+
*/
|
|
2335
|
+
const freshUserText = () => {
|
|
2336
|
+
try {
|
|
2337
|
+
const nodes = chatSourceOf(ctx, ctx.sessions.binding(childId))?.getLegacy()?.nodes ?? [];
|
|
2338
|
+
for (let i = nodes.length - 1; i >= 0; i -= 1) {
|
|
2339
|
+
const n = nodes[i];
|
|
2340
|
+
if (n?.kind !== "user" && n?.kind !== "steering") continue;
|
|
2341
|
+
if (forkBoundarySeq !== void 0 && typeof n.seq === "number" && n.seq <= forkBoundarySeq) return "";
|
|
2342
|
+
return contentTextOf(n.content);
|
|
2343
|
+
}
|
|
2344
|
+
} catch {}
|
|
2345
|
+
return "";
|
|
2346
|
+
};
|
|
2347
|
+
try {
|
|
2348
|
+
const binding = ctx.sessions.binding(childId);
|
|
2349
|
+
const session = binding?.session;
|
|
2350
|
+
const cancelFace = session;
|
|
2351
|
+
const chat = binding === void 0 ? void 0 : chatSourceOf(ctx, binding);
|
|
2352
|
+
if (session === void 0 || typeof cancelFace?.cancel !== "function" || chat === void 0) {
|
|
2353
|
+
console.warn("[dsh-sidenote] 子会话 cancel/chat 面缺席,遗传 turn 监护未启用:", childId, "session:", session !== void 0, "chat:", chat !== void 0);
|
|
2354
|
+
purgingChildren.delete(childId);
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
const cancelChild = cancelFace.cancel;
|
|
2358
|
+
offs.push(chat.subscribe(() => {
|
|
2359
|
+
if (settled) return;
|
|
2360
|
+
const text = freshUserText();
|
|
2361
|
+
if (text === "") return;
|
|
2362
|
+
if (text.startsWith("<mainline-progress-snapshot")) {
|
|
2363
|
+
disarm();
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
if (expectedPrefix !== void 0 && !text.startsWith(expectedPrefix)) {
|
|
2367
|
+
disarm();
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
settled = true;
|
|
2371
|
+
for (const off of offs) off();
|
|
2372
|
+
cancelChild.call(session).catch((error) => console.warn("[dsh-sidenote] 遗传 turn 取消失败:", error)).finally(() => {
|
|
2373
|
+
purgingChildren.delete(childId);
|
|
2374
|
+
markPurged();
|
|
2375
|
+
advanceBoundaryPastTail(ctx, parentSessionId, tabId, childId);
|
|
2376
|
+
reconcileChildQueueAfterCancel(ctx, childId, expectedPrefix);
|
|
2377
|
+
});
|
|
2378
|
+
}));
|
|
2379
|
+
} catch {
|
|
2380
|
+
purgingChildren.delete(childId);
|
|
2069
2381
|
}
|
|
2070
2382
|
}
|
|
2071
2383
|
/**
|
|
2072
|
-
*
|
|
2073
|
-
*
|
|
2384
|
+
* cancel 后队列和解(真机实证:宿主 cancel 保留 inbox 但不自动续跑,且
|
|
2385
|
+
* 休眠子会话的队列要等 agent 暖机才物化——认领前剔除对休眠子会话不可行)。
|
|
2386
|
+
* 等 turn 落定(running 翻 false)后读队列:
|
|
2387
|
+
* - 命中在飞前缀的项 = 没跑赢 claim 的泄漏/主线积压 → remove;
|
|
2388
|
+
* - 带快照前缀的项 = 用户的真实侧边消息 → remove + 原样重投(内容含快照
|
|
2389
|
+
* 前缀,remove 先行保证不重影),让它真正跑起来。
|
|
2390
|
+
* 两者皆无(主线积压、用户的快手第二条)→ 留在队列里不动(保守不删用户文字)。
|
|
2391
|
+
*/
|
|
2392
|
+
function reconcileChildQueueAfterCancel(ctx, childId, expectedPrefix) {
|
|
2393
|
+
let attempts = 0;
|
|
2394
|
+
const tick = () => {
|
|
2395
|
+
attempts += 1;
|
|
2396
|
+
try {
|
|
2397
|
+
const session = ctx.sessions.binding(childId)?.session;
|
|
2398
|
+
const snap = session?.getSnapshot?.();
|
|
2399
|
+
if (session === void 0 || snap === void 0 || snap === null || typeof session.updateQueue !== "function" || typeof session.prompt !== "function") return;
|
|
2400
|
+
if (snap.running === true) {
|
|
2401
|
+
if (attempts < 8) setTimeout(tick, 400);
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
const updateQueue = session.updateQueue.bind(session);
|
|
2405
|
+
const prompt = session.prompt.bind(session);
|
|
2406
|
+
for (const item of snap.queue ?? []) {
|
|
2407
|
+
if (typeof item?.id !== "string") continue;
|
|
2408
|
+
const text = typeof item.text === "string" ? item.text : contentTextOf(item.content);
|
|
2409
|
+
const isLeak = expectedPrefix !== void 0 && text.startsWith(expectedPrefix);
|
|
2410
|
+
const isUserReal = text.startsWith(SNAPSHOT_BLOCK_MARK);
|
|
2411
|
+
if (!isLeak && !isUserReal) continue;
|
|
2412
|
+
const itemId = item.id;
|
|
2413
|
+
const content = item.content;
|
|
2414
|
+
updateQueue(itemId, { kind: "remove" }).then(() => {
|
|
2415
|
+
if (!isUserReal) return void 0;
|
|
2416
|
+
return prompt(content, "queue");
|
|
2417
|
+
}).catch((error) => console.warn("[dsh-sidenote] 取消后队列和解失败:", error));
|
|
2418
|
+
}
|
|
2419
|
+
} catch {}
|
|
2420
|
+
};
|
|
2421
|
+
setTimeout(tick, 300);
|
|
2422
|
+
}
|
|
2423
|
+
/**
|
|
2424
|
+
* cancel 后把折叠边界推进到子会话内容尾(覆盖 aborted turn)。中断 turn 的
|
|
2425
|
+
* 冻结节点带小数 seq(宿主 fork 文档实证),故 boundarySeq 允许非整数。
|
|
2426
|
+
* 等 running 翻 false 且节点落定再读;最多重试 ~3s,失败不阻断(aborted
|
|
2427
|
+
* turn 会短暂可见,属可接受降级)。
|
|
2428
|
+
*/
|
|
2429
|
+
function advanceBoundaryPastTail(ctx, parentSessionId, tabId, childId) {
|
|
2430
|
+
let attempts = 0;
|
|
2431
|
+
const tick = () => {
|
|
2432
|
+
attempts += 1;
|
|
2433
|
+
try {
|
|
2434
|
+
const max = (ctx.sessions.binding(childId)?.session.getSnapshot())?.running === true ? void 0 : readMaxNodeSeq(ctx, childId);
|
|
2435
|
+
if (max !== void 0) {
|
|
2436
|
+
updateTabMeta(ctx, parentSessionId, tabId, (cur) => ({
|
|
2437
|
+
...cur,
|
|
2438
|
+
boundarySeq: Math.max(cur.boundarySeq ?? -1, max)
|
|
2439
|
+
}));
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
} catch {}
|
|
2443
|
+
if (attempts < 8) setTimeout(tick, 400);
|
|
2444
|
+
};
|
|
2445
|
+
setTimeout(tick, 300);
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* 首开编排:fork(全量历史快照)→ 登记 meta(先行,绝不丢登记)→
|
|
2449
|
+
* 归档隐藏出会话列表(best-effort)→ 模型跟随主会话(best-effort)。
|
|
2450
|
+
* fork 失败抛错(面板组件转错误态);后两步失败只告警不阻断。
|
|
2451
|
+
*
|
|
2452
|
+
* 主线 turn 在飞(busy)时走 busy-fork(Delivery_04):立即 fork + 遗传
|
|
2453
|
+
* turn 监护 + meta 打 forkedMidTurn 标记(首条侧边消息拼进展快照);
|
|
2454
|
+
* 主线零完成轮(fork-unavailable)退化为 create 纯快照会话(snapshotOnly)。
|
|
2455
|
+
* @returns fork/创建出的子会话 id。
|
|
2074
2456
|
*/
|
|
2075
|
-
function
|
|
2076
|
-
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2457
|
+
async function forkAndRegister(ctx, parentSessionId, tabId, signal) {
|
|
2458
|
+
if (signal?.aborted === true) throw new DOMException("side chat fork aborted", "AbortError");
|
|
2459
|
+
const busy = sessionBusyOf(ctx, parentSessionId);
|
|
2460
|
+
const boundarySeq = readInheritedBoundary(ctx, parentSessionId, busy);
|
|
2461
|
+
const leakedPromptPrefix = busy ? readInflightUserPrefix(ctx, parentSessionId) : void 0;
|
|
2462
|
+
const owner = rootContext(ctx);
|
|
2463
|
+
let forked;
|
|
2464
|
+
let snapshotOnly = false;
|
|
2465
|
+
if (!busy) forked = await ctx.sessions.fork({ sessionId: parentSessionId });
|
|
2466
|
+
else {
|
|
2467
|
+
try {
|
|
2468
|
+
forked = await ctx.sessions.fork({ sessionId: parentSessionId });
|
|
2469
|
+
} catch (error) {
|
|
2470
|
+
if (!isForkUnavailable(error)) throw error;
|
|
2471
|
+
forked = await createSideSession(owner, parentSessionId);
|
|
2472
|
+
snapshotOnly = true;
|
|
2473
|
+
}
|
|
2474
|
+
if (!snapshotOnly) armInheritedTurnPurge(ctx, forked, parentSessionId, tabId, leakedPromptPrefix, boundarySeq);
|
|
2090
2475
|
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2476
|
+
updateTabMeta(ctx, parentSessionId, tabId, (current) => ({
|
|
2477
|
+
...current,
|
|
2478
|
+
childId: forked,
|
|
2479
|
+
parentSessionId,
|
|
2480
|
+
...boundarySeq !== void 0 ? { boundarySeq } : {},
|
|
2481
|
+
...busy ? { forkedMidTurn: true } : {},
|
|
2482
|
+
...snapshotOnly ? { snapshotOnly: true } : {},
|
|
2483
|
+
...leakedPromptPrefix !== void 0 ? { leakedPromptPrefix } : {}
|
|
2484
|
+
}));
|
|
2485
|
+
try {
|
|
2486
|
+
await owner.workspaces.archiveSession(forked);
|
|
2487
|
+
} catch (error) {
|
|
2488
|
+
console.warn("[dsh-sidenote] 归档侧边会话失败(会话列表可能短暂可见):", error);
|
|
2101
2489
|
}
|
|
2102
|
-
|
|
2103
|
-
const
|
|
2104
|
-
if (
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
};
|
|
2490
|
+
try {
|
|
2491
|
+
const selection = await readModelSelection(owner, parentSessionId);
|
|
2492
|
+
if (selection === void 0) console.warn("[dsh-sidenote] 同步主会话模型跳过:主机模型 API 不可用");
|
|
2493
|
+
else if (!await writeModelSelection(owner, forked, selection)) console.warn("[dsh-sidenote] 同步主会话模型失败(子会话用默认模型)");
|
|
2494
|
+
} catch (error) {
|
|
2495
|
+
console.warn("[dsh-sidenote] 同步主会话模型失败(子会话用默认模型):", error);
|
|
2109
2496
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
oldText: null,
|
|
2126
|
-
newText: args.content
|
|
2127
|
-
}],
|
|
2128
|
-
locations: [{ path }]
|
|
2129
|
-
};
|
|
2497
|
+
return forked;
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Tab meta 读-改-写。双腿:直连原生腿的 meta 在自有 metaStore(原生无
|
|
2501
|
+
* updateTab 面,布局 memory-only);legacy 腿在布局快照 + betterSidebar.updateTab。
|
|
2502
|
+
* 分腿判据统一为 directNativeLeg(对抗性审查 m1:判据单点化——metaStore
|
|
2503
|
+
* 有无记录不能再当判据,否则记录缺失时 childId 登记会静默落到 no-op 的
|
|
2504
|
+
* legacy 面)。直连腿记录缺失 = 异常态(reconcile 应先已建):warn 并 no-op。
|
|
2505
|
+
*/
|
|
2506
|
+
function updateTabMeta(ctx, sessionId, tabId, mutate) {
|
|
2507
|
+
if (directNativeLeg(ctx)) {
|
|
2508
|
+
const direct = readSideChatMeta(sessionId, tabId);
|
|
2509
|
+
if (direct === void 0) {
|
|
2510
|
+
console.warn("[dsh-sidenote] meta 记录缺失(reconcile 应先已建),本次 meta 写入跳过:", tabId);
|
|
2511
|
+
return;
|
|
2130
2512
|
}
|
|
2513
|
+
const next = mutate(parseSideChatMeta(direct));
|
|
2514
|
+
writeSideChatMeta({
|
|
2515
|
+
tabId,
|
|
2516
|
+
sessionId: direct.sessionId,
|
|
2517
|
+
...next.childId !== void 0 ? { childId: next.childId } : {},
|
|
2518
|
+
...next.parentSessionId !== void 0 ? { parentSessionId: next.parentSessionId } : {},
|
|
2519
|
+
...next.pendingDraft !== void 0 ? { pendingDraft: next.pendingDraft } : {},
|
|
2520
|
+
...next.boundarySeq !== void 0 ? { boundarySeq: next.boundarySeq } : {},
|
|
2521
|
+
...next.forkedMidTurn === true ? { forkedMidTurn: true } : {},
|
|
2522
|
+
...next.snapshotOnly === true ? { snapshotOnly: true } : {},
|
|
2523
|
+
...next.leakedPromptPrefix !== void 0 ? { leakedPromptPrefix: next.leakedPromptPrefix } : {},
|
|
2524
|
+
...next.inheritedPurged === true ? { inheritedPurged: true } : {},
|
|
2525
|
+
...next.topic !== void 0 ? { topic: next.topic } : {},
|
|
2526
|
+
number: direct.number,
|
|
2527
|
+
createdAt: direct.createdAt,
|
|
2528
|
+
...direct.runId !== void 0 ? { runId: direct.runId } : {}
|
|
2529
|
+
});
|
|
2530
|
+
return;
|
|
2131
2531
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2532
|
+
const bs = betterSidebarOf(ctx);
|
|
2533
|
+
if (bs === void 0) return;
|
|
2534
|
+
const current = parseSideChatMeta(readTab(ctx, sessionId, tabId)?.meta);
|
|
2535
|
+
bs.updateTab(tabId, { meta: mutate(current) });
|
|
2536
|
+
}
|
|
2537
|
+
/**
|
|
2538
|
+
* 写入内容身份(Delivery_05):meta 双腿写 + legacy 腿补标题 patch
|
|
2539
|
+
* (native 芯片标题槽订阅 metaStore,meta 落库即免费响应;legacy 标题是
|
|
2540
|
+
* 布局快照里的静态字段,必须显式 patch;topic 在时编号不参与标题,
|
|
2541
|
+
* 故 legacy 侧无需反推编号)。sticky 守卫沉在 mutate 里(同帧连发两次
|
|
2542
|
+
* submit 时面板侧 ref 守卫有相位差,审查 L2)。
|
|
2543
|
+
*/
|
|
2544
|
+
function setSideChatTopic(ctx, sessionId, tabId, topic) {
|
|
2545
|
+
updateTabMeta(ctx, sessionId, tabId, (cur) => cur.topic !== void 0 ? cur : {
|
|
2546
|
+
...cur,
|
|
2547
|
+
topic
|
|
2548
|
+
});
|
|
2549
|
+
if (directNativeLeg(ctx)) return;
|
|
2550
|
+
const effective = parseSideChatMeta(readTab(ctx, sessionId, tabId)?.meta).topic ?? topic;
|
|
2551
|
+
betterSidebarOf(ctx)?.updateTab(tabId, { title: sideChatTitleOf({
|
|
2552
|
+
number: 1,
|
|
2553
|
+
topic: effective
|
|
2554
|
+
}) });
|
|
2555
|
+
}
|
|
2556
|
+
/** 关闭侧聊 tab:直连腿走 ISidebarRight.close,legacy 走 betterSidebar.closeTab。 */
|
|
2557
|
+
function closeSideTab(ctx, tabId, sessionId) {
|
|
2558
|
+
if (directNativeLeg(ctx)) {
|
|
2559
|
+
const face = sidebarRightOf(ctx);
|
|
2560
|
+
if (face !== void 0) {
|
|
2561
|
+
face.close(tabId);
|
|
2562
|
+
return;
|
|
2154
2563
|
}
|
|
2155
2564
|
}
|
|
2156
|
-
|
|
2157
|
-
const wm = webSearchMetaOf(input.meta);
|
|
2158
|
-
const queries = args.queries?.filter((q) => q.trim() !== "") ?? [];
|
|
2159
|
-
if (wm !== void 0 && queries.length > 0) return {
|
|
2160
|
-
kind: "web",
|
|
2161
|
-
title: queries.join(", "),
|
|
2162
|
-
webKind: "search",
|
|
2163
|
-
sources: wm.sources,
|
|
2164
|
-
truncated: wm.truncated,
|
|
2165
|
-
...wm.answer !== void 0 ? { answer: wm.answer } : {}
|
|
2166
|
-
};
|
|
2167
|
-
}
|
|
2168
|
-
if (name === "web_fetch" && input.isError !== true) {
|
|
2169
|
-
const wm = webFetchMetaOf(input.meta);
|
|
2170
|
-
if (wm !== void 0 && args.url !== void 0 && args.url.trim() !== "") return {
|
|
2171
|
-
kind: "web",
|
|
2172
|
-
title: args.url,
|
|
2173
|
-
webKind: "fetch",
|
|
2174
|
-
url: wm.url,
|
|
2175
|
-
statusCode: wm.statusCode,
|
|
2176
|
-
truncated: wm.truncated
|
|
2177
|
-
};
|
|
2178
|
-
}
|
|
2179
|
-
return {
|
|
2180
|
-
kind: "generic",
|
|
2181
|
-
title: args.path !== void 0 ? `${name} · ${shortPath(args.path)}` : args.command ?? name,
|
|
2182
|
-
icon: KIND_BY_NAME[name] ?? "other",
|
|
2183
|
-
bodyText: input.rawText === "" ? void 0 : input.rawText
|
|
2184
|
-
};
|
|
2565
|
+
betterSidebarOf(ctx)?.closeTab(tabId, { sessionId });
|
|
2185
2566
|
}
|
|
2186
|
-
/**
|
|
2187
|
-
*
|
|
2188
|
-
*
|
|
2189
|
-
*
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
grep: "search",
|
|
2200
|
-
glob: "search",
|
|
2201
|
-
web_search: "search",
|
|
2202
|
-
web_fetch: "fetch"
|
|
2203
|
-
};
|
|
2204
|
-
//#endregion
|
|
2205
|
-
//#region src/client/chat/transcript.ts
|
|
2206
|
-
/** 工具结果正文截断上限(面板是窄栏,超长输出不撑爆 DOM)。 */
|
|
2207
|
-
const TOOL_TEXT_LIMIT = 4e3;
|
|
2208
|
-
function truncateText(text, max) {
|
|
2209
|
-
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
2567
|
+
/**
|
|
2568
|
+
* 程序化入口(/side、bridge 划选提问)打开 Tab 时面板可能处于折叠态——
|
|
2569
|
+
* 类型型 openTab 不自动展开(better-sidebar 只对 path/url 内容型打开展开)。
|
|
2570
|
+
* 幂等展开(off-face:store.update 是 SidebarStore 公开 mutator 面;
|
|
2571
|
+
* feature-check + 吞错,兜底失败不影响功能)。
|
|
2572
|
+
*/
|
|
2573
|
+
function ensurePanelOpen(store) {
|
|
2574
|
+
const mutable = store;
|
|
2575
|
+
try {
|
|
2576
|
+
mutable?.update?.((state) => {
|
|
2577
|
+
if (state.panelOpen === false) state.panelOpen = true;
|
|
2578
|
+
});
|
|
2579
|
+
} catch {}
|
|
2210
2580
|
}
|
|
2211
|
-
/**
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2581
|
+
/**
|
|
2582
|
+
* 打开非 staged 会话的事件窗口(off-face:open() 在 concrete Session 上是
|
|
2583
|
+
* public 且幂等,但不在 SessionFace 契约上——契约面只有 staged 会话会被
|
|
2584
|
+
* 运行时自动 open)。feature-check + 吞错,运行时若移除则降级为只发不收。
|
|
2585
|
+
*/
|
|
2586
|
+
function openSessionWindow(session) {
|
|
2587
|
+
const openable = session;
|
|
2588
|
+
if (typeof openable?.open !== "function") return;
|
|
2589
|
+
openable.open().catch((error) => {
|
|
2590
|
+
console.warn("[dsh-sidenote] 会话窗口打开失败:", error);
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Current model selection of a session, read from its `modelSelection`
|
|
2595
|
+
* projection (dsh >= 0.1.2): the durable selection is projected state, so no RPC
|
|
2596
|
+
* is needed and the read is safe for any session id. Snapshot shape authority:
|
|
2597
|
+
* `@deepseek-ai/dsh-api-session-controller/lib/types/types.d.ts`
|
|
2598
|
+
* (`ModelSelectionProjection = { lastUsed, next }`,两者均可为 null——
|
|
2599
|
+
* `next` 缺省时按投影文档回落 `lastUsed`)。
|
|
2600
|
+
*/
|
|
2601
|
+
function projectedModelSelection(ctx, sessionId) {
|
|
2602
|
+
try {
|
|
2603
|
+
const snapshot = (ctx.sessions.binding(sessionId)?.session)?.projections?.faceOf?.("modelSelection")?.getSnapshot?.();
|
|
2604
|
+
return snapshot?.next ?? snapshot?.lastUsed ?? void 0;
|
|
2605
|
+
} catch {
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
/**
|
|
2610
|
+
* Remote session face carrying `selectModel`/`modelCatalog`(dsh 0.1.5 删除
|
|
2611
|
+
* `connection.api.sessions.*` client 面后的模型 RPC 面;事实上 0.1.2-rc.1
|
|
2612
|
+
* 起存在——authority: dsh-api-session-controller lib/typert.remote-client.d.ts)。
|
|
2613
|
+
* 探测走 `ctx.get`(cordis 的 get 不受 inject 门禁限制,本插件为兼容旧宿主
|
|
2614
|
+
* 不声明 inject 'remote.session';机制见 contracts.ts RemoteSessionModelFace)。
|
|
2615
|
+
* `ctx.get('remote')` 返回命名空间容器时下钻其 `.session` 成员。
|
|
2616
|
+
*/
|
|
2617
|
+
function remoteSessionFace(ctx) {
|
|
2618
|
+
for (const name of ["remote.session", "remote"]) {
|
|
2619
|
+
let candidate;
|
|
2620
|
+
try {
|
|
2621
|
+
candidate = ctx.get(name);
|
|
2622
|
+
} catch {
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
const face = candidate;
|
|
2626
|
+
if (typeof face?.selectModel === "function") return face;
|
|
2627
|
+
if (typeof face?.session?.selectModel === "function") return face.session;
|
|
2220
2628
|
}
|
|
2221
|
-
return parts.join("\n");
|
|
2222
|
-
}
|
|
2223
|
-
function seqKey(prefix, node) {
|
|
2224
|
-
return `${prefix}:${typeof node.seq === "number" ? node.seq : "?"}`;
|
|
2225
2629
|
}
|
|
2226
|
-
/**
|
|
2227
|
-
|
|
2228
|
-
|
|
2630
|
+
/**
|
|
2631
|
+
* 读会话当前模型选择:投影面(0.1.2+,同步无 RPC)优先;旧
|
|
2632
|
+
* `connection.api.sessions.models` RPC(<= 0.1.2 的 client 面,0.1.5 已删)
|
|
2633
|
+
* 回退(双版本链,同 chatSourceOf 先例)。永不抛错。
|
|
2634
|
+
*/
|
|
2635
|
+
async function readModelSelection(ctx, sessionId) {
|
|
2636
|
+
const projected = projectedModelSelection(ctx, sessionId);
|
|
2637
|
+
if (projected !== void 0) return projected;
|
|
2638
|
+
try {
|
|
2639
|
+
const res = await ctx.connection.api.sessions.models({ sessionId });
|
|
2640
|
+
if (res.result.ok) return res.result.value.current;
|
|
2641
|
+
} catch {}
|
|
2229
2642
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
return {
|
|
2242
|
-
text: texts.join("\n\n"),
|
|
2243
|
-
reasoning: reasonings.join("\n\n"),
|
|
2244
|
-
hasToolCall
|
|
2643
|
+
/**
|
|
2644
|
+
* 写会话模型选择:`remote.session.selectModel`(新面)优先,旧
|
|
2645
|
+
* `connection.api.sessions.selectModel` 回退。面在而调用失败(模型不可路由等)
|
|
2646
|
+
* 属真实失败,不再落旧面重试。
|
|
2647
|
+
*/
|
|
2648
|
+
async function writeModelSelection(ctx, sessionId, selection) {
|
|
2649
|
+
const request = {
|
|
2650
|
+
sessionId,
|
|
2651
|
+
provider: selection.provider,
|
|
2652
|
+
model: selection.model,
|
|
2653
|
+
...selection.reasoningEffort !== void 0 ? { reasoningEffort: selection.reasoningEffort } : {}
|
|
2245
2654
|
};
|
|
2655
|
+
const face = remoteSessionFace(ctx);
|
|
2656
|
+
if (face !== void 0) try {
|
|
2657
|
+
return (await face.selectModel(request)).ok;
|
|
2658
|
+
} catch {
|
|
2659
|
+
return false;
|
|
2660
|
+
}
|
|
2661
|
+
try {
|
|
2662
|
+
return (await ctx.connection.api.sessions.selectModel(request)).result.ok;
|
|
2663
|
+
} catch {
|
|
2664
|
+
return false;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
/** 读子会话当前模型名(composer 模型标签用);任何失败回退 null(默认文案)。 */
|
|
2668
|
+
async function readModelName(ctx, sessionId) {
|
|
2669
|
+
return (await readModelSelection(ctx, sessionId))?.model ?? null;
|
|
2246
2670
|
}
|
|
2247
2671
|
/**
|
|
2248
|
-
*
|
|
2249
|
-
*
|
|
2672
|
+
* 0.1.2 优先:`uiConversation` 服务 → binding(会话 binding) → target('chat')
|
|
2673
|
+
* → 快照 .legacy 切片(0.1.2 把内容面从 Session 快照顶层拆到此处——
|
|
2674
|
+
* 根因与证据见 reports/ux-review/W00-fork-replay-012.md);0.1.1 回退
|
|
2675
|
+
* Session 快照顶层(nodes 直接在)。均 miss 返回 undefined(面板维持
|
|
2676
|
+
* 既有降级,不崩)。
|
|
2250
2677
|
*/
|
|
2251
|
-
function
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
case "steering": return {
|
|
2262
|
-
key: seqKey("s", n),
|
|
2263
|
-
...seqOf(n),
|
|
2264
|
-
role: "user",
|
|
2265
|
-
text: contentTextOf(n.content)
|
|
2678
|
+
function chatSourceOf(ctx, binding) {
|
|
2679
|
+
const session = binding?.session;
|
|
2680
|
+
if (session === void 0) return void 0;
|
|
2681
|
+
try {
|
|
2682
|
+
const target = ctx.get("uiConversation")?.binding?.(binding)?.target?.("chat");
|
|
2683
|
+
if (target !== void 0 && typeof target.getSnapshot === "function" && typeof target.subscribe === "function") return {
|
|
2684
|
+
subscribe: (fn) => target.subscribe(fn),
|
|
2685
|
+
getLegacy: () => {
|
|
2686
|
+
return target.getSnapshot()?.legacy ?? null;
|
|
2687
|
+
}
|
|
2266
2688
|
};
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
callView: n.callView,
|
|
2286
|
-
resultView: n.resultView,
|
|
2287
|
-
rawText: text
|
|
2288
|
-
}) : cardModelFromNode({
|
|
2289
|
-
name: toolName,
|
|
2290
|
-
argsRaw: typeof call?.argsRaw === "string" ? call.argsRaw : void 0,
|
|
2291
|
-
meta: n.meta,
|
|
2292
|
-
rawText: text,
|
|
2293
|
-
...n.isError === true ? { isError: true } : {}
|
|
2294
|
-
});
|
|
2689
|
+
} catch {}
|
|
2690
|
+
const snap = session.getSnapshot();
|
|
2691
|
+
if (snap !== null && Array.isArray(snap.nodes)) return {
|
|
2692
|
+
subscribe: (fn) => session.subscribe(fn),
|
|
2693
|
+
getLegacy: () => session.getSnapshot()
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
/**
|
|
2697
|
+
* 拉会话模型目录:0.1.5 走 `remote.session.modelCatalog()`(目录是宿主级,
|
|
2698
|
+
* 菜单选中项所需的 current 由投影/旧面补上);旧面回退
|
|
2699
|
+
* `connection.api.sessions.models`。失败 null → 菜单保持只读标签态。
|
|
2700
|
+
*/
|
|
2701
|
+
async function listModels(ctx, sessionId) {
|
|
2702
|
+
const face = remoteSessionFace(ctx);
|
|
2703
|
+
if (typeof face?.modelCatalog === "function") try {
|
|
2704
|
+
const result = await face.modelCatalog();
|
|
2705
|
+
if (result.ok) {
|
|
2706
|
+
const catalog = result.value;
|
|
2295
2707
|
return {
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
text,
|
|
2301
|
-
card,
|
|
2302
|
-
...n.isError === true ? { isError: true } : {}
|
|
2708
|
+
current: await readModelSelection(ctx, sessionId) ?? catalog.default,
|
|
2709
|
+
routable: catalog.routableProviders.length > 0,
|
|
2710
|
+
groups: catalog.groups,
|
|
2711
|
+
...catalog.failures !== void 0 ? { failures: catalog.failures } : {}
|
|
2303
2712
|
};
|
|
2304
2713
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2714
|
+
} catch {}
|
|
2715
|
+
try {
|
|
2716
|
+
const res = await ctx.connection.api.sessions.models({ sessionId });
|
|
2717
|
+
return res.result.ok ? res.result.value : null;
|
|
2718
|
+
} catch {
|
|
2719
|
+
return null;
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
/** 切换子会话模型;成功返回新模型展示名,失败 null(best-effort)。 */
|
|
2723
|
+
async function switchModel(ctx, sessionId, provider, model) {
|
|
2724
|
+
return await writeModelSelection(ctx, sessionId, {
|
|
2725
|
+
provider,
|
|
2726
|
+
model
|
|
2727
|
+
}) ? model : null;
|
|
2728
|
+
}
|
|
2729
|
+
//#endregion
|
|
2730
|
+
//#region src/client/host/probes.ts
|
|
2731
|
+
const PROBES = [
|
|
2732
|
+
{
|
|
2733
|
+
name: "Session.open()(非 staged 会话开窗)",
|
|
2734
|
+
check: (ctx) => {
|
|
2735
|
+
return typeof ctx.sessions?.binding === "function";
|
|
2736
|
+
},
|
|
2737
|
+
fallback: "侧边聊天消息流降级为只发不收"
|
|
2738
|
+
},
|
|
2739
|
+
{
|
|
2740
|
+
name: "conversation.input 输入机器",
|
|
2741
|
+
check: (ctx) => {
|
|
2742
|
+
return typeof (ctx.get?.("conversation"))?.input?.for === "function";
|
|
2743
|
+
},
|
|
2744
|
+
fallback: "侧边 composer 降级为本地草稿 + session.prompt"
|
|
2745
|
+
},
|
|
2746
|
+
{
|
|
2747
|
+
name: "commandUi(斜杠命令注册)",
|
|
2748
|
+
check: (ctx) => {
|
|
2749
|
+
return typeof (ctx.get?.("commandUi"))?.register === "function";
|
|
2750
|
+
},
|
|
2751
|
+
fallback: "/side 与「/侧边」命令不可用(菜单/顶栏入口仍在)"
|
|
2752
|
+
},
|
|
2753
|
+
{
|
|
2754
|
+
name: "ctx.modules.import(动态组件复用)",
|
|
2755
|
+
check: (ctx) => {
|
|
2756
|
+
return typeof ctx.modules?.import === "function";
|
|
2757
|
+
},
|
|
2758
|
+
fallback: "ProducedFiles 等动态复用件降级为自绘"
|
|
2759
|
+
},
|
|
2760
|
+
{
|
|
2761
|
+
name: "sidebarRight.focus(native tab 程序化聚焦)",
|
|
2762
|
+
check: (ctx) => {
|
|
2763
|
+
if (!nativeSidebarHost(ctx)) return true;
|
|
2764
|
+
return typeof (ctx.get?.("sidebarRight"))?.focus === "function";
|
|
2765
|
+
},
|
|
2766
|
+
fallback: "聚焦既有侧边聊天 tab 不可用(activateTab 在 native 面是空操作)"
|
|
2767
|
+
},
|
|
2768
|
+
{
|
|
2769
|
+
name: "会话模型面(remote.session / connection.api.sessions 双版本链)",
|
|
2770
|
+
check: (ctx) => {
|
|
2771
|
+
if (remoteSessionFace(ctx) !== void 0) return true;
|
|
2772
|
+
return typeof ctx.connection?.api?.sessions?.models === "function";
|
|
2773
|
+
},
|
|
2774
|
+
fallback: "fork 模型同步、模型标签与模型切换不可用(子会话用宿主默认模型)"
|
|
2775
|
+
}
|
|
2776
|
+
];
|
|
2777
|
+
/** 激活时跑一遍探测清单;缺失项逐项 warn-once。返回缺失项数(测试可断言)。 */
|
|
2778
|
+
function probeHost(ctx) {
|
|
2779
|
+
let missing = 0;
|
|
2780
|
+
for (const probe of PROBES) {
|
|
2781
|
+
let ok = false;
|
|
2782
|
+
try {
|
|
2783
|
+
ok = probe.check(ctx);
|
|
2784
|
+
} catch {
|
|
2785
|
+
ok = false;
|
|
2786
|
+
}
|
|
2787
|
+
if (!ok) {
|
|
2788
|
+
missing += 1;
|
|
2789
|
+
console.warn(`[dsh-sidenote] 宿主能力缺失:${probe.name} → ${probe.fallback}`);
|
|
2334
2790
|
}
|
|
2335
|
-
case "compaction": return {
|
|
2336
|
-
key: seqKey("k", n),
|
|
2337
|
-
...seqOf(n),
|
|
2338
|
-
role: "notice",
|
|
2339
|
-
text: t("compacted")
|
|
2340
|
-
};
|
|
2341
|
-
default: return null;
|
|
2342
2791
|
}
|
|
2792
|
+
return missing;
|
|
2343
2793
|
}
|
|
2794
|
+
//#endregion
|
|
2795
|
+
//#region src/client/protocol.ts
|
|
2344
2796
|
/**
|
|
2345
|
-
*
|
|
2346
|
-
* 快照缺省(未绑定)时为空列表。
|
|
2797
|
+
* 模型-facing 协议块的 XML 属性位转义(L0 共享层)。
|
|
2347
2798
|
*
|
|
2348
|
-
*
|
|
2349
|
-
*
|
|
2350
|
-
*
|
|
2351
|
-
*
|
|
2352
|
-
* 归并(在途项永远属于当前 turn 的前沿,尾部插入点天然正确)。
|
|
2799
|
+
* 何时需要:把用户可推导的字符串(如侧边聊天的 topic 化标题,Delivery_05
|
|
2800
|
+
* 起标题来自首条消息)拼进 `<tag attribute="...">` 的属性位前必须转义,
|
|
2801
|
+
* 否则引号撑破属性、尖括号污染块结构。内容位(<问>/<答> 之间)保持原文,
|
|
2802
|
+
* 不转义——模型读取友好且不改写用户数据。
|
|
2353
2803
|
*/
|
|
2354
|
-
function
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2804
|
+
function xmlAttr(value) {
|
|
2805
|
+
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
|
2806
|
+
}
|
|
2807
|
+
//#endregion
|
|
2808
|
+
//#region src/client/reflow.ts
|
|
2809
|
+
/**
|
|
2810
|
+
* 回流通道的受控对象层(Delivery_02 Workitem_04 v2):侧边聊天的结论以
|
|
2811
|
+
* 「待回流对象」进入主会话——主 composer 上方的 chip(可预览/可移除),
|
|
2812
|
+
* 发送瞬间由 annotate/send.ts 拦截器与注释协议块一起序列化进消息。
|
|
2813
|
+
* 不经草稿文本流(用户裁定:直接进草稿「很脏」)。
|
|
2814
|
+
*
|
|
2815
|
+
* 气泡留痕:序列化形态是 XML 协议块(见 buildReflowBlock),随注释协议块
|
|
2816
|
+
* 一起作为消息连续前缀注入,bubble.ts 反解析后折叠为「含侧边回流上下文」标签。
|
|
2817
|
+
*
|
|
2818
|
+
* 持久化:localStorage 按会话键(dsh-sidenote:reflow:v1:<sessionId>),
|
|
2819
|
+
* 与注释 store 同款纪律(刷新不丢、容错 revive、空删键)。
|
|
2820
|
+
*/
|
|
2821
|
+
/**
|
|
2822
|
+
* reflow 注入通道(Workitem_06):优先经宿主路由 /sidenote/reflow 以
|
|
2823
|
+
* agent.inject 落父会话日志(plugin-source 独立事件,context row 折叠);
|
|
2824
|
+
* 路由缺席/父会话无 live agent/超时 → 调用方回落搭车形态(发送拦截器
|
|
2825
|
+
* 序列化前缀)。逐个 item 投递;成功项由调用方从 store 移除(不等发送
|
|
2826
|
+
* 确认——注入已入 inbox,chip 消失表达「已注入」)。
|
|
2827
|
+
*
|
|
2828
|
+
* 有界等待:注入尝试绝不能拖住发送手势——300ms 超时即回落搭车。
|
|
2829
|
+
*/
|
|
2830
|
+
const REFLOW_ROUTE_TIMEOUT_MS = 300;
|
|
2831
|
+
/** 尝试经宿主路由注入一批回流项;任何失败都降级为「全未注入」。 */
|
|
2832
|
+
async function tryInjectReflows(parentSessionId, items) {
|
|
2833
|
+
if (items.length === 0) return { injectedIds: /* @__PURE__ */ new Set() };
|
|
2834
|
+
const empty = { injectedIds: /* @__PURE__ */ new Set() };
|
|
2835
|
+
try {
|
|
2836
|
+
const controller = new AbortController();
|
|
2837
|
+
const timer = setTimeout(() => {
|
|
2838
|
+
controller.abort();
|
|
2839
|
+
}, REFLOW_ROUTE_TIMEOUT_MS);
|
|
2840
|
+
let res;
|
|
2841
|
+
try {
|
|
2842
|
+
res = await fetch("/sidenote/reflow", {
|
|
2843
|
+
method: "POST",
|
|
2844
|
+
headers: { "content-type": "application/json" },
|
|
2845
|
+
body: JSON.stringify({
|
|
2846
|
+
parentSessionId,
|
|
2847
|
+
items: items.map((item) => ({
|
|
2848
|
+
text: `以下是用户从侧边对话带回的结论(参考上下文):\n\n${buildReflowBlock(item)}`,
|
|
2849
|
+
sideTitle: item.sideTitle
|
|
2850
|
+
}))
|
|
2851
|
+
}),
|
|
2852
|
+
signal: controller.signal
|
|
2853
|
+
});
|
|
2854
|
+
} finally {
|
|
2855
|
+
clearTimeout(timer);
|
|
2856
|
+
}
|
|
2857
|
+
if (!res.ok) return empty;
|
|
2858
|
+
if ((await res.json()).ok !== true) return empty;
|
|
2859
|
+
return { injectedIds: new Set(items.map((item) => item.id)) };
|
|
2860
|
+
} catch {
|
|
2861
|
+
return empty;
|
|
2403
2862
|
}
|
|
2404
|
-
inflight.sort((a, b) => a.turn - b.turn || a.step - b.step || a.order - b.order);
|
|
2405
|
-
for (const item of inflight) out.push(item.message);
|
|
2406
|
-
return out;
|
|
2407
2863
|
}
|
|
2864
|
+
const STORAGE_PREFIX$1 = "dsh-sidenote:reflow:v1:";
|
|
2408
2865
|
/**
|
|
2409
|
-
*
|
|
2410
|
-
*
|
|
2411
|
-
*
|
|
2866
|
+
* 回流块序列化(XML 形态,与注释协议同族):
|
|
2867
|
+
*
|
|
2868
|
+
* <reflow source="侧边 2" reason="用户选择从侧边聊天带回主线">
|
|
2869
|
+
* <问>用户在侧边聊天里的提问</问>
|
|
2870
|
+
* <答>结论全文(不截断)</答>
|
|
2871
|
+
* </reflow>
|
|
2872
|
+
*
|
|
2873
|
+
* 问答成对:只有答没有问,模型理解打折;question 缺省时只有 <答>。
|
|
2874
|
+
* 来源与意图放在属性里,内容行保持纯净;模型对 XML 包裹的上下文理解最好
|
|
2875
|
+
* (Claude 官方推荐形态 / Codex additionalContext 同款思路)。
|
|
2412
2876
|
*/
|
|
2413
|
-
function
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2877
|
+
function buildReflowBlock(item) {
|
|
2878
|
+
const parts = [];
|
|
2879
|
+
if (item.question !== void 0 && item.question.trim() !== "") parts.push(`<问>${item.question}</问>`);
|
|
2880
|
+
parts.push(`<答>${item.text}</答>`);
|
|
2881
|
+
return `<reflow source="${xmlAttr(item.sideTitle)}" reason="${t("reflowReason")}">\n${parts.join("\n")}\n</reflow>`;
|
|
2882
|
+
}
|
|
2883
|
+
function revive(value) {
|
|
2884
|
+
if (typeof value !== "object" || value === null) return null;
|
|
2885
|
+
const r = value;
|
|
2886
|
+
if (typeof r.id !== "number" || typeof r.sessionId !== "string" || r.sessionId === "") return null;
|
|
2887
|
+
if (typeof r.sideTitle !== "string" || typeof r.text !== "string") return null;
|
|
2888
|
+
return {
|
|
2889
|
+
id: r.id,
|
|
2890
|
+
sessionId: r.sessionId,
|
|
2891
|
+
sideTitle: r.sideTitle,
|
|
2892
|
+
text: r.text,
|
|
2893
|
+
...typeof r.question === "string" && r.question.trim() !== "" ? { question: r.question } : {},
|
|
2894
|
+
createdAt: typeof r.createdAt === "number" ? r.createdAt : 0
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
function createReflowStore(now = () => Date.now(), storage) {
|
|
2898
|
+
let items = [];
|
|
2899
|
+
let nextId = 1;
|
|
2900
|
+
let version = 0;
|
|
2901
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2902
|
+
const store = storage === void 0 ? typeof localStorage !== "undefined" ? localStorage : null : storage;
|
|
2903
|
+
if (store != null) try {
|
|
2904
|
+
const keys = [];
|
|
2905
|
+
for (let i = 0; i < store.length; i += 1) {
|
|
2906
|
+
const key = store.key(i);
|
|
2907
|
+
if (typeof key === "string" && key.startsWith(STORAGE_PREFIX$1)) keys.push(key);
|
|
2908
|
+
}
|
|
2909
|
+
for (const key of keys) {
|
|
2910
|
+
const parsed = JSON.parse(store.getItem(key) ?? "null");
|
|
2911
|
+
if (!Array.isArray(parsed)) continue;
|
|
2912
|
+
for (const raw of parsed) {
|
|
2913
|
+
const item = revive(raw);
|
|
2914
|
+
if (item === null) continue;
|
|
2915
|
+
items.push(item);
|
|
2916
|
+
nextId = Math.max(nextId, item.id + 1);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
} catch (error) {
|
|
2920
|
+
console.warn("[dsh-sidenote] 回流持久化读取失败(按空起步):", error);
|
|
2921
|
+
}
|
|
2922
|
+
const persist = (sessionId) => {
|
|
2923
|
+
if (store == null) return;
|
|
2924
|
+
try {
|
|
2925
|
+
const mine = items.filter((i) => i.sessionId === sessionId);
|
|
2926
|
+
const key = STORAGE_PREFIX$1 + sessionId;
|
|
2927
|
+
if (mine.length === 0) store.removeItem(key);
|
|
2928
|
+
else store.setItem(key, JSON.stringify(mine));
|
|
2929
|
+
} catch (error) {
|
|
2930
|
+
console.warn("[dsh-sidenote] 回流持久化写入失败:", error);
|
|
2931
|
+
}
|
|
2932
|
+
};
|
|
2933
|
+
const notify = () => {
|
|
2934
|
+
version += 1;
|
|
2935
|
+
for (const fn of [...listeners]) fn();
|
|
2417
2936
|
};
|
|
2418
|
-
const inherited = [];
|
|
2419
|
-
const fresh = [];
|
|
2420
|
-
for (const message of messages) if (message.seq !== void 0 && message.seq <= boundarySeq) inherited.push(message);
|
|
2421
|
-
else fresh.push(message);
|
|
2422
2937
|
return {
|
|
2423
|
-
|
|
2424
|
-
|
|
2938
|
+
getSnapshot: () => version,
|
|
2939
|
+
subscribe(fn) {
|
|
2940
|
+
listeners.add(fn);
|
|
2941
|
+
return () => {
|
|
2942
|
+
listeners.delete(fn);
|
|
2943
|
+
};
|
|
2944
|
+
},
|
|
2945
|
+
add(sessionId, sideTitle, text, question) {
|
|
2946
|
+
const item = {
|
|
2947
|
+
id: nextId,
|
|
2948
|
+
sessionId,
|
|
2949
|
+
sideTitle,
|
|
2950
|
+
text,
|
|
2951
|
+
...typeof question === "string" && question.trim() !== "" ? { question } : {},
|
|
2952
|
+
createdAt: now()
|
|
2953
|
+
};
|
|
2954
|
+
nextId += 1;
|
|
2955
|
+
items = [...items, item];
|
|
2956
|
+
persist(sessionId);
|
|
2957
|
+
notify();
|
|
2958
|
+
return item;
|
|
2959
|
+
},
|
|
2960
|
+
remove(id) {
|
|
2961
|
+
const target = items.find((i) => i.id === id);
|
|
2962
|
+
if (target === void 0) return;
|
|
2963
|
+
items = items.filter((i) => i.id !== id);
|
|
2964
|
+
persist(target.sessionId);
|
|
2965
|
+
notify();
|
|
2966
|
+
},
|
|
2967
|
+
clearSession(sessionId) {
|
|
2968
|
+
if (!items.some((i) => i.sessionId === sessionId)) return;
|
|
2969
|
+
items = items.filter((i) => i.sessionId !== sessionId);
|
|
2970
|
+
persist(sessionId);
|
|
2971
|
+
notify();
|
|
2972
|
+
},
|
|
2973
|
+
list: (sessionId) => items.filter((i) => i.sessionId === sessionId)
|
|
2425
2974
|
};
|
|
2426
2975
|
}
|
|
2427
2976
|
//#endregion
|
|
2977
|
+
//#region src/client/bridge.ts
|
|
2978
|
+
const sideChatBridge = { current: null };
|
|
2979
|
+
//#endregion
|
|
2428
2980
|
//#region src/client/host/markdown.ts
|
|
2429
2981
|
let cache;
|
|
2430
2982
|
/** 当前语言的 chrome 对象(同语言复用同一引用)。 */
|
|
@@ -2560,7 +3112,7 @@ window.__ModuleLoader__.load({
|
|
|
2560
3112
|
}
|
|
2561
3113
|
//#endregion
|
|
2562
3114
|
//#region \0dsh-css:/home/runner/work/dsh-sidenote/dsh-sidenote/src/client/sidechat/sidechat.module.css.mjs
|
|
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}";
|
|
3115
|
+
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_snapshotBody{border-top:1px dashed var(--dsw-alias-border-l2);padding:6px 0 2px}._5KEEsG_snapshotText{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);max-height:320px;margin:0;overflow-y:auto}._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}";
|
|
2564
3116
|
const tagId$1 = "dsh-sidenote/sidechat.module.css";
|
|
2565
3117
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
2566
3118
|
const tag = document.createElement("style");
|
|
@@ -2570,93 +3122,95 @@ window.__ModuleLoader__.load({
|
|
|
2570
3122
|
document.head.appendChild(tag);
|
|
2571
3123
|
}
|
|
2572
3124
|
var sidechat_module_css_default = {
|
|
2573
|
-
"
|
|
2574
|
-
"
|
|
2575
|
-
"
|
|
2576
|
-
"
|
|
2577
|
-
"stateScreen": "_5KEEsG_stateScreen",
|
|
2578
|
-
"composerFoot": "_5KEEsG_composerFoot",
|
|
3125
|
+
"input": "_5KEEsG_input",
|
|
3126
|
+
"sendButton": "_5KEEsG_sendButton",
|
|
3127
|
+
"body": "_5KEEsG_body",
|
|
3128
|
+
"empty": "_5KEEsG_empty",
|
|
2579
3129
|
"flowRowError": "_5KEEsG_flowRowError",
|
|
2580
|
-
"
|
|
2581
|
-
"
|
|
3130
|
+
"sentChipRow": "_5KEEsG_sentChipRow",
|
|
3131
|
+
"todoText": "_5KEEsG_todoText",
|
|
3132
|
+
"slashItem": "_5KEEsG_slashItem",
|
|
3133
|
+
"slashItemDetail": "_5KEEsG_slashItemDetail",
|
|
3134
|
+
"noticeRow": "_5KEEsG_noticeRow",
|
|
3135
|
+
"pendingTitle": "_5KEEsG_pendingTitle",
|
|
3136
|
+
"userBubble": "_5KEEsG_userBubble",
|
|
2582
3137
|
"toolCard": "_5KEEsG_toolCard",
|
|
2583
|
-
"densityRow": "_5KEEsG_densityRow",
|
|
2584
|
-
"pendingActions": "_5KEEsG_pendingActions",
|
|
2585
|
-
"reasoning": "_5KEEsG_reasoning",
|
|
2586
3138
|
"slashMenu": "_5KEEsG_slashMenu",
|
|
2587
|
-
"
|
|
2588
|
-
"
|
|
2589
|
-
"
|
|
2590
|
-
"
|
|
2591
|
-
"toolError": "_5KEEsG_toolError",
|
|
3139
|
+
"stateDetail": "_5KEEsG_stateDetail",
|
|
3140
|
+
"pendingQHeader": "_5KEEsG_pendingQHeader",
|
|
3141
|
+
"densityToggle": "_5KEEsG_densityToggle",
|
|
3142
|
+
"pendingActions": "_5KEEsG_pendingActions",
|
|
2592
3143
|
"reflowButton": "_5KEEsG_reflowButton",
|
|
2593
|
-
"
|
|
2594
|
-
"
|
|
2595
|
-
"
|
|
2596
|
-
"
|
|
3144
|
+
"todoDot": "_5KEEsG_todoDot",
|
|
3145
|
+
"assistantBody": "_5KEEsG_assistantBody",
|
|
3146
|
+
"reasoning": "_5KEEsG_reasoning",
|
|
3147
|
+
"composerCard": "_5KEEsG_composerCard",
|
|
3148
|
+
"pendingQText": "_5KEEsG_pendingQText",
|
|
3149
|
+
"userRow": "_5KEEsG_userRow",
|
|
2597
3150
|
"pendingCard": "_5KEEsG_pendingCard",
|
|
2598
|
-
"
|
|
3151
|
+
"pendingBar": "_5KEEsG_pendingBar",
|
|
3152
|
+
"parentStrip": "_5KEEsG_parentStrip",
|
|
3153
|
+
"assistantRow": "_5KEEsG_assistantRow",
|
|
3154
|
+
"todoItem": "_5KEEsG_todoItem",
|
|
3155
|
+
"stopButton": "_5KEEsG_stopButton",
|
|
3156
|
+
"pendingOption": "_5KEEsG_pendingOption",
|
|
3157
|
+
"todoBody": "_5KEEsG_todoBody",
|
|
3158
|
+
"errorRow": "_5KEEsG_errorRow",
|
|
2599
3159
|
"root": "_5KEEsG_root",
|
|
3160
|
+
"stateScreen": "_5KEEsG_stateScreen",
|
|
3161
|
+
"snapshotBody": "_5KEEsG_snapshotBody",
|
|
3162
|
+
"toolBodyText": "_5KEEsG_toolBodyText",
|
|
3163
|
+
"dotRunning": "_5KEEsG_dotRunning",
|
|
3164
|
+
"modelLabel": "_5KEEsG_modelLabel",
|
|
2600
3165
|
"attachThumb": "_5KEEsG_attachThumb",
|
|
2601
|
-
"
|
|
3166
|
+
"toolGenericBody": "_5KEEsG_toolGenericBody",
|
|
3167
|
+
"emptyIcon": "_5KEEsG_emptyIcon",
|
|
3168
|
+
"rowActions": "_5KEEsG_rowActions",
|
|
3169
|
+
"emptyText": "_5KEEsG_emptyText",
|
|
3170
|
+
"emptyTitle": "_5KEEsG_emptyTitle",
|
|
2602
3171
|
"streamingHint": "_5KEEsG_streamingHint",
|
|
3172
|
+
"toolError": "_5KEEsG_toolError",
|
|
2603
3173
|
"dotIdle": "_5KEEsG_dotIdle",
|
|
2604
|
-
"sidechat-pulse": "_5KEEsG_sidechat-pulse",
|
|
2605
|
-
"todoBody": "_5KEEsG_todoBody",
|
|
2606
|
-
"todoItem": "_5KEEsG_todoItem",
|
|
2607
|
-
"sentChipRow": "_5KEEsG_sentChipRow",
|
|
2608
|
-
"sentChip": "_5KEEsG_sentChip",
|
|
2609
|
-
"toolRunning": "_5KEEsG_toolRunning",
|
|
2610
|
-
"toolBody": "_5KEEsG_toolBody",
|
|
2611
|
-
"pendingQText": "_5KEEsG_pendingQText",
|
|
2612
|
-
"empty": "_5KEEsG_empty",
|
|
2613
|
-
"assistantBody": "_5KEEsG_assistantBody",
|
|
2614
|
-
"attachImg": "_5KEEsG_attachImg",
|
|
2615
|
-
"sendButton": "_5KEEsG_sendButton",
|
|
2616
|
-
"attachButton": "_5KEEsG_attachButton",
|
|
2617
|
-
"userRow": "_5KEEsG_userRow",
|
|
2618
|
-
"toolHead": "_5KEEsG_toolHead",
|
|
2619
|
-
"emptyTitle": "_5KEEsG_emptyTitle",
|
|
2620
|
-
"slashGroupTitle": "_5KEEsG_slashGroupTitle",
|
|
2621
|
-
"writeNoticeClose": "_5KEEsG_writeNoticeClose",
|
|
2622
|
-
"slashItemActive": "_5KEEsG_slashItemActive",
|
|
2623
|
-
"pendingReason": "_5KEEsG_pendingReason",
|
|
2624
|
-
"writeNotice": "_5KEEsG_writeNotice",
|
|
2625
|
-
"nativeHost": "_5KEEsG_nativeHost",
|
|
2626
|
-
"userBubble": "_5KEEsG_userBubble",
|
|
2627
|
-
"stateDetail": "_5KEEsG_stateDetail",
|
|
2628
|
-
"slashItemName": "_5KEEsG_slashItemName",
|
|
2629
|
-
"todoDot": "_5KEEsG_todoDot",
|
|
2630
|
-
"toolLocations": "_5KEEsG_toolLocations",
|
|
2631
|
-
"pendingOptionDesc": "_5KEEsG_pendingOptionDesc",
|
|
2632
|
-
"emptyIcon": "_5KEEsG_emptyIcon",
|
|
2633
|
-
"composerCard": "_5KEEsG_composerCard",
|
|
2634
|
-
"noticeRow": "_5KEEsG_noticeRow",
|
|
2635
3174
|
"inheritedBody": "_5KEEsG_inheritedBody",
|
|
2636
|
-
"
|
|
2637
|
-
"
|
|
2638
|
-
"toolBodyText": "_5KEEsG_toolBodyText",
|
|
2639
|
-
"modelSelect": "_5KEEsG_modelSelect",
|
|
3175
|
+
"rowPreview": "_5KEEsG_rowPreview",
|
|
3176
|
+
"headerSide": "_5KEEsG_headerSide",
|
|
2640
3177
|
"actionGroup": "_5KEEsG_actionGroup",
|
|
2641
3178
|
"pendingBtnPrimary": "_5KEEsG_pendingBtnPrimary",
|
|
2642
|
-
"
|
|
2643
|
-
"
|
|
2644
|
-
"
|
|
2645
|
-
"
|
|
2646
|
-
"
|
|
2647
|
-
"
|
|
2648
|
-
"
|
|
2649
|
-
"
|
|
3179
|
+
"pendingOptionActive": "_5KEEsG_pendingOptionActive",
|
|
3180
|
+
"snapshotText": "_5KEEsG_snapshotText",
|
|
3181
|
+
"slashGroupTitle": "_5KEEsG_slashGroupTitle",
|
|
3182
|
+
"composerChips": "_5KEEsG_composerChips",
|
|
3183
|
+
"composer": "_5KEEsG_composer",
|
|
3184
|
+
"nativeHost": "_5KEEsG_nativeHost",
|
|
3185
|
+
"transcript": "_5KEEsG_transcript",
|
|
3186
|
+
"toolHead": "_5KEEsG_toolHead",
|
|
3187
|
+
"attachButton": "_5KEEsG_attachButton",
|
|
3188
|
+
"pendingQuestion": "_5KEEsG_pendingQuestion",
|
|
3189
|
+
"pendingCustom": "_5KEEsG_pendingCustom",
|
|
2650
3190
|
"pendingDesc": "_5KEEsG_pendingDesc",
|
|
2651
|
-
"
|
|
2652
|
-
"pendingQHeader": "_5KEEsG_pendingQHeader",
|
|
3191
|
+
"writeNoticeClose": "_5KEEsG_writeNoticeClose",
|
|
2653
3192
|
"flowRow": "_5KEEsG_flowRow",
|
|
2654
|
-
"
|
|
3193
|
+
"pendingOptionDesc": "_5KEEsG_pendingOptionDesc",
|
|
3194
|
+
"dotPending": "_5KEEsG_dotPending",
|
|
3195
|
+
"sidechat-pulse": "_5KEEsG_sidechat-pulse",
|
|
3196
|
+
"attachImg": "_5KEEsG_attachImg",
|
|
3197
|
+
"sentChip": "_5KEEsG_sentChip",
|
|
3198
|
+
"slashItemActive": "_5KEEsG_slashItemActive",
|
|
3199
|
+
"toolRunning": "_5KEEsG_toolRunning",
|
|
3200
|
+
"toolLocations": "_5KEEsG_toolLocations",
|
|
3201
|
+
"reasoningBody": "_5KEEsG_reasoningBody",
|
|
3202
|
+
"slashItemName": "_5KEEsG_slashItemName",
|
|
3203
|
+
"composerFoot": "_5KEEsG_composerFoot",
|
|
2655
3204
|
"attachRail": "_5KEEsG_attachRail",
|
|
2656
|
-
"
|
|
3205
|
+
"pendingHead": "_5KEEsG_pendingHead",
|
|
3206
|
+
"toolBody": "_5KEEsG_toolBody",
|
|
3207
|
+
"jumpBottom": "_5KEEsG_jumpBottom",
|
|
3208
|
+
"modelSelect": "_5KEEsG_modelSelect",
|
|
2657
3209
|
"attachRemove": "_5KEEsG_attachRemove",
|
|
2658
|
-
"
|
|
2659
|
-
"
|
|
3210
|
+
"densityRow": "_5KEEsG_densityRow",
|
|
3211
|
+
"pendingReason": "_5KEEsG_pendingReason",
|
|
3212
|
+
"pendingBtnGhost": "_5KEEsG_pendingBtnGhost",
|
|
3213
|
+
"writeNotice": "_5KEEsG_writeNotice"
|
|
2660
3214
|
};
|
|
2661
3215
|
//#endregion
|
|
2662
3216
|
//#region src/client/chat/ToolCard.tsx
|
|
@@ -2951,18 +3505,76 @@ window.__ModuleLoader__.load({
|
|
|
2951
3505
|
]
|
|
2952
3506
|
});
|
|
2953
3507
|
}
|
|
3508
|
+
/**
|
|
3509
|
+
* 剥出首条侧边用户消息里的主线进展快照块(Delivery_04):快照提升到继承卡
|
|
3510
|
+
* 区域渲染(SnapshotCard),气泡只留用户正文(零污染)。块不完整(缺闭合
|
|
3511
|
+
* 标签)视为无快照——消息原样渲染,绝不吃文本。
|
|
3512
|
+
*/
|
|
3513
|
+
function splitSnapshotPrefix(text) {
|
|
3514
|
+
if (!text.startsWith("<mainline-progress-snapshot")) return null;
|
|
3515
|
+
const close = text.indexOf("</mainline-progress-snapshot>");
|
|
3516
|
+
if (close < 0) return null;
|
|
3517
|
+
const block = text.slice(0, close + 29);
|
|
3518
|
+
return {
|
|
3519
|
+
snapshot: block,
|
|
3520
|
+
takenAt: /taken-at="([^"]*)"/.exec(block)?.[1] ?? null,
|
|
3521
|
+
rest: text.slice(close + 29).trimStart()
|
|
3522
|
+
};
|
|
3523
|
+
}
|
|
3524
|
+
/** 主线进展快照卡:继承卡同族的可展开行(协议块原文,等宽换行)。 */
|
|
3525
|
+
function SnapshotCard({ takenAt, text, fold }) {
|
|
3526
|
+
useLocaleTick();
|
|
3527
|
+
const rowKey = "mainline-snapshot";
|
|
3528
|
+
const open = (0, react.useSyncExternalStore)((fn) => fold.subscribe(fn), () => fold.isOpen(rowKey));
|
|
3529
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3530
|
+
className: sidechat_module_css_default.flowRow,
|
|
3531
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
|
|
3532
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, { size: 14 }),
|
|
3533
|
+
title: takenAt !== null ? t("snapshotLabelAt", { at: takenAt }) : t("snapshotLabel"),
|
|
3534
|
+
open,
|
|
3535
|
+
expandable: true,
|
|
3536
|
+
expandOnRowClick: true,
|
|
3537
|
+
previewChevron: true,
|
|
3538
|
+
onToggle: () => {
|
|
3539
|
+
fold.toggle(rowKey);
|
|
3540
|
+
},
|
|
3541
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3542
|
+
className: sidechat_module_css_default.snapshotBody,
|
|
3543
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
3544
|
+
className: sidechat_module_css_default.snapshotText,
|
|
3545
|
+
children: text
|
|
3546
|
+
})
|
|
3547
|
+
})
|
|
3548
|
+
})
|
|
3549
|
+
});
|
|
3550
|
+
}
|
|
2954
3551
|
function MessageList({ messages, fold, boundarySeq, reflow, parentSessionId, sideTitle, onPromote }) {
|
|
2955
3552
|
const [reflowAllDone, setReflowAllDone] = (0, react.useState)(false);
|
|
2956
3553
|
const questions = (0, react.useMemo)(() => pairQuestions(messages), [messages]);
|
|
2957
3554
|
const { inherited, fresh } = (0, react.useMemo)(() => partitionInherited(messages, boundarySeq), [messages, boundarySeq]);
|
|
2958
|
-
const
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
3555
|
+
const snapshotInfo = (0, react.useMemo)(() => {
|
|
3556
|
+
const first = fresh.find((m) => m.role === "user");
|
|
3557
|
+
if (first === void 0) return null;
|
|
3558
|
+
const split = splitSnapshotPrefix(first.text);
|
|
3559
|
+
return split === null ? null : {
|
|
3560
|
+
key: first.key,
|
|
3561
|
+
...split
|
|
3562
|
+
};
|
|
3563
|
+
}, [fresh]);
|
|
3564
|
+
const renderRow = (message) => {
|
|
3565
|
+
const m = snapshotInfo !== null && message.key === snapshotInfo.key ? {
|
|
3566
|
+
...message,
|
|
3567
|
+
text: snapshotInfo.rest
|
|
3568
|
+
} : message;
|
|
3569
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageRow, {
|
|
3570
|
+
message: m,
|
|
3571
|
+
question: questions.get(m.key),
|
|
3572
|
+
fold,
|
|
3573
|
+
reflow,
|
|
3574
|
+
parentSessionId,
|
|
3575
|
+
sideTitle
|
|
3576
|
+
}, m.key);
|
|
3577
|
+
};
|
|
2966
3578
|
const foldableKeys = (0, react.useMemo)(() => {
|
|
2967
3579
|
const keys = [];
|
|
2968
3580
|
if (inherited.length > 0) keys.push("inherited");
|
|
@@ -3017,6 +3629,11 @@ window.__ModuleLoader__.load({
|
|
|
3017
3629
|
fold,
|
|
3018
3630
|
children: inherited.map(renderRow)
|
|
3019
3631
|
}),
|
|
3632
|
+
snapshotInfo !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SnapshotCard, {
|
|
3633
|
+
takenAt: snapshotInfo.takenAt,
|
|
3634
|
+
text: snapshotInfo.snapshot,
|
|
3635
|
+
fold
|
|
3636
|
+
}),
|
|
3020
3637
|
fresh.map(renderRow)
|
|
3021
3638
|
]
|
|
3022
3639
|
});
|
|
@@ -4054,11 +4671,13 @@ window.__ModuleLoader__.load({
|
|
|
4054
4671
|
if (childId !== void 0 || forkStarted.current) return;
|
|
4055
4672
|
forkStarted.current = true;
|
|
4056
4673
|
let cancelled = false;
|
|
4057
|
-
|
|
4058
|
-
|
|
4674
|
+
const controller = new AbortController();
|
|
4675
|
+
forkAndRegister(ctx, scope.sessionId, tab.id, controller.signal).catch((error) => {
|
|
4676
|
+
if (!cancelled && !controller.signal.aborted) setForkError(error instanceof Error ? error.message : String(error));
|
|
4059
4677
|
});
|
|
4060
4678
|
return () => {
|
|
4061
4679
|
cancelled = true;
|
|
4680
|
+
controller.abort();
|
|
4062
4681
|
};
|
|
4063
4682
|
}, [
|
|
4064
4683
|
ctx,
|
|
@@ -4072,6 +4691,21 @@ window.__ModuleLoader__.load({
|
|
|
4072
4691
|
(0, react.useEffect)(() => {
|
|
4073
4692
|
openSessionWindow(session);
|
|
4074
4693
|
}, [session]);
|
|
4694
|
+
(0, react.useEffect)(() => {
|
|
4695
|
+
if (meta.forkedMidTurn !== true || meta.inheritedPurged === true) return;
|
|
4696
|
+
if (childId === void 0 || session === void 0) return;
|
|
4697
|
+
armInheritedTurnPurge(ctx, childId, scope.sessionId, tab.id, meta.leakedPromptPrefix, meta.boundarySeq);
|
|
4698
|
+
}, [
|
|
4699
|
+
ctx,
|
|
4700
|
+
childId,
|
|
4701
|
+
session,
|
|
4702
|
+
meta.forkedMidTurn,
|
|
4703
|
+
meta.inheritedPurged,
|
|
4704
|
+
meta.leakedPromptPrefix,
|
|
4705
|
+
meta.boundarySeq,
|
|
4706
|
+
scope.sessionId,
|
|
4707
|
+
tab.id
|
|
4708
|
+
]);
|
|
4075
4709
|
const phase = phaseOf({
|
|
4076
4710
|
childId,
|
|
4077
4711
|
forkError,
|
|
@@ -4097,7 +4731,30 @@ window.__ModuleLoader__.load({
|
|
|
4097
4731
|
]);
|
|
4098
4732
|
const pendingItems = (0, react.useSyncExternalStore)((0, react.useCallback)((notify) => visible && pendingStore !== void 0 ? pendingStore.subscribe(notify) : NOOP_UNSUBSCRIBE, [visible, pendingStore]), () => pendingStore?.getSnapshot() ?? NO_PENDING);
|
|
4099
4733
|
const [writeNoticeDismissed, setWriteNoticeDismissed] = (0, react.useState)(() => typeof localStorage !== "undefined" && localStorage.getItem(WRITE_NOTICE_KEY) === "1");
|
|
4100
|
-
const
|
|
4734
|
+
const tabMetaRef = (0, react.useRef)(meta);
|
|
4735
|
+
tabMetaRef.current = meta;
|
|
4736
|
+
const composer = useComposer(ctx, session, childId, (0, react.useMemo)(() => ({
|
|
4737
|
+
firstSendPrefix: () => {
|
|
4738
|
+
const m = tabMetaRef.current;
|
|
4739
|
+
if (m.forkedMidTurn !== true || m.parentSessionId === void 0) return null;
|
|
4740
|
+
const block = buildMainlineSnapshot(ctx, m.parentSessionId);
|
|
4741
|
+
if (block === null) return null;
|
|
4742
|
+
updateTabMeta(ctx, scope.sessionId, tab.id, (cur) => ({
|
|
4743
|
+
...cur,
|
|
4744
|
+
forkedMidTurn: void 0
|
|
4745
|
+
}));
|
|
4746
|
+
return block;
|
|
4747
|
+
},
|
|
4748
|
+
onSubmitText: (body) => {
|
|
4749
|
+
if (tabMetaRef.current.topic !== void 0) return;
|
|
4750
|
+
const topic = topicOf(body);
|
|
4751
|
+
if (topic !== void 0) setSideChatTopic(ctx, scope.sessionId, tab.id, topic);
|
|
4752
|
+
}
|
|
4753
|
+
}), [
|
|
4754
|
+
ctx,
|
|
4755
|
+
scope.sessionId,
|
|
4756
|
+
tab.id
|
|
4757
|
+
]));
|
|
4101
4758
|
const [modelName, setModelName] = (0, react.useState)(null);
|
|
4102
4759
|
(0, react.useEffect)(() => {
|
|
4103
4760
|
if (childId === void 0 || session === void 0) return;
|
|
@@ -4160,6 +4817,7 @@ window.__ModuleLoader__.load({
|
|
|
4160
4817
|
childId: m.childId,
|
|
4161
4818
|
parentSessionId: m.parentSessionId,
|
|
4162
4819
|
title: titleRef.current,
|
|
4820
|
+
...m.topic !== void 0 ? { topic: m.topic } : {},
|
|
4163
4821
|
closedAt: Date.now()
|
|
4164
4822
|
});
|
|
4165
4823
|
}
|
|
@@ -4368,7 +5026,8 @@ window.__ModuleLoader__.load({
|
|
|
4368
5026
|
return {
|
|
4369
5027
|
parentSessionId: p.parentSessionId,
|
|
4370
5028
|
...typeof p.pendingDraft === "string" && p.pendingDraft !== "" ? { pendingDraft: p.pendingDraft } : {},
|
|
4371
|
-
...typeof p.childId === "string" && p.childId !== "" ? { childId: p.childId } : {}
|
|
5029
|
+
...typeof p.childId === "string" && p.childId !== "" ? { childId: p.childId } : {},
|
|
5030
|
+
...typeof p.topic === "string" && p.topic !== "" ? { topic: p.topic } : {}
|
|
4372
5031
|
};
|
|
4373
5032
|
}
|
|
4374
5033
|
/**
|
|
@@ -4392,6 +5051,7 @@ window.__ModuleLoader__.load({
|
|
|
4392
5051
|
...params?.childId !== void 0 ? { childId: params.childId } : {},
|
|
4393
5052
|
parentSessionId: params?.parentSessionId ?? sessionId,
|
|
4394
5053
|
...params?.pendingDraft !== void 0 ? { pendingDraft: params.pendingDraft } : {},
|
|
5054
|
+
...params?.topic !== void 0 ? { topic: params.topic } : {},
|
|
4395
5055
|
number: nextSideChatNumber(sessionId),
|
|
4396
5056
|
createdAt: Date.now(),
|
|
4397
5057
|
runId: SIDENOTE_RUN_ID
|
|
@@ -4402,11 +5062,9 @@ window.__ModuleLoader__.load({
|
|
|
4402
5062
|
meta
|
|
4403
5063
|
};
|
|
4404
5064
|
}
|
|
4405
|
-
/** 芯片标题:metaStore
|
|
5065
|
+
/** 芯片标题:metaStore 兑现(topic 优先,编号回退——Delivery_05 单源)。 */
|
|
4406
5066
|
function titleOf(sessionId, tabId) {
|
|
4407
|
-
|
|
4408
|
-
if (meta === void 0 || meta.number <= 1) return t("tabBaseTitle");
|
|
4409
|
-
return `${t("tabBaseTitle")} ${meta.number}`;
|
|
5067
|
+
return sideChatTitleOf(readSideChatMeta(sessionId, tabId));
|
|
4410
5068
|
}
|
|
4411
5069
|
/** 面板体外壳:把 useTabInfo(可能对未提交 record throw)也包进错误边界。 */
|
|
4412
5070
|
function NativeSideChatBody(props) {
|
|
@@ -4620,14 +5278,11 @@ window.__ModuleLoader__.load({
|
|
|
4620
5278
|
label: t("cmdFocus", { title: live.readTitle() }),
|
|
4621
5279
|
detail: t("cmdFocusDetail")
|
|
4622
5280
|
});
|
|
4623
|
-
for (const meta of unloaded) {
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
detail: t("cmdFocusDetail")
|
|
4629
|
-
});
|
|
4630
|
-
}
|
|
5281
|
+
for (const meta of unloaded) options.push({
|
|
5282
|
+
id: `focus:${meta.tabId}`,
|
|
5283
|
+
label: t("cmdFocus", { title: sideChatTitleOf(meta) }),
|
|
5284
|
+
detail: t("cmdFocusDetail")
|
|
5285
|
+
});
|
|
4631
5286
|
} else {
|
|
4632
5287
|
const snapshot = betterSidebarOf(ctx)?.getSnapshot();
|
|
4633
5288
|
if (snapshot !== void 0 && snapshot.sessionId === session.sessionId && snapshot.state !== void 0) for (const tab of collectSideTabs(snapshot.state)) options.push({
|
|
@@ -4638,8 +5293,8 @@ window.__ModuleLoader__.load({
|
|
|
4638
5293
|
}
|
|
4639
5294
|
if (!(native && lives.length > 0)) for (const entry of listClosedSideChats(session.sessionId)) options.push({
|
|
4640
5295
|
id: `reopen:${entry.childId}`,
|
|
4641
|
-
label: t("cmdReopen", { title: entry.title }),
|
|
4642
|
-
detail:
|
|
5296
|
+
label: t("cmdReopen", { title: entry.topic ?? entry.title }),
|
|
5297
|
+
detail: formatClosedAt(entry.closedAt)
|
|
4643
5298
|
});
|
|
4644
5299
|
return Promise.resolve(options);
|
|
4645
5300
|
},
|
|
@@ -4655,7 +5310,9 @@ window.__ModuleLoader__.load({
|
|
|
4655
5310
|
}
|
|
4656
5311
|
if (option.id.startsWith("reopen:")) {
|
|
4657
5312
|
const childId = option.id.slice(7);
|
|
4658
|
-
|
|
5313
|
+
const entry = listClosedSideChats(session.sessionId).find((e) => e.childId === childId);
|
|
5314
|
+
const opts = entry?.topic !== void 0 ? { topic: entry.topic } : void 0;
|
|
5315
|
+
if (reopenSideChat(ctx, session.sessionId, childId, opts)) dropClosedSideChat(session.sessionId, childId);
|
|
4659
5316
|
}
|
|
4660
5317
|
}
|
|
4661
5318
|
}
|
|
@@ -4680,6 +5337,28 @@ window.__ModuleLoader__.load({
|
|
|
4680
5337
|
//#region src/client/sidechat/referenceSource.ts
|
|
4681
5338
|
const TRIGGER = "@";
|
|
4682
5339
|
const SOURCE = "sidenote-side-chats";
|
|
5340
|
+
/**
|
|
5341
|
+
* 候选名 → childId 的映射缓存,按会话分键(Delivery_05 碰撞护栏):onPick
|
|
5342
|
+
* 只能拿到候选 name 字符串(宿主 API 形状),topic 化标题后撞名成为可能
|
|
5343
|
+
* (legacy 多实例两个同主题)。candidates() 每次计算时重建本会话的映射
|
|
5344
|
+
* 并对重名追加序号消歧;onPick 只按 (sessionId, name) 查——跨会话复用
|
|
5345
|
+
* 陈旧候选拿不到 ref(审查 M2:全局单映射会把 A 会话的 childId 插进 B)。
|
|
5346
|
+
*/
|
|
5347
|
+
const lastCandidateRefs = /* @__PURE__ */ new Map();
|
|
5348
|
+
/** 候选名消歧(导出纯可测):重名依次追加「 · 2」「 · 3」,本批内唯一。 */
|
|
5349
|
+
function dedupeName(name, taken) {
|
|
5350
|
+
if (!taken.has(name)) {
|
|
5351
|
+
taken.add(name);
|
|
5352
|
+
return name;
|
|
5353
|
+
}
|
|
5354
|
+
for (let n = 2;; n += 1) {
|
|
5355
|
+
const candidate = `${name} · ${n}`;
|
|
5356
|
+
if (!taken.has(candidate)) {
|
|
5357
|
+
taken.add(candidate);
|
|
5358
|
+
return candidate;
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
4683
5362
|
/** 侧聊内容 → 结构化引用块(问答成对,全文不截断)。 */
|
|
4684
5363
|
async function serializeSideChatRef(ctx, childId) {
|
|
4685
5364
|
const snap = chatSourceOf(ctx, ctx.sessions.binding(childId))?.getLegacy();
|
|
@@ -4692,16 +5371,16 @@ window.__ModuleLoader__.load({
|
|
|
4692
5371
|
if (q !== void 0) parts.push(`<问>${q}</问>`);
|
|
4693
5372
|
parts.push(`<答>${m.text}</答>`);
|
|
4694
5373
|
}
|
|
4695
|
-
return `<side-chat-reference source="${(() => {
|
|
5374
|
+
return `<side-chat-reference source="${xmlAttr((() => {
|
|
4696
5375
|
try {
|
|
4697
5376
|
if (directNativeLeg(ctx)) {
|
|
4698
5377
|
const meta = sideChatMetasAll().find((m) => m.childId === childId);
|
|
4699
|
-
if (meta !== void 0) return meta
|
|
5378
|
+
if (meta !== void 0) return sideChatTitleOf(meta);
|
|
4700
5379
|
}
|
|
4701
5380
|
for (const tab of collectSideTabs(betterSidebarOf(ctx)?.getSnapshot().state)) if (parseSideChatMeta(tab.meta).childId === childId) return tab.title;
|
|
4702
5381
|
} catch {}
|
|
4703
5382
|
return childId;
|
|
4704
|
-
})()}" note="用户 @ 引用的侧边聊天内容(问答成对)">\n${parts.join("\n")}\n</side-chat-reference>`;
|
|
5383
|
+
})())}" note="用户 @ 引用的侧边聊天内容(问答成对)">\n${parts.join("\n")}\n</side-chat-reference>`;
|
|
4705
5384
|
}
|
|
4706
5385
|
/** 注册「@ 侧聊」触发源;服务缺席静默降级(不影响其他入口)。 */
|
|
4707
5386
|
function registerSideChatReferenceSource(ctx) {
|
|
@@ -4718,40 +5397,50 @@ window.__ModuleLoader__.load({
|
|
|
4718
5397
|
name: SOURCE,
|
|
4719
5398
|
order: 50,
|
|
4720
5399
|
candidates: (session) => {
|
|
5400
|
+
if (session?.sessionId === void 0) return Promise.resolve([]);
|
|
5401
|
+
const sessionId = session.sessionId;
|
|
4721
5402
|
try {
|
|
4722
|
-
|
|
4723
|
-
if (directNativeLeg(ctx))
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
5403
|
+
const base = [];
|
|
5404
|
+
if (directNativeLeg(ctx)) {
|
|
5405
|
+
for (const meta of sideChatMetasOf(sessionId)) if (meta.childId !== void 0) base.push({
|
|
5406
|
+
name: sideChatTitleOf(meta),
|
|
5407
|
+
childId: meta.childId
|
|
5408
|
+
});
|
|
5409
|
+
} else {
|
|
5410
|
+
const snapshot = betterSidebarOf(ctx)?.getSnapshot();
|
|
5411
|
+
if (snapshot === void 0 || snapshot.sessionId !== sessionId || snapshot.state === void 0) {
|
|
5412
|
+
lastCandidateRefs.set(sessionId, /* @__PURE__ */ new Map());
|
|
5413
|
+
return Promise.resolve([]);
|
|
5414
|
+
}
|
|
5415
|
+
for (const tab of collectSideTabs(snapshot.state)) {
|
|
5416
|
+
const childId = parseSideChatMeta(tab.meta).childId;
|
|
5417
|
+
if (childId !== void 0) base.push({
|
|
5418
|
+
name: tab.title,
|
|
5419
|
+
childId
|
|
5420
|
+
});
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
const taken = /* @__PURE__ */ new Set();
|
|
5424
|
+
const refs = /* @__PURE__ */ new Map();
|
|
5425
|
+
const out = base.map((entry) => {
|
|
5426
|
+
const name = dedupeName(entry.name, taken);
|
|
5427
|
+
refs.set(name, entry.childId);
|
|
5428
|
+
return {
|
|
5429
|
+
name,
|
|
5430
|
+
description: "侧边聊天",
|
|
5431
|
+
icon: "💬"
|
|
5432
|
+
};
|
|
5433
|
+
});
|
|
5434
|
+
lastCandidateRefs.set(sessionId, refs);
|
|
5435
|
+
return Promise.resolve(out);
|
|
4735
5436
|
} catch {
|
|
5437
|
+
lastCandidateRefs.set(sessionId, /* @__PURE__ */ new Map());
|
|
4736
5438
|
return Promise.resolve([]);
|
|
4737
5439
|
}
|
|
4738
5440
|
},
|
|
4739
5441
|
onPick: (pick, session) => {
|
|
4740
5442
|
if (session?.sessionId === void 0) return void 0;
|
|
4741
|
-
|
|
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);
|
|
4754
|
-
const childId = tab === void 0 ? void 0 : parseSideChatMeta(tab.meta).childId;
|
|
5443
|
+
const childId = lastCandidateRefs.get(session.sessionId)?.get(pick.candidate.name);
|
|
4755
5444
|
if (childId === void 0) return void 0;
|
|
4756
5445
|
return { insert: {
|
|
4757
5446
|
source: SOURCE,
|
|
@@ -4786,10 +5475,9 @@ window.__ModuleLoader__.load({
|
|
|
4786
5475
|
const collectAlive = () => {
|
|
4787
5476
|
if (directNativeLeg(ctx)) return sideChatMetasAll().flatMap((meta) => {
|
|
4788
5477
|
if (meta.childId === void 0) return [];
|
|
4789
|
-
const title = meta.number <= 1 ? t("tabBaseTitle") : `${t("tabBaseTitle")} ${meta.number}`;
|
|
4790
5478
|
return [{
|
|
4791
5479
|
childId: meta.childId,
|
|
4792
|
-
title
|
|
5480
|
+
title: sideChatTitleOf(meta)
|
|
4793
5481
|
}];
|
|
4794
5482
|
});
|
|
4795
5483
|
const snapshot = betterSidebarOf(ctx)?.getSnapshot();
|
|
@@ -4810,7 +5498,7 @@ window.__ModuleLoader__.load({
|
|
|
4810
5498
|
return;
|
|
4811
5499
|
}
|
|
4812
5500
|
const alive = /* @__PURE__ */ new Set();
|
|
4813
|
-
for (const { childId
|
|
5501
|
+
for (const { childId } of tabs) {
|
|
4814
5502
|
alive.add(childId);
|
|
4815
5503
|
if (watchers.has(childId)) continue;
|
|
4816
5504
|
try {
|
|
@@ -4824,7 +5512,11 @@ window.__ModuleLoader__.load({
|
|
|
4824
5512
|
};
|
|
4825
5513
|
watcher.unsub = session.subscribe(() => {
|
|
4826
5514
|
const running = session.getSnapshot()?.running === true;
|
|
4827
|
-
if (
|
|
5515
|
+
if (isPurgingInheritedTurn(childId)) {
|
|
5516
|
+
watcher.wasRunning = running;
|
|
5517
|
+
return;
|
|
5518
|
+
}
|
|
5519
|
+
if (shouldNotify(watcher.wasRunning, running, watcher.since, Date.now())) showToast(t("sideChatDone", { title: collectAlive().find((entry) => entry.childId === childId)?.title ?? t("tabBaseTitle") }));
|
|
4828
5520
|
watcher.wasRunning = running;
|
|
4829
5521
|
});
|
|
4830
5522
|
watcher.wasRunning = session.getSnapshot()?.running === true;
|
|
@@ -4897,6 +5589,7 @@ window.__ModuleLoader__.load({
|
|
|
4897
5589
|
childId: meta.childId,
|
|
4898
5590
|
parentSessionId: meta.parentSessionId,
|
|
4899
5591
|
title: closedTab.title,
|
|
5592
|
+
...meta.topic !== void 0 ? { topic: meta.topic } : {},
|
|
4900
5593
|
closedAt: Date.now()
|
|
4901
5594
|
});
|
|
4902
5595
|
},
|
|
@@ -5373,42 +6066,42 @@ window.__ModuleLoader__.load({
|
|
|
5373
6066
|
document.head.appendChild(tag);
|
|
5374
6067
|
}
|
|
5375
6068
|
var annotate_module_css_default = {
|
|
5376
|
-
"toolbar": "_SVViG_toolbar",
|
|
5377
6069
|
"chipRow": "_SVViG_chipRow",
|
|
5378
|
-
"chipWrap": "_SVViG_chipWrap",
|
|
5379
|
-
"editorAsk": "_SVViG_editorAsk",
|
|
5380
6070
|
"chipNumber": "_SVViG_chipNumber",
|
|
5381
|
-
"
|
|
5382
|
-
"
|
|
6071
|
+
"hintClose": "_SVViG_hintClose",
|
|
6072
|
+
"cancelButton": "_SVViG_cancelButton",
|
|
6073
|
+
"toolbarButton": "_SVViG_toolbarButton",
|
|
5383
6074
|
"badgeSent": "_SVViG_badgeSent",
|
|
5384
|
-
"
|
|
6075
|
+
"chipWrap": "_SVViG_chipWrap",
|
|
6076
|
+
"sentCardTitle": "_SVViG_sentCardTitle",
|
|
6077
|
+
"editorTarget": "_SVViG_editorTarget",
|
|
6078
|
+
"toolbar": "_SVViG_toolbar",
|
|
6079
|
+
"chip": "_SVViG_chip",
|
|
6080
|
+
"chipRemove": "_SVViG_chipRemove",
|
|
6081
|
+
"editorInput": "_SVViG_editorInput",
|
|
6082
|
+
"highlight": "_SVViG_highlight",
|
|
5385
6083
|
"dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
|
|
5386
|
-
"
|
|
5387
|
-
"
|
|
5388
|
-
"editorError": "_SVViG_editorError",
|
|
6084
|
+
"chipText": "_SVViG_chipText",
|
|
6085
|
+
"editorEdit": "_SVViG_editorEdit",
|
|
5389
6086
|
"chipClearAll": "_SVViG_chipClearAll",
|
|
6087
|
+
"editorError": "_SVViG_editorError",
|
|
5390
6088
|
"sentCardQuote": "_SVViG_sentCardQuote",
|
|
5391
6089
|
"editorSpacer": "_SVViG_editorSpacer",
|
|
5392
|
-
"confirmButton": "_SVViG_confirmButton",
|
|
5393
|
-
"editorInput": "_SVViG_editorInput",
|
|
5394
6090
|
"editorFooter": "_SVViG_editorFooter",
|
|
5395
6091
|
"badge": "_SVViG_badge",
|
|
5396
|
-
"sentChip": "_SVViG_sentChip",
|
|
5397
|
-
"chipText": "_SVViG_chipText",
|
|
5398
|
-
"hintClose": "_SVViG_hintClose",
|
|
5399
|
-
"chipPanel": "_SVViG_chipPanel",
|
|
5400
|
-
"deleteButton": "_SVViG_deleteButton",
|
|
5401
|
-
"editorNew": "_SVViG_editorNew",
|
|
5402
|
-
"highlight": "_SVViG_highlight",
|
|
5403
6092
|
"editorTextarea": "_SVViG_editorTextarea",
|
|
5404
|
-
"
|
|
6093
|
+
"saveButton": "_SVViG_saveButton",
|
|
6094
|
+
"chipPanel": "_SVViG_chipPanel",
|
|
6095
|
+
"confirmButton": "_SVViG_confirmButton",
|
|
6096
|
+
"editorAskRow": "_SVViG_editorAskRow",
|
|
5405
6097
|
"sentCardNote": "_SVViG_sentCardNote",
|
|
5406
|
-
"
|
|
5407
|
-
"
|
|
5408
|
-
"chip": "_SVViG_chip",
|
|
5409
|
-
"chipRemove": "_SVViG_chipRemove",
|
|
5410
|
-
"editorTarget": "_SVViG_editorTarget",
|
|
6098
|
+
"chipNote": "_SVViG_chipNote",
|
|
6099
|
+
"editorAsk": "_SVViG_editorAsk",
|
|
5411
6100
|
"toolbarBelow": "_SVViG_toolbarBelow",
|
|
6101
|
+
"sentChip": "_SVViG_sentChip",
|
|
6102
|
+
"deleteButton": "_SVViG_deleteButton",
|
|
6103
|
+
"editorNew": "_SVViG_editorNew",
|
|
6104
|
+
"hint": "_SVViG_hint",
|
|
5412
6105
|
"sentChipRow": "_SVViG_sentChipRow"
|
|
5413
6106
|
};
|
|
5414
6107
|
//#endregion
|
|
@@ -5740,7 +6433,8 @@ window.__ModuleLoader__.load({
|
|
|
5740
6433
|
/**
|
|
5741
6434
|
* 浮层水平落点(W03):优先锚点右侧;右侧剩余空间不足(贴着展开的
|
|
5742
6435
|
* better-sidebar 面板时必然不足——角标 gutter 就在消息列右缘)翻到左侧。
|
|
5743
|
-
* 右边界取面板左缘([data-dsh-better-sidebar]
|
|
6436
|
+
* 右边界取面板左缘([data-dsh-better-sidebar],better-sidebar 的根锚点——
|
|
6437
|
+
* 仅在装了它的宿主上存在,查不到按视口)。
|
|
5744
6438
|
*/
|
|
5745
6439
|
function popoverLeft(x, width) {
|
|
5746
6440
|
const panel = typeof document === "undefined" ? null : document.querySelector("[data-dsh-better-sidebar]")?.getBoundingClientRect() ?? null;
|