dsh-sidenote 0.3.1 → 0.3.3

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/lib/client.js CHANGED
@@ -4,57 +4,117 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
7
  let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  let react_dom_client = require("react-dom/client");
11
- //#region src/client/host/probes.ts
12
- const PROBES = [
13
- {
14
- name: "Session.open()(非 staged 会话开窗)",
15
- check: (ctx) => {
16
- return typeof ctx.sessions?.binding === "function";
17
- },
18
- fallback: "侧边聊天消息流降级为只发不收"
19
- },
20
- {
21
- name: "conversation.input 输入机器",
22
- check: (ctx) => {
23
- return typeof (ctx.get?.("conversation"))?.input?.for === "function";
24
- },
25
- fallback: "侧边 composer 降级为本地草稿 + session.prompt"
26
- },
27
- {
28
- name: "commandUi(斜杠命令注册)",
29
- check: (ctx) => {
30
- return typeof (ctx.get?.("commandUi"))?.register === "function";
31
- },
32
- fallback: "/side 与「/侧边」命令不可用(菜单/顶栏入口仍在)"
33
- },
34
- {
35
- name: "ctx.modules.import(动态组件复用)",
36
- check: (ctx) => {
37
- return typeof ctx.modules?.import === "function";
38
- },
39
- fallback: "ProducedFiles 等动态复用件降级为自绘"
11
+ //#region src/client/sidechat/native.ts
12
+ /** better-sidebar version that moved the right column to DSH's native sidebar. */
13
+ const NATIVE_SIDEBAR_MINOR = 19;
14
+ let root;
15
+ /** Remember the plugin root context at activation (see module docstring). */
16
+ function setRootContext(ctx) {
17
+ root = ctx;
18
+ }
19
+ /** Plugin root context; falls back to the caller's context (legacy behaviour). */
20
+ function rootContext(fallback) {
21
+ return root ?? fallback;
22
+ }
23
+ /**
24
+ * True when the host keeps sidebar tabs outside the legacy layout store
25
+ * (better-sidebar >= 0.19). Version parse is best-effort: an unparsable
26
+ * version reports false, which keeps the pre-0.19 behaviour.
27
+ */
28
+ function nativeSidebarHost(ctx) {
29
+ try {
30
+ const [major = 0, minor = 0] = ctx.betterSidebar.version.split(".").map((part) => Number.parseInt(part, 10));
31
+ return major > 0 || minor >= NATIVE_SIDEBAR_MINOR;
32
+ } catch {
33
+ return false;
40
34
  }
41
- ];
42
- /** 激活时跑一遍探测清单;缺失项逐项 warn-once。返回缺失项数(测试可断言)。 */
43
- function probeHost(ctx) {
44
- let missing = 0;
45
- for (const probe of PROBES) {
46
- let ok = false;
47
- try {
48
- ok = probe.check(ctx);
49
- } catch {
50
- ok = false;
51
- }
52
- if (!ok) {
53
- missing += 1;
54
- console.warn(`[dsh-sidenote] 宿主能力缺失:${probe.name} → ${probe.fallback}`);
55
- }
35
+ }
36
+ /** Native tab id → live panel. Native layouts are memory-only, so is this. */
37
+ const liveSideChats = /* @__PURE__ */ new Map();
38
+ /**
39
+ * Publish a mounted panel so programmatic entry points can focus it and seed
40
+ * its draft. Returns the disposer for the panel's effect cleanup.
41
+ */
42
+ function registerLiveSideChat(sessionId, tabId, seedDraft, readMeta) {
43
+ const entry = {
44
+ tabId,
45
+ sessionId,
46
+ seedDraft,
47
+ readMeta
48
+ };
49
+ liveSideChats.set(tabId, entry);
50
+ const opening = openings.get(sessionId);
51
+ if (opening !== void 0) {
52
+ openings.delete(sessionId);
53
+ for (const draft of opening.drafts) seedDraft(draft);
54
+ }
55
+ return () => {
56
+ if (liveSideChats.get(tabId) === entry) liveSideChats.delete(tabId);
57
+ };
58
+ }
59
+ /** 标记失效应力:openTab 被静默拒绝/面板未挂载时,超时后允许重试。 */
60
+ const OPENING_TTL_MS = 1e4;
61
+ const openings = /* @__PURE__ */ new Map();
62
+ /** 标记一次已发起、面板尚未注册的 native 打开(create/reopen 铸造后调用)。 */
63
+ function markSideChatOpening(sessionId) {
64
+ openings.set(sessionId, {
65
+ at: Date.now(),
66
+ drafts: []
67
+ });
68
+ }
69
+ /** 进行中的打开;TTL 外视为 openTab 被拒/面板未挂载,清除并返回 undefined。 */
70
+ function sideChatOpening(sessionId) {
71
+ const mark = openings.get(sessionId);
72
+ if (mark === void 0) return void 0;
73
+ if (Date.now() - mark.at > OPENING_TTL_MS) {
74
+ openings.delete(sessionId);
75
+ return;
76
+ }
77
+ return mark;
78
+ }
79
+ /**
80
+ * Most recently registered live panel of a session — the open-or-focus target
81
+ * when the legacy snapshot cannot see the tab (mirrors "last opened wins").
82
+ */
83
+ function lastLiveSideChat(sessionId) {
84
+ let found;
85
+ for (const entry of liveSideChats.values()) if (entry.sessionId === sessionId) found = entry;
86
+ return found;
87
+ }
88
+ /** Native tab shell for reads that go through the layout snapshot (`readTab`). */
89
+ function nativeTabShell(tabId, type) {
90
+ const entry = liveSideChats.get(tabId);
91
+ if (entry === void 0) return void 0;
92
+ return {
93
+ id: entry.tabId,
94
+ type,
95
+ title: "",
96
+ meta: entry.readMeta()
97
+ };
98
+ }
99
+ /**
100
+ * 聚焦一个 native tab:better-sidebar >= 0.19 的 `surface.activate` 是空操作
101
+ * (只返回记录存在性——authority: dsh-better-sidebar src/client/native/surface.ts),
102
+ * 真实聚焦面是 dsh 0.1.5 的 `ISidebarRight.focus`(authority:
103
+ * dsh-client-ui-sidebar-right lib/types/client/service.d.ts `focus(tabId)`)。
104
+ * focus 只标记活动 tab/pane,不展开折叠的栏——聚焦的语义包含「让用户看见」,
105
+ * 故折叠时补 toggleExpanded(authority: 同文件 `isExpanded/toggleExpanded`)。
106
+ * @returns true = 已通过 focus 聚焦;false = 面缺席(调用方回退 activateTab)。
107
+ */
108
+ function focusNativeTab(ctx, tabId) {
109
+ try {
110
+ const face = ctx.get("sidebarRight");
111
+ if (typeof face?.focus !== "function") return false;
112
+ face.focus(tabId);
113
+ if (face.isExpanded?.() === false) face.toggleExpanded?.();
114
+ return true;
115
+ } catch {
116
+ return false;
56
117
  }
57
- return missing;
58
118
  }
59
119
  //#endregion
60
120
  //#region src/client/locales.ts
@@ -88,10 +148,20 @@ window.__ModuleLoader__.load({
88
148
  modelSwitchTitle: "Switch the side chat model",
89
149
  permChipTitle: "Permission mode",
90
150
  permFullAccess: "Full access",
91
- pendingNotice: "⚠ 侧边会话有审批/提问待处理——点击跳到主视图处理",
92
- writeNotice: "侧边会话与主会话同权限,可以修改文件(当前模式见输入框旁 chip)",
151
+ pendingNotice: "⚠ The side session has a pending approval/question — click to handle it in the main view",
152
+ writeNotice: "The side session shares the main session's permissions and can modify files (current mode: chip next to the input)",
153
+ pendingApprovalTitle: "Waiting for approval",
154
+ pendingApprovalDesc: "Tool {name} requests elevated execution",
155
+ pendingReject: "Reject",
156
+ pendingAllowOnce: "Allow once",
157
+ pendingQuestionTitle: "Question",
158
+ pendingPlanReview: "Plan review",
159
+ pendingQuestionSubmit: "Submit",
160
+ pendingQuestionCancel: "Cancel",
161
+ pendingCustomPlaceholder: "Custom answer…",
93
162
  modelFollowsMain: "follows main session",
94
163
  stopReply: "Stop",
164
+ sideChatDone: "「{title}」finished replying",
95
165
  stopReplyTitle: "Stop the current reply",
96
166
  send: "Send",
97
167
  attachTitle: "Attach images",
@@ -214,8 +284,18 @@ window.__ModuleLoader__.load({
214
284
  permFullAccess: "Full access",
215
285
  pendingNotice: "⚠ 侧边会话有审批/提问待处理——点击跳到主视图处理",
216
286
  writeNotice: "侧边会话与主会话同权限,可以修改文件(当前模式见输入框旁 chip)",
287
+ pendingApprovalTitle: "等待审批",
288
+ pendingApprovalDesc: "工具 {name} 请求越权执行",
289
+ pendingReject: "拒绝",
290
+ pendingAllowOnce: "允许一次",
291
+ pendingQuestionTitle: "提问",
292
+ pendingPlanReview: "计划审批",
293
+ pendingQuestionSubmit: "提交回答",
294
+ pendingQuestionCancel: "取消",
295
+ pendingCustomPlaceholder: "自定义回答…",
217
296
  modelFollowsMain: "跟随主会话",
218
297
  stopReply: "停止",
298
+ sideChatDone: "「{title}」回复完成",
219
299
  stopReplyTitle: "停止当前回复",
220
300
  send: "发送",
221
301
  attachTitle: "添加图片附件",
@@ -330,135 +410,6 @@ window.__ModuleLoader__.load({
330
410
  return text;
331
411
  }
332
412
  //#endregion
333
- //#region src/client/reflow.ts
334
- /**
335
- * 回流通道的受控对象层(Delivery_02 Workitem_04 v2):侧边聊天的结论以
336
- * 「待回流对象」进入主会话——主 composer 上方的 chip(可预览/可移除),
337
- * 发送瞬间由 annotate/send.ts 拦截器与注释协议块一起序列化进消息。
338
- * 不经草稿文本流(用户裁定:直接进草稿「很脏」)。
339
- *
340
- * 气泡留痕:序列化形态是 XML 协议块(见 buildReflowBlock),随注释协议块
341
- * 一起作为消息连续前缀注入,bubble.ts 反解析后折叠为「含侧边回流上下文」标签。
342
- *
343
- * 持久化:localStorage 按会话键(dsh-sidenote:reflow:v1:<sessionId>),
344
- * 与注释 store 同款纪律(刷新不丢、容错 revive、空删键)。
345
- */
346
- const STORAGE_PREFIX$1 = "dsh-sidenote:reflow:v1:";
347
- /**
348
- * 回流块序列化(XML 形态,与注释协议同族):
349
- *
350
- * <reflow source="侧边 2" reason="用户选择从侧边聊天带回主线">
351
- * <问>用户在侧边聊天里的提问</问>
352
- * <答>结论全文(不截断)</答>
353
- * </reflow>
354
- *
355
- * 问答成对:只有答没有问,模型理解打折;question 缺省时只有 <答>。
356
- * 来源与意图放在属性里,内容行保持纯净;模型对 XML 包裹的上下文理解最好
357
- * (Claude 官方推荐形态 / Codex additionalContext 同款思路)。
358
- */
359
- function buildReflowBlock(item) {
360
- const parts = [];
361
- if (item.question !== void 0 && item.question.trim() !== "") parts.push(`<问>${item.question}</问>`);
362
- parts.push(`<答>${item.text}</答>`);
363
- return `<reflow source="${item.sideTitle}" reason="${t("reflowReason")}">\n${parts.join("\n")}\n</reflow>`;
364
- }
365
- function revive(value) {
366
- if (typeof value !== "object" || value === null) return null;
367
- const r = value;
368
- if (typeof r.id !== "number" || typeof r.sessionId !== "string" || r.sessionId === "") return null;
369
- if (typeof r.sideTitle !== "string" || typeof r.text !== "string") return null;
370
- return {
371
- id: r.id,
372
- sessionId: r.sessionId,
373
- sideTitle: r.sideTitle,
374
- text: r.text,
375
- ...typeof r.question === "string" && r.question.trim() !== "" ? { question: r.question } : {},
376
- createdAt: typeof r.createdAt === "number" ? r.createdAt : 0
377
- };
378
- }
379
- function createReflowStore(now = () => Date.now(), storage) {
380
- let items = [];
381
- let nextId = 1;
382
- let version = 0;
383
- const listeners = /* @__PURE__ */ new Set();
384
- const store = storage === void 0 ? typeof localStorage !== "undefined" ? localStorage : null : storage;
385
- if (store != null) try {
386
- const keys = [];
387
- for (let i = 0; i < store.length; i += 1) {
388
- const key = store.key(i);
389
- if (typeof key === "string" && key.startsWith(STORAGE_PREFIX$1)) keys.push(key);
390
- }
391
- for (const key of keys) {
392
- const parsed = JSON.parse(store.getItem(key) ?? "null");
393
- if (!Array.isArray(parsed)) continue;
394
- for (const raw of parsed) {
395
- const item = revive(raw);
396
- if (item === null) continue;
397
- items.push(item);
398
- nextId = Math.max(nextId, item.id + 1);
399
- }
400
- }
401
- } catch (error) {
402
- console.warn("[dsh-sidenote] 回流持久化读取失败(按空起步):", error);
403
- }
404
- const persist = (sessionId) => {
405
- if (store == null) return;
406
- try {
407
- const mine = items.filter((i) => i.sessionId === sessionId);
408
- const key = STORAGE_PREFIX$1 + sessionId;
409
- if (mine.length === 0) store.removeItem(key);
410
- else store.setItem(key, JSON.stringify(mine));
411
- } catch (error) {
412
- console.warn("[dsh-sidenote] 回流持久化写入失败:", error);
413
- }
414
- };
415
- const notify = () => {
416
- version += 1;
417
- for (const fn of [...listeners]) fn();
418
- };
419
- return {
420
- getSnapshot: () => version,
421
- subscribe(fn) {
422
- listeners.add(fn);
423
- return () => {
424
- listeners.delete(fn);
425
- };
426
- },
427
- add(sessionId, sideTitle, text, question) {
428
- const item = {
429
- id: nextId,
430
- sessionId,
431
- sideTitle,
432
- text,
433
- ...typeof question === "string" && question.trim() !== "" ? { question } : {},
434
- createdAt: now()
435
- };
436
- nextId += 1;
437
- items = [...items, item];
438
- persist(sessionId);
439
- notify();
440
- return item;
441
- },
442
- remove(id) {
443
- const target = items.find((i) => i.id === id);
444
- if (target === void 0) return;
445
- items = items.filter((i) => i.id !== id);
446
- persist(target.sessionId);
447
- notify();
448
- },
449
- clearSession(sessionId) {
450
- if (!items.some((i) => i.sessionId === sessionId)) return;
451
- items = items.filter((i) => i.sessionId !== sessionId);
452
- persist(sessionId);
453
- notify();
454
- },
455
- list: (sessionId) => items.filter((i) => i.sessionId === sessionId)
456
- };
457
- }
458
- //#endregion
459
- //#region src/client/bridge.ts
460
- const sideChatBridge = { current: null };
461
- //#endregion
462
413
  //#region src/client/annotate/format.ts
463
414
  /** 协议头(双语):zh「我批注了以下 2 处内容:」/ en 见 locales。 */
464
415
  function protocolHeader(n) {
@@ -811,55 +762,589 @@ window.__ModuleLoader__.load({
811
762
  };
812
763
  }
813
764
  //#endregion
814
- //#region src/client/chat/cards.ts
815
- /** ContentBlock[] → 纯文本(transcript.ts 同款的极简版,避免互依赖)。 */
816
- function blocksText(content) {
817
- if (!Array.isArray(content)) return void 0;
818
- const parts = [];
819
- for (const block of content) {
820
- const b = block;
821
- if (b?.type === "text" && typeof b.text === "string") parts.push(b.text);
822
- }
823
- return parts.length === 0 ? void 0 : parts.join("\n");
824
- }
825
- /** 未知 card 值的 warn-once 登记(T3 纪律:降级不静默)。 */
826
- const warnedCards = /* @__PURE__ */ new Set();
827
- function warnUnknownCard(value) {
828
- if (warnedCards.has(value)) return;
829
- warnedCards.add(value);
830
- console.warn(`[dsh-sidenote] 未知工具卡种类 "${value}"——降级为 generic 卡(宿主可能扩展了 card union)`);
831
- }
832
- /** 相对 cwd 按会话工作区折成绝对路径(UI bridge 责任——presentation 注释明文)。 */
833
- function resolveCwd(cwd, cwdBase) {
834
- if (cwd === void 0) return cwdBase;
835
- if (cwd.startsWith("/") || /^[A-Za-z]:[\\/]/.test(cwd)) return cwd;
836
- if (cwdBase === void 0) return cwd;
837
- return `${cwdBase.replace(/\/+$/, "")}/${cwd}`;
838
- }
765
+ //#region src/client/sidechat/open.ts
839
766
  /**
840
- * 展示态路径缩短:>3 段的绝对路径只保末 3 段(≈仓库相对形态,与主区
841
- * 「Read · dsh-sidenote/scripts/x.sh」同款)。窄栏标题位只够放语义尾部。
767
+ * 从最新快照读一个 Tab(meta 合并写入前的读取面;布局即注册表)。
768
+ * better-sidebar >= 0.19: the open tab lives in the native right sidebar, not
769
+ * in the layout snapshot — fall back to the live-panel registry.
842
770
  */
843
- function shortPath(p) {
844
- if (!p.startsWith("/")) return p;
845
- const segs = p.split("/").filter((s) => s !== "");
846
- return segs.length > 3 ? segs.slice(-3).join("/") : p;
771
+ function readTab(ctx, tabId) {
772
+ return collectTabs(ctx.betterSidebar.getSnapshot().state).find((candidate) => candidate.id === tabId) ?? nativeTabShell(tabId, "dsh-sidenote:side");
847
773
  }
848
774
  /**
849
- * 标题整形:「Tool /abs/path」→「Tool · 末3段」(主区同名同款);
850
- * 裸绝对路径标题 → 末 3 段;不含路径的标题原样。
775
+ * 为 sessionId 新建或聚焦一个侧边聊天 Tab;draftText 给出时写入其草稿。
776
+ * @returns false = 未能打开(调用方保持自己的流程)。
851
777
  */
852
- function displayTitle(title) {
853
- const m = /^([A-Za-z][\w-]*) (\/.+)$/.exec(title);
854
- const name = m?.[1];
855
- const path = m?.[2];
856
- if (name !== void 0 && path !== void 0) return `${name} · ${shortPath(path)}`;
857
- return title.startsWith("/") ? shortPath(title) : title;
858
- }
859
- /** todo_write 入参形状校验(字段不齐即放弃 todo 卡)。两种数据源形状:
860
- * 0.1.1 wire:callView.rawInput = todos 数组本身(dsh-tool-todo presentCall 实证);
861
- * 0.1.2 argsRaw:{ todos: [...] }。 */
862
- function todoItemsOf(raw) {
778
+ function openOrFocusSideChat(ctx, sessionId, draftText) {
779
+ try {
780
+ const snapshot = ctx.betterSidebar.getSnapshot();
781
+ if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
782
+ const existing = collectSideTabs(snapshot.state);
783
+ const live = lastLiveSideChat(sessionId);
784
+ if (live !== void 0) {
785
+ if (draftText !== void 0 && draftText !== "") live.seedDraft(draftText);
786
+ if (!focusNativeTab(ctx, live.tabId)) ctx.betterSidebar.activateTab(live.tabId, { sessionId });
787
+ return true;
788
+ }
789
+ if (existing.length > 0) {
790
+ const target = existing[existing.length - 1];
791
+ if (draftText !== void 0 && draftText !== "") {
792
+ const meta = parseSideChatMeta(target.meta);
793
+ const input = meta.childId === void 0 ? null : resolveSessionInput(ctx, meta.childId);
794
+ if (input !== null) input.setDraft(appendDraftText(readInputDraft(input), draftText));
795
+ else ctx.betterSidebar.updateTab(target.id, { meta: {
796
+ ...meta,
797
+ pendingDraft: appendDraftText(meta.pendingDraft ?? "", draftText)
798
+ } });
799
+ }
800
+ ctx.betterSidebar.activateTab(target.id, { sessionId });
801
+ return true;
802
+ }
803
+ const opening = sideChatOpening(sessionId);
804
+ if (opening !== void 0) {
805
+ if (draftText !== void 0 && draftText !== "") opening.drafts.push(draftText);
806
+ return true;
807
+ }
808
+ return createSideChat(ctx, sessionId, draftText);
809
+ } catch (error) {
810
+ console.warn("[dsh-sidenote] 打开侧边聊天失败:", error);
811
+ return false;
812
+ }
813
+ }
814
+ /**
815
+ * 无条件新建一个侧边聊天 Tab(/side「新建侧边聊天」选项的语义——既有实例
816
+ * 在弹层里另有聚焦项,「新建」必须真的新建,否则标签与行为不符)。
817
+ * @returns false = 未能创建(调用方保持自己的流程)。
818
+ */
819
+ function createSideChat(ctx, sessionId, draftText) {
820
+ try {
821
+ const snapshot = ctx.betterSidebar.getSnapshot();
822
+ if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
823
+ if (!ctx.betterSidebar.isTabEnabled("dsh-sidenote:side")) return false;
824
+ const before = new Set(collectTabs(snapshot.state).map((tab) => tab.id));
825
+ const seed = draftText !== void 0 && draftText !== "" ? {
826
+ type: SIDE_TAB_TYPE,
827
+ meta: { pendingDraft: draftText }
828
+ } : { type: SIDE_TAB_TYPE };
829
+ ctx.betterSidebar.openTab(seed, { sessionId });
830
+ const created = collectSideTabs(ctx.betterSidebar.getSnapshot().state).find((tab) => !before.has(tab.id));
831
+ if (created === void 0) {
832
+ if (!nativeSidebarHost(ctx)) return false;
833
+ markSideChatOpening(sessionId);
834
+ return true;
835
+ }
836
+ if (draftText !== void 0 && draftText !== "") ctx.betterSidebar.updateTab(created.id, { meta: { pendingDraft: draftText } });
837
+ return true;
838
+ } catch (error) {
839
+ console.warn("[dsh-sidenote] 新建侧边聊天失败:", error);
840
+ return false;
841
+ }
842
+ }
843
+ /**
844
+ * 桥接/命令的目标预览:askInSideChat 此时若执行会落在哪个 Tab 的标题
845
+ * (既有并存 = 最后一个;无 = 即将新建的首个标题)。用于编辑器上标明
846
+ * 「发送至:侧边 N」。会话不在屏上/状态缺失时返回 undefined。
847
+ */
848
+ function sideChatTargetTitle(ctx, sessionId) {
849
+ try {
850
+ const snapshot = ctx.betterSidebar.getSnapshot();
851
+ if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return void 0;
852
+ const existing = collectSideTabs(snapshot.state);
853
+ if (existing.length > 0) return existing[existing.length - 1].title;
854
+ return sideTabTitle([]);
855
+ } catch {
856
+ return;
857
+ }
858
+ }
859
+ /**
860
+ * 重开最近关闭的侧边聊天(D3 后悔药):开 Tab + meta 预置 childId/
861
+ * parentSessionId——面板挂载后走绑定恢复路径(不重新 fork)。
862
+ * 会话本体必须仍 archived 在列(若被清理,面板落「会话已不存在」态——
863
+ * 有现成错误态兜着)。
864
+ */
865
+ function reopenSideChat(ctx, sessionId, childId, title) {
866
+ try {
867
+ const snapshot = ctx.betterSidebar.getSnapshot();
868
+ if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
869
+ if (!ctx.betterSidebar.isTabEnabled("dsh-sidenote:side")) return false;
870
+ const before = new Set(collectTabs(snapshot.state).map((tab) => tab.id));
871
+ const meta = {
872
+ childId,
873
+ parentSessionId: sessionId
874
+ };
875
+ ctx.betterSidebar.openTab({
876
+ type: SIDE_TAB_TYPE,
877
+ ...title !== void 0 ? { title } : {},
878
+ meta
879
+ }, { sessionId });
880
+ const created = collectSideTabs(ctx.betterSidebar.getSnapshot().state).find((tab) => !before.has(tab.id));
881
+ if (created === void 0) {
882
+ if (!nativeSidebarHost(ctx)) return false;
883
+ markSideChatOpening(sessionId);
884
+ return true;
885
+ }
886
+ ctx.betterSidebar.updateTab(created.id, {
887
+ ...title !== void 0 ? { title } : {},
888
+ meta
889
+ });
890
+ return true;
891
+ } catch (error) {
892
+ console.warn("[dsh-sidenote] 重开侧边聊天失败:", error);
893
+ return false;
894
+ }
895
+ }
896
+ //#endregion
897
+ //#region src/client/sidechat/lifecycle.ts
898
+ /**
899
+ * 首开编排:fork(全量历史快照)→ 登记 meta(先行,绝不丢登记)→
900
+ * 归档隐藏出会话列表(best-effort)→ 模型跟随主会话(best-effort)。
901
+ * fork 失败抛错(面板组件转错误态);后两步失败只告警不阻断。
902
+ * @returns fork 出的子会话 id。
903
+ */
904
+ async function forkAndRegister(ctx, parentSessionId, tabId) {
905
+ let boundarySeq;
906
+ try {
907
+ const snap = chatSourceOf(ctx, ctx.sessions.binding(parentSessionId))?.getLegacy();
908
+ let max = -1;
909
+ for (const node of snap?.nodes ?? []) {
910
+ const seq = node.seq;
911
+ if (typeof seq === "number" && seq > max) max = seq;
912
+ }
913
+ if (max >= 0) boundarySeq = max;
914
+ } catch {}
915
+ const forked = await ctx.sessions.fork({ sessionId: parentSessionId });
916
+ const owner = rootContext(ctx);
917
+ updateTabMeta(ctx, tabId, (current) => ({
918
+ ...current,
919
+ childId: forked,
920
+ parentSessionId,
921
+ ...boundarySeq !== void 0 ? { boundarySeq } : {}
922
+ }));
923
+ try {
924
+ await owner.workspaces.archiveSession(forked);
925
+ } catch (error) {
926
+ console.warn("[dsh-sidenote] 归档侧边会话失败(会话列表可能短暂可见):", error);
927
+ }
928
+ try {
929
+ const selection = await readModelSelection(owner, parentSessionId);
930
+ if (selection === void 0) console.warn("[dsh-sidenote] 同步主会话模型跳过:主机模型 API 不可用");
931
+ else if (!await writeModelSelection(owner, forked, selection)) console.warn("[dsh-sidenote] 同步主会话模型失败(子会话用默认模型)");
932
+ } catch (error) {
933
+ console.warn("[dsh-sidenote] 同步主会话模型失败(子会话用默认模型):", error);
934
+ }
935
+ return forked;
936
+ }
937
+ /** Tab meta 读-改-写(布局持久化寄存处);tab 已消失时 no-op。 */
938
+ function updateTabMeta(ctx, tabId, mutate) {
939
+ const current = parseSideChatMeta(readTab(ctx, tabId)?.meta);
940
+ ctx.betterSidebar.updateTab(tabId, { meta: mutate(current) });
941
+ }
942
+ /**
943
+ * 程序化入口(/side、bridge 划选提问)打开 Tab 时面板可能处于折叠态——
944
+ * 类型型 openTab 不自动展开(better-sidebar 只对 path/url 内容型打开展开)。
945
+ * 幂等展开(off-face:store.update 是 SidebarStore 公开 mutator 面;
946
+ * feature-check + 吞错,兜底失败不影响功能)。
947
+ */
948
+ function ensurePanelOpen(store) {
949
+ const mutable = store;
950
+ try {
951
+ mutable?.update?.((state) => {
952
+ if (state.panelOpen === false) state.panelOpen = true;
953
+ });
954
+ } catch {}
955
+ }
956
+ /**
957
+ * 打开非 staged 会话的事件窗口(off-face:open() 在 concrete Session 上是
958
+ * public 且幂等,但不在 SessionFace 契约上——契约面只有 staged 会话会被
959
+ * 运行时自动 open)。feature-check + 吞错,运行时若移除则降级为只发不收。
960
+ */
961
+ function openSessionWindow(session) {
962
+ const openable = session;
963
+ if (typeof openable?.open !== "function") return;
964
+ openable.open().catch((error) => {
965
+ console.warn("[dsh-sidenote] 会话窗口打开失败:", error);
966
+ });
967
+ }
968
+ /**
969
+ * Current model selection of a session, read from its `modelSelection`
970
+ * projection (dsh >= 0.1.2): the durable selection is projected state, so no RPC
971
+ * is needed and the read is safe for any session id. Snapshot shape authority:
972
+ * `@deepseek-ai/dsh-api-session-controller/lib/types/types.d.ts`
973
+ * (`ModelSelectionProjection = { lastUsed, next }`,两者均可为 null——
974
+ * `next` 缺省时按投影文档回落 `lastUsed`)。
975
+ */
976
+ function projectedModelSelection(ctx, sessionId) {
977
+ try {
978
+ const snapshot = (ctx.sessions.binding(sessionId)?.session)?.projections?.faceOf?.("modelSelection")?.getSnapshot?.();
979
+ return snapshot?.next ?? snapshot?.lastUsed ?? void 0;
980
+ } catch {
981
+ return;
982
+ }
983
+ }
984
+ /**
985
+ * Remote session face carrying `selectModel`/`modelCatalog`(dsh 0.1.5 删除
986
+ * `connection.api.sessions.*` client 面后的模型 RPC 面;事实上 0.1.2-rc.1
987
+ * 起存在——authority: dsh-api-session-controller lib/typert.remote-client.d.ts)。
988
+ * 探测走 `ctx.get`(cordis 的 get 不受 inject 门禁限制,本插件为兼容旧宿主
989
+ * 不声明 inject 'remote.session';机制见 contracts.ts RemoteSessionModelFace)。
990
+ * `ctx.get('remote')` 返回命名空间容器时下钻其 `.session` 成员。
991
+ */
992
+ function remoteSessionFace(ctx) {
993
+ for (const name of ["remote.session", "remote"]) {
994
+ let candidate;
995
+ try {
996
+ candidate = ctx.get(name);
997
+ } catch {
998
+ continue;
999
+ }
1000
+ const face = candidate;
1001
+ if (typeof face?.selectModel === "function") return face;
1002
+ if (typeof face?.session?.selectModel === "function") return face.session;
1003
+ }
1004
+ }
1005
+ /**
1006
+ * 读会话当前模型选择:投影面(0.1.2+,同步无 RPC)优先;旧
1007
+ * `connection.api.sessions.models` RPC(<= 0.1.2 的 client 面,0.1.5 已删)
1008
+ * 回退(双版本链,同 chatSourceOf 先例)。永不抛错。
1009
+ */
1010
+ async function readModelSelection(ctx, sessionId) {
1011
+ const projected = projectedModelSelection(ctx, sessionId);
1012
+ if (projected !== void 0) return projected;
1013
+ try {
1014
+ const res = await ctx.connection.api.sessions.models({ sessionId });
1015
+ if (res.result.ok) return res.result.value.current;
1016
+ } catch {}
1017
+ }
1018
+ /**
1019
+ * 写会话模型选择:`remote.session.selectModel`(新面)优先,旧
1020
+ * `connection.api.sessions.selectModel` 回退。面在而调用失败(模型不可路由等)
1021
+ * 属真实失败,不再落旧面重试。
1022
+ */
1023
+ async function writeModelSelection(ctx, sessionId, selection) {
1024
+ const request = {
1025
+ sessionId,
1026
+ provider: selection.provider,
1027
+ model: selection.model,
1028
+ ...selection.reasoningEffort !== void 0 ? { reasoningEffort: selection.reasoningEffort } : {}
1029
+ };
1030
+ const face = remoteSessionFace(ctx);
1031
+ if (face !== void 0) try {
1032
+ return (await face.selectModel(request)).ok;
1033
+ } catch {
1034
+ return false;
1035
+ }
1036
+ try {
1037
+ return (await ctx.connection.api.sessions.selectModel(request)).result.ok;
1038
+ } catch {
1039
+ return false;
1040
+ }
1041
+ }
1042
+ /** 读子会话当前模型名(composer 模型标签用);任何失败回退 null(默认文案)。 */
1043
+ async function readModelName(ctx, sessionId) {
1044
+ return (await readModelSelection(ctx, sessionId))?.model ?? null;
1045
+ }
1046
+ /**
1047
+ * 0.1.2 优先:`uiConversation` 服务 → binding(会话 binding) → target('chat')
1048
+ * → 快照 .legacy 切片(0.1.2 把内容面从 Session 快照顶层拆到此处——
1049
+ * 根因与证据见 reports/ux-review/W00-fork-replay-012.md);0.1.1 回退
1050
+ * Session 快照顶层(nodes 直接在)。均 miss 返回 undefined(面板维持
1051
+ * 既有降级,不崩)。
1052
+ */
1053
+ function chatSourceOf(ctx, binding) {
1054
+ const session = binding?.session;
1055
+ if (session === void 0) return void 0;
1056
+ try {
1057
+ const target = ctx.get("uiConversation")?.binding?.(binding)?.target?.("chat");
1058
+ if (target !== void 0 && typeof target.getSnapshot === "function" && typeof target.subscribe === "function") return {
1059
+ subscribe: (fn) => target.subscribe(fn),
1060
+ getLegacy: () => {
1061
+ return target.getSnapshot()?.legacy ?? null;
1062
+ }
1063
+ };
1064
+ } catch {}
1065
+ const snap = session.getSnapshot();
1066
+ if (snap !== null && Array.isArray(snap.nodes)) return {
1067
+ subscribe: (fn) => session.subscribe(fn),
1068
+ getLegacy: () => session.getSnapshot()
1069
+ };
1070
+ }
1071
+ /**
1072
+ * 拉会话模型目录:0.1.5 走 `remote.session.modelCatalog()`(目录是宿主级,
1073
+ * 菜单选中项所需的 current 由投影/旧面补上);旧面回退
1074
+ * `connection.api.sessions.models`。失败 null → 菜单保持只读标签态。
1075
+ */
1076
+ async function listModels(ctx, sessionId) {
1077
+ const face = remoteSessionFace(ctx);
1078
+ if (typeof face?.modelCatalog === "function") try {
1079
+ const result = await face.modelCatalog();
1080
+ if (result.ok) {
1081
+ const catalog = result.value;
1082
+ return {
1083
+ current: await readModelSelection(ctx, sessionId) ?? catalog.default,
1084
+ routable: catalog.routableProviders.length > 0,
1085
+ groups: catalog.groups,
1086
+ ...catalog.failures !== void 0 ? { failures: catalog.failures } : {}
1087
+ };
1088
+ }
1089
+ } catch {}
1090
+ try {
1091
+ const res = await ctx.connection.api.sessions.models({ sessionId });
1092
+ return res.result.ok ? res.result.value : null;
1093
+ } catch {
1094
+ return null;
1095
+ }
1096
+ }
1097
+ /** 切换子会话模型;成功返回新模型展示名,失败 null(best-effort)。 */
1098
+ async function switchModel(ctx, sessionId, provider, model) {
1099
+ return await writeModelSelection(ctx, sessionId, {
1100
+ provider,
1101
+ model
1102
+ }) ? model : null;
1103
+ }
1104
+ //#endregion
1105
+ //#region src/client/host/probes.ts
1106
+ const PROBES = [
1107
+ {
1108
+ name: "Session.open()(非 staged 会话开窗)",
1109
+ check: (ctx) => {
1110
+ return typeof ctx.sessions?.binding === "function";
1111
+ },
1112
+ fallback: "侧边聊天消息流降级为只发不收"
1113
+ },
1114
+ {
1115
+ name: "conversation.input 输入机器",
1116
+ check: (ctx) => {
1117
+ return typeof (ctx.get?.("conversation"))?.input?.for === "function";
1118
+ },
1119
+ fallback: "侧边 composer 降级为本地草稿 + session.prompt"
1120
+ },
1121
+ {
1122
+ name: "commandUi(斜杠命令注册)",
1123
+ check: (ctx) => {
1124
+ return typeof (ctx.get?.("commandUi"))?.register === "function";
1125
+ },
1126
+ fallback: "/side 与「/侧边」命令不可用(菜单/顶栏入口仍在)"
1127
+ },
1128
+ {
1129
+ name: "ctx.modules.import(动态组件复用)",
1130
+ check: (ctx) => {
1131
+ return typeof ctx.modules?.import === "function";
1132
+ },
1133
+ fallback: "ProducedFiles 等动态复用件降级为自绘"
1134
+ },
1135
+ {
1136
+ name: "sidebarRight.focus(native tab 程序化聚焦)",
1137
+ check: (ctx) => {
1138
+ if (!nativeSidebarHost(ctx)) return true;
1139
+ return typeof (ctx.get?.("sidebarRight"))?.focus === "function";
1140
+ },
1141
+ fallback: "聚焦既有侧边聊天 tab 不可用(activateTab 在 native 面是空操作)"
1142
+ },
1143
+ {
1144
+ name: "会话模型面(remote.session / connection.api.sessions 双版本链)",
1145
+ check: (ctx) => {
1146
+ if (remoteSessionFace(ctx) !== void 0) return true;
1147
+ return typeof ctx.connection?.api?.sessions?.models === "function";
1148
+ },
1149
+ fallback: "fork 模型同步、模型标签与模型切换不可用(子会话用宿主默认模型)"
1150
+ }
1151
+ ];
1152
+ /** 激活时跑一遍探测清单;缺失项逐项 warn-once。返回缺失项数(测试可断言)。 */
1153
+ function probeHost(ctx) {
1154
+ let missing = 0;
1155
+ for (const probe of PROBES) {
1156
+ let ok = false;
1157
+ try {
1158
+ ok = probe.check(ctx);
1159
+ } catch {
1160
+ ok = false;
1161
+ }
1162
+ if (!ok) {
1163
+ missing += 1;
1164
+ console.warn(`[dsh-sidenote] 宿主能力缺失:${probe.name} → ${probe.fallback}`);
1165
+ }
1166
+ }
1167
+ return missing;
1168
+ }
1169
+ //#endregion
1170
+ //#region src/client/reflow.ts
1171
+ /**
1172
+ * 回流通道的受控对象层(Delivery_02 Workitem_04 v2):侧边聊天的结论以
1173
+ * 「待回流对象」进入主会话——主 composer 上方的 chip(可预览/可移除),
1174
+ * 发送瞬间由 annotate/send.ts 拦截器与注释协议块一起序列化进消息。
1175
+ * 不经草稿文本流(用户裁定:直接进草稿「很脏」)。
1176
+ *
1177
+ * 气泡留痕:序列化形态是 XML 协议块(见 buildReflowBlock),随注释协议块
1178
+ * 一起作为消息连续前缀注入,bubble.ts 反解析后折叠为「含侧边回流上下文」标签。
1179
+ *
1180
+ * 持久化:localStorage 按会话键(dsh-sidenote:reflow:v1:<sessionId>),
1181
+ * 与注释 store 同款纪律(刷新不丢、容错 revive、空删键)。
1182
+ */
1183
+ const STORAGE_PREFIX$1 = "dsh-sidenote:reflow:v1:";
1184
+ /**
1185
+ * 回流块序列化(XML 形态,与注释协议同族):
1186
+ *
1187
+ * <reflow source="侧边 2" reason="用户选择从侧边聊天带回主线">
1188
+ * <问>用户在侧边聊天里的提问</问>
1189
+ * <答>结论全文(不截断)</答>
1190
+ * </reflow>
1191
+ *
1192
+ * 问答成对:只有答没有问,模型理解打折;question 缺省时只有 <答>。
1193
+ * 来源与意图放在属性里,内容行保持纯净;模型对 XML 包裹的上下文理解最好
1194
+ * (Claude 官方推荐形态 / Codex additionalContext 同款思路)。
1195
+ */
1196
+ function buildReflowBlock(item) {
1197
+ const parts = [];
1198
+ if (item.question !== void 0 && item.question.trim() !== "") parts.push(`<问>${item.question}</问>`);
1199
+ parts.push(`<答>${item.text}</答>`);
1200
+ return `<reflow source="${item.sideTitle}" reason="${t("reflowReason")}">\n${parts.join("\n")}\n</reflow>`;
1201
+ }
1202
+ function revive(value) {
1203
+ if (typeof value !== "object" || value === null) return null;
1204
+ const r = value;
1205
+ if (typeof r.id !== "number" || typeof r.sessionId !== "string" || r.sessionId === "") return null;
1206
+ if (typeof r.sideTitle !== "string" || typeof r.text !== "string") return null;
1207
+ return {
1208
+ id: r.id,
1209
+ sessionId: r.sessionId,
1210
+ sideTitle: r.sideTitle,
1211
+ text: r.text,
1212
+ ...typeof r.question === "string" && r.question.trim() !== "" ? { question: r.question } : {},
1213
+ createdAt: typeof r.createdAt === "number" ? r.createdAt : 0
1214
+ };
1215
+ }
1216
+ function createReflowStore(now = () => Date.now(), storage) {
1217
+ let items = [];
1218
+ let nextId = 1;
1219
+ let version = 0;
1220
+ const listeners = /* @__PURE__ */ new Set();
1221
+ const store = storage === void 0 ? typeof localStorage !== "undefined" ? localStorage : null : storage;
1222
+ if (store != null) try {
1223
+ const keys = [];
1224
+ for (let i = 0; i < store.length; i += 1) {
1225
+ const key = store.key(i);
1226
+ if (typeof key === "string" && key.startsWith(STORAGE_PREFIX$1)) keys.push(key);
1227
+ }
1228
+ for (const key of keys) {
1229
+ const parsed = JSON.parse(store.getItem(key) ?? "null");
1230
+ if (!Array.isArray(parsed)) continue;
1231
+ for (const raw of parsed) {
1232
+ const item = revive(raw);
1233
+ if (item === null) continue;
1234
+ items.push(item);
1235
+ nextId = Math.max(nextId, item.id + 1);
1236
+ }
1237
+ }
1238
+ } catch (error) {
1239
+ console.warn("[dsh-sidenote] 回流持久化读取失败(按空起步):", error);
1240
+ }
1241
+ const persist = (sessionId) => {
1242
+ if (store == null) return;
1243
+ try {
1244
+ const mine = items.filter((i) => i.sessionId === sessionId);
1245
+ const key = STORAGE_PREFIX$1 + sessionId;
1246
+ if (mine.length === 0) store.removeItem(key);
1247
+ else store.setItem(key, JSON.stringify(mine));
1248
+ } catch (error) {
1249
+ console.warn("[dsh-sidenote] 回流持久化写入失败:", error);
1250
+ }
1251
+ };
1252
+ const notify = () => {
1253
+ version += 1;
1254
+ for (const fn of [...listeners]) fn();
1255
+ };
1256
+ return {
1257
+ getSnapshot: () => version,
1258
+ subscribe(fn) {
1259
+ listeners.add(fn);
1260
+ return () => {
1261
+ listeners.delete(fn);
1262
+ };
1263
+ },
1264
+ add(sessionId, sideTitle, text, question) {
1265
+ const item = {
1266
+ id: nextId,
1267
+ sessionId,
1268
+ sideTitle,
1269
+ text,
1270
+ ...typeof question === "string" && question.trim() !== "" ? { question } : {},
1271
+ createdAt: now()
1272
+ };
1273
+ nextId += 1;
1274
+ items = [...items, item];
1275
+ persist(sessionId);
1276
+ notify();
1277
+ return item;
1278
+ },
1279
+ remove(id) {
1280
+ const target = items.find((i) => i.id === id);
1281
+ if (target === void 0) return;
1282
+ items = items.filter((i) => i.id !== id);
1283
+ persist(target.sessionId);
1284
+ notify();
1285
+ },
1286
+ clearSession(sessionId) {
1287
+ if (!items.some((i) => i.sessionId === sessionId)) return;
1288
+ items = items.filter((i) => i.sessionId !== sessionId);
1289
+ persist(sessionId);
1290
+ notify();
1291
+ },
1292
+ list: (sessionId) => items.filter((i) => i.sessionId === sessionId)
1293
+ };
1294
+ }
1295
+ //#endregion
1296
+ //#region src/client/bridge.ts
1297
+ const sideChatBridge = { current: null };
1298
+ //#endregion
1299
+ //#region src/client/chat/cards.ts
1300
+ /** ContentBlock[] → 纯文本(transcript.ts 同款的极简版,避免互依赖)。 */
1301
+ function blocksText(content) {
1302
+ if (!Array.isArray(content)) return void 0;
1303
+ const parts = [];
1304
+ for (const block of content) {
1305
+ const b = block;
1306
+ if (b?.type === "text" && typeof b.text === "string") parts.push(b.text);
1307
+ }
1308
+ return parts.length === 0 ? void 0 : parts.join("\n");
1309
+ }
1310
+ /** 未知 card 值的 warn-once 登记(T3 纪律:降级不静默)。 */
1311
+ const warnedCards = /* @__PURE__ */ new Set();
1312
+ function warnUnknownCard(value) {
1313
+ if (warnedCards.has(value)) return;
1314
+ warnedCards.add(value);
1315
+ console.warn(`[dsh-sidenote] 未知工具卡种类 "${value}"——降级为 generic 卡(宿主可能扩展了 card union)`);
1316
+ }
1317
+ /** 相对 cwd 按会话工作区折成绝对路径(UI bridge 责任——presentation 注释明文)。 */
1318
+ function resolveCwd(cwd, cwdBase) {
1319
+ if (cwd === void 0) return cwdBase;
1320
+ if (cwd.startsWith("/") || /^[A-Za-z]:[\\/]/.test(cwd)) return cwd;
1321
+ if (cwdBase === void 0) return cwd;
1322
+ return `${cwdBase.replace(/\/+$/, "")}/${cwd}`;
1323
+ }
1324
+ /**
1325
+ * 展示态路径缩短:>3 段的绝对路径只保末 3 段(≈仓库相对形态,与主区
1326
+ * 「Read · dsh-sidenote/scripts/x.sh」同款)。窄栏标题位只够放语义尾部。
1327
+ */
1328
+ function shortPath(p) {
1329
+ if (!p.startsWith("/")) return p;
1330
+ const segs = p.split("/").filter((s) => s !== "");
1331
+ return segs.length > 3 ? segs.slice(-3).join("/") : p;
1332
+ }
1333
+ /**
1334
+ * 标题整形:「Tool /abs/path」→「Tool · 末3段」(主区同名同款);
1335
+ * 裸绝对路径标题 → 末 3 段;不含路径的标题原样。
1336
+ */
1337
+ function displayTitle(title) {
1338
+ const m = /^([A-Za-z][\w-]*) (\/.+)$/.exec(title);
1339
+ const name = m?.[1];
1340
+ const path = m?.[2];
1341
+ if (name !== void 0 && path !== void 0) return `${name} · ${shortPath(path)}`;
1342
+ return title.startsWith("/") ? shortPath(title) : title;
1343
+ }
1344
+ /** todo_write 入参形状校验(字段不齐即放弃 todo 卡)。两种数据源形状:
1345
+ * 0.1.1 wire:callView.rawInput = todos 数组本身(dsh-tool-todo presentCall 实证);
1346
+ * 0.1.2 argsRaw:{ todos: [...] }。 */
1347
+ function todoItemsOf(raw) {
863
1348
  const list = Array.isArray(raw) ? raw : typeof raw === "object" && raw !== null ? raw.todos : void 0;
864
1349
  if (!Array.isArray(list) || list.length === 0) return void 0;
865
1350
  const items = [];
@@ -1055,6 +1540,114 @@ window.__ModuleLoader__.load({
1055
1540
  ...typeof m.lang === "string" ? { lang: m.lang } : {}
1056
1541
  };
1057
1542
  }
1543
+ /**
1544
+ * FsDiffMeta 校验(0.1.2 diff 卡,dsh-tool-fs computeHunkDiffs 产物的忠实子集)。
1545
+ * 返回值三态:{diffs 非空} / 'empty'(空数组——write create/同内容覆写是合法值,
1546
+ * 客户端回退 args 整文件 diff)/ undefined(malformed——降级)。
1547
+ * 逐项校验,一项违规整卡降级(官方 narrowDiffs 同款)。
1548
+ */
1549
+ function diffMetaOf(meta) {
1550
+ if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return void 0;
1551
+ const m = meta;
1552
+ if (!Array.isArray(m.diffs)) return void 0;
1553
+ if (m.diffs.length === 0) return "empty";
1554
+ const diffs = [];
1555
+ for (const raw of m.diffs) {
1556
+ const d = raw;
1557
+ if (typeof d?.path !== "string") return void 0;
1558
+ if (d.oldText !== null && typeof d.oldText !== "string") return void 0;
1559
+ if (typeof d.newText !== "string") return void 0;
1560
+ diffs.push({
1561
+ path: d.path,
1562
+ oldText: d.oldText,
1563
+ newText: d.newText
1564
+ });
1565
+ }
1566
+ return { diffs };
1567
+ }
1568
+ /** SearchMeta 校验(0.1.2 search 卡;truncated/total 是硬字段;空结果合法)。 */
1569
+ function searchMetaOf(meta) {
1570
+ if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return void 0;
1571
+ const m = meta;
1572
+ if (typeof m.truncated !== "boolean") return void 0;
1573
+ if (typeof m.total !== "number" || !Number.isInteger(m.total) || m.total < 0) return void 0;
1574
+ if (m.shape === "matches") {
1575
+ if (!Array.isArray(m.files)) return void 0;
1576
+ const files = [];
1577
+ for (const rawF of m.files) {
1578
+ const f = rawF;
1579
+ if (typeof f?.path !== "string" || !Array.isArray(f.matches)) return void 0;
1580
+ const matches = [];
1581
+ for (const rawM of f.matches) {
1582
+ const mm = rawM;
1583
+ if (typeof mm?.lineNumber !== "number" || !Number.isInteger(mm.lineNumber) || mm.lineNumber < 1) return void 0;
1584
+ if (typeof mm.line !== "string") return void 0;
1585
+ matches.push({
1586
+ lineNumber: mm.lineNumber,
1587
+ line: mm.line
1588
+ });
1589
+ }
1590
+ files.push({
1591
+ path: f.path,
1592
+ matches
1593
+ });
1594
+ }
1595
+ return {
1596
+ shape: "matches",
1597
+ files,
1598
+ truncated: m.truncated,
1599
+ total: m.total
1600
+ };
1601
+ }
1602
+ if (m.shape === "paths") {
1603
+ if (!Array.isArray(m.paths) || !m.paths.every((p) => typeof p === "string")) return void 0;
1604
+ return {
1605
+ shape: "paths",
1606
+ paths: m.paths,
1607
+ truncated: m.truncated,
1608
+ total: m.total
1609
+ };
1610
+ }
1611
+ }
1612
+ /** WebSearchMeta 校验(sources 逐项 url 必填、title/snippet 可选;publishedAt 校验后丢弃)。 */
1613
+ function webSearchMetaOf(meta) {
1614
+ if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return void 0;
1615
+ const m = meta;
1616
+ if (typeof m.truncated !== "boolean") return void 0;
1617
+ if (!Array.isArray(m.sources)) return void 0;
1618
+ const sources = [];
1619
+ for (const raw of m.sources) {
1620
+ const s = raw;
1621
+ if (typeof s?.url !== "string") return void 0;
1622
+ if (s.title !== void 0 && typeof s.title !== "string") return void 0;
1623
+ if (s.snippet !== void 0 && typeof s.snippet !== "string") return void 0;
1624
+ if (s.publishedAt !== void 0 && typeof s.publishedAt !== "string") return void 0;
1625
+ sources.push({
1626
+ url: s.url,
1627
+ ...s.title !== void 0 ? { title: s.title } : {},
1628
+ ...s.snippet !== void 0 ? { snippet: s.snippet } : {}
1629
+ });
1630
+ }
1631
+ if (m.answer !== void 0 && typeof m.answer !== "string") return void 0;
1632
+ return {
1633
+ sources,
1634
+ truncated: m.truncated,
1635
+ ...m.answer !== void 0 ? { answer: m.answer } : {}
1636
+ };
1637
+ }
1638
+ /** WebFetchMeta 校验(url + 整数 statusCode + truncated 硬字段)。 */
1639
+ function webFetchMetaOf(meta) {
1640
+ if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return void 0;
1641
+ const m = meta;
1642
+ if (typeof m.url !== "string") return void 0;
1643
+ if (typeof m.statusCode !== "number" || !Number.isInteger(m.statusCode)) return void 0;
1644
+ if (typeof m.truncated !== "boolean") return void 0;
1645
+ return {
1646
+ url: m.url,
1647
+ statusCode: m.statusCode,
1648
+ truncated: m.truncated
1649
+ };
1650
+ }
1058
1651
  /** argsRaw(JSON 字符串)里尽力提取展示字段。 */
1059
1652
  function argsSummary(argsRaw) {
1060
1653
  if (argsRaw === void 0 || argsRaw === "") return {};
@@ -1066,7 +1659,14 @@ window.__ModuleLoader__.load({
1066
1659
  ...typeof a.command === "string" ? { command: a.command } : {},
1067
1660
  ...typeof a.description === "string" ? { description: a.description } : {},
1068
1661
  ...typeof a.file_path === "string" ? { path: a.file_path } : typeof a.path === "string" ? { path: a.path } : {},
1069
- ...Array.isArray(a.todos) ? { todos: a.todos } : {}
1662
+ ...Array.isArray(a.todos) ? { todos: a.todos } : {},
1663
+ ...typeof a.content === "string" ? { content: a.content } : {},
1664
+ ...typeof a.old_string === "string" ? { oldString: a.old_string } : {},
1665
+ ...typeof a.new_string === "string" ? { newString: a.new_string } : {},
1666
+ ...typeof a.pattern === "string" ? { pattern: a.pattern } : {},
1667
+ ...typeof a.include === "string" ? { include: a.include } : {},
1668
+ ...Array.isArray(a.queries) && a.queries.every((q) => typeof q === "string") ? { queries: a.queries } : {},
1669
+ ...typeof a.url === "string" ? { url: a.url } : {}
1070
1670
  };
1071
1671
  } catch {
1072
1672
  return {};
@@ -1111,6 +1711,75 @@ window.__ModuleLoader__.load({
1111
1711
  items
1112
1712
  };
1113
1713
  }
1714
+ if ((name === "write" || name === "edit") && input.isError !== true) {
1715
+ const path = args.path;
1716
+ if (path !== void 0 && path.trim() !== "") {
1717
+ const dm = diffMetaOf(input.meta);
1718
+ if (dm !== void 0 && dm !== "empty") return {
1719
+ kind: "diff",
1720
+ title: displayTitle(`${name === "write" ? "Write" : "Edit"} ${path}`),
1721
+ diffs: dm.diffs,
1722
+ locations: [{ path }]
1723
+ };
1724
+ if (name === "write" && args.content !== void 0) return {
1725
+ kind: "diff",
1726
+ title: displayTitle(`Write ${path}`),
1727
+ diffs: [{
1728
+ path,
1729
+ oldText: null,
1730
+ newText: args.content
1731
+ }],
1732
+ locations: [{ path }]
1733
+ };
1734
+ }
1735
+ }
1736
+ if ((name === "grep" || name === "glob") && input.isError !== true) {
1737
+ const sm = searchMetaOf(input.meta);
1738
+ const wantShape = name === "grep" ? "matches" : "paths";
1739
+ if (sm !== void 0 && sm.shape === wantShape && args.pattern !== void 0) {
1740
+ const where = args.path !== void 0 && args.path.trim() !== "" ? ` in ${shortPath(args.path)}` : "";
1741
+ const inc = name === "grep" && args.include !== void 0 && args.include.trim() !== "" ? ` (${args.include})` : "";
1742
+ const title = `${name === "grep" ? "Grep" : "Glob"} ${args.pattern}${where}${inc}`;
1743
+ return sm.shape === "matches" ? {
1744
+ kind: "search",
1745
+ title,
1746
+ shape: "matches",
1747
+ files: sm.files,
1748
+ truncated: sm.truncated,
1749
+ total: sm.total
1750
+ } : {
1751
+ kind: "search",
1752
+ title,
1753
+ shape: "paths",
1754
+ paths: sm.paths,
1755
+ truncated: sm.truncated,
1756
+ total: sm.total
1757
+ };
1758
+ }
1759
+ }
1760
+ if (name === "web_search" && input.isError !== true) {
1761
+ const wm = webSearchMetaOf(input.meta);
1762
+ const queries = args.queries?.filter((q) => q.trim() !== "") ?? [];
1763
+ if (wm !== void 0 && queries.length > 0) return {
1764
+ kind: "web",
1765
+ title: queries.join(", "),
1766
+ webKind: "search",
1767
+ sources: wm.sources,
1768
+ truncated: wm.truncated,
1769
+ ...wm.answer !== void 0 ? { answer: wm.answer } : {}
1770
+ };
1771
+ }
1772
+ if (name === "web_fetch" && input.isError !== true) {
1773
+ const wm = webFetchMetaOf(input.meta);
1774
+ if (wm !== void 0 && args.url !== void 0 && args.url.trim() !== "") return {
1775
+ kind: "web",
1776
+ title: args.url,
1777
+ webKind: "fetch",
1778
+ url: wm.url,
1779
+ statusCode: wm.statusCode,
1780
+ truncated: wm.truncated
1781
+ };
1782
+ }
1114
1783
  return {
1115
1784
  kind: "generic",
1116
1785
  title: args.path !== void 0 ? `${name} · ${shortPath(args.path)}` : args.command ?? name,
@@ -1118,19 +1787,22 @@ window.__ModuleLoader__.load({
1118
1787
  bodyText: input.rawText === "" ? void 0 : input.rawText
1119
1788
  };
1120
1789
  }
1121
- /** 工具名 → 图标类别的静态映射(0.1.2 没有 callView.kind 可用)。 */
1790
+ /** 工具名 → 图标类别的静态映射(0.1.2 没有 callView.kind 可用)。
1791
+ * wire 名实证:str_replace_editor 是下划线(uitool client.js L381);
1792
+ * web_search 的 0.1.1 wire kind 是 'search'(presentSearchCall),
1793
+ * 'fetch' 是 web_fetch 的(presentFetchCall)。 */
1122
1794
  const KIND_BY_NAME = {
1123
1795
  read: "read",
1124
1796
  write: "edit",
1125
1797
  edit: "edit",
1126
- "str-replace-editor": "edit",
1798
+ str_replace_editor: "edit",
1127
1799
  bash: "execute",
1128
1800
  "bash-persistent": "execute",
1129
1801
  pwsh: "execute",
1130
1802
  "pwsh-persistent": "execute",
1131
1803
  grep: "search",
1132
1804
  glob: "search",
1133
- web_search: "fetch",
1805
+ web_search: "search",
1134
1806
  web_fetch: "fetch"
1135
1807
  };
1136
1808
  //#endregion
@@ -1221,7 +1893,8 @@ window.__ModuleLoader__.load({
1221
1893
  name: toolName,
1222
1894
  argsRaw: typeof call?.argsRaw === "string" ? call.argsRaw : void 0,
1223
1895
  meta: n.meta,
1224
- rawText: text
1896
+ rawText: text,
1897
+ ...n.isError === true ? { isError: true } : {}
1225
1898
  });
1226
1899
  return {
1227
1900
  key: seqKey("t", n),
@@ -1476,7 +2149,7 @@ window.__ModuleLoader__.load({
1476
2149
  }
1477
2150
  //#endregion
1478
2151
  //#region \0dsh-css:/home/runner/work/dsh-sidenote/dsh-sidenote/src/client/sidechat/sidechat.module.css.mjs
1479
- const css$1 = "._5KEEsG_root{height:100%;color:var(--dsw-alias-label-primary);flex-direction:column;font-size:13px;display:flex;position:relative}._5KEEsG_body{flex-direction:column;flex:1;padding:12px 10px;display:flex;overflow-y:auto}._5KEEsG_transcript{flex-direction:column;gap:10px;display:flex}._5KEEsG_empty,._5KEEsG_stateScreen{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:24px 16px;display:flex}._5KEEsG_emptyIcon{color:var(--dsw-alias-label-tertiary)}._5KEEsG_emptyTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600}._5KEEsG_emptyText{color:var(--dsw-alias-label-tertiary);max-width:240px;font-size:12px;line-height:1.6}._5KEEsG_stateDetail{color:var(--dsw-alias-state-error-primary);word-break:break-word;max-width:260px;font-size:12px;line-height:1.6}._5KEEsG_userRow{justify-content:flex-end;display:flex}._5KEEsG_userBubble{background:var(--dsw-specific-bubble,#edf3fe);max-width:88%;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:16px;padding:8px 12px;font-size:13px;line-height:1.55}._5KEEsG_assistantRow{justify-content:flex-start;display:flex;position:relative}._5KEEsG_rowActions{opacity:0;z-index:2;transition:opacity .12s ease-out;position:absolute;top:-8px;right:0}._5KEEsG_assistantRow:hover ._5KEEsG_rowActions{opacity:1}._5KEEsG_reflowButton{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-bg-layer-1,#fff);width:22px;height:22px;color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;border-radius:999px;place-items:center;padding:0;display:grid;box-shadow:0 2px 8px #00000014}._5KEEsG_reflowButton:hover{color:var(--dsw-alias-state-business-primary,#2f88ff);border-color:var(--dsw-alias-state-business-primary,#2f88ff)}._5KEEsG_assistantBody{word-break:break-word;max-width:100%;font-size:13px;line-height:1.6}._5KEEsG_reasoning{border-left:3px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);margin-bottom:6px;padding-left:8px;font-size:12px}._5KEEsG_reasoning>summary{cursor:pointer;user-select:none}._5KEEsG_reasoningBody{white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-tertiary);margin-top:2px;padding-bottom:2px;padding-left:22px}._5KEEsG_streamingHint{color:var(--dsw-alias-label-dimmed)}._5KEEsG_toolCard{font-size:12px}._5KEEsG_toolHead{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:12px;display:flex}._5KEEsG_toolError{color:var(--dsw-alias-state-error-primary)}._5KEEsG_toolRunning{color:var(--dsw-alias-label-tertiary)}._5KEEsG_toolBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;max-height:160px;padding:2px 0 2px 22px;font-size:12px;overflow-y:auto}._5KEEsG_errorRow{color:var(--dsw-alias-state-error-primary);white-space:pre-wrap;padding:4px 2px;font-size:12px}._5KEEsG_noticeRow{color:var(--dsw-alias-label-dimmed);text-align:center;padding:2px 0;font-size:12px}._5KEEsG_composer{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:6px;padding:8px 10px;display:flex;position:relative}._5KEEsG_composerCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:14px;flex-direction:column;gap:2px;padding:4px 6px 6px;transition:border-color .12s;display:flex}._5KEEsG_composerCard:focus-within{border-color:var(--dsw-alias-state-business-primary)}._5KEEsG_input{resize:none;width:100%;min-height:36px;max-height:160px;color:var(--dsw-alias-label-primary);font:inherit;box-sizing:border-box;background:0 0;border:none;padding:6px 6px 2px}._5KEEsG_input:focus{outline:none}._5KEEsG_composerFoot{justify-content:space-between;align-items:center;gap:8px;padding:0 2px;display:flex}._5KEEsG_modelLabel{color:var(--dsw-alias-label-dimmed);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._5KEEsG_sendButton,._5KEEsG_stopButton{appearance:none;background:var(--dsw-alias-button-info-fill);color:#fff;cursor:pointer;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;width:28px;height:28px;padding:0;display:inline-flex}._5KEEsG_sendButton:disabled{opacity:.45;cursor:default}._5KEEsG_headerSide{border:1px solid var(--dsw-alias-border-l1,#0000001a);color:var(--dsw-alias-label-secondary,#3f454d);cursor:pointer;user-select:none;background:0 0;border-radius:999px;align-items:center;gap:5px;padding:3px 10px;font-size:12px;line-height:18px;display:inline-flex}._5KEEsG_headerSide:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2329)}._5KEEsG_sentChipRow{align-items:center;gap:6px;margin-right:8px;display:inline-flex}._5KEEsG_sentChip{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-secondary,#3f454d);user-select:none;border-radius:999px;align-items:center;gap:4px;padding:1px 8px;font-size:11px;line-height:16px;display:inline-flex}._5KEEsG_flowRow{font-size:12px}._5KEEsG_rowPreview{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-dimmed);flex:1;margin-left:8px;font-size:12px;overflow:hidden}._5KEEsG_flowRowError{color:var(--dsw-alias-state-error-primary)}._5KEEsG_todoBody{flex-direction:column;gap:3px;padding:2px 0 4px 22px;display:flex}._5KEEsG_todoItem{align-items:baseline;gap:7px;min-width:0;font-size:12px;line-height:1.5;display:flex}._5KEEsG_todoDot{background:var(--dsw-alias-label-dimmed);border-radius:50%;flex:none;width:6px;height:6px;transform:translateY(-1px)}._5KEEsG_todoDot[data-status=completed]{background:var(--dsw-alias-state-success-primary,#34a853)}._5KEEsG_todoDot[data-status=in_progress]{background:var(--dsw-alias-state-business-primary)}._5KEEsG_todoText[data-status=completed]{color:var(--dsw-alias-label-dimmed)}._5KEEsG_toolGenericBody{flex-direction:column;gap:6px;padding:4px 0 4px 22px;display:flex}._5KEEsG_toolBodyText{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.55;font-family:var(--dsw-alias-font-mono,ui-monospace, monospace);margin:0}._5KEEsG_toolLocations{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-all;font-size:11px}._5KEEsG_inheritedBody{border-top:1px dashed var(--dsw-alias-border-l2);opacity:.92;padding:6px 0 2px}._5KEEsG_densityRow{justify-content:flex-end;align-items:center;padding:2px 2px 4px;display:flex}._5KEEsG_densityToggle{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:2px 6px;font-size:11px}._5KEEsG_densityToggle:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_jumpBottom{z-index:5;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:999px;padding:3px 10px;font-size:11px;position:absolute;bottom:96px;right:12px;box-shadow:0 2px 8px #0000001f}._5KEEsG_jumpBottom:hover{background:var(--dsw-alias-bg-layer-2)}._5KEEsG_modelSelect{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:6px;align-items:center;gap:3px;max-width:180px;padding:3px 6px;font-size:12px;display:inline-flex;overflow:hidden}._5KEEsG_modelSelect>span:first-child{text-overflow:ellipsis;overflow:hidden}._5KEEsG_modelSelect>svg{color:var(--dsw-alias-label-dimmed);flex:none}._5KEEsG_modelSelect:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_composerChips{align-items:center;gap:4px;min-width:0;display:inline-flex;overflow:hidden}._5KEEsG_slashMenu{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);z-index:20;border-radius:10px;max-height:260px;margin-bottom:6px;padding:4px;position:absolute;bottom:100%;left:8px;right:8px;overflow-y:auto;box-shadow:0 8px 24px #00000024}._5KEEsG_slashGroupTitle{color:var(--dsw-alias-label-tertiary);text-transform:none;padding:4px 8px 2px;font-size:11px}._5KEEsG_slashItem,._5KEEsG_slashItemActive{text-align:left;cursor:pointer;width:100%;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:6px;align-items:baseline;gap:8px;padding:6px 8px;font-size:13px;display:flex}._5KEEsG_slashItemActive,._5KEEsG_slashItem:hover{background:var(--dsw-alias-interactive-bg-hover)}._5KEEsG_slashItemName{flex:none;font-weight:500}._5KEEsG_slashItemDetail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._5KEEsG_attachRail{flex-wrap:wrap;gap:6px;padding:2px 0;display:flex}._5KEEsG_attachThumb{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;width:44px;height:44px;position:relative;overflow:hidden}._5KEEsG_attachImg{object-fit:cover;width:100%;height:100%;display:block}._5KEEsG_attachRemove{color:#fff;cursor:pointer;background:#0000008c;border:none;border-radius:999px;place-items:center;width:16px;height:16px;padding:0;font-size:11px;line-height:1;display:grid;position:absolute;top:1px;right:1px}._5KEEsG_attachButton{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:4px;display:inline-flex}._5KEEsG_attachButton:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_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}";
2152
+ const css$1 = "._5KEEsG_root{height:100%;color:var(--dsw-alias-label-primary);flex-direction:column;font-size:13px;display:flex;position:relative}._5KEEsG_body{flex-direction:column;flex:1;padding:12px 10px;display:flex;overflow-y:auto}._5KEEsG_transcript{flex-direction:column;gap:10px;display:flex}._5KEEsG_empty,._5KEEsG_stateScreen{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:24px 16px;display:flex}._5KEEsG_emptyIcon{color:var(--dsw-alias-label-tertiary)}._5KEEsG_emptyTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600}._5KEEsG_emptyText{color:var(--dsw-alias-label-tertiary);max-width:240px;font-size:12px;line-height:1.6}._5KEEsG_stateDetail{color:var(--dsw-alias-state-error-primary);word-break:break-word;max-width:260px;font-size:12px;line-height:1.6}._5KEEsG_userRow{justify-content:flex-end;display:flex}._5KEEsG_userBubble{background:var(--dsw-specific-bubble,#edf3fe);max-width:88%;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:16px;padding:8px 12px;font-size:13px;line-height:1.55}._5KEEsG_assistantRow{justify-content:flex-start;display:flex;position:relative}._5KEEsG_rowActions{opacity:0;z-index:2;transition:opacity .12s ease-out;position:absolute;top:-8px;right:0}._5KEEsG_assistantRow:hover ._5KEEsG_rowActions{opacity:1}._5KEEsG_reflowButton{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-bg-layer-1,#fff);width:22px;height:22px;color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;border-radius:999px;place-items:center;padding:0;display:grid;box-shadow:0 2px 8px #00000014}._5KEEsG_reflowButton:hover{color:var(--dsw-alias-state-business-primary,#2f88ff);border-color:var(--dsw-alias-state-business-primary,#2f88ff)}._5KEEsG_assistantBody{word-break:break-word;max-width:100%;font-size:13px;line-height:1.6}._5KEEsG_reasoning{border-left:3px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);margin-bottom:6px;padding-left:8px;font-size:12px}._5KEEsG_reasoning>summary{cursor:pointer;user-select:none}._5KEEsG_reasoningBody{white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-tertiary);margin-top:2px;padding-bottom:2px;padding-left:22px}._5KEEsG_streamingHint{color:var(--dsw-alias-label-dimmed)}._5KEEsG_toolCard{font-size:12px}._5KEEsG_toolHead{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:12px;display:flex}._5KEEsG_toolError{color:var(--dsw-alias-state-error-primary)}._5KEEsG_toolRunning{color:var(--dsw-alias-label-tertiary)}._5KEEsG_toolBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;max-height:160px;padding:2px 0 2px 22px;font-size:12px;overflow-y:auto}._5KEEsG_errorRow{color:var(--dsw-alias-state-error-primary);white-space:pre-wrap;padding:4px 2px;font-size:12px}._5KEEsG_noticeRow{color:var(--dsw-alias-label-dimmed);text-align:center;padding:2px 0;font-size:12px}._5KEEsG_composer{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:6px;padding:8px 10px;display:flex;position:relative}._5KEEsG_composerCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:14px;flex-direction:column;gap:2px;padding:4px 6px 6px;transition:border-color .12s;display:flex}._5KEEsG_composerCard:focus-within{border-color:var(--dsw-alias-state-business-primary)}._5KEEsG_input{resize:none;width:100%;min-height:36px;max-height:160px;color:var(--dsw-alias-label-primary);font:inherit;box-sizing:border-box;background:0 0;border:none;padding:6px 6px 2px}._5KEEsG_input:focus{outline:none}._5KEEsG_composerFoot{justify-content:space-between;align-items:center;gap:8px;padding:0 2px;display:flex}._5KEEsG_modelLabel{color:var(--dsw-alias-label-dimmed);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._5KEEsG_sendButton,._5KEEsG_stopButton{appearance:none;background:var(--dsw-alias-button-info-fill);color:#fff;cursor:pointer;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;width:28px;height:28px;padding:0;display:inline-flex}._5KEEsG_sendButton:disabled{opacity:.45;cursor:default}._5KEEsG_headerSide{border:1px solid var(--dsw-alias-border-l1,#0000001a);color:var(--dsw-alias-label-secondary,#3f454d);cursor:pointer;user-select:none;background:0 0;border-radius:999px;align-items:center;gap:5px;padding:3px 10px;font-size:12px;line-height:18px;display:inline-flex}._5KEEsG_headerSide:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2329)}._5KEEsG_sentChipRow{align-items:center;gap:6px;margin-right:8px;display:inline-flex}._5KEEsG_sentChip{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-secondary,#3f454d);user-select:none;border-radius:999px;align-items:center;gap:4px;padding:1px 8px;font-size:11px;line-height:16px;display:inline-flex}._5KEEsG_flowRow{font-size:12px}._5KEEsG_rowPreview{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-dimmed);flex:1;margin-left:8px;font-size:12px;overflow:hidden}._5KEEsG_flowRowError{color:var(--dsw-alias-state-error-primary)}._5KEEsG_todoBody{flex-direction:column;gap:3px;padding:2px 0 4px 22px;display:flex}._5KEEsG_todoItem{align-items:baseline;gap:7px;min-width:0;font-size:12px;line-height:1.5;display:flex}._5KEEsG_todoDot{background:var(--dsw-alias-label-dimmed);border-radius:50%;flex:none;width:6px;height:6px;transform:translateY(-1px)}._5KEEsG_todoDot[data-status=completed]{background:var(--dsw-alias-state-success-primary,#34a853)}._5KEEsG_todoDot[data-status=in_progress]{background:var(--dsw-alias-state-business-primary)}._5KEEsG_todoText[data-status=completed]{color:var(--dsw-alias-label-dimmed)}._5KEEsG_toolGenericBody{flex-direction:column;gap:6px;padding:4px 0 4px 22px;display:flex}._5KEEsG_toolBodyText{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.55;font-family:var(--dsw-alias-font-mono,ui-monospace, monospace);margin:0}._5KEEsG_toolLocations{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-all;font-size:11px}._5KEEsG_inheritedBody{border-top:1px dashed var(--dsw-alias-border-l2);opacity:.92;padding:6px 0 2px}._5KEEsG_densityRow{justify-content:flex-end;align-items:center;padding:2px 2px 4px;display:flex}._5KEEsG_densityToggle{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:2px 6px;font-size:11px}._5KEEsG_densityToggle:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_jumpBottom{z-index:5;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:999px;padding:3px 10px;font-size:11px;position:absolute;bottom:96px;right:12px;box-shadow:0 2px 8px #0000001f}._5KEEsG_jumpBottom:hover{background:var(--dsw-alias-bg-layer-2)}._5KEEsG_modelSelect{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:6px;align-items:center;gap:3px;max-width:180px;padding:3px 6px;font-size:12px;display:inline-flex;overflow:hidden}._5KEEsG_modelSelect>span:first-child{text-overflow:ellipsis;overflow:hidden}._5KEEsG_modelSelect>svg{color:var(--dsw-alias-label-dimmed);flex:none}._5KEEsG_modelSelect:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_composerChips{align-items:center;gap:4px;min-width:0;display:inline-flex;overflow:hidden}._5KEEsG_slashMenu{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);z-index:20;border-radius:10px;max-height:260px;margin-bottom:6px;padding:4px;position:absolute;bottom:100%;left:8px;right:8px;overflow-y:auto;box-shadow:0 8px 24px #00000024}._5KEEsG_slashGroupTitle{color:var(--dsw-alias-label-tertiary);text-transform:none;padding:4px 8px 2px;font-size:11px}._5KEEsG_slashItem,._5KEEsG_slashItemActive{text-align:left;cursor:pointer;width:100%;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:6px;align-items:baseline;gap:8px;padding:6px 8px;font-size:13px;display:flex}._5KEEsG_slashItemActive,._5KEEsG_slashItem:hover{background:var(--dsw-alias-interactive-bg-hover)}._5KEEsG_slashItemName{flex:none;font-weight:500}._5KEEsG_slashItemDetail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._5KEEsG_attachRail{flex-wrap:wrap;gap:6px;padding:2px 0;display:flex}._5KEEsG_attachThumb{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;width:44px;height:44px;position:relative;overflow:hidden}._5KEEsG_attachImg{object-fit:cover;width:100%;height:100%;display:block}._5KEEsG_attachRemove{color:#fff;cursor:pointer;background:#0000008c;border:none;border-radius:999px;place-items:center;width:16px;height:16px;padding:0;font-size:11px;line-height:1;display:grid;position:absolute;top:1px;right:1px}._5KEEsG_attachButton{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:4px;display:inline-flex}._5KEEsG_attachButton:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}._5KEEsG_pendingCard{border:1px solid var(--dsw-alias-border-l2);border-left:3px solid var(--dsw-alias-state-warning-primary,#e8a13a);background:var(--dsw-alias-bg-layer-2);border-radius:10px;flex-direction:column;gap:6px;margin:0 10px 6px;padding:10px 12px;font-size:12px;display:flex}._5KEEsG_pendingHead{color:var(--dsw-alias-state-warning-primary,#e8a13a);align-items:center;gap:6px;display:flex}._5KEEsG_pendingTitle{color:var(--dsw-alias-label-primary);font-weight:600}._5KEEsG_pendingDesc{color:var(--dsw-alias-label-secondary)}._5KEEsG_pendingReason{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word}._5KEEsG_pendingQuestion{flex-direction:column;gap:4px;padding:4px 0;display:flex}._5KEEsG_pendingQHeader{color:var(--dsw-alias-label-primary);font-weight:600}._5KEEsG_pendingQText{color:var(--dsw-alias-label-secondary)}._5KEEsG_pendingOption{text-align:left;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:6px;flex-direction:column;align-items:flex-start;gap:2px;padding:5px 8px;font-size:12px;display:flex}._5KEEsG_pendingOption:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d)}._5KEEsG_pendingOptionActive{text-align:left;border:1px solid var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex-direction:column;align-items:flex-start;gap:2px;padding:5px 8px;font-size:12px;display:flex}._5KEEsG_pendingOptionDesc{color:var(--dsw-alias-label-dimmed);font-size:11px}._5KEEsG_pendingCustom{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);font-size:12px;font:inherit;background:0 0;border-radius:6px;padding:5px 8px}._5KEEsG_pendingCustom:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}._5KEEsG_pendingActions{justify-content:flex-end;gap:8px;margin-top:2px;display:flex}._5KEEsG_pendingBtnGhost{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:6px;padding:4px 12px;font-size:12px}._5KEEsG_pendingBtnPrimary{background:var(--dsw-alias-button-info-fill);color:#fff;cursor:pointer;border:none;border-radius:6px;padding:4px 12px;font-size:12px}._5KEEsG_pendingBtnGhost:disabled,._5KEEsG_pendingBtnPrimary:disabled{opacity:.5;cursor:default}._5KEEsG_pendingBar{border:1px solid var(--dsw-alias-state-warning-primary,#d4a017);background:var(--dsw-alias-bg-layer-2);width:calc(100% - 16px);color:var(--dsw-alias-state-warning-primary,#d4a017);text-align:left;cursor:pointer;border-radius:8px;margin:0 8px 6px;padding:6px 10px;font-size:12px;display:block}._5KEEsG_pendingBar:hover{background:var(--dsw-alias-bg-layer-3)}._5KEEsG_writeNotice{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);border-radius:8px;align-items:center;gap:8px;margin:0 8px 6px;padding:6px 10px;font-size:12px;display:flex}._5KEEsG_writeNoticeClose{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;margin-left:auto;padding:0 4px;font-size:13px}._5KEEsG_parentStrip{border:none;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:left;align-items:center;gap:6px;padding:5px 12px;font-size:11px;display:flex}._5KEEsG_parentStrip:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}._5KEEsG_dotRunning,._5KEEsG_dotPending,._5KEEsG_dotIdle{border-radius:999px;flex:none;width:7px;height:7px}._5KEEsG_dotRunning{background:var(--dsw-alias-state-business-primary,#2f88ff);animation:1.2s ease-in-out infinite _5KEEsG_sidechat-pulse}._5KEEsG_dotPending{background:var(--dsw-alias-state-warning-primary,#d4a017)}._5KEEsG_dotIdle{background:var(--dsw-alias-label-dimmed)}@keyframes _5KEEsG_sidechat-pulse{50%{opacity:.45}}._5KEEsG_actionGroup{gap:2px;margin-right:auto;display:inline-flex}";
1480
2153
  const tagId$1 = "dsh-sidenote/sidechat.module.css";
1481
2154
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1482
2155
  const tag = document.createElement("style");
@@ -1486,77 +2159,92 @@ window.__ModuleLoader__.load({
1486
2159
  document.head.appendChild(tag);
1487
2160
  }
1488
2161
  var sidechat_module_css_default = {
1489
- "noticeRow": "_5KEEsG_noticeRow",
1490
- "toolBody": "_5KEEsG_toolBody",
1491
- "densityRow": "_5KEEsG_densityRow",
1492
- "toolCard": "_5KEEsG_toolCard",
1493
- "sidechat-pulse": "_5KEEsG_sidechat-pulse",
1494
- "slashItemDetail": "_5KEEsG_slashItemDetail",
2162
+ "reasoningBody": "_5KEEsG_reasoningBody",
2163
+ "pendingTitle": "_5KEEsG_pendingTitle",
2164
+ "todoItem": "_5KEEsG_todoItem",
2165
+ "todoDot": "_5KEEsG_todoDot",
2166
+ "composer": "_5KEEsG_composer",
2167
+ "sendButton": "_5KEEsG_sendButton",
2168
+ "userBubble": "_5KEEsG_userBubble",
2169
+ "toolLocations": "_5KEEsG_toolLocations",
2170
+ "pendingCard": "_5KEEsG_pendingCard",
2171
+ "pendingQuestion": "_5KEEsG_pendingQuestion",
2172
+ "errorRow": "_5KEEsG_errorRow",
1495
2173
  "composerFoot": "_5KEEsG_composerFoot",
1496
- "assistantRow": "_5KEEsG_assistantRow",
1497
- "toolGenericBody": "_5KEEsG_toolGenericBody",
1498
- "attachImg": "_5KEEsG_attachImg",
1499
- "slashItemActive": "_5KEEsG_slashItemActive",
1500
- "emptyIcon": "_5KEEsG_emptyIcon",
1501
- "toolError": "_5KEEsG_toolError",
1502
- "empty": "_5KEEsG_empty",
1503
- "flowRow": "_5KEEsG_flowRow",
2174
+ "sentChip": "_5KEEsG_sentChip",
2175
+ "noticeRow": "_5KEEsG_noticeRow",
2176
+ "stopButton": "_5KEEsG_stopButton",
2177
+ "stateDetail": "_5KEEsG_stateDetail",
1504
2178
  "flowRowError": "_5KEEsG_flowRowError",
1505
- "slashItem": "_5KEEsG_slashItem",
1506
- "streamingHint": "_5KEEsG_streamingHint",
2179
+ "emptyTitle": "_5KEEsG_emptyTitle",
2180
+ "slashGroupTitle": "_5KEEsG_slashGroupTitle",
2181
+ "pendingCustom": "_5KEEsG_pendingCustom",
2182
+ "pendingBtnGhost": "_5KEEsG_pendingBtnGhost",
2183
+ "attachButton": "_5KEEsG_attachButton",
2184
+ "pendingOptionDesc": "_5KEEsG_pendingOptionDesc",
2185
+ "writeNotice": "_5KEEsG_writeNotice",
1507
2186
  "input": "_5KEEsG_input",
1508
- "sendButton": "_5KEEsG_sendButton",
1509
- "todoBody": "_5KEEsG_todoBody",
1510
- "parentStrip": "_5KEEsG_parentStrip",
1511
- "dotRunning": "_5KEEsG_dotRunning",
1512
- "dotPending": "_5KEEsG_dotPending",
1513
2187
  "userRow": "_5KEEsG_userRow",
1514
- "toolLocations": "_5KEEsG_toolLocations",
1515
- "todoText": "_5KEEsG_todoText",
1516
- "actionGroup": "_5KEEsG_actionGroup",
1517
- "inheritedBody": "_5KEEsG_inheritedBody",
1518
- "todoItem": "_5KEEsG_todoItem",
1519
- "errorRow": "_5KEEsG_errorRow",
1520
- "attachRemove": "_5KEEsG_attachRemove",
1521
- "sentChipRow": "_5KEEsG_sentChipRow",
2188
+ "sidechat-pulse": "_5KEEsG_sidechat-pulse",
2189
+ "empty": "_5KEEsG_empty",
2190
+ "emptyIcon": "_5KEEsG_emptyIcon",
2191
+ "toolCard": "_5KEEsG_toolCard",
1522
2192
  "composerCard": "_5KEEsG_composerCard",
1523
- "stateScreen": "_5KEEsG_stateScreen",
1524
- "headerSide": "_5KEEsG_headerSide",
1525
- "transcript": "_5KEEsG_transcript",
1526
- "emptyText": "_5KEEsG_emptyText",
2193
+ "toolError": "_5KEEsG_toolError",
2194
+ "attachImg": "_5KEEsG_attachImg",
2195
+ "pendingQHeader": "_5KEEsG_pendingQHeader",
2196
+ "pendingActions": "_5KEEsG_pendingActions",
1527
2197
  "toolHead": "_5KEEsG_toolHead",
1528
- "pendingBar": "_5KEEsG_pendingBar",
1529
- "assistantBody": "_5KEEsG_assistantBody",
1530
- "attachButton": "_5KEEsG_attachButton",
2198
+ "sentChipRow": "_5KEEsG_sentChipRow",
2199
+ "pendingBtnPrimary": "_5KEEsG_pendingBtnPrimary",
2200
+ "dotPending": "_5KEEsG_dotPending",
2201
+ "rowActions": "_5KEEsG_rowActions",
2202
+ "transcript": "_5KEEsG_transcript",
2203
+ "modelLabel": "_5KEEsG_modelLabel",
2204
+ "flowRow": "_5KEEsG_flowRow",
2205
+ "toolBodyText": "_5KEEsG_toolBodyText",
2206
+ "toolGenericBody": "_5KEEsG_toolGenericBody",
2207
+ "composerChips": "_5KEEsG_composerChips",
2208
+ "attachRail": "_5KEEsG_attachRail",
2209
+ "pendingQText": "_5KEEsG_pendingQText",
2210
+ "pendingOption": "_5KEEsG_pendingOption",
1531
2211
  "dotIdle": "_5KEEsG_dotIdle",
2212
+ "stateScreen": "_5KEEsG_stateScreen",
2213
+ "attachRemove": "_5KEEsG_attachRemove",
2214
+ "dotRunning": "_5KEEsG_dotRunning",
2215
+ "actionGroup": "_5KEEsG_actionGroup",
2216
+ "assistantBody": "_5KEEsG_assistantBody",
2217
+ "todoText": "_5KEEsG_todoText",
2218
+ "pendingReason": "_5KEEsG_pendingReason",
1532
2219
  "root": "_5KEEsG_root",
2220
+ "pendingOptionActive": "_5KEEsG_pendingOptionActive",
2221
+ "parentStrip": "_5KEEsG_parentStrip",
1533
2222
  "toolRunning": "_5KEEsG_toolRunning",
1534
- "stateDetail": "_5KEEsG_stateDetail",
1535
- "slashMenu": "_5KEEsG_slashMenu",
1536
- "slashItemName": "_5KEEsG_slashItemName",
1537
- "writeNotice": "_5KEEsG_writeNotice",
1538
- "attachRail": "_5KEEsG_attachRail",
1539
- "reasoningBody": "_5KEEsG_reasoningBody",
1540
- "sentChip": "_5KEEsG_sentChip",
1541
- "densityToggle": "_5KEEsG_densityToggle",
1542
2223
  "modelSelect": "_5KEEsG_modelSelect",
1543
- "slashGroupTitle": "_5KEEsG_slashGroupTitle",
1544
- "emptyTitle": "_5KEEsG_emptyTitle",
2224
+ "pendingDesc": "_5KEEsG_pendingDesc",
1545
2225
  "body": "_5KEEsG_body",
1546
- "userBubble": "_5KEEsG_userBubble",
2226
+ "densityToggle": "_5KEEsG_densityToggle",
2227
+ "headerSide": "_5KEEsG_headerSide",
1547
2228
  "reflowButton": "_5KEEsG_reflowButton",
1548
- "toolBodyText": "_5KEEsG_toolBodyText",
1549
- "rowPreview": "_5KEEsG_rowPreview",
1550
- "attachThumb": "_5KEEsG_attachThumb",
1551
- "stopButton": "_5KEEsG_stopButton",
1552
- "rowActions": "_5KEEsG_rowActions",
2229
+ "emptyText": "_5KEEsG_emptyText",
2230
+ "densityRow": "_5KEEsG_densityRow",
1553
2231
  "jumpBottom": "_5KEEsG_jumpBottom",
2232
+ "inheritedBody": "_5KEEsG_inheritedBody",
1554
2233
  "reasoning": "_5KEEsG_reasoning",
1555
- "modelLabel": "_5KEEsG_modelLabel",
2234
+ "pendingBar": "_5KEEsG_pendingBar",
2235
+ "toolBody": "_5KEEsG_toolBody",
1556
2236
  "writeNoticeClose": "_5KEEsG_writeNoticeClose",
1557
- "todoDot": "_5KEEsG_todoDot",
1558
- "composerChips": "_5KEEsG_composerChips",
1559
- "composer": "_5KEEsG_composer"
2237
+ "slashItemActive": "_5KEEsG_slashItemActive",
2238
+ "slashMenu": "_5KEEsG_slashMenu",
2239
+ "todoBody": "_5KEEsG_todoBody",
2240
+ "streamingHint": "_5KEEsG_streamingHint",
2241
+ "rowPreview": "_5KEEsG_rowPreview",
2242
+ "slashItem": "_5KEEsG_slashItem",
2243
+ "slashItemDetail": "_5KEEsG_slashItemDetail",
2244
+ "attachThumb": "_5KEEsG_attachThumb",
2245
+ "pendingHead": "_5KEEsG_pendingHead",
2246
+ "slashItemName": "_5KEEsG_slashItemName",
2247
+ "assistantRow": "_5KEEsG_assistantRow"
1560
2248
  };
1561
2249
  //#endregion
1562
2250
  //#region src/client/chat/ToolCard.tsx
@@ -2056,245 +2744,50 @@ window.__ModuleLoader__.load({
2056
2744
  return a === b || a.key === b.key && a.role === b.role && a.text === b.text && a.reasoning === b.reasoning && a.streaming === b.streaming && a.isError === b.isError && a.interrupted === b.interrupted && a.toolName === b.toolName && a.card === b.card && a.seq === b.seq;
2057
2745
  }
2058
2746
  //#endregion
2059
- //#region src/client/sidechat/open.ts
2060
- /** 从最新快照读一个 Tab(meta 合并写入前的读取面;布局即注册表)。 */
2061
- function readTab(ctx, tabId) {
2062
- return collectTabs(ctx.betterSidebar.getSnapshot().state).find((tab) => tab.id === tabId);
2063
- }
2064
- /**
2065
- * 为 sessionId 新建或聚焦一个侧边聊天 Tab;draftText 给出时写入其草稿。
2066
- * @returns false = 未能打开(调用方保持自己的流程)。
2067
- */
2068
- function openOrFocusSideChat(ctx, sessionId, draftText) {
2069
- try {
2070
- const snapshot = ctx.betterSidebar.getSnapshot();
2071
- if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
2072
- const existing = collectSideTabs(snapshot.state);
2073
- if (existing.length > 0) {
2074
- const target = existing[existing.length - 1];
2075
- if (draftText !== void 0 && draftText !== "") {
2076
- const meta = parseSideChatMeta(target.meta);
2077
- const input = meta.childId === void 0 ? null : resolveSessionInput(ctx, meta.childId);
2078
- if (input !== null) input.setDraft(appendDraftText(readInputDraft(input), draftText));
2079
- else ctx.betterSidebar.updateTab(target.id, { meta: {
2080
- ...meta,
2081
- pendingDraft: appendDraftText(meta.pendingDraft ?? "", draftText)
2082
- } });
2083
- }
2084
- ctx.betterSidebar.activateTab(target.id, { sessionId });
2085
- return true;
2086
- }
2087
- return createSideChat(ctx, sessionId, draftText);
2088
- } catch (error) {
2089
- console.warn("[dsh-sidenote] 打开侧边聊天失败:", error);
2090
- return false;
2091
- }
2092
- }
2093
- /**
2094
- * 无条件新建一个侧边聊天 Tab(/side「新建侧边聊天」选项的语义——既有实例
2095
- * 在弹层里另有聚焦项,「新建」必须真的新建,否则标签与行为不符)。
2096
- * @returns false = 未能创建(调用方保持自己的流程)。
2097
- */
2098
- function createSideChat(ctx, sessionId, draftText) {
2099
- try {
2100
- const snapshot = ctx.betterSidebar.getSnapshot();
2101
- if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
2102
- const before = new Set(collectTabs(snapshot.state).map((tab) => tab.id));
2103
- ctx.betterSidebar.openTab({ type: SIDE_TAB_TYPE }, { sessionId });
2104
- const created = collectSideTabs(ctx.betterSidebar.getSnapshot().state).find((tab) => !before.has(tab.id));
2105
- if (created === void 0) return false;
2106
- if (draftText !== void 0 && draftText !== "") ctx.betterSidebar.updateTab(created.id, { meta: { pendingDraft: draftText } });
2107
- return true;
2108
- } catch (error) {
2109
- console.warn("[dsh-sidenote] 新建侧边聊天失败:", error);
2110
- return false;
2111
- }
2112
- }
2113
- /**
2114
- * 桥接/命令的目标预览:askInSideChat 此时若执行会落在哪个 Tab 的标题
2115
- * (既有并存 = 最后一个;无 = 即将新建的首个标题)。用于编辑器上标明
2116
- * 「发送至:侧边 N」。会话不在屏上/状态缺失时返回 undefined。
2117
- */
2118
- function sideChatTargetTitle(ctx, sessionId) {
2119
- try {
2120
- const snapshot = ctx.betterSidebar.getSnapshot();
2121
- if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return void 0;
2122
- const existing = collectSideTabs(snapshot.state);
2123
- if (existing.length > 0) return existing[existing.length - 1].title;
2124
- return sideTabTitle([]);
2125
- } catch {
2126
- return;
2127
- }
2128
- }
2129
- /**
2130
- * 重开最近关闭的侧边聊天(D3 后悔药):开 Tab + meta 预置 childId/
2131
- * parentSessionId——面板挂载后走绑定恢复路径(不重新 fork)。
2132
- * 会话本体必须仍 archived 在列(若被清理,面板落「会话已不存在」态——
2133
- * 有现成错误态兜着)。
2134
- */
2135
- function reopenSideChat(ctx, sessionId, childId, title) {
2136
- try {
2137
- const snapshot = ctx.betterSidebar.getSnapshot();
2138
- if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
2139
- const before = new Set(collectTabs(snapshot.state).map((tab) => tab.id));
2140
- ctx.betterSidebar.openTab({ type: SIDE_TAB_TYPE }, { sessionId });
2141
- const created = collectSideTabs(ctx.betterSidebar.getSnapshot().state).find((tab) => !before.has(tab.id));
2142
- if (created === void 0) return false;
2143
- ctx.betterSidebar.updateTab(created.id, {
2144
- ...title !== void 0 ? { title } : {},
2145
- meta: {
2146
- childId,
2147
- parentSessionId: sessionId
2148
- }
2149
- });
2150
- return true;
2151
- } catch (error) {
2152
- console.warn("[dsh-sidenote] 重开侧边聊天失败:", error);
2153
- return false;
2154
- }
2155
- }
2156
- //#endregion
2157
- //#region src/client/sidechat/lifecycle.ts
2158
- /**
2159
- * 首开编排:fork(全量历史快照)→ 登记 meta(先行,绝不丢登记)→
2160
- * 归档隐藏出会话列表(best-effort)→ 模型跟随主会话(best-effort)。
2161
- * fork 失败抛错(面板组件转错误态);后两步失败只告警不阻断。
2162
- * @returns fork 出的子会话 id。
2163
- */
2164
- async function forkAndRegister(ctx, parentSessionId, tabId) {
2165
- let boundarySeq;
2166
- try {
2167
- const snap = chatSourceOf(ctx, ctx.sessions.binding(parentSessionId))?.getLegacy();
2168
- let max = -1;
2169
- for (const node of snap?.nodes ?? []) {
2170
- const seq = node.seq;
2171
- if (typeof seq === "number" && seq > max) max = seq;
2172
- }
2173
- if (max >= 0) boundarySeq = max;
2174
- } catch {}
2175
- const forked = await ctx.sessions.fork({ sessionId: parentSessionId });
2176
- updateTabMeta(ctx, tabId, (current) => ({
2177
- ...current,
2178
- childId: forked,
2179
- parentSessionId,
2180
- ...boundarySeq !== void 0 ? { boundarySeq } : {}
2181
- }));
2182
- try {
2183
- await ctx.workspaces.archiveSession(forked);
2184
- } catch (error) {
2185
- console.warn("[dsh-sidenote] 归档侧边会话失败(会话列表可能短暂可见):", error);
2186
- }
2187
- try {
2188
- const parentModels = await ctx.connection.api.sessions.models({ sessionId: parentSessionId });
2189
- if (parentModels.result.ok) {
2190
- const current = parentModels.result.value.current;
2191
- await ctx.connection.api.sessions.selectModel({
2192
- sessionId: forked,
2193
- provider: current.provider,
2194
- model: current.model,
2195
- ...current.reasoningEffort !== void 0 ? { reasoningEffort: current.reasoningEffort } : {}
2196
- });
2197
- }
2198
- } catch (error) {
2199
- console.warn("[dsh-sidenote] 同步主会话模型失败(子会话用默认模型):", error);
2200
- }
2201
- return forked;
2202
- }
2203
- /** Tab meta 读-改-写(布局持久化寄存处);tab 已消失时 no-op。 */
2204
- function updateTabMeta(ctx, tabId, mutate) {
2205
- const current = parseSideChatMeta(readTab(ctx, tabId)?.meta);
2206
- ctx.betterSidebar.updateTab(tabId, { meta: mutate(current) });
2207
- }
2208
- /**
2209
- * 程序化入口(/side、bridge 划选提问)打开 Tab 时面板可能处于折叠态——
2210
- * 类型型 openTab 不自动展开(better-sidebar 只对 path/url 内容型打开展开)。
2211
- * 幂等展开(off-face:store.update 是 SidebarStore 公开 mutator 面;
2212
- * feature-check + 吞错,兜底失败不影响功能)。
2213
- */
2214
- function ensurePanelOpen(store) {
2215
- const mutable = store;
2216
- try {
2217
- mutable?.update?.((state) => {
2218
- if (state.panelOpen === false) state.panelOpen = true;
2219
- });
2220
- } catch {}
2221
- }
2222
- /**
2223
- * 打开非 staged 会话的事件窗口(off-face:open() 在 concrete Session 上是
2224
- * public 且幂等,但不在 SessionFace 契约上——契约面只有 staged 会话会被
2225
- * 运行时自动 open)。feature-check + 吞错,运行时若移除则降级为只发不收。
2226
- */
2227
- function openSessionWindow(session) {
2228
- const openable = session;
2229
- if (typeof openable?.open !== "function") return;
2230
- openable.open().catch((error) => {
2231
- console.warn("[dsh-sidenote] 会话窗口打开失败:", error);
2232
- });
2233
- }
2234
- /** 读子会话当前模型名(composer 模型标签用);任何失败回退 null(默认文案)。 */
2235
- async function readModelName(ctx, sessionId) {
2236
- try {
2237
- const res = await ctx.connection.api.sessions.models({ sessionId });
2238
- return res.result.ok ? res.result.value.current.model : null;
2239
- } catch {
2240
- return null;
2241
- }
2242
- }
2243
- /**
2244
- * 0.1.2 优先:`uiConversation` 服务 → binding(会话 binding) → target('chat')
2245
- * → 快照 .legacy 切片(0.1.2 把内容面从 Session 快照顶层拆到此处——
2246
- * 根因与证据见 reports/ux-review/W00-fork-replay-012.md);0.1.1 回退
2247
- * Session 快照顶层(nodes 直接在)。均 miss 返回 undefined(面板维持
2248
- * 既有降级,不崩)。
2249
- */
2250
- function chatSourceOf(ctx, binding) {
2251
- const session = binding?.session;
2252
- if (session === void 0) return void 0;
2253
- try {
2254
- const target = ctx.get("uiConversation")?.binding?.(binding)?.target?.("chat");
2255
- if (target !== void 0 && typeof target.getSnapshot === "function" && typeof target.subscribe === "function") return {
2256
- subscribe: (fn) => target.subscribe(fn),
2257
- getLegacy: () => {
2258
- return target.getSnapshot()?.legacy ?? null;
2259
- }
2260
- };
2261
- } catch {}
2262
- const snap = session.getSnapshot();
2263
- if (snap !== null && Array.isArray(snap.nodes)) return {
2264
- subscribe: (fn) => session.subscribe(fn),
2265
- getLegacy: () => session.getSnapshot()
2266
- };
2267
- }
2268
- /** 拉会话模型目录(models RPC 容错;失败 null → 菜单保持只读标签态)。 */
2269
- async function listModels(ctx, sessionId) {
2747
+ //#region src/client/sidechat/recentClosed.ts
2748
+ const KEY_PREFIX = "dsh-sidenote:closed-side:v1:";
2749
+ const CAP = 5;
2750
+ function readAll(storageKey) {
2270
2751
  try {
2271
- const res = await ctx.connection.api.sessions.models({ sessionId });
2272
- return res.result.ok ? res.result.value : null;
2752
+ const raw = localStorage.getItem(storageKey);
2753
+ if (raw === null) return [];
2754
+ const parsed = JSON.parse(raw);
2755
+ if (!Array.isArray(parsed)) return [];
2756
+ return parsed.filter((e) => typeof e === "object" && e !== null && typeof e.childId === "string" && typeof e.parentSessionId === "string" && typeof e.title === "string");
2273
2757
  } catch {
2274
- return null;
2758
+ return [];
2275
2759
  }
2276
2760
  }
2277
- /** 切换子会话模型;成功返回新模型展示名,失败 null(best-effort)。 */
2278
- async function switchModel(ctx, sessionId, provider, model) {
2761
+ function recordClosedSideChat(parentSessionId, entry) {
2279
2762
  try {
2280
- return (await ctx.connection.api.sessions.selectModel({
2281
- sessionId,
2282
- provider,
2283
- model
2284
- })).result.ok ? model : null;
2285
- } catch {
2286
- return null;
2287
- }
2763
+ const key = KEY_PREFIX + parentSessionId;
2764
+ const list = [entry, ...readAll(key).filter((e) => e.childId !== entry.childId)].slice(0, CAP);
2765
+ localStorage.setItem(key, JSON.stringify(list));
2766
+ } catch {}
2767
+ }
2768
+ /** 读某主会话的最近关闭列表(新者在前)。 */
2769
+ function listClosedSideChats(parentSessionId) {
2770
+ if (typeof localStorage === "undefined") return [];
2771
+ return readAll(KEY_PREFIX + parentSessionId);
2772
+ }
2773
+ /** 重开消费:从列表移除(重开后若再关会重新入列)。 */
2774
+ function dropClosedSideChat(parentSessionId, childId) {
2775
+ try {
2776
+ const key = KEY_PREFIX + parentSessionId;
2777
+ const list = readAll(key).filter((e) => e.childId !== childId);
2778
+ if (list.length === 0) localStorage.removeItem(key);
2779
+ else localStorage.setItem(key, JSON.stringify(list));
2780
+ } catch {}
2288
2781
  }
2289
2782
  //#endregion
2290
2783
  //#region src/client/sidechat/ModelMenu.tsx
2291
2784
  /**
2292
2785
  * 模型选择器(WI-02):composer 脚部的模型 chip → 两级 Menu(provider 分组
2293
- * 标签 + 型号行)。数据走 wire 面(sessions.models/selectModel——fork 时
2294
- * 同步主会话的同款通道);切的是子会话自己的选择,不回写主会话。
2786
+ * 标签 + 型号行)。数据面走 lifecycle.ts 的双版本链(0.1.5 的
2787
+ * remote.session.modelCatalog/selectModel 优先,<= 0.1.2 的
2788
+ * connection.api.sessions 回退);切的是子会话自己的选择,不回写主会话。
2295
2789
  *
2296
- * 懒加载:首次打开才拉目录(models RPC 有成本);切换后本地标签立即更新
2297
- * (selectModel 返回的 selected 为准)。
2790
+ * 懒加载:首次打开才拉目录(catalog RPC 有成本);切换后本地标签立即更新。
2298
2791
  */
2299
2792
  function ModelMenu(props) {
2300
2793
  useLocaleTick();
@@ -2762,6 +3255,342 @@ window.__ModuleLoader__.load({
2762
3255
  }, "dsh-sidenote: toast host");
2763
3256
  }
2764
3257
  //#endregion
3258
+ //#region src/client/sidechat/pending.ts
3259
+ function adapt012(pending) {
3260
+ if (typeof pending !== "object" || pending === null) return [];
3261
+ const o = pending;
3262
+ if (o.kind === "approval" && typeof o.answer === "function") return [{
3263
+ kind: "approval",
3264
+ key: typeof o.key === "string" ? o.key : "approval",
3265
+ toolName: typeof o.toolName === "string" ? o.toolName : "",
3266
+ ...typeof o.reason === "string" ? { reason: o.reason } : {},
3267
+ answer: (outcome) => o.answer.call(o, outcome)
3268
+ }];
3269
+ if ((o.kind === "question" || o.kind === "plan-review") && typeof o.answer === "function" && typeof o.cancel === "function") {
3270
+ const questions = normalizeQuestions(o.questions ?? o.payload?.questions);
3271
+ if (questions === void 0) return [];
3272
+ return [{
3273
+ kind: "question",
3274
+ key: typeof o.key === "string" ? o.key : "question",
3275
+ planReview: o.kind === "plan-review",
3276
+ questions,
3277
+ answer: (answers) => o.answer.call(o, { answers }),
3278
+ cancel: () => o.cancel.call(o)
3279
+ }];
3280
+ }
3281
+ return [];
3282
+ }
3283
+ function adapt011(pending) {
3284
+ const items = [];
3285
+ for (const raw of pending) {
3286
+ const w = raw;
3287
+ if (typeof w.respond !== "function") continue;
3288
+ const respond = w.respond;
3289
+ const sessionId = typeof w.sessionId === "string" ? w.sessionId : void 0;
3290
+ if (sessionId === void 0) continue;
3291
+ const payload = w.payload ?? {};
3292
+ const key = typeof w.key === "string" ? w.key : `pending:${items.length}`;
3293
+ if (w.kind === "approval") {
3294
+ const approvalId = payload.approvalId;
3295
+ if (typeof approvalId !== "string") continue;
3296
+ items.push({
3297
+ kind: "approval",
3298
+ key,
3299
+ toolName: typeof payload.toolName === "string" ? payload.toolName : "",
3300
+ ...typeof payload.reason === "string" ? { reason: payload.reason } : {},
3301
+ answer: async (outcome) => {
3302
+ if ((await respond({
3303
+ ok: true,
3304
+ value: {
3305
+ sessionId,
3306
+ approvalId,
3307
+ outcome
3308
+ }
3309
+ })).accepted === false) throw new Error("approval response rejected (not pending)");
3310
+ }
3311
+ });
3312
+ } else if (w.kind === "question") {
3313
+ const questions = normalizeQuestions(payload.questions);
3314
+ if (questions === void 0) continue;
3315
+ items.push({
3316
+ kind: "question",
3317
+ key,
3318
+ planReview: false,
3319
+ questions,
3320
+ answer: async (answers) => {
3321
+ if ((await respond({
3322
+ ok: true,
3323
+ value: {
3324
+ sessionId,
3325
+ answer: { answers }
3326
+ }
3327
+ })).accepted === false) throw new Error("question response rejected (not pending)");
3328
+ },
3329
+ cancel: async () => {
3330
+ await respond({
3331
+ ok: false,
3332
+ error: {
3333
+ code: "cancelled",
3334
+ message: "the user closed this question request",
3335
+ details: {}
3336
+ }
3337
+ });
3338
+ }
3339
+ });
3340
+ }
3341
+ }
3342
+ return items;
3343
+ }
3344
+ /** questions 字段归一(选项 {label,description?} 与纯字符串两形都收)。 */
3345
+ function normalizeQuestions(raw) {
3346
+ if (!Array.isArray(raw)) return void 0;
3347
+ const out = [];
3348
+ for (const rawQ of raw) {
3349
+ if (typeof rawQ !== "object" || rawQ === null) return void 0;
3350
+ const q = rawQ;
3351
+ if (typeof q.question !== "string") return void 0;
3352
+ const options = [];
3353
+ if (Array.isArray(q.options)) for (const rawO of q.options) if (typeof rawO === "string") options.push({ label: rawO });
3354
+ else if (typeof rawO === "object" && rawO !== null && typeof rawO.label === "string") {
3355
+ const oo = rawO;
3356
+ options.push({
3357
+ label: oo.label,
3358
+ ...typeof oo.description === "string" ? { description: oo.description } : {}
3359
+ });
3360
+ } else return void 0;
3361
+ out.push({
3362
+ id: typeof q.id === "string" ? q.id : `q${out.length}`,
3363
+ question: q.question,
3364
+ ...typeof q.header === "string" ? { header: q.header } : {},
3365
+ ...typeof q.detail === "string" ? { detail: q.detail } : {},
3366
+ options,
3367
+ multiSelect: q.multiSelect === true
3368
+ });
3369
+ }
3370
+ return out;
3371
+ }
3372
+ /**
3373
+ * 双版本探测链(chatSourceOf 同款纪律):0.1.2 uiSession.pendingInteractions
3374
+ * (getSnapshot() 是 Map 才认)优先;0.1.1 Session 快照 pending 数组回退;
3375
+ * 双缺席 undefined(面板回退 pendingBar 跳转)。
3376
+ */
3377
+ function createPendingStore(ctx, session, childId) {
3378
+ try {
3379
+ const pim = (typeof ctx.get === "function" ? ctx.get("uiSession") : void 0)?.pendingInteractions;
3380
+ if (pim !== void 0 && typeof pim.subscribe === "function" && typeof pim.getSnapshot === "function") {
3381
+ const obs = pim;
3382
+ if (obs.getSnapshot() instanceof Map) {
3383
+ let cachedMap = null;
3384
+ let cachedItems = [];
3385
+ return {
3386
+ available: true,
3387
+ subscribe: (fn) => obs.subscribe(fn),
3388
+ getSnapshot: () => {
3389
+ const map = obs.getSnapshot();
3390
+ if (map === cachedMap) return cachedItems;
3391
+ cachedMap = map;
3392
+ cachedItems = map instanceof Map ? adapt012(map.get(childId)) : [];
3393
+ return cachedItems;
3394
+ }
3395
+ };
3396
+ }
3397
+ }
3398
+ } catch {}
3399
+ if (session === void 0) return void 0;
3400
+ try {
3401
+ const snap = session.getSnapshot();
3402
+ if (Array.isArray(snap?.pending)) {
3403
+ let cachedArr = null;
3404
+ let cachedItems = [];
3405
+ return {
3406
+ available: true,
3407
+ subscribe: (fn) => session.subscribe(fn),
3408
+ getSnapshot: () => {
3409
+ const arr = session.getSnapshot()?.pending;
3410
+ if (arr === cachedArr) return cachedItems;
3411
+ cachedArr = arr;
3412
+ cachedItems = Array.isArray(arr) ? adapt011(arr) : [];
3413
+ return cachedItems;
3414
+ }
3415
+ };
3416
+ }
3417
+ } catch {}
3418
+ }
3419
+ //#endregion
3420
+ //#region src/client/sidechat/PendingCard.tsx
3421
+ /**
3422
+ * R8 审批/提问答复卡(L2 视图):钉在 composer 上方(原 pendingBar 位置)。
3423
+ * 自绘 + primitives 图标(材质同源,不求像素级复刻宿主审批卡)。
3424
+ *
3425
+ * 竞答纪律:先答先得——点击后置 answered 本地态;answer 失败(已被主视图
3426
+ * 抢答/settle)catch 吞错并回退按钮态(宿主 `.catch(() => setAnswered(false))`
3427
+ * 同款);卡片消失由快照驱动(pending 项消失即卸载),UI 不自造状态机。
3428
+ */
3429
+ function PendingCardList({ items }) {
3430
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: items.map((item) => item.kind === "approval" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApprovalCard, { item }, item.key) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuestionCard, { item }, item.key)) });
3431
+ }
3432
+ /** 审批卡:reason + 拒绝 / 允许一次。 */
3433
+ function ApprovalCard({ item }) {
3434
+ useLocaleTick();
3435
+ const [answered, setAnswered] = (0, react.useState)(false);
3436
+ const answer = (outcome) => {
3437
+ setAnswered(true);
3438
+ item.answer(outcome).catch(() => {
3439
+ setAnswered(false);
3440
+ });
3441
+ };
3442
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3443
+ className: sidechat_module_css_default.pendingCard,
3444
+ "data-kind": "approval",
3445
+ children: [
3446
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3447
+ className: sidechat_module_css_default.pendingHead,
3448
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3449
+ className: sidechat_module_css_default.pendingTitle,
3450
+ children: t("pendingApprovalTitle")
3451
+ })]
3452
+ }),
3453
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3454
+ className: sidechat_module_css_default.pendingDesc,
3455
+ children: t("pendingApprovalDesc", { name: item.toolName })
3456
+ }),
3457
+ item.reason !== void 0 && item.reason !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3458
+ className: sidechat_module_css_default.pendingReason,
3459
+ children: item.reason
3460
+ }),
3461
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3462
+ className: sidechat_module_css_default.pendingActions,
3463
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3464
+ type: "button",
3465
+ className: sidechat_module_css_default.pendingBtnGhost,
3466
+ disabled: answered,
3467
+ onClick: () => {
3468
+ answer("rejected");
3469
+ },
3470
+ children: t("pendingReject")
3471
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3472
+ type: "button",
3473
+ className: sidechat_module_css_default.pendingBtnPrimary,
3474
+ disabled: answered,
3475
+ onClick: () => {
3476
+ answer("allowed-once");
3477
+ },
3478
+ children: t("pendingAllowOnce")
3479
+ })]
3480
+ })
3481
+ ]
3482
+ });
3483
+ }
3484
+ /** 提问卡:选项(单选即点即答语义并入整批提交)+ 自定义 + 整批提交/取消。 */
3485
+ function QuestionCard({ item }) {
3486
+ useLocaleTick();
3487
+ const [selections, setSelections] = (0, react.useState)(/* @__PURE__ */ new Map());
3488
+ const [customs, setCustoms] = (0, react.useState)(/* @__PURE__ */ new Map());
3489
+ const [answered, setAnswered] = (0, react.useState)(false);
3490
+ const toggle = (qid, label, multi) => {
3491
+ const next = new Map(selections);
3492
+ const cur = new Set(next.get(qid) ?? []);
3493
+ if (multi) {
3494
+ if (cur.has(label)) cur.delete(label);
3495
+ else cur.add(label);
3496
+ } else {
3497
+ cur.clear();
3498
+ cur.add(label);
3499
+ }
3500
+ next.set(qid, cur);
3501
+ setSelections(next);
3502
+ };
3503
+ const ready = item.questions.every((q) => (selections.get(q.id)?.size ?? 0) > 0 || (customs.get(q.id)?.trim() ?? "") !== "");
3504
+ const submit = () => {
3505
+ setAnswered(true);
3506
+ item.answer(item.questions.map((q) => ({
3507
+ id: q.id,
3508
+ selected: [...selections.get(q.id) ?? []],
3509
+ ...(customs.get(q.id)?.trim() ?? "") !== "" ? { custom: customs.get(q.id).trim() } : {}
3510
+ }))).catch(() => {
3511
+ setAnswered(false);
3512
+ });
3513
+ };
3514
+ const cancel = () => {
3515
+ setAnswered(true);
3516
+ item.cancel().catch(() => {
3517
+ setAnswered(false);
3518
+ });
3519
+ };
3520
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3521
+ className: sidechat_module_css_default.pendingCard,
3522
+ "data-kind": "question",
3523
+ children: [
3524
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3525
+ className: sidechat_module_css_default.pendingHead,
3526
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQuestionOutline14, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3527
+ className: sidechat_module_css_default.pendingTitle,
3528
+ children: item.planReview ? t("pendingPlanReview") : t("pendingQuestionTitle")
3529
+ })]
3530
+ }),
3531
+ item.questions.map((q) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3532
+ className: sidechat_module_css_default.pendingQuestion,
3533
+ children: [
3534
+ q.header !== void 0 && q.header !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3535
+ className: sidechat_module_css_default.pendingQHeader,
3536
+ children: q.header
3537
+ }),
3538
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3539
+ className: sidechat_module_css_default.pendingQText,
3540
+ children: q.question
3541
+ }),
3542
+ q.detail !== void 0 && q.detail !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3543
+ className: sidechat_module_css_default.pendingReason,
3544
+ children: q.detail
3545
+ }),
3546
+ q.options.map((o) => {
3547
+ const active = selections.get(q.id)?.has(o.label) === true;
3548
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3549
+ type: "button",
3550
+ className: active ? sidechat_module_css_default.pendingOptionActive : sidechat_module_css_default.pendingOption,
3551
+ disabled: answered,
3552
+ onClick: () => {
3553
+ toggle(q.id, o.label, q.multiSelect);
3554
+ },
3555
+ children: [o.label, o.description !== void 0 && o.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3556
+ className: sidechat_module_css_default.pendingOptionDesc,
3557
+ children: o.description
3558
+ })]
3559
+ }, o.label);
3560
+ }),
3561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3562
+ className: sidechat_module_css_default.pendingCustom,
3563
+ placeholder: t("pendingCustomPlaceholder"),
3564
+ disabled: answered,
3565
+ value: customs.get(q.id) ?? "",
3566
+ onChange: (event) => {
3567
+ const next = new Map(customs);
3568
+ next.set(q.id, event.target.value);
3569
+ setCustoms(next);
3570
+ }
3571
+ })
3572
+ ]
3573
+ }, q.id)),
3574
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3575
+ className: sidechat_module_css_default.pendingActions,
3576
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3577
+ type: "button",
3578
+ className: sidechat_module_css_default.pendingBtnGhost,
3579
+ disabled: answered,
3580
+ onClick: cancel,
3581
+ children: t("pendingQuestionCancel")
3582
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3583
+ type: "button",
3584
+ className: sidechat_module_css_default.pendingBtnPrimary,
3585
+ disabled: answered || !ready,
3586
+ onClick: submit,
3587
+ children: t("pendingQuestionSubmit")
3588
+ })]
3589
+ })
3590
+ ]
3591
+ });
3592
+ }
3593
+ //#endregion
2765
3594
  //#region src/client/sidechat/SideChatPanel.tsx
2766
3595
  /**
2767
3596
  * 侧边聊天 Tab 面板(L2 薄视图):相位编排 + 消息流 + composer。
@@ -2778,6 +3607,8 @@ window.__ModuleLoader__.load({
2778
3607
  * 否则消息流永远为空(client-runtime 只为 staged 会话开窗的已知偏差)。
2779
3608
  */
2780
3609
  const NOOP_UNSUBSCRIBE = () => {};
3610
+ /** uSES 空快照常量(禁产新引用——`?? []` 字面量每次新数组会无限重渲)。 */
3611
+ const NO_PENDING = [];
2781
3612
  /** D2 写型工具名(无 diff 卡时的名字兜底)。 */
2782
3613
  const WRITE_TOOLS = /* @__PURE__ */ new Set([
2783
3614
  "edit",
@@ -2838,6 +3669,12 @@ window.__ModuleLoader__.load({
2838
3669
  const fold = (0, react.useMemo)(() => createFoldStore(childId === void 0 ? void 0 : `dsh-sidenote:fold:v1:${childId}`), [childId]);
2839
3670
  const parentSession = meta.parentSessionId === void 0 ? void 0 : ctx.sessions.binding(meta.parentSessionId)?.session;
2840
3671
  const parentSnap = (0, react.useSyncExternalStore)((0, react.useCallback)((notify) => visible && parentSession !== void 0 ? parentSession.subscribe(notify) : NOOP_UNSUBSCRIBE, [visible, parentSession]), () => parentSession === void 0 ? null : parentSession.getSnapshot());
3672
+ const pendingStore = (0, react.useMemo)(() => childId === void 0 || session === void 0 ? void 0 : createPendingStore(ctx, session, childId), [
3673
+ ctx,
3674
+ session,
3675
+ childId
3676
+ ]);
3677
+ const pendingItems = (0, react.useSyncExternalStore)((0, react.useCallback)((notify) => visible && pendingStore !== void 0 ? pendingStore.subscribe(notify) : NOOP_UNSUBSCRIBE, [visible, pendingStore]), () => pendingStore?.getSnapshot() ?? NO_PENDING);
2841
3678
  const [writeNoticeDismissed, setWriteNoticeDismissed] = (0, react.useState)(() => typeof localStorage !== "undefined" && localStorage.getItem(WRITE_NOTICE_KEY) === "1");
2842
3679
  const composer = useComposer(ctx, session, childId);
2843
3680
  const [modelName, setModelName] = (0, react.useState)(null);
@@ -2870,6 +3707,47 @@ window.__ModuleLoader__.load({
2870
3707
  ctx,
2871
3708
  tab.id
2872
3709
  ]);
3710
+ const composerRef = (0, react.useRef)(composer);
3711
+ composerRef.current = composer;
3712
+ const metaRef = (0, react.useRef)(tab.meta);
3713
+ metaRef.current = tab.meta;
3714
+ const phaseRef = (0, react.useRef)(phase);
3715
+ phaseRef.current = phase;
3716
+ const titleRef = (0, react.useRef)(tab.title);
3717
+ titleRef.current = tab.title;
3718
+ (0, react.useEffect)(() => {
3719
+ const metaNow = parseSideChatMeta(metaRef.current);
3720
+ if (nativeSidebarHost(ctx) && metaNow.childId !== void 0) dropClosedSideChat(scope.sessionId, metaNow.childId);
3721
+ const dispose = registerLiveSideChat(scope.sessionId, tab.id, (text) => {
3722
+ if (phaseRef.current !== "chat") {
3723
+ updateTabMeta(ctx, tab.id, (m) => ({
3724
+ ...m,
3725
+ pendingDraft: appendDraftText(m.pendingDraft ?? "", text)
3726
+ }));
3727
+ return;
3728
+ }
3729
+ composerRef.current.appendDraft(text);
3730
+ requestAnimationFrame(() => {
3731
+ rootRef.current?.querySelector("textarea")?.focus();
3732
+ });
3733
+ }, () => metaRef.current);
3734
+ return () => {
3735
+ dispose();
3736
+ if (nativeSidebarHost(ctx)) {
3737
+ const m = parseSideChatMeta(metaRef.current);
3738
+ if (m.childId !== void 0 && m.parentSessionId !== void 0) recordClosedSideChat(m.parentSessionId, {
3739
+ childId: m.childId,
3740
+ parentSessionId: m.parentSessionId,
3741
+ title: titleRef.current,
3742
+ closedAt: Date.now()
3743
+ });
3744
+ }
3745
+ };
3746
+ }, [
3747
+ scope.sessionId,
3748
+ tab.id,
3749
+ ctx
3750
+ ]);
2873
3751
  const promote = (0, react.useCallback)(() => {
2874
3752
  if (childId === void 0) return;
2875
3753
  (async () => {
@@ -3011,7 +3889,8 @@ window.__ModuleLoader__.load({
3011
3889
  children: "×"
3012
3890
  })]
3013
3891
  }),
3014
- pendingCount > 0 && childId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3892
+ pendingStore !== void 0 && pendingItems.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PendingCardList, { items: pendingItems }),
3893
+ pendingStore === void 0 && pendingCount > 0 && childId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3015
3894
  type: "button",
3016
3895
  className: sidechat_module_css_default.pendingBar,
3017
3896
  onClick: () => {
@@ -3039,42 +3918,6 @@ window.__ModuleLoader__.load({
3039
3918
  });
3040
3919
  }
3041
3920
  //#endregion
3042
- //#region src/client/sidechat/recentClosed.ts
3043
- const KEY_PREFIX = "dsh-sidenote:closed-side:v1:";
3044
- const CAP = 5;
3045
- function readAll(storageKey) {
3046
- try {
3047
- const raw = localStorage.getItem(storageKey);
3048
- if (raw === null) return [];
3049
- const parsed = JSON.parse(raw);
3050
- if (!Array.isArray(parsed)) return [];
3051
- return parsed.filter((e) => typeof e === "object" && e !== null && typeof e.childId === "string" && typeof e.parentSessionId === "string" && typeof e.title === "string");
3052
- } catch {
3053
- return [];
3054
- }
3055
- }
3056
- function recordClosedSideChat(parentSessionId, entry) {
3057
- try {
3058
- const key = KEY_PREFIX + parentSessionId;
3059
- const list = [entry, ...readAll(key).filter((e) => e.childId !== entry.childId)].slice(0, CAP);
3060
- localStorage.setItem(key, JSON.stringify(list));
3061
- } catch {}
3062
- }
3063
- /** 读某主会话的最近关闭列表(新者在前)。 */
3064
- function listClosedSideChats(parentSessionId) {
3065
- if (typeof localStorage === "undefined") return [];
3066
- return readAll(KEY_PREFIX + parentSessionId);
3067
- }
3068
- /** 重开消费:从列表移除(重开后若再关会重新入列)。 */
3069
- function dropClosedSideChat(parentSessionId, childId) {
3070
- try {
3071
- const key = KEY_PREFIX + parentSessionId;
3072
- const list = readAll(key).filter((e) => e.childId !== childId);
3073
- if (list.length === 0) localStorage.removeItem(key);
3074
- else localStorage.setItem(key, JSON.stringify(list));
3075
- } catch {}
3076
- }
3077
- //#endregion
3078
3921
  //#region src/client/sidechat/header.tsx
3079
3922
  /**
3080
3923
  * 顶栏「侧边」入口(Delivery_02 Workitem_02 发现性):注册进宿主的
@@ -3116,11 +3959,16 @@ window.__ModuleLoader__.load({
3116
3959
  }
3117
3960
  //#endregion
3118
3961
  //#region src/client/sidechat/slash.ts
3962
+ /** 0.1.5 信号:`connection.api.sessions` 面在 0.1.5 被删(与模型面迁移同判据)。 */
3963
+ function isDsh015Plus(ctx) {
3964
+ const legacy = ctx.connection?.api?.sessions;
3965
+ return legacy === void 0 || legacy === null;
3966
+ }
3119
3967
  /** 单个命令名对应的完整贡献(side / 侧边 共用)。 */
3120
3968
  function makeContribution(ctx, name) {
3121
3969
  return {
3122
3970
  name,
3123
- description: t("cmdDesc"),
3971
+ description: isDsh015Plus(ctx) ? () => t("cmdDesc") : t("cmdDesc"),
3124
3972
  available: (session) => canForkFrom(ctx, session.sessionId),
3125
3973
  ui: {
3126
3974
  kind: "popupSelect",
@@ -3250,6 +4098,66 @@ window.__ModuleLoader__.load({
3250
4098
  }
3251
4099
  }
3252
4100
  //#endregion
4101
+ //#region src/client/sidechat/notify.ts
4102
+ /** 挂接保护窗:watcher 建立后该窗口内的翻转不视为「一个真实 turn 跑完」。 */
4103
+ const ATTACH_GRACE_MS = 800;
4104
+ /**
4105
+ * 翻转侦测核心(抽出来纯可测):running true→false 且距挂接超过保护窗
4106
+ * 时返回 true。调用方持状态。
4107
+ */
4108
+ function shouldNotify(wasRunning, running, attachedAt, now) {
4109
+ return wasRunning && !running && now - attachedAt > ATTACH_GRACE_MS;
4110
+ }
4111
+ function registerCompletionNotify(ctx) {
4112
+ ctx.effect(() => {
4113
+ const watchers = /* @__PURE__ */ new Map();
4114
+ const rescan = () => {
4115
+ let tabs = [];
4116
+ try {
4117
+ tabs = collectSideTabs(ctx.betterSidebar.getSnapshot());
4118
+ } catch {
4119
+ return;
4120
+ }
4121
+ const alive = /* @__PURE__ */ new Set();
4122
+ for (const tab of tabs) {
4123
+ const childId = parseSideChatMeta(tab.meta).childId;
4124
+ if (childId === void 0) continue;
4125
+ alive.add(childId);
4126
+ if (watchers.has(childId)) continue;
4127
+ try {
4128
+ const session = ctx.sessions.binding(childId)?.session;
4129
+ if (session === void 0) continue;
4130
+ openSessionWindow(session);
4131
+ const title = tab.title;
4132
+ const watcher = {
4133
+ unsub: () => {},
4134
+ wasRunning: false,
4135
+ since: Date.now()
4136
+ };
4137
+ watcher.unsub = session.subscribe(() => {
4138
+ const running = session.getSnapshot()?.running === true;
4139
+ if (shouldNotify(watcher.wasRunning, running, watcher.since, Date.now())) showToast(t("sideChatDone", { title }));
4140
+ watcher.wasRunning = running;
4141
+ });
4142
+ watcher.wasRunning = session.getSnapshot()?.running === true;
4143
+ watchers.set(childId, watcher);
4144
+ } catch {}
4145
+ }
4146
+ for (const [id, w] of watchers) if (!alive.has(id)) {
4147
+ w.unsub();
4148
+ watchers.delete(id);
4149
+ }
4150
+ };
4151
+ const offState = ctx.betterSidebar.subscribeState(rescan);
4152
+ rescan();
4153
+ return () => {
4154
+ offState();
4155
+ for (const w of watchers.values()) w.unsub();
4156
+ watchers.clear();
4157
+ };
4158
+ }, "dsh-sidenote: completion notify");
4159
+ }
4160
+ //#endregion
3253
4161
  //#region src/client/sidechat/index.tsx
3254
4162
  /**
3255
4163
  * Workitem 01 — 侧边聊天面板。
@@ -3308,6 +4216,7 @@ window.__ModuleLoader__.load({
3308
4216
  registerSideChatReferenceSource(ctx);
3309
4217
  registerHeaderEntry(ctx);
3310
4218
  registerToastHost(ctx);
4219
+ registerCompletionNotify(ctx);
3311
4220
  }
3312
4221
  //#endregion
3313
4222
  //#region src/client/annotate/model.ts
@@ -3760,43 +4669,43 @@ window.__ModuleLoader__.load({
3760
4669
  document.head.appendChild(tag);
3761
4670
  }
3762
4671
  var annotate_module_css_default = {
3763
- "cancelButton": "_SVViG_cancelButton",
3764
4672
  "editorNew": "_SVViG_editorNew",
3765
- "editorFooter": "_SVViG_editorFooter",
3766
- "confirmButton": "_SVViG_confirmButton",
3767
- "chipClearAll": "_SVViG_chipClearAll",
3768
- "editorTarget": "_SVViG_editorTarget",
3769
- "chipNumber": "_SVViG_chipNumber",
3770
4673
  "chipText": "_SVViG_chipText",
3771
- "editorAskRow": "_SVViG_editorAskRow",
3772
- "chipWrap": "_SVViG_chipWrap",
4674
+ "toolbarBelow": "_SVViG_toolbarBelow",
4675
+ "sentCardTitle": "_SVViG_sentCardTitle",
4676
+ "chipClearAll": "_SVViG_chipClearAll",
3773
4677
  "chip": "_SVViG_chip",
3774
4678
  "highlight": "_SVViG_highlight",
3775
- "toolbarButton": "_SVViG_toolbarButton",
3776
4679
  "chipNote": "_SVViG_chipNote",
3777
- "chipPanel": "_SVViG_chipPanel",
3778
- "editorInput": "_SVViG_editorInput",
3779
- "hint": "_SVViG_hint",
3780
- "toolbar": "_SVViG_toolbar",
3781
- "saveButton": "_SVViG_saveButton",
3782
- "sentCardTitle": "_SVViG_sentCardTitle",
4680
+ "chipRemove": "_SVViG_chipRemove",
4681
+ "editorEdit": "_SVViG_editorEdit",
3783
4682
  "sentCardNote": "_SVViG_sentCardNote",
4683
+ "editorFooter": "_SVViG_editorFooter",
4684
+ "toolbar": "_SVViG_toolbar",
4685
+ "dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
4686
+ "sentCardQuote": "_SVViG_sentCardQuote",
4687
+ "editorInput": "_SVViG_editorInput",
3784
4688
  "deleteButton": "_SVViG_deleteButton",
3785
- "chipRemove": "_SVViG_chipRemove",
3786
- "editorAsk": "_SVViG_editorAsk",
4689
+ "badge": "_SVViG_badge",
4690
+ "confirmButton": "_SVViG_confirmButton",
4691
+ "saveButton": "_SVViG_saveButton",
4692
+ "badgeSent": "_SVViG_badgeSent",
4693
+ "sentChip": "_SVViG_sentChip",
4694
+ "chipNumber": "_SVViG_chipNumber",
4695
+ "hint": "_SVViG_hint",
3787
4696
  "hintClose": "_SVViG_hintClose",
3788
- "toolbarBelow": "_SVViG_toolbarBelow",
4697
+ "editorTarget": "_SVViG_editorTarget",
4698
+ "chipWrap": "_SVViG_chipWrap",
4699
+ "cancelButton": "_SVViG_cancelButton",
4700
+ "editorError": "_SVViG_editorError",
4701
+ "editorAskRow": "_SVViG_editorAskRow",
3789
4702
  "sentChipRow": "_SVViG_sentChipRow",
3790
- "sentChip": "_SVViG_sentChip",
3791
4703
  "editorTextarea": "_SVViG_editorTextarea",
3792
- "badgeSent": "_SVViG_badgeSent",
3793
- "sentCardQuote": "_SVViG_sentCardQuote",
3794
- "editorEdit": "_SVViG_editorEdit",
4704
+ "editorAsk": "_SVViG_editorAsk",
3795
4705
  "chipRow": "_SVViG_chipRow",
3796
- "dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
3797
- "editorSpacer": "_SVViG_editorSpacer",
3798
- "editorError": "_SVViG_editorError",
3799
- "badge": "_SVViG_badge"
4706
+ "chipPanel": "_SVViG_chipPanel",
4707
+ "toolbarButton": "_SVViG_toolbarButton",
4708
+ "editorSpacer": "_SVViG_editorSpacer"
3800
4709
  };
3801
4710
  //#endregion
3802
4711
  //#region src/client/annotate/hint.tsx
@@ -4908,6 +5817,7 @@ window.__ModuleLoader__.load({
4908
5817
  "locale"
4909
5818
  ];
4910
5819
  function apply(ctx) {
5820
+ setRootContext(ctx);
4911
5821
  attachLocale(ctx.locale);
4912
5822
  probeHost(ctx);
4913
5823
  const reflow = createReflowStore();