dsh-sidenote 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/lib/client.js +164 -119
- package/lib/types/client/sidechat/native.d.ts +9 -2
- package/lib/types/client/sidechat/slash.d.ts +3 -1
- package/package.json +1 -1
- package/src/client/index.tsx +6 -2
- package/src/client/sidechat/SideChatPanel.tsx +1 -0
- package/src/client/sidechat/index.tsx +13 -3
- package/src/client/sidechat/native.ts +31 -7
- package/src/client/sidechat/open.ts +8 -1
- package/src/client/sidechat/slash.ts +34 -11
package/README.md
CHANGED
|
@@ -78,9 +78,12 @@ dsh plugin --profile web add dsh-sidenote
|
|
|
78
78
|
|---|---|---|
|
|
79
79
|
| 0.1.1-rc.x | ≥ 0.12.3 | ✅ CI 双档矩阵 |
|
|
80
80
|
| 0.1.2-rc.x | ≥ 0.18.0 | ✅ CI 双档矩阵 |
|
|
81
|
+
| 0.1.5-rc.x | ≥ 0.19.0 | ✅ 沙箱实测(见下) |
|
|
81
82
|
|
|
82
83
|
每周 canary 跟进宿主新版本;宿主面缺席时插件按能力降级(不崩页面)。
|
|
83
84
|
|
|
85
|
+
**native 右栏(better-sidebar ≥ 0.19)语义差异**:侧边聊天 tab 托管给 DSH 原生右侧边栏,受宿主「page kind 每 pane 单实例」规则约束——同一会话同时只开一个侧边聊天(再次打开/新建 = 聚焦既有 tab;关闭后可经 /side 重开恢复原会话)。多开诉求可用原生侧栏的分栏(每 pane 一个)。
|
|
86
|
+
|
|
84
87
|
## 设计要点
|
|
85
88
|
|
|
86
89
|
- **真 fork,不压缩**:侧边会话是真实 DSH 会话(全量历史快照),与主会话能力对等;不是「摘要 + 一次性问答」。
|
package/lib/client.js
CHANGED
|
@@ -36,17 +36,25 @@ window.__ModuleLoader__.load({
|
|
|
36
36
|
/** Native tab id → live panel. Native layouts are memory-only, so is this. */
|
|
37
37
|
const liveSideChats = /* @__PURE__ */ new Map();
|
|
38
38
|
/**
|
|
39
|
+
* tabId → 首次注册序号(打开序的近似)。tab.id 跨重挂载稳定,重注册不改写
|
|
40
|
+
* ——遍历 Map 的插入序会随重挂漂移,「最近打开」只能锚在首次序号上。
|
|
41
|
+
*/
|
|
42
|
+
const birthOrder = /* @__PURE__ */ new Map();
|
|
43
|
+
let birthSeq = 0;
|
|
44
|
+
/**
|
|
39
45
|
* Publish a mounted panel so programmatic entry points can focus it and seed
|
|
40
46
|
* its draft. Returns the disposer for the panel's effect cleanup.
|
|
41
47
|
*/
|
|
42
|
-
function registerLiveSideChat(sessionId, tabId, seedDraft, readMeta) {
|
|
48
|
+
function registerLiveSideChat(sessionId, tabId, seedDraft, readMeta, readTitle) {
|
|
43
49
|
const entry = {
|
|
44
50
|
tabId,
|
|
45
51
|
sessionId,
|
|
46
52
|
seedDraft,
|
|
47
|
-
readMeta
|
|
53
|
+
readMeta,
|
|
54
|
+
readTitle
|
|
48
55
|
};
|
|
49
56
|
liveSideChats.set(tabId, entry);
|
|
57
|
+
if (!birthOrder.has(tabId)) birthOrder.set(tabId, ++birthSeq);
|
|
50
58
|
const opening = openings.get(sessionId);
|
|
51
59
|
if (opening !== void 0) {
|
|
52
60
|
openings.delete(sessionId);
|
|
@@ -76,14 +84,25 @@ window.__ModuleLoader__.load({
|
|
|
76
84
|
}
|
|
77
85
|
return mark;
|
|
78
86
|
}
|
|
87
|
+
/** 一个会话的全部存活面板(按打开序升序;native 下枚举 side tab 的唯一数据源)。 */
|
|
88
|
+
function liveSideChatsOf(sessionId) {
|
|
89
|
+
return [...liveSideChats.values()].filter((entry) => entry.sessionId === sessionId).sort((a, b) => (birthOrder.get(a.tabId) ?? 0) - (birthOrder.get(b.tabId) ?? 0));
|
|
90
|
+
}
|
|
79
91
|
/**
|
|
80
|
-
* Most recently
|
|
92
|
+
* Most recently OPENED live panel of a session — the open-or-focus target
|
|
81
93
|
* when the legacy snapshot cannot see the tab (mirrors "last opened wins").
|
|
94
|
+
* 排序锚在首次注册序号上:面板重挂载不翻序(见 birthOrder)。
|
|
82
95
|
*/
|
|
83
96
|
function lastLiveSideChat(sessionId) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
const all = liveSideChatsOf(sessionId);
|
|
98
|
+
return all[all.length - 1];
|
|
99
|
+
}
|
|
100
|
+
/** 插件卸载/重载时清场(apply 的 ctx.effect disposer 调用)。 */
|
|
101
|
+
function clearNativeRuntime() {
|
|
102
|
+
root = void 0;
|
|
103
|
+
liveSideChats.clear();
|
|
104
|
+
openings.clear();
|
|
105
|
+
birthOrder.clear();
|
|
87
106
|
}
|
|
88
107
|
/** Native tab shell for reads that go through the layout snapshot (`readTab`). */
|
|
89
108
|
function nativeTabShell(tabId, type) {
|
|
@@ -851,6 +870,8 @@ window.__ModuleLoader__.load({
|
|
|
851
870
|
if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return void 0;
|
|
852
871
|
const existing = collectSideTabs(snapshot.state);
|
|
853
872
|
if (existing.length > 0) return existing[existing.length - 1].title;
|
|
873
|
+
const live = lastLiveSideChat(sessionId);
|
|
874
|
+
if (live !== void 0) return live.readTitle();
|
|
854
875
|
return sideTabTitle([]);
|
|
855
876
|
} catch {
|
|
856
877
|
return;
|
|
@@ -867,6 +888,7 @@ window.__ModuleLoader__.load({
|
|
|
867
888
|
const snapshot = ctx.betterSidebar.getSnapshot();
|
|
868
889
|
if (snapshot.sessionId !== sessionId || snapshot.state === void 0) return false;
|
|
869
890
|
if (!ctx.betterSidebar.isTabEnabled("dsh-sidenote:side")) return false;
|
|
891
|
+
if (nativeSidebarHost(ctx) && liveSideChatsOf(sessionId).length > 0) return false;
|
|
870
892
|
const before = new Set(collectTabs(snapshot.state).map((tab) => tab.id));
|
|
871
893
|
const meta = {
|
|
872
894
|
childId,
|
|
@@ -2159,92 +2181,92 @@ window.__ModuleLoader__.load({
|
|
|
2159
2181
|
document.head.appendChild(tag);
|
|
2160
2182
|
}
|
|
2161
2183
|
var sidechat_module_css_default = {
|
|
2184
|
+
"pendingBar": "_5KEEsG_pendingBar",
|
|
2185
|
+
"composerCard": "_5KEEsG_composerCard",
|
|
2186
|
+
"attachRail": "_5KEEsG_attachRail",
|
|
2162
2187
|
"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",
|
|
2173
|
-
"composerFoot": "_5KEEsG_composerFoot",
|
|
2174
|
-
"sentChip": "_5KEEsG_sentChip",
|
|
2175
|
-
"noticeRow": "_5KEEsG_noticeRow",
|
|
2176
|
-
"stopButton": "_5KEEsG_stopButton",
|
|
2177
|
-
"stateDetail": "_5KEEsG_stateDetail",
|
|
2178
|
-
"flowRowError": "_5KEEsG_flowRowError",
|
|
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",
|
|
2186
|
-
"input": "_5KEEsG_input",
|
|
2187
|
-
"userRow": "_5KEEsG_userRow",
|
|
2188
|
-
"sidechat-pulse": "_5KEEsG_sidechat-pulse",
|
|
2189
2188
|
"empty": "_5KEEsG_empty",
|
|
2190
|
-
"
|
|
2191
|
-
"
|
|
2192
|
-
"composerCard": "_5KEEsG_composerCard",
|
|
2193
|
-
"toolError": "_5KEEsG_toolError",
|
|
2189
|
+
"dotRunning": "_5KEEsG_dotRunning",
|
|
2190
|
+
"toolBody": "_5KEEsG_toolBody",
|
|
2194
2191
|
"attachImg": "_5KEEsG_attachImg",
|
|
2195
|
-
"
|
|
2196
|
-
"
|
|
2197
|
-
"toolHead": "_5KEEsG_toolHead",
|
|
2192
|
+
"slashItem": "_5KEEsG_slashItem",
|
|
2193
|
+
"stateDetail": "_5KEEsG_stateDetail",
|
|
2198
2194
|
"sentChipRow": "_5KEEsG_sentChipRow",
|
|
2199
|
-
"
|
|
2200
|
-
"
|
|
2201
|
-
"
|
|
2202
|
-
"
|
|
2203
|
-
"modelLabel": "_5KEEsG_modelLabel",
|
|
2204
|
-
"flowRow": "_5KEEsG_flowRow",
|
|
2205
|
-
"toolBodyText": "_5KEEsG_toolBodyText",
|
|
2195
|
+
"userBubble": "_5KEEsG_userBubble",
|
|
2196
|
+
"rowPreview": "_5KEEsG_rowPreview",
|
|
2197
|
+
"reflowButton": "_5KEEsG_reflowButton",
|
|
2198
|
+
"toolLocations": "_5KEEsG_toolLocations",
|
|
2206
2199
|
"toolGenericBody": "_5KEEsG_toolGenericBody",
|
|
2207
|
-
"
|
|
2208
|
-
"
|
|
2209
|
-
"
|
|
2200
|
+
"jumpBottom": "_5KEEsG_jumpBottom",
|
|
2201
|
+
"streamingHint": "_5KEEsG_streamingHint",
|
|
2202
|
+
"pendingTitle": "_5KEEsG_pendingTitle",
|
|
2203
|
+
"inheritedBody": "_5KEEsG_inheritedBody",
|
|
2204
|
+
"pendingQHeader": "_5KEEsG_pendingQHeader",
|
|
2210
2205
|
"pendingOption": "_5KEEsG_pendingOption",
|
|
2211
|
-
"
|
|
2206
|
+
"pendingOptionActive": "_5KEEsG_pendingOptionActive",
|
|
2207
|
+
"rowActions": "_5KEEsG_rowActions",
|
|
2212
2208
|
"stateScreen": "_5KEEsG_stateScreen",
|
|
2213
|
-
"
|
|
2214
|
-
"
|
|
2209
|
+
"pendingActions": "_5KEEsG_pendingActions",
|
|
2210
|
+
"pendingBtnGhost": "_5KEEsG_pendingBtnGhost",
|
|
2215
2211
|
"actionGroup": "_5KEEsG_actionGroup",
|
|
2212
|
+
"toolHead": "_5KEEsG_toolHead",
|
|
2213
|
+
"toolBodyText": "_5KEEsG_toolBodyText",
|
|
2214
|
+
"sidechat-pulse": "_5KEEsG_sidechat-pulse",
|
|
2216
2215
|
"assistantBody": "_5KEEsG_assistantBody",
|
|
2217
|
-
"
|
|
2218
|
-
"
|
|
2219
|
-
"
|
|
2220
|
-
"pendingOptionActive": "_5KEEsG_pendingOptionActive",
|
|
2221
|
-
"parentStrip": "_5KEEsG_parentStrip",
|
|
2222
|
-
"toolRunning": "_5KEEsG_toolRunning",
|
|
2223
|
-
"modelSelect": "_5KEEsG_modelSelect",
|
|
2224
|
-
"pendingDesc": "_5KEEsG_pendingDesc",
|
|
2225
|
-
"body": "_5KEEsG_body",
|
|
2226
|
-
"densityToggle": "_5KEEsG_densityToggle",
|
|
2227
|
-
"headerSide": "_5KEEsG_headerSide",
|
|
2228
|
-
"reflowButton": "_5KEEsG_reflowButton",
|
|
2229
|
-
"emptyText": "_5KEEsG_emptyText",
|
|
2230
|
-
"densityRow": "_5KEEsG_densityRow",
|
|
2231
|
-
"jumpBottom": "_5KEEsG_jumpBottom",
|
|
2232
|
-
"inheritedBody": "_5KEEsG_inheritedBody",
|
|
2216
|
+
"todoDot": "_5KEEsG_todoDot",
|
|
2217
|
+
"todoBody": "_5KEEsG_todoBody",
|
|
2218
|
+
"toolError": "_5KEEsG_toolError",
|
|
2233
2219
|
"reasoning": "_5KEEsG_reasoning",
|
|
2234
|
-
"
|
|
2235
|
-
"
|
|
2236
|
-
"
|
|
2220
|
+
"attachThumb": "_5KEEsG_attachThumb",
|
|
2221
|
+
"attachButton": "_5KEEsG_attachButton",
|
|
2222
|
+
"densityToggle": "_5KEEsG_densityToggle",
|
|
2223
|
+
"composerFoot": "_5KEEsG_composerFoot",
|
|
2224
|
+
"stopButton": "_5KEEsG_stopButton",
|
|
2237
2225
|
"slashItemActive": "_5KEEsG_slashItemActive",
|
|
2238
2226
|
"slashMenu": "_5KEEsG_slashMenu",
|
|
2239
|
-
"
|
|
2240
|
-
"
|
|
2241
|
-
"
|
|
2242
|
-
"
|
|
2227
|
+
"writeNotice": "_5KEEsG_writeNotice",
|
|
2228
|
+
"input": "_5KEEsG_input",
|
|
2229
|
+
"densityRow": "_5KEEsG_densityRow",
|
|
2230
|
+
"composerChips": "_5KEEsG_composerChips",
|
|
2231
|
+
"sentChip": "_5KEEsG_sentChip",
|
|
2232
|
+
"pendingQuestion": "_5KEEsG_pendingQuestion",
|
|
2233
|
+
"todoText": "_5KEEsG_todoText",
|
|
2234
|
+
"pendingReason": "_5KEEsG_pendingReason",
|
|
2235
|
+
"pendingBtnPrimary": "_5KEEsG_pendingBtnPrimary",
|
|
2236
|
+
"dotIdle": "_5KEEsG_dotIdle",
|
|
2237
|
+
"assistantRow": "_5KEEsG_assistantRow",
|
|
2238
|
+
"flowRow": "_5KEEsG_flowRow",
|
|
2239
|
+
"userRow": "_5KEEsG_userRow",
|
|
2240
|
+
"toolCard": "_5KEEsG_toolCard",
|
|
2241
|
+
"errorRow": "_5KEEsG_errorRow",
|
|
2242
|
+
"noticeRow": "_5KEEsG_noticeRow",
|
|
2243
2243
|
"slashItemDetail": "_5KEEsG_slashItemDetail",
|
|
2244
|
-
"
|
|
2244
|
+
"transcript": "_5KEEsG_transcript",
|
|
2245
|
+
"toolRunning": "_5KEEsG_toolRunning",
|
|
2246
|
+
"emptyIcon": "_5KEEsG_emptyIcon",
|
|
2247
|
+
"pendingCustom": "_5KEEsG_pendingCustom",
|
|
2248
|
+
"headerSide": "_5KEEsG_headerSide",
|
|
2249
|
+
"parentStrip": "_5KEEsG_parentStrip",
|
|
2250
|
+
"modelSelect": "_5KEEsG_modelSelect",
|
|
2245
2251
|
"pendingHead": "_5KEEsG_pendingHead",
|
|
2252
|
+
"root": "_5KEEsG_root",
|
|
2253
|
+
"flowRowError": "_5KEEsG_flowRowError",
|
|
2254
|
+
"writeNoticeClose": "_5KEEsG_writeNoticeClose",
|
|
2255
|
+
"slashGroupTitle": "_5KEEsG_slashGroupTitle",
|
|
2256
|
+
"sendButton": "_5KEEsG_sendButton",
|
|
2246
2257
|
"slashItemName": "_5KEEsG_slashItemName",
|
|
2247
|
-
"
|
|
2258
|
+
"body": "_5KEEsG_body",
|
|
2259
|
+
"emptyText": "_5KEEsG_emptyText",
|
|
2260
|
+
"composer": "_5KEEsG_composer",
|
|
2261
|
+
"emptyTitle": "_5KEEsG_emptyTitle",
|
|
2262
|
+
"attachRemove": "_5KEEsG_attachRemove",
|
|
2263
|
+
"dotPending": "_5KEEsG_dotPending",
|
|
2264
|
+
"todoItem": "_5KEEsG_todoItem",
|
|
2265
|
+
"pendingQText": "_5KEEsG_pendingQText",
|
|
2266
|
+
"pendingOptionDesc": "_5KEEsG_pendingOptionDesc",
|
|
2267
|
+
"pendingDesc": "_5KEEsG_pendingDesc",
|
|
2268
|
+
"modelLabel": "_5KEEsG_modelLabel",
|
|
2269
|
+
"pendingCard": "_5KEEsG_pendingCard"
|
|
2248
2270
|
};
|
|
2249
2271
|
//#endregion
|
|
2250
2272
|
//#region src/client/chat/ToolCard.tsx
|
|
@@ -3730,7 +3752,7 @@ window.__ModuleLoader__.load({
|
|
|
3730
3752
|
requestAnimationFrame(() => {
|
|
3731
3753
|
rootRef.current?.querySelector("textarea")?.focus();
|
|
3732
3754
|
});
|
|
3733
|
-
}, () => metaRef.current);
|
|
3755
|
+
}, () => metaRef.current, () => titleRef.current);
|
|
3734
3756
|
return () => {
|
|
3735
3757
|
dispose();
|
|
3736
3758
|
if (nativeSidebarHost(ctx)) {
|
|
@@ -3973,18 +3995,28 @@ window.__ModuleLoader__.load({
|
|
|
3973
3995
|
ui: {
|
|
3974
3996
|
kind: "popupSelect",
|
|
3975
3997
|
options: (session) => {
|
|
3976
|
-
const options = [
|
|
3998
|
+
const options = [];
|
|
3999
|
+
const native = nativeSidebarHost(ctx);
|
|
4000
|
+
const lives = native ? liveSideChatsOf(session.sessionId) : [];
|
|
4001
|
+
if (!native || lives.length === 0) options.push({
|
|
3977
4002
|
id: "new",
|
|
3978
4003
|
label: t("cmdNew"),
|
|
3979
4004
|
detail: t("cmdNewDetail")
|
|
3980
|
-
}
|
|
3981
|
-
const
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
label: t("cmdFocus", { title: tab.title }),
|
|
4005
|
+
});
|
|
4006
|
+
if (native) for (const live of lives) options.push({
|
|
4007
|
+
id: `focus:${live.tabId}`,
|
|
4008
|
+
label: t("cmdFocus", { title: live.readTitle() }),
|
|
3985
4009
|
detail: t("cmdFocusDetail")
|
|
3986
4010
|
});
|
|
3987
|
-
|
|
4011
|
+
else {
|
|
4012
|
+
const snapshot = ctx.betterSidebar.getSnapshot();
|
|
4013
|
+
if (snapshot.sessionId === session.sessionId && snapshot.state !== void 0) for (const tab of collectSideTabs(snapshot.state)) options.push({
|
|
4014
|
+
id: `focus:${tab.id}`,
|
|
4015
|
+
label: t("cmdFocus", { title: tab.title }),
|
|
4016
|
+
detail: t("cmdFocusDetail")
|
|
4017
|
+
});
|
|
4018
|
+
}
|
|
4019
|
+
if (!(native && lives.length > 0)) for (const entry of listClosedSideChats(session.sessionId)) options.push({
|
|
3988
4020
|
id: `reopen:${entry.childId}`,
|
|
3989
4021
|
label: t("cmdReopen", { title: entry.title }),
|
|
3990
4022
|
detail: t("cmdReopenDetail")
|
|
@@ -3997,7 +4029,8 @@ window.__ModuleLoader__.load({
|
|
|
3997
4029
|
return;
|
|
3998
4030
|
}
|
|
3999
4031
|
if (option.id.startsWith("focus:")) {
|
|
4000
|
-
|
|
4032
|
+
const tabId = option.id.slice(6);
|
|
4033
|
+
if (!focusNativeTab(ctx, tabId)) ctx.betterSidebar.activateTab(tabId, { sessionId: session.sessionId });
|
|
4001
4034
|
return;
|
|
4002
4035
|
}
|
|
4003
4036
|
if (option.id.startsWith("reopen:")) {
|
|
@@ -4183,11 +4216,18 @@ window.__ModuleLoader__.load({
|
|
|
4183
4216
|
icon: (size) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutline16, { size }),
|
|
4184
4217
|
order: 60,
|
|
4185
4218
|
available: (availableCtx, scope) => canForkFrom(availableCtx, scope.sessionId),
|
|
4186
|
-
createTab: (state) =>
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4219
|
+
createTab: (state) => {
|
|
4220
|
+
const titles = collectSideTabs(state).map((tab) => tab.title);
|
|
4221
|
+
if (nativeSidebarHost(ctx)) {
|
|
4222
|
+
const current = ctx.sessions.list.getSnapshot().current;
|
|
4223
|
+
if (current !== void 0) for (const live of liveSideChatsOf(current)) titles.push(live.readTitle());
|
|
4224
|
+
}
|
|
4225
|
+
return { tab: {
|
|
4226
|
+
id: mintSideTabId(),
|
|
4227
|
+
type: SIDE_TAB_TYPE,
|
|
4228
|
+
title: sideTabTitle(titles)
|
|
4229
|
+
} };
|
|
4230
|
+
},
|
|
4191
4231
|
onClose: (closedTab) => {
|
|
4192
4232
|
const meta = parseSideChatMeta(closedTab.meta);
|
|
4193
4233
|
if (meta.childId !== void 0 && meta.parentSessionId !== void 0) recordClosedSideChat(meta.parentSessionId, {
|
|
@@ -4669,43 +4709,43 @@ window.__ModuleLoader__.load({
|
|
|
4669
4709
|
document.head.appendChild(tag);
|
|
4670
4710
|
}
|
|
4671
4711
|
var annotate_module_css_default = {
|
|
4672
|
-
"
|
|
4673
|
-
"chipText": "_SVViG_chipText",
|
|
4674
|
-
"toolbarBelow": "_SVViG_toolbarBelow",
|
|
4675
|
-
"sentCardTitle": "_SVViG_sentCardTitle",
|
|
4712
|
+
"badgeSent": "_SVViG_badgeSent",
|
|
4676
4713
|
"chipClearAll": "_SVViG_chipClearAll",
|
|
4677
|
-
"chip": "_SVViG_chip",
|
|
4678
4714
|
"highlight": "_SVViG_highlight",
|
|
4679
|
-
"chipNote": "_SVViG_chipNote",
|
|
4680
4715
|
"chipRemove": "_SVViG_chipRemove",
|
|
4681
|
-
"editorEdit": "_SVViG_editorEdit",
|
|
4682
|
-
"sentCardNote": "_SVViG_sentCardNote",
|
|
4683
|
-
"editorFooter": "_SVViG_editorFooter",
|
|
4684
|
-
"toolbar": "_SVViG_toolbar",
|
|
4685
4716
|
"dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
|
|
4717
|
+
"editorNew": "_SVViG_editorNew",
|
|
4718
|
+
"chipRow": "_SVViG_chipRow",
|
|
4719
|
+
"editorTextarea": "_SVViG_editorTextarea",
|
|
4720
|
+
"editorAsk": "_SVViG_editorAsk",
|
|
4721
|
+
"chipNote": "_SVViG_chipNote",
|
|
4722
|
+
"chip": "_SVViG_chip",
|
|
4723
|
+
"chipText": "_SVViG_chipText",
|
|
4724
|
+
"chipPanel": "_SVViG_chipPanel",
|
|
4725
|
+
"editorError": "_SVViG_editorError",
|
|
4726
|
+
"toolbarBelow": "_SVViG_toolbarBelow",
|
|
4686
4727
|
"sentCardQuote": "_SVViG_sentCardQuote",
|
|
4687
|
-
"
|
|
4728
|
+
"hint": "_SVViG_hint",
|
|
4729
|
+
"sentChipRow": "_SVViG_sentChipRow",
|
|
4688
4730
|
"deleteButton": "_SVViG_deleteButton",
|
|
4689
|
-
"
|
|
4731
|
+
"sentCardNote": "_SVViG_sentCardNote",
|
|
4690
4732
|
"confirmButton": "_SVViG_confirmButton",
|
|
4691
|
-
"saveButton": "_SVViG_saveButton",
|
|
4692
|
-
"badgeSent": "_SVViG_badgeSent",
|
|
4693
|
-
"sentChip": "_SVViG_sentChip",
|
|
4694
|
-
"chipNumber": "_SVViG_chipNumber",
|
|
4695
|
-
"hint": "_SVViG_hint",
|
|
4696
|
-
"hintClose": "_SVViG_hintClose",
|
|
4697
|
-
"editorTarget": "_SVViG_editorTarget",
|
|
4698
|
-
"chipWrap": "_SVViG_chipWrap",
|
|
4699
4733
|
"cancelButton": "_SVViG_cancelButton",
|
|
4700
|
-
"
|
|
4734
|
+
"editorFooter": "_SVViG_editorFooter",
|
|
4735
|
+
"sentCardTitle": "_SVViG_sentCardTitle",
|
|
4736
|
+
"badge": "_SVViG_badge",
|
|
4737
|
+
"editorTarget": "_SVViG_editorTarget",
|
|
4738
|
+
"chipNumber": "_SVViG_chipNumber",
|
|
4701
4739
|
"editorAskRow": "_SVViG_editorAskRow",
|
|
4702
|
-
"
|
|
4703
|
-
"
|
|
4704
|
-
"
|
|
4705
|
-
"
|
|
4706
|
-
"chipPanel": "_SVViG_chipPanel",
|
|
4740
|
+
"editorSpacer": "_SVViG_editorSpacer",
|
|
4741
|
+
"hintClose": "_SVViG_hintClose",
|
|
4742
|
+
"editorInput": "_SVViG_editorInput",
|
|
4743
|
+
"saveButton": "_SVViG_saveButton",
|
|
4707
4744
|
"toolbarButton": "_SVViG_toolbarButton",
|
|
4708
|
-
"
|
|
4745
|
+
"editorEdit": "_SVViG_editorEdit",
|
|
4746
|
+
"chipWrap": "_SVViG_chipWrap",
|
|
4747
|
+
"sentChip": "_SVViG_sentChip",
|
|
4748
|
+
"toolbar": "_SVViG_toolbar"
|
|
4709
4749
|
};
|
|
4710
4750
|
//#endregion
|
|
4711
4751
|
//#region src/client/annotate/hint.tsx
|
|
@@ -5817,7 +5857,12 @@ window.__ModuleLoader__.load({
|
|
|
5817
5857
|
"locale"
|
|
5818
5858
|
];
|
|
5819
5859
|
function apply(ctx) {
|
|
5820
|
-
|
|
5860
|
+
ctx.effect(() => {
|
|
5861
|
+
setRootContext(ctx);
|
|
5862
|
+
return () => {
|
|
5863
|
+
clearNativeRuntime();
|
|
5864
|
+
};
|
|
5865
|
+
}, "dsh-sidenote: native bridge");
|
|
5821
5866
|
attachLocale(ctx.locale);
|
|
5822
5867
|
probeHost(ctx);
|
|
5823
5868
|
const reflow = createReflowStore();
|
|
@@ -38,12 +38,14 @@ interface LiveSideChat {
|
|
|
38
38
|
seedDraft(text: string): void;
|
|
39
39
|
/** Current tab meta, read at call time (the panel re-renders on meta change). */
|
|
40
40
|
readMeta(): unknown;
|
|
41
|
+
/** Current tab title(聚焦列表/编号数据源的读取面)。 */
|
|
42
|
+
readTitle(): string;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* Publish a mounted panel so programmatic entry points can focus it and seed
|
|
44
46
|
* its draft. Returns the disposer for the panel's effect cleanup.
|
|
45
47
|
*/
|
|
46
|
-
export declare function registerLiveSideChat(sessionId: string, tabId: string, seedDraft: (text: string) => void, readMeta: () => unknown): () => void;
|
|
48
|
+
export declare function registerLiveSideChat(sessionId: string, tabId: string, seedDraft: (text: string) => void, readMeta: () => unknown, readTitle: () => string): () => void;
|
|
47
49
|
interface OpeningMark {
|
|
48
50
|
readonly at: number;
|
|
49
51
|
/** 窗口内到达的待投递草稿(面板注册时经 seedDraft 回放)。 */
|
|
@@ -55,11 +57,16 @@ export declare function markSideChatOpening(sessionId: string): void;
|
|
|
55
57
|
export declare function sideChatOpening(sessionId: string): OpeningMark | undefined;
|
|
56
58
|
/** Live panel for a native tab id (undefined for legacy layout tabs). */
|
|
57
59
|
export declare function liveSideChat(tabId: string): LiveSideChat | undefined;
|
|
60
|
+
/** 一个会话的全部存活面板(按打开序升序;native 下枚举 side tab 的唯一数据源)。 */
|
|
61
|
+
export declare function liveSideChatsOf(sessionId: string): LiveSideChat[];
|
|
58
62
|
/**
|
|
59
|
-
* Most recently
|
|
63
|
+
* Most recently OPENED live panel of a session — the open-or-focus target
|
|
60
64
|
* when the legacy snapshot cannot see the tab (mirrors "last opened wins").
|
|
65
|
+
* 排序锚在首次注册序号上:面板重挂载不翻序(见 birthOrder)。
|
|
61
66
|
*/
|
|
62
67
|
export declare function lastLiveSideChat(sessionId: string): LiveSideChat | undefined;
|
|
68
|
+
/** 插件卸载/重载时清场(apply 的 ctx.effect disposer 调用)。 */
|
|
69
|
+
export declare function clearNativeRuntime(): void;
|
|
63
70
|
/** Native tab shell for reads that go through the layout snapshot (`readTab`). */
|
|
64
71
|
export declare function nativeTabShell(tabId: string, type: string): {
|
|
65
72
|
id: string;
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* /side 斜杠命令(spike 落点):经 client 侧 `ctx.commandUi.register`
|
|
3
3
|
* (dsh-client-ui-commands 的 CommandContribution,ui 形态 popupSelect)
|
|
4
4
|
* 注册。popupSelect 是该注册面唯一的 UI 形态 —— 菜单行被选中后弹一个
|
|
5
|
-
* 选项壳,选项 = 新建 +
|
|
5
|
+
* 选项壳,选项 = 新建 + 当前会话已并存的侧边聊天(聚焦)+ 最近关闭(重开)。
|
|
6
|
+
* native 宿主(better-sidebar >= 0.19)受 dsh sidebar-right 的 held 规则约束
|
|
7
|
+
* (page kind 每 pane 单实例):有存活实例时不列「新建」与「重开」。
|
|
6
8
|
*
|
|
7
9
|
* 服务经 ctx.get 惰性解析(commandUi 不在 inject 清单里):服务缺失、
|
|
8
10
|
* 注册抛错(如与 host 命令撞名)都降级为「只有 Tab 入口」,绝不影响面板。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-sidenote",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "DSH web plugin: Codex-style side chat (fork the current session into a persistent side panel) + selection annotations with numbered badges and a composer chip. A thin consumer of dsh-better-sidebar. / Codex 风格侧边聊天 + 划选注释的 DSH 插件。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/client/index.tsx
CHANGED
|
@@ -13,14 +13,18 @@ import { attachLocale, type LocaleServiceLike } from './locales.ts'
|
|
|
13
13
|
import { createReflowStore } from './reflow.ts'
|
|
14
14
|
import { registerSideChat } from './sidechat/index.tsx'
|
|
15
15
|
import { registerAnnotations } from './annotate/index.tsx'
|
|
16
|
-
import { setRootContext } from './sidechat/native.ts'
|
|
16
|
+
import { clearNativeRuntime, setRootContext } from './sidechat/native.ts'
|
|
17
17
|
|
|
18
18
|
export const inject = ['betterSidebar', 'sessions', 'workspaces', 'slots', 'connection', 'locale']
|
|
19
19
|
|
|
20
20
|
export function apply(ctx: Context): void {
|
|
21
21
|
// 侧栏 Tab 的原生面(better-sidebar >= 0.19)与宿主调用都要用插件根 ctx:
|
|
22
22
|
// 面板拿到的是 slot 注入的收缩 ctx(inject 里没有 workspaces),见 native.ts。
|
|
23
|
-
|
|
23
|
+
// fiber 撤销即清场(registry/openings/birthOrder 全模块级,重激活不得带病存活)。
|
|
24
|
+
ctx.effect(() => {
|
|
25
|
+
setRootContext(ctx)
|
|
26
|
+
return () => { clearNativeRuntime() }
|
|
27
|
+
}, 'dsh-sidenote: native bridge')
|
|
24
28
|
// 跟随 DSH 通用设置里的语言(locale.preference,Host-backed,实时切换)。
|
|
25
29
|
attachLocale(ctx.locale as LocaleServiceLike | undefined)
|
|
26
30
|
// T2 off-face 全景探测:宿主升级后哪些能力降级了,开发者工具里一眼可见。
|
|
@@ -222,6 +222,7 @@ export function SideChatPanel(props: TabComponentProps & { reflow: ReflowStore }
|
|
|
222
222
|
requestAnimationFrame(() => { rootRef.current?.querySelector('textarea')?.focus() })
|
|
223
223
|
},
|
|
224
224
|
() => metaRef.current,
|
|
225
|
+
() => titleRef.current,
|
|
225
226
|
)
|
|
226
227
|
return () => {
|
|
227
228
|
dispose()
|
|
@@ -23,6 +23,7 @@ import { SideChatPanel } from './SideChatPanel.tsx'
|
|
|
23
23
|
import { SIDE_TAB_TYPE, canForkFrom, collectSideTabs, mintSideTabId, sideTabTitle } from './model.ts'
|
|
24
24
|
import { parseSideChatMeta } from './model.ts'
|
|
25
25
|
import { recordClosedSideChat } from './recentClosed.ts'
|
|
26
|
+
import { liveSideChatsOf, nativeSidebarHost } from './native.ts'
|
|
26
27
|
import { openOrFocusSideChat, sideChatTargetTitle } from './open.ts'
|
|
27
28
|
import { t } from '../locales.ts'
|
|
28
29
|
import { registerHeaderEntry } from './header.tsx'
|
|
@@ -39,9 +40,18 @@ export function registerSideChat(ctx: Context, reflow: ReflowStore): void {
|
|
|
39
40
|
icon: (size: number) => <IconNewChatOutline16 size={size} />,
|
|
40
41
|
order: 60,
|
|
41
42
|
available: (availableCtx, scope) => canForkFrom(availableCtx, scope.sessionId),
|
|
42
|
-
createTab: (state) =>
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
createTab: (state) => {
|
|
44
|
+
// 标题编号数据源:legacy 读布局快照;native 的 tab 不进快照,并入
|
|
45
|
+
// live registry(当前会话——openTab 的会话守卫保证 state 属在屏会话)。
|
|
46
|
+
const titles = collectSideTabs(state).map(tab => tab.title)
|
|
47
|
+
if (nativeSidebarHost(ctx)) {
|
|
48
|
+
const current = ctx.sessions.list.getSnapshot().current
|
|
49
|
+
if (current !== undefined) {
|
|
50
|
+
for (const live of liveSideChatsOf(current)) titles.push(live.readTitle())
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { tab: { id: mintSideTabId(), type: SIDE_TAB_TYPE, title: sideTabTitle(titles) } }
|
|
54
|
+
},
|
|
45
55
|
// × 即焚观感 + 后悔药:关 Tab 时登记「最近关闭」(会话本体仍归档在盘)。
|
|
46
56
|
onClose: (closedTab) => {
|
|
47
57
|
const meta = parseSideChatMeta(closedTab.meta)
|
|
@@ -58,11 +58,20 @@ interface LiveSideChat {
|
|
|
58
58
|
seedDraft(text: string): void
|
|
59
59
|
/** Current tab meta, read at call time (the panel re-renders on meta change). */
|
|
60
60
|
readMeta(): unknown
|
|
61
|
+
/** Current tab title(聚焦列表/编号数据源的读取面)。 */
|
|
62
|
+
readTitle(): string
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
/** Native tab id → live panel. Native layouts are memory-only, so is this. */
|
|
64
66
|
const liveSideChats = new Map<string, LiveSideChat>()
|
|
65
67
|
|
|
68
|
+
/**
|
|
69
|
+
* tabId → 首次注册序号(打开序的近似)。tab.id 跨重挂载稳定,重注册不改写
|
|
70
|
+
* ——遍历 Map 的插入序会随重挂漂移,「最近打开」只能锚在首次序号上。
|
|
71
|
+
*/
|
|
72
|
+
const birthOrder = new Map<string, number>()
|
|
73
|
+
let birthSeq = 0
|
|
74
|
+
|
|
66
75
|
/**
|
|
67
76
|
* Publish a mounted panel so programmatic entry points can focus it and seed
|
|
68
77
|
* its draft. Returns the disposer for the panel's effect cleanup.
|
|
@@ -72,9 +81,11 @@ export function registerLiveSideChat(
|
|
|
72
81
|
tabId: string,
|
|
73
82
|
seedDraft: (text: string) => void,
|
|
74
83
|
readMeta: () => unknown,
|
|
84
|
+
readTitle: () => string,
|
|
75
85
|
): () => void {
|
|
76
|
-
const entry: LiveSideChat = { tabId, sessionId, seedDraft, readMeta }
|
|
86
|
+
const entry: LiveSideChat = { tabId, sessionId, seedDraft, readMeta, readTitle }
|
|
77
87
|
liveSideChats.set(tabId, entry)
|
|
88
|
+
if (!birthOrder.has(tabId)) birthOrder.set(tabId, ++birthSeq)
|
|
78
89
|
// 挂载即回放在 openTab→注册窗口内暂存的草稿(seedDraft 内有相位门)。
|
|
79
90
|
const opening = openings.get(sessionId)
|
|
80
91
|
if (opening !== undefined) {
|
|
@@ -123,16 +134,29 @@ export function liveSideChat(tabId: string): LiveSideChat | undefined {
|
|
|
123
134
|
return liveSideChats.get(tabId)
|
|
124
135
|
}
|
|
125
136
|
|
|
137
|
+
/** 一个会话的全部存活面板(按打开序升序;native 下枚举 side tab 的唯一数据源)。 */
|
|
138
|
+
export function liveSideChatsOf(sessionId: string): LiveSideChat[] {
|
|
139
|
+
return [...liveSideChats.values()]
|
|
140
|
+
.filter(entry => entry.sessionId === sessionId)
|
|
141
|
+
.sort((a, b) => (birthOrder.get(a.tabId) ?? 0) - (birthOrder.get(b.tabId) ?? 0))
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
/**
|
|
127
|
-
* Most recently
|
|
145
|
+
* Most recently OPENED live panel of a session — the open-or-focus target
|
|
128
146
|
* when the legacy snapshot cannot see the tab (mirrors "last opened wins").
|
|
147
|
+
* 排序锚在首次注册序号上:面板重挂载不翻序(见 birthOrder)。
|
|
129
148
|
*/
|
|
130
149
|
export function lastLiveSideChat(sessionId: string): LiveSideChat | undefined {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
150
|
+
const all = liveSideChatsOf(sessionId)
|
|
151
|
+
return all[all.length - 1]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 插件卸载/重载时清场(apply 的 ctx.effect disposer 调用)。 */
|
|
155
|
+
export function clearNativeRuntime(): void {
|
|
156
|
+
root = undefined
|
|
157
|
+
liveSideChats.clear()
|
|
158
|
+
openings.clear()
|
|
159
|
+
birthOrder.clear()
|
|
136
160
|
}
|
|
137
161
|
|
|
138
162
|
/** Native tab shell for reads that go through the layout snapshot (`readTab`). */
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
sideTabTitle,
|
|
17
17
|
} from './model.ts'
|
|
18
18
|
import { readInputDraft, resolveSessionInput } from './composer.ts'
|
|
19
|
-
import { focusNativeTab, lastLiveSideChat, markSideChatOpening, nativeSidebarHost, nativeTabShell, sideChatOpening } from './native.ts'
|
|
19
|
+
import { focusNativeTab, lastLiveSideChat, liveSideChatsOf, markSideChatOpening, nativeSidebarHost, nativeTabShell, sideChatOpening } from './native.ts'
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* 从最新快照读一个 Tab(meta 合并写入前的读取面;布局即注册表)。
|
|
@@ -133,6 +133,9 @@ export function sideChatTargetTitle(ctx: Context, sessionId: string): string | u
|
|
|
133
133
|
if (snapshot.sessionId !== sessionId || snapshot.state === undefined) return undefined
|
|
134
134
|
const existing = collectSideTabs(snapshot.state)
|
|
135
135
|
if (existing.length > 0) return existing[existing.length - 1]!.title
|
|
136
|
+
// native:布局快照看不到 tab,改读 live registry(最近打开者)。
|
|
137
|
+
const live = lastLiveSideChat(sessionId)
|
|
138
|
+
if (live !== undefined) return live.readTitle()
|
|
136
139
|
return sideTabTitle([])
|
|
137
140
|
} catch {
|
|
138
141
|
return undefined
|
|
@@ -150,6 +153,10 @@ export function reopenSideChat(ctx: Context, sessionId: string, childId: string,
|
|
|
150
153
|
const snapshot = ctx.betterSidebar.getSnapshot()
|
|
151
154
|
if (snapshot.sessionId !== sessionId || snapshot.state === undefined) return false
|
|
152
155
|
if (!ctx.betterSidebar.isTabEnabled(SIDE_TAB_TYPE)) return false
|
|
156
|
+
// native 下同 kind 每 pane 单实例(dsh sidebar-right held 规则):有存活
|
|
157
|
+
// 实例时 openTab 必折叠为聚焦既有 tab 且 seed.meta 被丢弃——拒绝(弹层
|
|
158
|
+
// 侧此时本就不列重开项,这里是双保险)。
|
|
159
|
+
if (nativeSidebarHost(ctx) && liveSideChatsOf(sessionId).length > 0) return false
|
|
153
160
|
const before = new Set(collectTabs(snapshot.state).map(tab => tab.id))
|
|
154
161
|
// native(>= 0.19):seed.meta 携带恢复信息(native 面采纳 seed.meta;
|
|
155
162
|
// 面板挂载即走绑定恢复路径)。legacy 的 createTab 铸造忽略 seed.meta,
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* /side 斜杠命令(spike 落点):经 client 侧 `ctx.commandUi.register`
|
|
3
3
|
* (dsh-client-ui-commands 的 CommandContribution,ui 形态 popupSelect)
|
|
4
4
|
* 注册。popupSelect 是该注册面唯一的 UI 形态 —— 菜单行被选中后弹一个
|
|
5
|
-
* 选项壳,选项 = 新建 +
|
|
5
|
+
* 选项壳,选项 = 新建 + 当前会话已并存的侧边聊天(聚焦)+ 最近关闭(重开)。
|
|
6
|
+
* native 宿主(better-sidebar >= 0.19)受 dsh sidebar-right 的 held 规则约束
|
|
7
|
+
* (page kind 每 pane 单实例):有存活实例时不列「新建」与「重开」。
|
|
6
8
|
*
|
|
7
9
|
* 服务经 ctx.get 惰性解析(commandUi 不在 inject 清单里):服务缺失、
|
|
8
10
|
* 注册抛错(如与 host 命令撞名)都降级为「只有 Tab 入口」,绝不影响面板。
|
|
@@ -12,6 +14,7 @@
|
|
|
12
14
|
*/
|
|
13
15
|
import type { Context } from '../host/contracts.ts'
|
|
14
16
|
import { canForkFrom, collectSideTabs } from './model.ts'
|
|
17
|
+
import { focusNativeTab, liveSideChatsOf, nativeSidebarHost } from './native.ts'
|
|
15
18
|
import { createSideChat, reopenSideChat } from './open.ts'
|
|
16
19
|
import { dropClosedSideChat, listClosedSideChats } from './recentClosed.ts'
|
|
17
20
|
import { t } from '../locales.ts'
|
|
@@ -61,19 +64,37 @@ function makeContribution(ctx: Context, name: string) {
|
|
|
61
64
|
ui: {
|
|
62
65
|
kind: 'popupSelect' as const,
|
|
63
66
|
options: (session: CommandSession) => {
|
|
64
|
-
const options: SelectOption[] = [
|
|
65
|
-
|
|
66
|
-
]
|
|
67
|
+
const options: SelectOption[] = []
|
|
68
|
+
const native = nativeSidebarHost(ctx)
|
|
69
|
+
const lives = native ? liveSideChatsOf(session.sessionId) : []
|
|
70
|
+
// native 下同 kind 每 pane 单实例(dsh-client-ui-sidebar-right 的 held
|
|
71
|
+
// 规则:page kind 的 contentId 恒为 sidebar://<kind>,openTab 必折叠为
|
|
72
|
+
// 聚焦既有 tab)——已有存活实例时「新建」名不副实,不列。
|
|
73
|
+
if (!native || lives.length === 0) {
|
|
74
|
+
options.push({ id: 'new', label: t('cmdNew'), detail: t('cmdNewDetail') })
|
|
75
|
+
}
|
|
67
76
|
// 已并存的侧边聊天列为聚焦项(命令弹层即多实例管理入口)。
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
// native(>= 0.19):tab 不进布局快照,枚举改读 live registry;
|
|
78
|
+
// legacy:读布局快照(现状)。
|
|
79
|
+
if (native) {
|
|
80
|
+
for (const live of lives) {
|
|
81
|
+
options.push({ id: `focus:${live.tabId}`, label: t('cmdFocus', { title: live.readTitle() }), detail: t('cmdFocusDetail') })
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
const snapshot = ctx.betterSidebar.getSnapshot()
|
|
85
|
+
if (snapshot.sessionId === session.sessionId && snapshot.state !== undefined) {
|
|
86
|
+
for (const tab of collectSideTabs(snapshot.state)) {
|
|
87
|
+
options.push({ id: `focus:${tab.id}`, label: t('cmdFocus', { title: tab.title }), detail: t('cmdFocusDetail') })
|
|
88
|
+
}
|
|
72
89
|
}
|
|
73
90
|
}
|
|
74
91
|
// D3 后悔药:最近关闭的可重开(Cmd+Shift+T 心智)。
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
// native 单实例约束下,有存活实例时 reopen 必被 held 折叠(seed.meta
|
|
93
|
+
// 丢弃且聚焦错对象)——此时不列重开项。
|
|
94
|
+
if (!(native && lives.length > 0)) {
|
|
95
|
+
for (const entry of listClosedSideChats(session.sessionId)) {
|
|
96
|
+
options.push({ id: `reopen:${entry.childId}`, label: t('cmdReopen', { title: entry.title }), detail: t('cmdReopenDetail') })
|
|
97
|
+
}
|
|
77
98
|
}
|
|
78
99
|
return Promise.resolve(options)
|
|
79
100
|
},
|
|
@@ -85,7 +106,9 @@ function makeContribution(ctx: Context, name: string) {
|
|
|
85
106
|
return
|
|
86
107
|
}
|
|
87
108
|
if (option.id.startsWith('focus:')) {
|
|
88
|
-
|
|
109
|
+
const tabId = option.id.slice('focus:'.length)
|
|
110
|
+
// native 宿主的 activateTab 是空操作——聚焦走 ISidebarRight.focus 探测。
|
|
111
|
+
if (!focusNativeTab(ctx, tabId)) ctx.betterSidebar.activateTab(tabId, { sessionId: session.sessionId })
|
|
89
112
|
return
|
|
90
113
|
}
|
|
91
114
|
if (option.id.startsWith('reopen:')) {
|