dsh-plugin-tool-management 0.21.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +5 -4
- package/README_EN.md +5 -4
- package/docs/images/icon.png +0 -0
- package/docs/images/icon.svg +1 -0
- package/docs/update.md +1 -0
- package/lib/client.js +718 -148
- package/lib/context-inject-frame.js +36 -5
- package/lib/context-inject.js +1 -1
- package/lib/index.js +29 -5
- package/lib/memories/snapshot.js +17 -9
- package/lib/op-registry.js +3 -0
- package/lib/ops/trash.js +12 -1
- package/lib/settings-sidecars.js +37 -0
- package/lib/sidebar-settings.js +13 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +4 -1
package/lib/client.js
CHANGED
|
@@ -110,7 +110,11 @@ window.__ModuleLoader__.load({
|
|
|
110
110
|
.dsm-btn.dsm-btn-icon{width:24px;min-width:24px;min-height:24px;padding:0}
|
|
111
111
|
/* 目录卡片右侧动作组:间距收窄一档,配合图标化后不再显挤。 */
|
|
112
112
|
/* 卡片头里的标签抬到 11px:左侧「N 个技能」是 12px,10px 的标签在这里显得忽大忽小。 */
|
|
113
|
-
.dsm-source-head .dsm-tag{font-size:12px;min-height:20px;padding:0 8px}
|
|
113
|
+
.dsm-source-head .dsm-tag{font-size:12px;line-height:18px;min-height:20px;padding:0 8px}
|
|
114
|
+
/* 卡头里的按钮与标题同用 18px 行高:字号不同(11/12/13px)时只靠居中,文字基线会各差
|
|
115
|
+
一两像素,一行里看着"没对齐"(用户 2026-10-18)。 */
|
|
116
|
+
.dsm-source-head .dsm-btn{line-height:18px}
|
|
117
|
+
.dsm-path,.dsm-count,.dsm-source-title{line-height:18px}
|
|
114
118
|
/* 「N 个附件」标签:和 tag 同形,但它是**能点的**(点了去编辑弹窗的附件区),
|
|
115
119
|
所以要把 button 的默认外观抹掉,并给一个可点的悬停反馈 —— 光靠颜色区分不出"能不能点"。 */
|
|
116
120
|
button.dsm-tag{background:transparent;font:inherit;font-size:12px;cursor:pointer}
|
|
@@ -133,6 +137,10 @@ button.dsm-qty:disabled{cursor:default}
|
|
|
133
137
|
淡到读不出的话,用户只会以为这两颗按钮没了。危险项只有红字、没有红框 —— 边框是"常驻控件"
|
|
134
138
|
的语言,收在菜单里的动作不配再占一份视觉重量。 */
|
|
135
139
|
.dsm-rowmenu{display:flex;flex:none;align-items:center}
|
|
140
|
+
/* 场景卡里那颗 ⋯ 与开关同高(用户 2026-10-18:「场景的 switch 按键和展开三个点按钮要
|
|
141
|
+
上下居中」):⋯ 自身是 26×24、开关 36×20,两个盒子的中线本来就都在卡片的居中线上,
|
|
142
|
+
差的是 ⋯ 那 24px 的高度把行撑得比开关高一点点 —— 给它固定 20px 高、图标靠行盒居中。 */
|
|
143
|
+
.dsm-scene-tile .dsm-rowmenu-btn{height:20px}
|
|
136
144
|
.dsm-rowmenu-btn{display:inline-flex;width:26px;height:24px;align-items:center;justify-content:center;padding:0;border:0;border-radius:var(--dsm-radius-s);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
137
145
|
.dsm-rowmenu-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
138
146
|
.dsm-rowmenu-btn:disabled{opacity:.45;cursor:default}
|
|
@@ -143,7 +151,7 @@ button.dsm-qty:disabled{cursor:default}
|
|
|
143
151
|
.dsm-select-menu .dsm-option-danger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
|
|
144
152
|
/* 未启用场景卡里的开关跟着灰(.dsm-rule-off 已把名称与描述压成弱色,原先漏了开关):
|
|
145
153
|
整卡置灰却留一颗亮绿,读着像"开着却没用",两条口径打架。 */
|
|
146
|
-
.dsm-rule-off .dsm-switch-
|
|
154
|
+
.dsm-rule-off .dsm-switch[aria-checked=true]{background:var(--dsw-alias-border-l3)}
|
|
147
155
|
/* 按钮的"档位"里包含尺寸,别把它当纯修饰色(REDESIGN §5,2026-10-11 切片五定稿):
|
|
148
156
|
.dsm-btn 实心 30px(本屏主操作)/ .dsm-btn-secondary 描边 30px(常规动作)/
|
|
149
157
|
.dsm-btn-quiet 描边 24px(次要动作;叠 -danger 即 quiet-danger)。
|
|
@@ -175,7 +183,10 @@ button.dsm-qty:disabled{cursor:default}
|
|
|
175
183
|
省下的 60px 全部归搜索框(它 flex:1)。 */
|
|
176
184
|
.dsm-filters{display:flex;gap:8px}.dsm-search{flex:1}.dsm-source-filter{width:150px;flex:none}
|
|
177
185
|
.dsm-control,.dsm-select-trigger{box-sizing:border-box;width:100%;min-height:30px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}.dsm-control::placeholder{color:var(--dsw-alias-label-tertiary)}textarea.dsm-control{min-height:160px;padding-top:8px;resize:vertical;line-height:20px}.dsm-select{position:relative}.dsm-select-trigger{display:flex;align-items:center;justify-content:space-between;text-align:left;cursor:pointer}.dsm-select-menu{z-index:40;display:flex;max-height:260px;padding:4px;overflow:auto;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv2)}.dsm-option{padding:8px;border:0;border-radius:var(--dsm-radius-s);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;text-align:left;cursor:pointer}.dsm-option:hover,.dsm-option[aria-selected=true]{background:var(--dsw-alias-interactive-bg-hover)}
|
|
178
|
-
.dsm-sources{display:flex;flex-direction:column;gap:8px}.dsm-source{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-2)}.dsm-source-head{box-sizing:border-box;display:flex;width:100%;min-height:48px;align-items:center;padding:0 12px}.dsm-source-head-main{display:flex;min-width:0;min-height:48px;flex:1;align-items:center;gap:8px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.dsm-source-head:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-source-title{font-size:13px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsm-count{color:var(--dsw-alias-label-tertiary);font-size:12px;font-variant-numeric:tabular-nums;flex:none}.dsm-path{min-width:0;
|
|
186
|
+
.dsm-sources{display:flex;flex-direction:column;gap:8px}.dsm-source{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-2)}.dsm-source-head{box-sizing:border-box;display:flex;width:100%;min-height:48px;align-items:center;padding:0 12px}.dsm-source-head-main{display:flex;min-width:0;min-height:48px;flex:1;flex-wrap:wrap;align-items:center;align-content:center;gap:3px 8px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.dsm-source-head:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-source-title{font-size:13px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsm-count{color:var(--dsw-alias-label-tertiary);font-size:12px;font-variant-numeric:tabular-nums;flex:none}.dsm-path{min-width:0;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:11px;text-overflow:ellipsis;white-space:nowrap}/* 目录挪到标题下面一行、与标题左端对齐(用户 2026-10-18:「应该开头对齐……放在标题下面
|
|
187
|
+
是不是更好」):行内右对齐时它的位置随右侧按钮多少而变,几行摞起来参差;单独占一行后
|
|
188
|
+
每条路径都从同一个 x 起头,也读得下更长的目录。width:100% 负责强制换行。 */
|
|
189
|
+
.dsm-source-head-main>.dsm-path{flex:none;width:100%;margin-left:0;order:99}.dsm-source-actions{display:flex;flex:none;align-items:center;gap:4px;margin-left:8px}.dsm-source-body{border-top:1px solid var(--dsw-alias-border-l1)}
|
|
179
190
|
/* 计数已经改成「启用/总数」这种自带括号感的写法,卡头 8px 的间距把它推太远,读起来像
|
|
180
191
|
另一栏;贴回名称后面(用户 2026-10-16)。 */
|
|
181
192
|
.dsm-count-tight{margin-left:-4px}
|
|
@@ -198,7 +209,19 @@ button.dsm-qty:disabled{cursor:default}
|
|
|
198
209
|
.dsm-source-body .dsm-source{border:0;border-radius:0;border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
199
210
|
.dsm-source-body .dsm-source:last-child{border-bottom:0}
|
|
200
211
|
.dsm-note{overflow:hidden;margin-top:4px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-tags{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.dsm-tag{display:inline-flex;min-height:20px;align-items:center;padding:0 8px;border:1px solid var(--dsw-alias-border-l3);border-radius:var(--dsm-radius-s);color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dsm-tag-on{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.dsm-tag-off{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.dsm-tag-warn{border-color:var(--dsm-warn);color:var(--dsm-warn)}
|
|
201
|
-
|
|
212
|
+
/* 开关与官方同款(用户 2026-10-18:「switch 按键统一和官方一样的模板」→「改成完全一样」):
|
|
213
|
+
36×20 外壳 + 16px 拇指、内边距 2px、滑块位移 16px、开态用 brand-primary,
|
|
214
|
+
关态拇指用 switch-thumb —— 尺寸与配色照抄宿主设置面板那颗。
|
|
215
|
+
⚠️ corner-shape:round 不能省:宿主主题在 :root 上全局设了
|
|
216
|
+
corner-shape:superellipse(1.5)(dsh-client-ui-theme 的 corner_shape_css),
|
|
217
|
+
官方开关自己用 corner-shape:round 把它按回正圆角 —— 我们漏了这一句,于是同一个
|
|
218
|
+
border-radius 画出来是"方肩"的超级椭圆,看着就和官方那颗不是一个东西。
|
|
219
|
+
aria-checked 是官方判据,本组件的 role=switch 本来就带着它。 */
|
|
220
|
+
.dsm-switch{box-sizing:border-box;position:relative;width:36px;height:20px;flex:none;padding:2px;border:0;border-radius:999px;corner-shape:round;background:var(--dsw-alias-border-l3);cursor:pointer}
|
|
221
|
+
.dsm-switch:after{display:block;width:16px;height:16px;border-radius:50%;corner-shape:round;background:var(--dsw-alias-switch-thumb);content:"";transition:transform .12s ease}
|
|
222
|
+
.dsm-switch[aria-checked=true]{background:var(--dsw-alias-brand-primary)}
|
|
223
|
+
.dsm-switch[aria-checked=true]:after{background:var(--dsw-alias-label-primary-foreground);transform:translate(16px)}
|
|
224
|
+
.dsm-switch:disabled{opacity:.5;cursor:default}.dsm-trash-count{margin-left:auto;padding:4px 8px;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:11px;font-variant-numeric:tabular-nums}.dsm-empty{padding:24px 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-align:center}.dsm-feedback{padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-undo{display:flex;align-items:center;gap:8px}.dsm-undo-text{min-width:0;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsm-undo-close{padding:0;border:0;background:transparent;font:inherit;font-size:13px;cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary)}.dsm-undo-close:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px);outline-offset:2px}.dsm-undo.dsm-warning{background:var(--dsm-warn-bg)}.dsm-undo.dsm-warning .dsm-undo-text{font-weight:400}.dsm-undo.dsm-warning .dsm-undo-close{color:var(--dsm-warn)}.dsm-warning{border-color:var(--dsm-warn);color:var(--dsm-warn)}.dsm-error{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
202
225
|
.dsm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.dsm-modal{box-sizing:border-box;display:flex;width:min(560px,100%)!important;max-height:min(760px,calc(100vh - 48px));min-width:0;flex-direction:column;gap:16px;padding:20px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-l);background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dsm-modal-sm{width:min(480px,100%)!important}.dsm-modal-lg,.dsm-modal-wide{width:min(720px,100%)!important}.dsm-modal-list{width:min(560px,100%)!important;height:min(640px,calc(100vh - 48px))!important;max-height:none!important;overflow:hidden}.dsm-modal-list.dsm-modal-editor{width:min(720px,100%)!important}.dsm-modal-body{display:flex;min-height:0;flex-direction:column;gap:8px}.dsm-modal-list .dsm-modal-body{flex:1;overflow:auto;overscroll-behavior:contain}.dsm-modal-head{display:flex;align-items:flex-start;gap:12px}
|
|
203
226
|
/* 头部这一层包住「标题行 + 令牌提示」:提示挂在头部里面(而不是 head 与 body 之间)是为了
|
|
204
227
|
让 body 的子节点位置稳定 —— 位置一变 React 会把 body 整棵重挂(输入框失焦、下拉归零)。 */
|
|
@@ -209,7 +232,14 @@ button.dsm-qty:disabled{cursor:default}
|
|
|
209
232
|
/* 条目清单必须自己声明字号:裸 ul/li 会从宿主那里继承 16px 近白正文(兼容页「工具能用 ≠
|
|
210
233
|
模型知道」三条),比同页的说明文字大两号。口径照 .dsm-help-list。 */
|
|
211
234
|
.dsm-upload-requirements ul{display:flex;margin:4px 0 0;padding-left:16px;flex-direction:column;gap:4px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}
|
|
212
|
-
.dsm-upload-req{margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}.dsm-detail-section{gap:8px}
|
|
235
|
+
.dsm-upload-req{margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}.dsm-detail-section{gap:8px}
|
|
236
|
+
/* 详情页只读的备注正文(用户 2026-10-18:「详细页只能看」):与配置表同形的一格,
|
|
237
|
+
但它是文字块不是输入框 —— 换行原样保留,长段落读得开。
|
|
238
|
+
字色用正文色(用户 2026-10-18:「备注后的字应该是黑色的备注」):它是内容本身,
|
|
239
|
+
不是"辅助说明",弱色会让备注看着像灰掉的占位。没备注时那一格照样有边框,
|
|
240
|
+
只是字换成最弱的一档 —— 空状态也说得出口。 */
|
|
241
|
+
.dsm-detail-note{margin:0;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;white-space:pre-wrap;word-break:break-word}
|
|
242
|
+
.dsm-detail-note-empty{color:var(--dsw-alias-label-tertiary)}.dsm-detail-path,.dsm-code{font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px}.dsm-detail-path{padding:8px 8px;border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);word-break:break-all}
|
|
213
243
|
/* 技能详情的「路径 + 编辑器查看」同一行(用户 2026-10-12):路径条 flex:1 吃掉剩余宽度,
|
|
214
244
|
按钮 flex:none 固定不动。按钮那颗 .dsm-btn 自带 white-space:nowrap,长路径自己折行。 */
|
|
215
245
|
.dsm-detail-path-row{display:flex;min-width:0;align-items:center;gap:8px}
|
|
@@ -246,9 +276,10 @@ button.dsm-qty:disabled{cursor:default}
|
|
|
246
276
|
「没有滚动条 + 内容缺一截」。列表项的尺寸一律由内容决定,滚动只交给 body 这一层。 */
|
|
247
277
|
.dsm-modal-list .dsm-modal-body>*{flex:none}
|
|
248
278
|
.dsm-fm{display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1)}.dsm-fm-row{display:grid;grid-template-columns:minmax(96px,150px) minmax(0,1fr);gap:8px;padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:18px}.dsm-fm-row:last-child{border-bottom:0}.dsm-fm-key{color:var(--dsw-alias-label-secondary);word-break:break-word}.dsm-fm-raw{margin-left:4px;color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px}.dsm-fm-val{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-word;white-space:pre-wrap}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
279
|
+
/* 窄面板里**不再隐藏目录**(用户 2026-10-18:「设置页的工具管理不显示路径是怎么回事」):
|
|
280
|
+
那条 display:none 是目录还挂在行尾、与标题抢宽度时写的;现在它单独占第二行,
|
|
281
|
+
窄到 700px 也只是自己截断,没有理由整段不显示。 */
|
|
282
|
+
@container(max-width:520px){.dsm-head{flex-direction:column}.dsm-actions{width:100%;margin-left:0}.dsm-actions .dsm-btn{flex:1}.dsm-filters{flex-direction:column}.dsm-source-filter{width:100%}}
|
|
252
283
|
.dsm-hist-group-head{gap:8px}.dsm-hist-group-extra{display:flex;align-items:center;gap:8px;flex:none;margin-left:4px}.dsm-hist-group-extra .dsm-tag{flex:none}.dsm-hist-dir-missing{flex:none;padding:1px 6px;border:1px solid var(--dsw-alias-state-error-primary);border-radius:var(--dsm-radius-s);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:16px;white-space:nowrap;cursor:default;user-select:none}.dsm-hist-register-path{margin-top:-4px;color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code,monospace);font-size:11px;word-break:break-all}/* (.dsm-hist-batch / .dsm-hist-batch-actions 已随批量动作移进列表工具条删除;
|
|
253
284
|
.dsm-hist-batch-count 仍用于导出弹窗里的「已选 N 项」计数。) */
|
|
254
285
|
.dsm-hist-batch-count{color:var(--dsw-alias-label-secondary);font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap}.dsm-dir-row{display:flex;gap:8px}.dsm-dir-row .dsm-control{flex:1}.dsm-dir-list{display:flex;max-height:220px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1)}.dsm-dir-item{display:flex;width:100%;align-items:center;padding:8px 12px;border:0;border-bottom:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-align:left;cursor:pointer}.dsm-dir-item:last-child{border-bottom:0}.dsm-dir-item:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-dir-item::before{content:"📁";margin-right:8px;font-size:12px}
|
|
@@ -262,6 +293,9 @@ button.dsm-qty:disabled{cursor:default}
|
|
|
262
293
|
.dsm-attach-actions{display:inline-flex;align-items:center;gap:6px}
|
|
263
294
|
.dsm-attach-cap{max-height:126px;overflow:auto;padding-right:2px}
|
|
264
295
|
.dsm-attach-cap .dsm-file{gap:6px;padding:5px 8px}
|
|
296
|
+
/* 回收站里技能条目的正文:与技能详情同款的一段代码区,吃掉弹窗剩余高度(用户 2026-10-18:
|
|
297
|
+
「技能的回收站详细还是挤在一起,不能套用技能详细弹窗吗」)。 */
|
|
298
|
+
.dsm-code-fill{flex:1;min-height:200px;max-height:none}
|
|
265
299
|
/* 附件小窗里的拖放区:导入弹窗那一块是给整页用的(170px),这一档 140 —— 再矮就不像能拖东西了。 */
|
|
266
300
|
.dsm-dropzone-sm{min-height:140px;padding:18px}
|
|
267
301
|
.dsm-toast{position:fixed;right:22px;bottom:22px;z-index:1250;box-sizing:border-box;max-width:min(420px,calc(100vw - 44px));padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;animation:dsm-toast-in 160ms ease-out}@keyframes dsm-toast-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}/* 动效降级:系统开了「减少动态效果」就一律关掉。新增 transition / animation 时记得同步加到这里,
|
|
@@ -302,22 +336,48 @@ button.dsm-qty:disabled{cursor:default}
|
|
|
302
336
|
/* 当前模式条:只在进入模式后出现(没有条 = 自由模式,不需要常驻的解释文案)。 */
|
|
303
337
|
.dsm-mode-bar{display:flex;min-width:0;align-items:center;gap:8px;padding:12px 12px;border:1px solid var(--dsw-alias-state-success-primary);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-2)}
|
|
304
338
|
.dsm-mode-bar .dsm-btn{margin-left:auto;flex:none}
|
|
339
|
+
/* 右侧两颗动作(锁定 + 退出模式)成一组、靠右(用户 2026-10-18:「锁定……出现在退出模式
|
|
340
|
+
旁边」):margin-left:auto 只给这一组,两颗之间按 8px 排。
|
|
341
|
+
锁定键在这一组里**与「退出模式」同高同宽口径**(用户 2026-10-18:「锁定按钮的大小对齐
|
|
342
|
+
旁边按键」):quiet 默认 24px 高、8px 内边距,旁边那颗 secondary 是 30px / 12px —— 并排
|
|
343
|
+
看着像两档按钮。这里只在本组内把它抬到同一档。 */
|
|
344
|
+
.dsm-mode-acts{display:flex;flex:none;align-items:center;gap:8px;margin-left:auto}
|
|
345
|
+
.dsm-mode-acts .dsm-btn{margin-left:0}
|
|
346
|
+
/* 锁定键:与旁边「退出模式」同高同宽口径,字重加粗(用户 2026-10-18:「锁定要加粗」)。
|
|
347
|
+
底色保持透明 —— 用户 2026-10-18:「锁定键白色部分不用变黄了,鼠标放上去微微变黄就行」:
|
|
348
|
+
常态是"琥珀字 + 琥珀描边"的描边键,只有悬停才铺一层极淡的琥珀。 */
|
|
349
|
+
.dsm-mode-acts .dsm-btn-quiet{min-height:30px;padding:0 12px;font-size:13px}
|
|
350
|
+
.dsm-mode-acts .dsm-btn-lock{font-weight:600;background:transparent}
|
|
351
|
+
.dsm-mode-acts .dsm-btn-lock:hover:not(:disabled){background:var(--dsm-warn-bg);color:var(--dsm-warn)}
|
|
352
|
+
/* 退出模式是**破坏性/收尾**动作(用户 2026-10-18:「退出模式改成红色字体红色边框」):
|
|
353
|
+
红字红框的描边键(不是实底 —— 全站实底红只留给确认弹窗里的主操作)。悬停只铺一层
|
|
354
|
+
极淡的红底,与上面那颗锁定的悬停同一档强度。 */
|
|
355
|
+
.dsm-mode-acts .dsm-btn-secondary{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}
|
|
356
|
+
.dsm-mode-acts .dsm-btn-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);opacity:1}
|
|
305
357
|
.dsm-mode-dot{width:8px;height:8px;flex:none;border-radius:50%;background:var(--dsw-alias-state-success-primary)}
|
|
306
358
|
.dsm-mode-main{min-width:0;flex:1}
|
|
307
359
|
.dsm-mode-title{overflow:hidden;font-size:13px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}
|
|
308
360
|
.dsm-mode-sub{overflow:hidden;margin-top:4px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}
|
|
309
|
-
|
|
310
|
-
|
|
361
|
+
/* 场景**一条一行**(用户 2026-10-18 裁定):原先按 minmax(300px,1fr) 铺网格,名字与标签多的
|
|
362
|
+
场景被挤成两三行;改成单列之后开关 / 锁定 / ⋯ 一列对齐,扫起来是一张表。 */
|
|
363
|
+
.dsm-scenes{display:flex;flex-direction:column;gap:6px}
|
|
364
|
+
.dsm-scene-tile{display:flex;min-width:0;align-items:center;gap:10px;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-2);padding:8px 10px}
|
|
311
365
|
.dsm-scene-tile-mode{border-color:var(--dsw-alias-state-success-primary)}
|
|
312
|
-
|
|
313
|
-
|
|
366
|
+
/* 左列(名字 + 描述两行)与右侧控件(开关 / ⋯)是**同一条横排**的两个格子(用户 2026-10-18:
|
|
367
|
+
「场景的 switch 按键和三个点按键没有上下居中」)。描述挪到第二行之后,卡头只剩第一行高,
|
|
368
|
+
开关若还挂在卡头里,它的中线就贴在上半截 —— 卡片越高偏得越多。改成整张卡横排、右侧控件
|
|
369
|
+
按卡高中线对齐,和列表行(.dsm-listrow 的 side / actions)是同一种排法。 */
|
|
370
|
+
.dsm-scene-tile-main{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}
|
|
371
|
+
.dsm-scene-tile-head{display:flex;min-width:0;align-items:center;gap:8px}
|
|
372
|
+
.dsm-scene-tile-name{overflow:hidden;flex:none;max-width:40%;font-size:13px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}
|
|
314
373
|
.dsm-scene-tile-key{flex:none;color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px}
|
|
315
374
|
.dsm-scene-tile-head .dsm-tag{flex:none}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
.dsm-scene-tile
|
|
320
|
-
|
|
375
|
+
/* 描述固定占**第二行**(用户 2026-10-18:「应该显示在第二行」):此前它挤在名字同一行、
|
|
376
|
+
窄屏下还被容器查询整段隐藏 —— 而它恰恰是这个场景"是干什么的"唯一说明。 */
|
|
377
|
+
.dsm-scene-tile-desc{overflow:hidden;min-width:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}
|
|
378
|
+
/* 右侧控件是一列横排的两格(开关 + ⋯),二者与卡片中线对齐(见 .dsm-scene-tile)。 */
|
|
379
|
+
.dsm-scene-tile-side{display:flex;flex:none;align-items:center;gap:8px}
|
|
380
|
+
.dsm-scene-tile-switch{flex:none}
|
|
321
381
|
.dsm-tools-grid{display:flex;max-height:180px;padding:4px;overflow:auto;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1)}
|
|
322
382
|
.dsm-tools-grid>*{flex:none}
|
|
323
383
|
/* ── 高级选项折叠区(人设表单;默认收起,展开后才拉候选数据)────────────────── */
|
|
@@ -721,6 +781,12 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
721
781
|
/* 锁定键:文字是 warn 色,边框必须同色 —— 否则「琥珀字 + 灰框」看着像两颗按钮拼的
|
|
722
782
|
(用户 2026-10-12「锁定的边框应该是字体一样」)。 */
|
|
723
783
|
.dsm-btn-quiet.dsm-btn-lock{border-color:var(--dsm-warn);background:transparent;color:var(--dsm-warn)}
|
|
784
|
+
/* 悬停时**字色仍是琥珀**(用户 2026-10-18:「锁定键鼠标放上去也应该是黄色字」):
|
|
785
|
+
.dsm-btn-quiet:hover 把字色统一改成正文色,那条通用规则排在后面、权重又只低一级,
|
|
786
|
+
于是锁键一悬停就变黑 —— 这里把它按同色写回来。
|
|
787
|
+
2026-10-18 第二轮:底色不再常驻琥珀(「白色部分不用变黄了」),只在悬停铺一层极淡的底,
|
|
788
|
+
这一条只管"悬停时字别变黑",底由模式横幅那一组按上下文给。 */
|
|
789
|
+
.dsm-btn-quiet.dsm-btn-lock:hover:not(:disabled){background:var(--dsm-warn-bg);color:var(--dsm-warn)}
|
|
724
790
|
.dsm-bulk-ghost{border: 0; background: none; padding: 0 2px; font: inherit; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); cursor: pointer;}
|
|
725
791
|
.dsm-file-remove:hover{background: var(--dsw-alias-interactive-bg-hover-danger);}
|
|
726
792
|
.dsm-select-trigger > span{min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
|
|
@@ -1096,8 +1162,29 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1096
1162
|
.dsm-listrow-meta-info{flex:none;color:var(--dsw-alias-label-tertiary)}
|
|
1097
1163
|
/* 技术值(路径 / id)排印(§2.3):与普通文案在字形上区分,不靠颜色。 */
|
|
1098
1164
|
.dsm-listrow-meta .dsm-mono{font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace)}
|
|
1165
|
+
/* 目录与「归档日期」分开读(用户 2026-10-18:「目录栏应该和归档对话区分」):两者原先都是
|
|
1166
|
+
12px 弱色、只隔一个中间点,长路径读起来跟日期连成一句。目录改用等宽字体 + 更弱的色,
|
|
1167
|
+
日期保持正文色 —— 一段是"在哪",一段是"什么时候",字形不同就不用再看标点分辨。
|
|
1168
|
+
字号整体压到 10/11px(用户 2026-10-18:「字体都再缩小」):这一页一屏要扫十几行,
|
|
1169
|
+
两行字都按正文大小排,行与行之间没有主次、只剩一片字。 */
|
|
1170
|
+
.dsm-listrow-meta-path{font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:10px;line-height:15px;color:var(--dsw-alias-label-tertiary)}
|
|
1171
|
+
.dsm-hist-row .dsm-listrow-meta-info{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:15px}
|
|
1099
1172
|
.dsm-listrow-side{display:flex;flex:none;align-items:center;gap:8px}
|
|
1100
1173
|
.dsm-listrow-actions{display:flex;flex:none;align-items:center;justify-content:flex-end;gap:4px}
|
|
1174
|
+
/* 归档会话行放宽一档(用户 2026-10-18:「归档会话太拥挤了」):这一页每行是「标题 + 目录 +
|
|
1175
|
+
日期」两段文字,右端还常驻两颗按钮比别处密。48px 的通用行高在这里把三段挤成一坨 ——
|
|
1176
|
+
行高抬到 52px、两行之间 4px,逐行读开。 */
|
|
1177
|
+
.dsm-hist-row{min-height:52px;padding:6px 12px}
|
|
1178
|
+
.dsm-hist-row .dsm-listrow-main{gap:4px}
|
|
1179
|
+
/* 层级靠字号、不靠加粗(用户 2026-10-18:「归档会话的标题不用加粗,放大字体就行」→
|
|
1180
|
+
「字体都再缩小」):标题去掉 600,字号与正文齐平,靠上面那行目录的小字拉开主次。 */
|
|
1181
|
+
.dsm-hist-row .dsm-listrow-name{font-size:13px;font-weight:400}
|
|
1182
|
+
/* 分组卡头(工作区目录)与其他页的收缩栏同款(用户 2026-10-18:「会话目录栏感觉太粗太高了,
|
|
1183
|
+
改成和其他收缩栏一样」):48px、标题 13px/600 —— 上一版为它单独加高的 64px / 15px/700
|
|
1184
|
+
已撤掉,技能 / MCP / 记忆三页的组头就是这套尺寸。 */
|
|
1185
|
+
/* 行内按钮**保留描边**(用户 2026-10-18:「归档会话的恢复和永久删除边框怎么不见了」):
|
|
1186
|
+
上一版为了"降噪"把它们去掉了边框,结果两颗键成了裸文字,跟旁边的目录文字混成一片 ——
|
|
1187
|
+
在行尾它们本来就是按钮,边框是"这是能点的"最直接的信号。降噪改由行高与字号承担。 */
|
|
1101
1188
|
/* 行2 之下的追加内容(体检结果 / ⚠ 提示 / 被覆盖说明)原本按表格网格留白,
|
|
1102
1189
|
两行制里由主列 gap 接管,各自的 margin-top 归零。 */
|
|
1103
1190
|
.dsm-listrow .dsm-row-hint,.dsm-listrow .dsm-rule-hint,.dsm-listrow .dsm-rule-shadow-hint{margin-top:0}
|
|
@@ -1143,6 +1230,10 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
1143
1230
|
.dsm-mode-caret,.dsm-pick-caret{width:12px;height:12px;flex:none;color:var(--dsw-alias-label-tertiary)}
|
|
1144
1231
|
/* 快照导入预览:行选择纵向堆叠(原为横向勾选 chips,切片五换 RowSelect)。 */
|
|
1145
1232
|
.dsm-snap-picks{display:flex;flex-direction:column;gap:2px}
|
|
1233
|
+
/* 侧栏入口打开的那一页(main 面板):宿主只给一个空的中栏,滚动与留白都得自己声明。
|
|
1234
|
+
顶端让出标题栏净空(macOS / Windows 桌面端由宿主在这两个属性上给高度),与 dshmarket
|
|
1235
|
+
的主面板同一套算法;内容仍是 .dsm-section —— 宽度上限与居中它自己管。 */
|
|
1236
|
+
.dsm-panel{box-sizing:border-box;width:100%;height:100%;min-height:0;overflow:auto;padding:calc(20px + var(--dsh-frame-top-clearance,0px)) clamp(16px,4vw,40px) 32px}
|
|
1146
1237
|
`
|
|
1147
1238
|
|
|
1148
1239
|
/** 统一的异常文案:Error 取 message,其余(字符串/对象/null)原样 String。此前 63 处各写一遍。 */
|
|
@@ -1258,7 +1349,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
1258
1349
|
function MoreIcon(props) {
|
|
1259
1350
|
var size = (props && props.size) || 14
|
|
1260
1351
|
return h('svg', { viewBox: '0 0 16 16', width: size, height: size, 'aria-hidden': 'true', focusable: 'false', className: props && props.className },
|
|
1261
|
-
h('path', { fill: 'currentColor', d: 'M3
|
|
1352
|
+
h('path', { fill: 'currentColor', d: 'M3 7h2v2H3zM7 7h2v2H7zM11 7h2v2h-2z' }))
|
|
1262
1353
|
}
|
|
1263
1354
|
|
|
1264
1355
|
// `react` / `h` 别名(2026-10-11 切片五从 42 片上移):工厂作用域声明对 apply 内外
|
|
@@ -1324,7 +1415,9 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
1324
1415
|
* 2026-10-11 切片五从 42 片上移:兼容页的原生勾选框(斜杠入口 / 注入域 / 快照)
|
|
1325
1416
|
* 要换成开关,而兼容页在 apply 之外看不到 42 片的声明。
|
|
1326
1417
|
*/
|
|
1327
|
-
|
|
1418
|
+
// 开关的视觉完全交给 `[aria-checked]`(用户 2026-10-18:「switch 按键统一和官方一样的模板」):
|
|
1419
|
+
// 官方那颗也是只靠这个属性切换外观,多挂一个 dsm-switch-on 类等于留两条互相打架的判据。
|
|
1420
|
+
function Switch(props) { return h('button', { type: 'button', className: 'dsm-switch', role: 'switch', 'aria-checked': props.on === true, 'aria-label': props.label, title: props.title, disabled: props.disabled, onClick: props.onClick }); }
|
|
1328
1421
|
|
|
1329
1422
|
/**
|
|
1330
1423
|
* 下拉菜单的高度上限,**必须与 CSS 里 .dsm-select-menu 的 max-height 一致** ——
|
|
@@ -1864,6 +1957,58 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
1864
1957
|
else slashMountStates = { tools: state, quickPrompts: state }
|
|
1865
1958
|
}
|
|
1866
1959
|
|
|
1960
|
+
// ── 侧栏入口的开关(兼容页那颗按钮写,apply 侧的注册读)──────────────────────
|
|
1961
|
+
//
|
|
1962
|
+
// 为什么放在 factory 作用域:写它的是兼容页(22 片,apply 之外),读它的是 apply 侧的
|
|
1963
|
+
// 两处注册(49 片)—— 只有这个共享作用域两边都够得着,谁也不跨 apply 边界引对方的声明。
|
|
1964
|
+
//
|
|
1965
|
+
// 默认开:侧车那边的默认值也是开,读失败一律按开处理(见 sidebar-settings.ts)——
|
|
1966
|
+
// 判不出来时不该把一个入口藏掉。
|
|
1967
|
+
let sidebarEntryState = { enabled: true, busy: false }
|
|
1968
|
+
const sidebarEntrySubs = new Set()
|
|
1969
|
+
function sidebarEntrySnapshot() { return sidebarEntryState }
|
|
1970
|
+
function setSidebarEntryState(patch) {
|
|
1971
|
+
const next = Object.assign({}, sidebarEntryState, patch)
|
|
1972
|
+
if (next.enabled === sidebarEntryState.enabled && next.busy === sidebarEntryState.busy) return
|
|
1973
|
+
sidebarEntryState = next
|
|
1974
|
+
for (const fn of Array.from(sidebarEntrySubs)) { try { fn() } catch (e) { /* 单个订阅者出错不影响其它 */ } }
|
|
1975
|
+
}
|
|
1976
|
+
/** 订阅这份状态(兼容页与 apply 侧的注册共用,保证两边永远看到同一份)。 */
|
|
1977
|
+
function onSidebarEntryChange(fn) {
|
|
1978
|
+
sidebarEntrySubs.add(fn)
|
|
1979
|
+
return function () { sidebarEntrySubs.delete(fn) }
|
|
1980
|
+
}
|
|
1981
|
+
function useSidebarEntrySnapshot() {
|
|
1982
|
+
const st = React.useState(sidebarEntrySnapshot)
|
|
1983
|
+
React.useEffect(function () {
|
|
1984
|
+
const fn = function () { st[1](sidebarEntrySnapshot()) }
|
|
1985
|
+
sidebarEntrySubs.add(fn)
|
|
1986
|
+
fn()
|
|
1987
|
+
return function () { sidebarEntrySubs.delete(fn) }
|
|
1988
|
+
}, [])
|
|
1989
|
+
return st[0]
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* 写开关:先乐观更新(点一下就该有反馈),服务端回包为准,失败回滚。
|
|
1993
|
+
* @returns `{ ok: boolean, error?: string }` —— 文案由调用方按当前语言给(这里没有 t)。
|
|
1994
|
+
*/
|
|
1995
|
+
function saveSidebarEntry(enabled) {
|
|
1996
|
+
const before = sidebarEntryState.enabled
|
|
1997
|
+
const next = enabled === true
|
|
1998
|
+
setSidebarEntryState({ enabled: next, busy: true })
|
|
1999
|
+
return apiCall('sidebar-settings', { set: true, enabled: next }).then(function (r) {
|
|
2000
|
+
if (r && r.ok && r.settings) {
|
|
2001
|
+
setSidebarEntryState({ enabled: r.settings.enabled !== false, busy: false })
|
|
2002
|
+
return { ok: true }
|
|
2003
|
+
}
|
|
2004
|
+
setSidebarEntryState({ enabled: before, busy: false })
|
|
2005
|
+
return { ok: false, error: (r && r.error) || '' }
|
|
2006
|
+
}).catch(function (e) {
|
|
2007
|
+
setSidebarEntryState({ enabled: before, busy: false })
|
|
2008
|
+
return { ok: false, error: errMsg(e) }
|
|
2009
|
+
})
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1867
2012
|
/**
|
|
1868
2013
|
* 一条全局轻提示(body 级,与页面/弹窗无关)。
|
|
1869
2014
|
*
|
|
@@ -2026,6 +2171,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
2026
2171
|
'tool-table': 'compat.audit.op.toolTable',
|
|
2027
2172
|
'inject-settings': 'compat.audit.op.injectSettings',
|
|
2028
2173
|
'scene-settings': 'compat.audit.op.sceneSettings',
|
|
2174
|
+
'sidebar-settings': 'compat.audit.op.sidebarSettings',
|
|
2029
2175
|
'backups-clean': 'compat.audit.op.backupsClean',
|
|
2030
2176
|
'history-archive': 'compat.audit.op.sessionArchive',
|
|
2031
2177
|
'history-unarchive': 'compat.audit.op.sessionUnarchive',
|
|
@@ -4216,6 +4362,19 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4216
4362
|
React.createElement('div', { className: 'dsm-modal-body' }, presetRows)))
|
|
4217
4363
|
}
|
|
4218
4364
|
|
|
4365
|
+
/**
|
|
4366
|
+
* 侧栏入口的开关(2026-10-18):按钮文字说的是**点下去会发生什么**(开着 → 「关闭侧栏」),
|
|
4367
|
+
* 与「全选 ↔ 取消全选」「刷新 ↔ 刷新中…」同一套写法。默认开(侧车默认值同)。
|
|
4368
|
+
* 失败只回一行提示,不改按钮 —— 回滚由 saveSidebarEntry 做(乐观更新那一步)。
|
|
4369
|
+
*/
|
|
4370
|
+
const sidebarEntry = useSidebarEntrySnapshot()
|
|
4371
|
+
const toggleSidebarEntry = function () {
|
|
4372
|
+
if (sidebarEntry.busy === true) return
|
|
4373
|
+
saveSidebarEntry(sidebarEntry.enabled === false).then(function (r) {
|
|
4374
|
+
if (!r || r.ok) return
|
|
4375
|
+
showPluginToast(r.error || t('compat.sidebar.failed'), 'err')
|
|
4376
|
+
})
|
|
4377
|
+
}
|
|
4219
4378
|
// 三块内容各自只在**打开时**挂进弹窗;说明句留在框内第一行(原来长在区块小标题右侧,
|
|
4220
4379
|
// 进了弹窗就没有"标题右边"这个位置了)。
|
|
4221
4380
|
// 按钮**永远点得开**:数据没读到时框里给一句话,而不是摆一枚按了没反应的按钮
|
|
@@ -4264,6 +4423,16 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4264
4423
|
: t('compat.backups.btnHint'),
|
|
4265
4424
|
onClick: openBackupClean,
|
|
4266
4425
|
}, backups && backups.total ? t('compat.backups.btnCount', { count: backups.total }) : t('compat.backups.btn')),
|
|
4426
|
+
// 侧栏入口的开关:放在诊断工具后面,它改的是本插件自己的入口,与上面三块"看数据"
|
|
4427
|
+
// 不是一类(用户 2026-10-18 要求摆在兼容页这一排)。
|
|
4428
|
+
React.createElement('button', {
|
|
4429
|
+
key: 'sidebar-entry',
|
|
4430
|
+
type: 'button',
|
|
4431
|
+
className: 'dsm-btn dsm-btn-quiet',
|
|
4432
|
+
disabled: sidebarEntry.busy === true,
|
|
4433
|
+
title: t(sidebarEntry.enabled === false ? 'compat.sidebar.showHint' : 'compat.sidebar.hideHint'),
|
|
4434
|
+
onClick: toggleSidebarEntry,
|
|
4435
|
+
}, t(sidebarEntry.enabled === false ? 'compat.sidebar.show' : 'compat.sidebar.hide')),
|
|
4267
4436
|
],
|
|
4268
4437
|
}),
|
|
4269
4438
|
body,
|
|
@@ -4374,6 +4543,23 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4374
4543
|
"trash.preview.readonly": "只读预览:内容不能修改,这里也没有保存。",
|
|
4375
4544
|
"trash.preview.files": "文件 {count} 个",
|
|
4376
4545
|
"trash.preview.meta": "来源记录",
|
|
4546
|
+
"trash.preview.entry": "条目信息",
|
|
4547
|
+
"trash.preview.field.deletedAt": "删除时间",
|
|
4548
|
+
"trash.preview.field.scene": "所属场景",
|
|
4549
|
+
"trash.preview.field.form": "形态",
|
|
4550
|
+
"trash.preview.field.label": "显示名",
|
|
4551
|
+
"trash.preview.field.desc": "描述",
|
|
4552
|
+
"trash.preview.field.locked": "锁定",
|
|
4553
|
+
"trash.preview.field.mcp": "MCP",
|
|
4554
|
+
"trash.preview.sourceGone": "已移除的来源",
|
|
4555
|
+
"trash.preview.field.skills": "技能",
|
|
4556
|
+
"trash.preview.field.subagents": "子智能体",
|
|
4557
|
+
"trash.preview.field.quick": "快捷提示词",
|
|
4558
|
+
"trash.preview.field.toolTable": "工具表方案",
|
|
4559
|
+
"trash.preview.field.prompt": "提示词预设",
|
|
4560
|
+
"trash.preview.field.memories": "随场景删除的记忆",
|
|
4561
|
+
"trash.preview.none": "(空)",
|
|
4562
|
+
"trash.preview.yes": "是",
|
|
4377
4563
|
"trash.preview.field.scope": "范围",
|
|
4378
4564
|
"trash.preview.field.origin": "来源类型",
|
|
4379
4565
|
"trash.preview.field.path": "目录",
|
|
@@ -4434,7 +4620,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4434
4620
|
"mcp.msg.ok": "操作成功", "mcp.msg.failed": "操作失败", "mcp.msg.loadFailed": "加载失败", "mcp.msg.warn": "操作完成,但加载器有提示:{warning}", "mcp.msg.toolsRefreshed": "已取到 {count} 个工具", "mcp.msg.restartWarn": "已重启,但有告警:{warning}", "mcp.msg.restarted": "已重启:{name}",
|
|
4435
4621
|
"mcp.form.addTitle": "新增 MCP", "mcp.form.editTitle": "编辑 MCP 服务:",
|
|
4436
4622
|
"mcp.form.err.serverName": "服务名需为 1-32 位字母、数字、下划线或短横线(不能有空格与中文)", "mcp.form.err.url": "地址需以 http:// 或 https:// 开头", "mcp.form.err.command": "命令不能为空",
|
|
4437
|
-
"mcp.form.maskedHint": "
|
|
4623
|
+
"mcp.form.maskedHint": "该 MCP 服务的密钥已打码,可以直接更改;看原文去详细页点「显示密钥」。",
|
|
4438
4624
|
"mcp.field.serverName": "服务名称", "mcp.field.serverName.hint": "1-32 位 [A-Za-z0-9_-],补丁里按此名注册",
|
|
4439
4625
|
"mcp.field.note": "备注(可选)", "mcp.field.note.placeholder": "给模型看的决策提示,比如「这台要 sudo 免密」;留空不写",
|
|
4440
4626
|
"mcp.field.transport": "传输方式", "mcp.field.level": "级别",
|
|
@@ -4449,7 +4635,8 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4449
4635
|
"mcp.compact.confirm": "确认整理", "mcp.compact.done": "补丁文件已整理:清掉 {count} 条失效的旧开关记录,服务的启用/停用状态未变", "mcp.compact.clean": "补丁文件已经很干净,没有可清理的旧记录",
|
|
4450
4636
|
"mcp.detail.entryId": "条目 ID", "mcp.detail.status": "运行状态", "mcp.detail.loader": "Loader 登记", "mcp.detail.loader.yes": "已登记", "mcp.detail.loader.no": "未登记",
|
|
4451
4637
|
"mcp.tools.stale": "上次运行时", "mcp.tools.expand": "展开", "mcp.tools.collapse": "收起",
|
|
4452
|
-
"mcp.detail.note": "备注(随上下文注入发给模型)", "mcp.detail.note.
|
|
4638
|
+
"mcp.detail.note": "备注(随上下文注入发给模型)", "mcp.detail.note.empty": "当前没有备注",
|
|
4639
|
+
"mcp.msg.noteFailed": "服务已保存,但备注没写进去。",
|
|
4453
4640
|
"mcp.detail.tools": "工具({count})", "mcp.detail.toolsRefresh": "刷新工具",
|
|
4454
4641
|
"mcp.field.headersShort": "请求头", "mcp.field.envShort": "环境变量",
|
|
4455
4642
|
"mcp.tools.loading": "正在获取工具列表…", "mcp.tools.loadFailed": "加载工具失败:", "mcp.tools.none": "该服务暂无已注册工具。",
|
|
@@ -4494,6 +4681,8 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4494
4681
|
"tabs.scenes": "场景", "tabs.skills": "技能", "tabs.subagents": "子智能体", "tabs.prompts": "提示词", "tabs.memory": "记忆", "tabs.sessions": "会话", "tabs.compat": "兼容",
|
|
4495
4682
|
"compat.title": "宿主兼容", "compat.desc": "本插件与 DSH 的对接体检(只读)。",
|
|
4496
4683
|
"compat.backups.btn": "清理旧备份", "compat.backups.btnCount": "清理旧备份 · {count} 份", "compat.backups.btnHint": "删掉多余的配置备份副本(它们含明文密钥)", "compat.backups.btnTitle": "现有 {count} 份配置备份(共 {size},含明文密钥)", "compat.backups.title": "清理旧备份",
|
|
4684
|
+
// 侧栏入口的开关(按钮文字说"点下去会发生什么":开着 → 「关闭侧栏」)。
|
|
4685
|
+
"compat.sidebar.hide": "关闭侧栏", "compat.sidebar.show": "开启侧栏", "compat.sidebar.hideHint": "把本插件的入口从侧栏撤掉(「设置 → 工具管理」照旧可用)", "compat.sidebar.showHint": "在侧栏里挂出本插件的入口,点一下就在这里打开同一套页面", "compat.sidebar.failed": "侧栏开关没保存成功",
|
|
4497
4686
|
"compat.backups.desc": "每份备份都是整份配置的副本,MCP 密钥与访问令牌在里面是明文。",
|
|
4498
4687
|
"snapshot.title": "整机迁移", "snapshot.hint": "一次带走 MCP、技能、记忆、子智能体、提示词、场景档案与侧车配置,换机器时再导入。", "snapshot.browse": "选择",
|
|
4499
4688
|
"snapshot.export.dir": "导出目录", "snapshot.export.btn": "导出", "snapshot.export.busy": "导出中…", "snapshot.export.done": "快照已写到:{dir}", "snapshot.export.failed": "导出失败",
|
|
@@ -4526,7 +4715,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4526
4715
|
"compat.audit.op.personaSave": "保存人设", "compat.audit.op.personaDelete": "删除人设", "compat.audit.op.personaImport": "导入人设", "compat.audit.op.personaSwitch": "启停人设",
|
|
4527
4716
|
"compat.audit.op.presetSave": "保存提示词", "compat.audit.op.quickSave": "保存快捷提示词", "compat.audit.op.quickSwitch": "启停快捷提示词", "compat.audit.op.quickDelete": "删除快捷提示词", "compat.audit.op.presetDelete": "删除提示词", "compat.audit.op.presetImport": "导入提示词", "compat.audit.op.presetApply": "应用提示词",
|
|
4528
4717
|
"compat.audit.op.trashRestore": "从回收站恢复", "compat.audit.op.trashDelete": "永久删除",
|
|
4529
|
-
"compat.audit.op.toolTable": "模型工具表", "compat.audit.op.injectSettings": "注入设置", "compat.audit.op.sceneSettings": "场景页设置", "compat.audit.op.backupsClean": "清理旧备份",
|
|
4718
|
+
"compat.audit.op.toolTable": "模型工具表", "compat.audit.op.injectSettings": "注入设置", "compat.audit.op.sceneSettings": "场景页设置", "compat.audit.op.sidebarSettings": "侧栏入口", "compat.audit.op.backupsClean": "清理旧备份",
|
|
4530
4719
|
"compat.audit.op.sessionArchive": "归档会话", "compat.audit.op.sessionUnarchive": "取消归档", "compat.audit.op.sessionDelete": "删除会话", "compat.audit.op.sessionRetention": "改保留策略", "compat.audit.op.trashRetention": "改回收站保留期", "compat.audit.op.sessionImport": "导入会话", "compat.audit.op.sessionExport": "导出会话", "compat.audit.op.sessionRegister": "登记工作区",
|
|
4531
4720
|
"compat.backups.count": "现有 {count} 份,共 {size}",
|
|
4532
4721
|
"compat.backups.del": "删除份数:", "compat.backups.delOption": "{count} 份",
|
|
@@ -4549,8 +4738,8 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4549
4738
|
"compat.op.batch": "批量归档", "compat.op.delete": "永久删除",
|
|
4550
4739
|
"compat.op.native": "宿主原生入口", "compat.op.adapter": "插件适配层", "compat.op.unavailable": "不可用(已禁用)",
|
|
4551
4740
|
"compat.degraded": "已降级能力", "compat.degraded.hint": "缺什么 + 后果", "compat.reason": "原因:",
|
|
4552
|
-
"compat.others": "其它能力项", "compat.others.hint": "不算降级:宿主没有这些入口时,由插件自己顶上",
|
|
4553
|
-
"compat.contracts": "宿主契约", "compat.contracts.hint": "本版适配依赖的宿主成员:缺了不抛错,但对应功能会少一半", "compat.contracts.missing": "不在位", "compat.contracts.allOk": "本版依赖的宿主契约全部在位。",
|
|
4741
|
+
"compat.others": "其它能力项", "compat.others.hint": "不算降级:宿主没有这些入口时,由插件自己顶上",
|
|
4742
|
+
"compat.contracts": "宿主契约", "compat.contracts.hint": "本版适配依赖的宿主成员:缺了不抛错,但对应功能会少一半", "compat.contracts.missing": "不在位", "compat.contracts.allOk": "本版依赖的宿主契约全部在位。",
|
|
4554
4743
|
"compat.bareModules": "裸模块解析", "compat.bareModules.hint": "插件在运行时按裸模块名 require 的官方包:它们不是 peer,所以不参与上面的模块比对", "compat.bareModules.present": "在位", "compat.bareModules.absent": "宿主未提供", "compat.bareModules.drift": "契约疑似漂移", "compat.bareModules.presentWhy": "影响:{feature}", "compat.bareModules.absentWhy": "宿主已不提供这个包:{feature}会降级", "compat.bareModules.driftWhy": "{feature}的契约标识有缺失({markers})—— 请人工核对该包的实现是否改过",
|
|
4555
4744
|
"compat.fallback.substituted": "插件适配层(宿主无此入口)",
|
|
4556
4745
|
"compat.fallback.native": "改走原生入口", "compat.fallback.refuse": "该操作会被拒绝", "compat.fallback.blocked": "相关按钮已禁用", "compat.fallback.inform": "不影响写入",
|
|
@@ -4632,7 +4821,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4632
4821
|
"trash.group.quick": "快捷提示词",
|
|
4633
4822
|
"trash.section.quick.sub": "快捷提示词目录已移走:恢复能还原,永久删除会删掉文件",
|
|
4634
4823
|
"slash.section.quick": "快捷提示词",
|
|
4635
|
-
"slash.quick.empty": "这条快捷提示词没有正文,先到「设置 →
|
|
4824
|
+
"slash.quick.empty": "这条快捷提示词没有正文,先到「设置 → 工具管理 → 提示词」补上",
|
|
4636
4825
|
"compat.ov.slashCommands": "斜杠命令入口", "compat.ov.slashCommands.on": "两段都开(打 / 唤出「工具」与「快捷提示词」两段)",
|
|
4637
4826
|
"compat.ov.slashCommands.onTools": "只开「工具」段", "compat.ov.slashCommands.onQuick": "只开「快捷提示词」段",
|
|
4638
4827
|
"compat.ov.slashCommands.off": "两段都已关闭(本页可重新打开)",
|
|
@@ -4682,7 +4871,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4682
4871
|
"compat.token.sentence.off": "写操作不再要凭证;明文密钥仍然凭令牌查看。",
|
|
4683
4872
|
"compat.token.sentence.locked": "改配置会被拒绝。",
|
|
4684
4873
|
"compat.token.sentence.unlocked": "本次启动已解锁;重启 DSH 后要重新解锁。",
|
|
4685
|
-
"compat.token.lock.composer": "未填写访问令牌,暂时无法对话 ——
|
|
4874
|
+
"compat.token.lock.composer": "未填写访问令牌,暂时无法对话 —— 请到「工具管理 → 兼容」页填写",
|
|
4686
4875
|
"compat.token.banner": "访问令牌未验证:现在发出的对话会被宿主拒绝(消息不会进入对话)。",
|
|
4687
4876
|
"token.dialog.title": "填写访问令牌",
|
|
4688
4877
|
"compat.token.hostHint": "写进配置文件,重启 DSH 后生效。",
|
|
@@ -4707,7 +4896,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4707
4896
|
"compat.token.clearHost.btn": "删除令牌", "compat.token.offHost": "关闭保护", "compat.token.offHost.btn": "关闭保护",
|
|
4708
4897
|
"compat.token.onHost": "开启保护", "compat.token.onHost.btn": "开启保护", "compat.token.onConfirm.yes": "确认开启",
|
|
4709
4898
|
"compat.token.pendingRestart": "改动还没生效(胶囊说的是当前进程):重启 DSH 后才是当前状态。",
|
|
4710
|
-
"compat.token.notFound": "
|
|
4899
|
+
"compat.token.notFound": "未设置令牌:点「设置令牌」,或设环境变量 DSH_PLUGIN_TOOL_MANAGEMENT_TOKEN。",
|
|
4711
4900
|
// 三种用法:一句话一条。上一版是一整段散文(讲了原理、机制、两种 code 的区别),
|
|
4712
4901
|
// 用户裁定「写简单简洁一点,用户大致了解就行」——机制层面的东西留在这里的注释里。
|
|
4713
4902
|
"compat.token.edit.current": "当前令牌", "compat.token.edit.new": "新令牌", "compat.token.edit.repeat": "再输一次", "compat.token.edit.save": "保存新令牌", "compat.token.edit.cancel": "取消",
|
|
@@ -4990,8 +5179,8 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
4990
5179
|
"import.clear": "清空选择", "import.submit": "导入",
|
|
4991
5180
|
"import.none": "没有导入任何文件(全部被跳过)", "import.skipped": "已跳过:{items}",
|
|
4992
5181
|
"error.import.noFiles": "没有选择要导入的文件",
|
|
4993
|
-
"nav.title": "
|
|
4994
|
-
"agm.stat.total": "全局提示词", "agm.stat.active": "生效中", "agm.loading": "加载中…", "agm.empty": "暂无提示词,点「新增」创建", "agm.empty.search": "没有匹配的提示词", "agm.search.placeholder": "搜索提示词 id 或描述", "agm.btn.delete.blocked.refs": "不能删除:{refs}。先解除引用再删 —— 场景绑定的去场景页换绑提示词、或退出场景;正被应用的那份先点左边那颗开关「取消应用」。", "agm.ref.sep": ";", "agm.ref.scene": "场景「{scene}」绑定了它", "agm.ref.sceneActive": "场景「{scene}」(已启用)绑定了它", "agm.ref.file": "AGENTS.md 当前内容就是它", "agm.ref.fileApplied": "它正被应用在 AGENTS.md 里(当前生效的那一份)", "agm.ref.restore": "退出场景后要恢复的全局提示词", "agm.sceneLock.notice": "
|
|
5182
|
+
"nav.title": "工具管理",
|
|
5183
|
+
"agm.stat.total": "全局提示词", "agm.stat.active": "生效中", "agm.loading": "加载中…", "agm.empty": "暂无提示词,点「新增」创建", "agm.empty.search": "没有匹配的提示词", "agm.search.placeholder": "搜索提示词 id 或描述", "agm.btn.delete.blocked.refs": "不能删除:{refs}。先解除引用再删 —— 场景绑定的去场景页换绑提示词、或退出场景;正被应用的那份先点左边那颗开关「取消应用」。", "agm.ref.sep": ";", "agm.ref.scene": "场景「{scene}」绑定了它", "agm.ref.sceneActive": "场景「{scene}」(已启用)绑定了它", "agm.ref.file": "AGENTS.md 当前内容就是它", "agm.ref.fileApplied": "它正被应用在 AGENTS.md 里(当前生效的那一份)", "agm.ref.restore": "退出场景后要恢复的全局提示词", "agm.sceneLock.notice": "处于「{scene}」场景:依旧可以更改配置,改动会同步档案,退出后状态还原。", "agm.active": "生效中", "agm.active.hint.scene": "当前生效:场景「{scene}」绑定的提示词预设", "agm.active.hint.file": "当前生效:AGENTS.md 就是这一份", "agm.inactive": "未生效", "agm.file.applied": "文件里是它", "agm.file.applied.hint": "AGENTS.md 当前内容是它;启用的场景绑了别的提示词,切换场景时已按场景把文件改成那一份", "agm.apply.hint": "应用后写入 AGENTS.md;宿主每轮重读该文件,下一轮对话生效", "agm.apply.switch": "应用提示词", "agm.apply.switch.on": "当前生效的就是这一份 · 点一下重新写回 AGENTS.md(撤销手动改动)", "agm.applyModal.title": "应用预设", "agm.edit.title": "编辑预设", "agm.remove.title": "删除预设", "agm.btn.edit": "编辑", "agm.btn.delete": "删除", "agm.btn.new": "新增", "agm.new.title": "新增提示词", "agm.btn.import": "导入", "agm.result.imported": "已导入 {count} 份预设:{names}", "agm.btn.save": "保存", "agm.btn.create": "创建", "agm.btn.confirmRemove": "确认删除", "agm.btn.confirmApply": "确认应用", "agm.field.id": "显示名称", "agm.field.id.hint": "允许中文、空格、点;不能含斜杠/反斜杠与 : * ? < > 等符号;≤64 字符,改 id 即目录改名。", "agm.field.id.placeholder": "例如:工作基线", "agm.field.content": "内容", "agm.field.content.placeholder": "直接写这份预设的正文(Markdown)", "agm.field.desc": "描述(模型在清单里看得到)", "agm.field.desc.placeholder": "这份预设是干什么的、什么时候用它", "agm.result.renamed": "已保存,id 改为「{id}」", "agm.result.rebound": "(同步了 {count} 个场景的绑定)", "agm.remove.suffix": " ?此操作不可恢复。", "agm.apply.prefix": "将把 ", "agm.apply.suffix": " 的内容写入 ~/.dsh/AGENTS.md。", "agm.apply.note": "下一轮对话生效;当前内容已备份到 __last-applied__(保留最近 5 代)", "agm.unapply.title": "取消应用", "agm.unapply.body": "将把 ~/.dsh/AGENTS.md 恢复成开始应用提示词之前的那份内容(__last-applied__ 里保存的),此后不再有任何提示词预设生效。", "agm.unapply.note": "回到的是开始应用之前的那一份,中途应用过别的预设也一并撤销 —— 想换成其中某一份,直接点它自己的开关即可(效果等价)。这一步本身也会备份,可以再应用回来。", "agm.btn.confirmUnapply": "确认取消应用", "agm.unapply.switch.on": "AGENTS.md 里就是这一份 · 点一下取消应用(把 AGENTS.md 恢复成开始应用之前的内容)", "agm.unapply.switch.blocked": "AGENTS.md 里就是这一份,但没有「应用之前的状态」可恢复 —— 它不是由「应用」写进去的,所以关不掉。要改掉文件内容请应用别的预设或直接编辑 AGENTS.md;删除这份预设不会改动 AGENTS.md", "agm.unapply.switch.onRemove": "AGENTS.md 里就是这一份 · 点一下取消应用(开始应用之前没有 AGENTS.md,这一步会把它删掉)", "agm.unapply.bodyRemove": "开始应用提示词之前 ~/.dsh/AGENTS.md 并不存在,所以取消应用会把这个文件删掉(而不是写回内容),此后不再有任何提示词预设生效。", "agm.unapply.noteRemove": "删掉的是「应用」写进去的内容;这一份已备份到 __last-applied__(保留最近 5 代),需要时可以再应用回来。", "agm.btn.confirmUnapplyRemove": "确认取消应用并删除文件", "agm.result.unapplied": "已取消应用:AGENTS.md 已恢复成开始应用之前的内容", "agm.result.unappliedRemove": "已取消应用:AGENTS.md 已删除(开始应用之前它不存在)", "agm.unapply.unavailable": "AGENTS.md 的当前内容不是由「应用」写进去的(例如首次打开时自动生成的那份预设),没有「应用之前的状态」可恢复,所以关不掉。要改掉文件里的内容:应用别的预设,或直接编辑 AGENTS.md 本身;删除这份预设不会改动 AGENTS.md。", "agm.remove.fileNote": "注意:AGENTS.md 里仍然是这份内容,全局提示词照旧生效 —— 删除预设只把它移进回收站,不会改动 AGENTS.md。要让它不再生效,请改 AGENTS.md 本身或应用别的预设。", "hist.err.load": "加载失败", "hist.err.unarchive": "恢复失败", "hist.err.delete": "删除失败", "hist.err.retention": "设置失败", "hist.err.import": "导入失败", "hist.err.sessions": "加载会话列表失败", "hist.err.export": "导出失败", "hist.retention.forever": "永久保留", "hist.retention.days": "{count} 天", "hist.arch.all": "全部会话", "hist.arch.archived": "仅已归档", "hist.arch.live": "仅未归档", "hist.arch.missing": "仅目录丢失", "hist.ws.all": "全部工作区", "hist.ungrouped": "未分组", "hist.group.removed": "已移除", "hist.group.removed.hint": "该目录当前在 DSH 里没有工作区登记;分组按会话目录重建,重新登记后会自动并回同一组", "hist.group.dirMissing": "原目录不存在", "hist.group.dirMissing.hint": "工作区目录已从磁盘删除,无法重新登记;组内会话仍可恢复(归入未分组)或永久删除", "hist.btn.registerWs": "重新登记", "hist.btn.registerWs.hint": "重新登记这个目录,并把该目录下已有的会话挂回该组(只写工作区记账,不移动、不删除文件与会话)", "hist.register.title": "重新登记?", "hist.register.body": "将为「{title}」在 DSH 中重新创建一条工作区登记,并把该目录下已有的会话挂回这个工作区(恢复其分组)。只写工作区记账,不移动、不删除任何文件与会话。", "hist.register.confirm": "确认登记", "hist.register.done.title": "已重新登记", "hist.register.done.body": "工作区「{title}」已登记;该目录下的归档会话会在刷新后并入这一组。", "hist.register.done.attached": "已把该目录下 {count} 个已有会话挂回这个工作区。", "hist.register.done.skipped": "{count} 个会话未能挂回:{reason}", "hist.err.register": "登记失败", "scenes.field.prompt": "全局提示词",
|
|
4995
5184
|
"scenes.field.prompt.none": "无", "scenes.agents.note.applied": "已把「{id}」写入 AGENTS.md", "scenes.agents.note.restored": "已恢复进场景前的 AGENTS.md", "scenes.agents.note.error": "AGENTS.md 未写入:{reason}", "scenes.agents.note.baselineLost": "⚠ 退出场景时将恢复不回原来的 AGENTS.md:{reason}", "scenes.prompt.activeTag": "生效中", "scenes.prompt.noPresets": "还没有提示词预设;可先去「提示词」页新建一个。", "scenes.prompt.bound": "提示词:{id}", "scenes.prompt.live": "提示词生效中:{id}", "scenes.prompt.missing": "预设不存在:{id}", "scenes.prompt.tag.hint": "这个场景绑定的提示词预设", "scenes.prompt.mismatch.hint": "场景绑定的「{id}」与 AGENTS.md 当前内容不一致(可能被手动改过);到提示词页对它点「重新应用」写回", "scenes.enable.blocked": "已启用场景「{name}」;先关掉它才能启用别的", "scenes.result.created.collapsed": "场景「{name}」已创建,默认不启动(原先的「全部启用」已收敛为单选)", "scenes.legacyAll": "检测到 {count} 个场景同时处于启用状态(历史「全部启用」遗留);除「全局」外同时只能启用一个。", "scenes.legacyAll.fix": "收敛为单选", "scenes.preview.title": "进入场景「{name}」前,先看一眼会改什么", "scenes.preview.loading": "正在计算这次会改什么…", "scenes.preview.failed": "预览没算出来:{error}", "scenes.preview.enter": "确认进入", "scenes.preview.dismiss": "不再显示", "scenes.preview.dismiss.title": "关闭这张卡,并且以后进入场景前不再弹(想恢复用页面右上角的「开启提醒」)", "scenes.preview.toggle.off": "关闭提醒", "scenes.preview.toggle.on": "开启提醒", "scenes.preview.toggle.title": "进入场景前先弹一张「会改什么」的卡", "scenes.preview.nochange": "什么都不用改:当前运行时已经和这个场景要的一致了。", "scenes.preview.noArchive": "这个场景还没保存过档案 —— MCP / 技能 / 人设三个域都按「一个都没勾」处理,进入即全部停用。", "scenes.preview.exitFirst": "先退出「{scene}」:它改过的东西按进场景前的快照还原。", "scenes.preview.exitNoSnapshot": "当前在场景「{scene}」里,但进场景前的快照已丢失:退出只能清掉模式标记,运行时回不到原样。", "scenes.preview.row.serversOn": "启用 {count} 台 MCP 服务器:{names}", "scenes.preview.row.serversOff": "停用 {count} 台 MCP 服务器:{names}", "scenes.preview.row.toolsOn": "放回 {count} 个 MCP 工具:{names}", "scenes.preview.row.toolsOff": "停用 {count} 个 MCP 工具:{names}", "scenes.preview.row.skillsOn": "启用 {count} 个技能:{names}", "scenes.preview.row.skillsOff": "停用 {count} 个技能:{names}", "scenes.preview.row.personasOn": "启用 {count} 个人设:{names}", "scenes.preview.row.personasOff": "停用 {count} 个人设:{names}", "scenes.preview.enter.servers": "启用 {count} 台 MCP:{names}", "scenes.preview.enter.memories": "启用 {count} 条记忆:{names}", "scenes.preview.enter.personas": "启用 {count} 个子智能体:{names}", "scenes.preview.enter.personasNone": "子智能体一个都不启用",
|
|
4996
5185
|
// 绑定三行(预览卡第一段):一律 `标签:值`,"保持现状"必须带当前那份的名字。
|
|
4997
5186
|
"scenes.preview.bind.prompt": "全局提示词:切到「{name}」",
|
|
@@ -5096,6 +5285,23 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5096
5285
|
"trash.preview.readonly": "Read-only preview: nothing can be edited and there is no save here.",
|
|
5097
5286
|
"trash.preview.files": "{count} file(s)",
|
|
5098
5287
|
"trash.preview.meta": "Source record",
|
|
5288
|
+
"trash.preview.entry": "Entry",
|
|
5289
|
+
"trash.preview.field.deletedAt": "Deleted at",
|
|
5290
|
+
"trash.preview.field.scene": "Scene",
|
|
5291
|
+
"trash.preview.field.form": "Form",
|
|
5292
|
+
"trash.preview.field.label": "Display name",
|
|
5293
|
+
"trash.preview.field.desc": "Description",
|
|
5294
|
+
"trash.preview.field.locked": "Locked",
|
|
5295
|
+
"trash.preview.field.mcp": "MCP",
|
|
5296
|
+
"trash.preview.sourceGone": "removed source",
|
|
5297
|
+
"trash.preview.field.skills": "Skills",
|
|
5298
|
+
"trash.preview.field.subagents": "Subagents",
|
|
5299
|
+
"trash.preview.field.quick": "Quick prompts",
|
|
5300
|
+
"trash.preview.field.toolTable": "Tool-table scheme",
|
|
5301
|
+
"trash.preview.field.prompt": "Prompt preset",
|
|
5302
|
+
"trash.preview.field.memories": "Memories deleted with the scene",
|
|
5303
|
+
"trash.preview.none": "(empty)",
|
|
5304
|
+
"trash.preview.yes": "yes",
|
|
5099
5305
|
"trash.preview.field.scope": "Scope",
|
|
5100
5306
|
"trash.preview.field.origin": "Origin",
|
|
5101
5307
|
"trash.preview.field.path": "Folder",
|
|
@@ -5155,7 +5361,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5155
5361
|
"mcp.msg.ok": "Done", "mcp.msg.failed": "Operation failed", "mcp.msg.loadFailed": "Load failed", "mcp.msg.warn": "Done, but the loader reported: {warning}", "mcp.msg.toolsRefreshed": "Fetched {count} tool(s)", "mcp.msg.restartWarn": "Restarted with warnings: {warning}", "mcp.msg.restarted": "Restarted: {name}",
|
|
5156
5362
|
"mcp.form.addTitle": "Add MCP server", "mcp.form.editTitle": "Edit MCP server: ",
|
|
5157
5363
|
"mcp.form.err.serverName": "Server name must be 1-32 chars of letters, digits, underscore or hyphen (no spaces, no CJK)", "mcp.form.err.url": "URL must start with http:// or https://", "mcp.form.err.command": "Command cannot be empty",
|
|
5158
|
-
"mcp.form.maskedHint": "
|
|
5364
|
+
"mcp.form.maskedHint": "Secrets here are masked — you can edit them freely. To read one, open the details and click Reveal secrets.",
|
|
5159
5365
|
"mcp.field.serverName": "Server name", "mcp.field.serverName.hint": "1-32 chars of [A-Za-z0-9_-]; the patch registers it under this name",
|
|
5160
5366
|
"mcp.field.note": "Note (optional)", "mcp.field.note.placeholder": "A hint for the model, e.g. \“this one needs passwordless sudo\”; empty means nothing is stored",
|
|
5161
5367
|
"mcp.field.transport": "Transport", "mcp.field.level": "Level",
|
|
@@ -5170,7 +5376,8 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5170
5376
|
"mcp.compact.confirm": "Tidy now", "mcp.compact.done": "Patch file tidied: cleared {count} stale on/off record(s); no server changed state", "mcp.compact.clean": "The patch file is already clean — nothing stale to clear",
|
|
5171
5377
|
"mcp.detail.entryId": "Entry id", "mcp.detail.status": "Status", "mcp.detail.loader": "Loader registration", "mcp.detail.loader.yes": "Registered", "mcp.detail.loader.no": "Not registered",
|
|
5172
5378
|
"mcp.tools.stale": "from last run", "mcp.tools.expand": "Show more", "mcp.tools.collapse": "Show less",
|
|
5173
|
-
"mcp.detail.note": "Note (sent to the model with the injected runtime snapshot)", "mcp.detail.note.
|
|
5379
|
+
"mcp.detail.note": "Note (sent to the model with the injected runtime snapshot)", "mcp.detail.note.empty": "No note yet",
|
|
5380
|
+
"mcp.msg.noteFailed": "The server was saved, but the note was not written.",
|
|
5174
5381
|
"mcp.detail.tools": "Tools ({count})", "mcp.detail.toolsRefresh": "Refresh tools",
|
|
5175
5382
|
"mcp.field.headersShort": "Headers", "mcp.field.envShort": "Environment",
|
|
5176
5383
|
"mcp.tools.loading": "Fetching the tool list…", "mcp.tools.loadFailed": "Could not load tools: ", "mcp.tools.none": "This server has no registered tools.",
|
|
@@ -5215,6 +5422,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5215
5422
|
"tabs.scenes": "Scenes", "tabs.skills": "Skills", "tabs.subagents": "Subagents", "tabs.prompts": "Prompts", "tabs.memory": "Memories", "tabs.sessions": "Sessions", "tabs.compat": "Host",
|
|
5216
5423
|
"compat.title": "Host compatibility", "compat.desc": "How this plugin attaches to DSH (read-only).",
|
|
5217
5424
|
"compat.backups.btn": "Clean old backups", "compat.backups.btnCount": "Clean old backups · {count}", "compat.backups.btnHint": "Delete surplus config backup copies (they hold secrets in cleartext)", "compat.backups.btnTitle": "{count} config backups exist ({size}, secrets in cleartext)", "compat.backups.title": "Clean old backups",
|
|
5425
|
+
"compat.sidebar.hide": "Hide from sidebar", "compat.sidebar.show": "Show in sidebar", "compat.sidebar.hideHint": "Remove this plugin's row from the sidebar (Settings → Tools Management still works)", "compat.sidebar.showHint": "Add this plugin's row to the sidebar; it opens the same pages right here", "compat.sidebar.failed": "Could not save the sidebar toggle",
|
|
5218
5426
|
"compat.backups.desc": "Every backup is a full copy of the config — MCP secrets and the access token included, in cleartext.",
|
|
5219
5427
|
"snapshot.title": "Machine migration", "snapshot.hint": "Carry MCP, skills, memories, personas, prompts, scene profiles and the sidecar configs in one go, then import them on the other machine.", "snapshot.browse": "Browse",
|
|
5220
5428
|
"snapshot.export.dir": "Export directory", "snapshot.export.btn": "Export", "snapshot.export.busy": "Exporting…", "snapshot.export.done": "Snapshot written to: {dir}", "snapshot.export.failed": "Export failed",
|
|
@@ -5247,7 +5455,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5247
5455
|
"compat.audit.op.personaSave": "Saved a persona", "compat.audit.op.personaDelete": "Deleted a persona", "compat.audit.op.personaImport": "Imported a persona", "compat.audit.op.personaSwitch": "Enabled/disabled a persona",
|
|
5248
5456
|
"compat.audit.op.presetSave": "Saved a prompt", "compat.audit.op.quickSave": "Saved a quick prompt", "compat.audit.op.quickSwitch": "Toggled a quick prompt", "compat.audit.op.quickDelete": "Deleted a quick prompt", "compat.audit.op.presetDelete": "Deleted a prompt", "compat.audit.op.presetImport": "Imported a prompt", "compat.audit.op.presetApply": "Applied a prompt",
|
|
5249
5457
|
"compat.audit.op.trashRestore": "Restored from trash", "compat.audit.op.trashDelete": "Deleted forever",
|
|
5250
|
-
"compat.audit.op.toolTable": "Model tool table", "compat.audit.op.injectSettings": "Injection settings", "compat.audit.op.sceneSettings": "Scenes page settings", "compat.audit.op.backupsClean": "Cleaned old backups",
|
|
5458
|
+
"compat.audit.op.toolTable": "Model tool table", "compat.audit.op.injectSettings": "Injection settings", "compat.audit.op.sceneSettings": "Scenes page settings", "compat.audit.op.sidebarSettings": "Sidebar entry", "compat.audit.op.backupsClean": "Cleaned old backups",
|
|
5251
5459
|
"compat.audit.op.sessionArchive": "Archived a session", "compat.audit.op.sessionUnarchive": "Unarchived a session", "compat.audit.op.sessionDelete": "Deleted a session", "compat.audit.op.sessionRetention": "Changed retention", "compat.audit.op.trashRetention": "Changed trash retention", "compat.audit.op.sessionImport": "Imported sessions", "compat.audit.op.sessionExport": "Exported sessions", "compat.audit.op.sessionRegister": "Registered a workspace",
|
|
5252
5460
|
"compat.backups.count": "{count} copies, {size} in total",
|
|
5253
5461
|
"compat.backups.del": "How many to delete:", "compat.backups.delOption": "{count}",
|
|
@@ -5270,8 +5478,8 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5270
5478
|
"compat.op.batch": "Batch archive", "compat.op.delete": "Delete permanently",
|
|
5271
5479
|
"compat.op.native": "Host-native entry", "compat.op.adapter": "Plugin adapter", "compat.op.unavailable": "Unavailable (disabled)",
|
|
5272
5480
|
"compat.degraded": "Degraded capabilities", "compat.degraded.hint": "what is missing + the consequence", "compat.reason": "Reason:",
|
|
5273
|
-
"compat.others": "Other capability slots", "compat.others.hint": "not degraded: the plugin supplies its own implementation when the host has no such entry",
|
|
5274
|
-
"compat.contracts": "Host contracts", "compat.contracts.hint": "host members this release depends on: absence never throws, but the matching feature loses half of itself", "compat.contracts.missing": "absent", "compat.contracts.allOk": "Every host contract this release depends on is present.",
|
|
5481
|
+
"compat.others": "Other capability slots", "compat.others.hint": "not degraded: the plugin supplies its own implementation when the host has no such entry",
|
|
5482
|
+
"compat.contracts": "Host contracts", "compat.contracts.hint": "host members this release depends on: absence never throws, but the matching feature loses half of itself", "compat.contracts.missing": "absent", "compat.contracts.allOk": "Every host contract this release depends on is present.",
|
|
5275
5483
|
"compat.bareModules": "Bare module resolution", "compat.bareModules.hint": "official packages the plugin requires by bare name at runtime; they are not peers, so they take no part in the module comparison above", "compat.bareModules.present": "present", "compat.bareModules.absent": "not shipped by host", "compat.bareModules.drift": "contract drift suspected", "compat.bareModules.presentWhy": "affects: {feature}", "compat.bareModules.absentWhy": "the host no longer ships this package: {feature} degrades", "compat.bareModules.driftWhy": "{feature} is missing contract markers ({markers}) — please re-check that package's implementation manually",
|
|
5276
5484
|
"compat.fallback.substituted": "Plugin adapter (no host entry)",
|
|
5277
5485
|
"compat.fallback.native": "Falls back to native entry", "compat.fallback.refuse": "Operation refused", "compat.fallback.blocked": "Affected buttons disabled", "compat.fallback.inform": "Writes unaffected",
|
|
@@ -5352,7 +5560,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5352
5560
|
"trash.group.quick": "Quick prompts",
|
|
5353
5561
|
"trash.section.quick.sub": "Quick-prompt folders were moved: restore brings them back, delete-forever removes them",
|
|
5354
5562
|
"slash.section.quick": "Quick prompts",
|
|
5355
|
-
"slash.quick.empty": "This quick prompt has no text — add it under Settings → Tools → Prompts",
|
|
5563
|
+
"slash.quick.empty": "This quick prompt has no text — add it under Settings → Tools Management → Prompts",
|
|
5356
5564
|
"compat.ov.slashCommands": "Slash command entry", "compat.ov.slashCommands.on": "Both on (type / for the “Tools” and “Quick prompts” blocks)",
|
|
5357
5565
|
"compat.ov.slashCommands.onTools": "Only the “Tools” section is on", "compat.ov.slashCommands.onQuick": "Only the “Quick prompts” section is on",
|
|
5358
5566
|
"compat.ov.slashCommands.off": "Both sections turned off (re-enable them on this page)",
|
|
@@ -5401,7 +5609,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5401
5609
|
"compat.token.sentence.off": "Writes no longer need a token; revealing plaintext still does.",
|
|
5402
5610
|
"compat.token.sentence.locked": "Config changes will be rejected.",
|
|
5403
5611
|
"compat.token.sentence.unlocked": "Unlocked for this run; unlock again after DSH restarts.",
|
|
5404
|
-
"compat.token.lock.composer": "Access token not entered — chatting is off until you fill it in (Tools → Compat)",
|
|
5612
|
+
"compat.token.lock.composer": "Access token not entered — chatting is off until you fill it in (Tools Management → Compat)",
|
|
5405
5613
|
"compat.token.banner": "Access token not verified: prompts you send now are refused by the Host (the message never enters the conversation).",
|
|
5406
5614
|
"token.dialog.title": "Enter the access token",
|
|
5407
5615
|
"compat.token.hostHint": "Written to the config file; takes effect after a DSH restart.",
|
|
@@ -5426,7 +5634,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5426
5634
|
"compat.token.clearHost.btn": "Delete token", "compat.token.offHost": "Turn protection off", "compat.token.offHost.btn": "Turn off",
|
|
5427
5635
|
"compat.token.onHost": "Turn protection on", "compat.token.onHost.btn": "Turn on", "compat.token.onConfirm.yes": "Turn it on",
|
|
5428
5636
|
"compat.token.pendingRestart": "The change is not in effect yet (the pill describes the running process): restart DSH and it becomes the current state.",
|
|
5429
|
-
"compat.token.notFound": "No token
|
|
5637
|
+
"compat.token.notFound": "No token set — click “Set token”, or set DSH_PLUGIN_TOOL_MANAGEMENT_TOKEN.",
|
|
5430
5638
|
"compat.token.edit.current": "Current token", "compat.token.edit.new": "New token", "compat.token.edit.repeat": "Type it again", "compat.token.edit.save": "Save new token", "compat.token.edit.cancel": "Cancel",
|
|
5431
5639
|
"compat.token.edit.mismatch": "The two entries do not match.",
|
|
5432
5640
|
"compat.token.empty": "Enter a token first",
|
|
@@ -5707,8 +5915,8 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
5707
5915
|
"import.clear": "Clear", "import.submit": "Import",
|
|
5708
5916
|
"import.none": "Nothing was imported (all skipped)", "import.skipped": "Skipped: {items}",
|
|
5709
5917
|
"error.import.noFiles": "No files selected",
|
|
5710
|
-
"nav.title": "Tools",
|
|
5711
|
-
"agm.stat.total": "Global prompts", "agm.stat.active": "In effect", "agm.loading": "Loading…", "agm.empty": "No prompts yet — click “New” to create one", "agm.empty.search": "No matching prompts", "agm.search.placeholder": "Search prompt ids or descriptions", "agm.btn.delete.blocked.refs": "Cannot delete: {refs}. Remove that reference first — for a scene binding, rebind the prompt on the scene page or leave the scene; for the one currently applied, flip the switch on the left to un-apply.", "agm.ref.sep": "; ", "agm.ref.scene": "bound by scene “{scene}”", "agm.ref.sceneActive": "bound by scene “{scene}” (enabled)", "agm.ref.file": "this is what AGENTS.md currently holds", "agm.ref.fileApplied": "it is applied in AGENTS.md (the one currently in effect)", "agm.ref.restore": "the global prompt to restore after leaving the scene", "agm.sceneLock.notice": "
|
|
5918
|
+
"nav.title": "Tools Management",
|
|
5919
|
+
"agm.stat.total": "Global prompts", "agm.stat.active": "In effect", "agm.loading": "Loading…", "agm.empty": "No prompts yet — click “New” to create one", "agm.empty.search": "No matching prompts", "agm.search.placeholder": "Search prompt ids or descriptions", "agm.btn.delete.blocked.refs": "Cannot delete: {refs}. Remove that reference first — for a scene binding, rebind the prompt on the scene page or leave the scene; for the one currently applied, flip the switch on the left to un-apply.", "agm.ref.sep": "; ", "agm.ref.scene": "bound by scene “{scene}”", "agm.ref.sceneActive": "bound by scene “{scene}” (enabled)", "agm.ref.file": "this is what AGENTS.md currently holds", "agm.ref.fileApplied": "it is applied in AGENTS.md (the one currently in effect)", "agm.ref.restore": "the global prompt to restore after leaving the scene", "agm.sceneLock.notice": "In scene “{scene}”: you can still change settings — edits sync to the profile and the state is restored when you leave.", "agm.active": "Active", "agm.active.hint.scene": "Currently active: the prompt preset bound to scene “{scene}”", "agm.active.hint.file": "Currently active: AGENTS.md is exactly this preset", "agm.inactive": "Not applied", "agm.file.applied": "In AGENTS.md", "agm.file.applied.hint": "AGENTS.md currently holds this one; the enabled scene binds a different preset, which now takes effect", "agm.apply.hint": "Writes AGENTS.md; the host re-reads that file every turn, so it takes effect on the next turn", "agm.apply.switch": "Apply prompt", "agm.apply.switch.on": "This one is in effect · click to write AGENTS.md again (undoing hand edits)", "agm.applyModal.title": "Apply preset", "agm.edit.title": "Edit preset", "agm.remove.title": "Delete preset", "agm.btn.edit": "Edit", "agm.btn.delete": "Delete", "agm.btn.new": "New", "agm.new.title": "New prompt", "agm.btn.import": "Import", "agm.result.imported": "Imported {count} preset(s): {names}", "agm.btn.save": "Save", "agm.btn.create": "Create", "agm.btn.confirmRemove": "Delete", "agm.btn.confirmApply": "Apply", "agm.field.id": "Display name", "agm.field.id.hint": "Letters, spaces and dots are fine; no slashes or : * ? < >; up to 64 chars. Changing it renames the directory.", "agm.field.id.placeholder": "e.g. work-baseline", "agm.field.content": "Content", "agm.field.content.placeholder": "Write the preset body (Markdown) here", "agm.field.desc": "Description (the model sees this in the list)", "agm.field.desc.placeholder": "What this preset is for and when to use it", "agm.result.renamed": "Saved; id is now “{id}”", "agm.result.rebound": " ({count} scene binding(s) updated)", "agm.remove.suffix": "? This cannot be undone.", "agm.apply.prefix": "Write the contents of ", "agm.apply.suffix": " into ~/.dsh/AGENTS.md.", "agm.apply.note": "Takes effect on the next turn; the current content is backed up to __last-applied__ (last 5 kept)", "agm.unapply.title": "Un-apply", "agm.unapply.body": "This restores ~/.dsh/AGENTS.md to the content from before you started applying presets (kept in __last-applied__); no preset takes effect afterwards.", "agm.unapply.note": "What comes back is the content from before you started applying: presets you applied in between are rolled back too — to switch to one of them instead, just flip its own switch (same result). This step is backed up too, so you can apply it again.", "agm.btn.confirmUnapply": "Un-apply", "agm.unapply.switch.on": "This one is in AGENTS.md · click to un-apply (restore AGENTS.md to the content from before you started applying)", "agm.unapply.switch.blocked": "This one is in AGENTS.md, but there is no “state from before the apply” to restore — it was not written by “Apply”, so it cannot be turned off. To change the file, apply another preset or edit AGENTS.md directly; deleting this preset does not touch AGENTS.md", "agm.unapply.switch.onRemove": "This one is in AGENTS.md · click to un-apply (there was no AGENTS.md before you started applying, so this deletes the file)", "agm.unapply.bodyRemove": "There was no ~/.dsh/AGENTS.md before you started applying presets, so un-applying deletes that file instead of restoring content; no preset takes effect afterwards.", "agm.unapply.noteRemove": "What gets deleted is the content “Apply” wrote; that content is backed up to __last-applied__ (last 5 kept), so you can apply it back if needed.", "agm.btn.confirmUnapplyRemove": "Un-apply and delete the file", "agm.result.unapplied": "Un-applied: AGENTS.md is back to the content from before you started applying", "agm.result.unappliedRemove": "Un-applied: AGENTS.md was deleted (it did not exist before you started applying)", "agm.unapply.unavailable": "The current AGENTS.md was not written by “Apply” (the preset created automatically the first time you open this page is one such case), so there is no “state from before the apply” to restore and it cannot be turned off. To change what AGENTS.md holds: apply another preset, or edit AGENTS.md itself; deleting this preset does not touch AGENTS.md.", "agm.remove.fileNote": "Note: AGENTS.md still holds this content and your global prompt stays in effect — deleting the preset only moves it to the trash and does not touch AGENTS.md. To stop it taking effect, edit AGENTS.md itself or apply another preset.", "hist.err.load": "Load failed", "hist.err.unarchive": "Restore failed", "hist.err.delete": "Delete failed", "hist.err.retention": "Update failed", "hist.err.import": "Import failed", "hist.err.sessions": "Failed to load sessions", "hist.err.export": "Export failed", "hist.retention.forever": "Keep forever", "hist.retention.days": "{count} days", "hist.arch.all": "All sessions", "hist.arch.archived": "Archived only", "hist.arch.live": "Not archived only", "hist.arch.missing": "Missing directory only", "hist.ws.all": "All workspaces", "hist.ungrouped": "Ungrouped", "hist.group.removed": "Removed", "hist.group.removed.hint": "This directory has no DSH workspace registration right now; the group is rebuilt from session directories and rejoins automatically once registered again", "hist.group.dirMissing": "Original folder missing", "hist.group.dirMissing.hint": "The workspace directory was deleted from disk, so it cannot be registered again; sessions in this group can still be restored (they join Ungrouped) or deleted forever", "hist.btn.registerWs": "Register", "hist.btn.registerWs.hint": "Register this directory again and attach its existing sessions back to that group (workspace accounting only — no file or session is moved or deleted)", "hist.register.title": "Register again?", "hist.register.body": "A workspace registration will be created in DSH for “{title}”, and the sessions under this directory will be attached back to it (restoring their grouping). This writes workspace accounting only — no file or session is moved or deleted.", "hist.register.confirm": "Register", "hist.register.done.title": "Registered", "hist.register.done.body": "Workspace “{title}” is registered; archived sessions under this directory join that group after a refresh.", "hist.register.done.attached": "Attached {count} existing session(s) under this directory back to the workspace.", "hist.register.done.skipped": "{count} session(s) could not be attached: {reason}", "hist.err.register": "Registration failed", "scenes.field.prompt": "Global prompt",
|
|
5712
5920
|
"scenes.field.prompt.none": "None", "scenes.agents.note.applied": "wrote “{id}” into AGENTS.md", "scenes.agents.note.restored": "restored the AGENTS.md baseline from before the scene", "scenes.agents.note.error": "AGENTS.md was not written: {reason}", "scenes.agents.note.baselineLost": "⚠ Leaving the scene will not restore the previous AGENTS.md: {reason}", "scenes.prompt.activeTag": "in effect", "scenes.prompt.noPresets": "No prompt presets yet — create one on the Prompts page.", "scenes.prompt.bound": "Prompt: {id}", "scenes.prompt.live": "Prompt active: {id}", "scenes.prompt.missing": "Preset missing: {id}", "scenes.prompt.tag.hint": "The prompt preset bound to this scene", "scenes.prompt.mismatch.hint": "The scene-bound “{id}” does not match the current contents of AGENTS.md (it may have been edited by hand); click “Apply again” for it on the Prompts page to write it back", "scenes.enable.blocked": "Scene “{name}” is already enabled; turn it off first", "scenes.result.created.collapsed": "Scene “{name}” created and left off (the legacy “all enabled” default collapsed to a single choice)", "scenes.legacyAll": "{count} scenes are enabled at once (a leftover of the legacy “all enabled” default); only one scene besides “Global” may be enabled.", "scenes.legacyAll.fix": "Collapse to one", "scenes.preview.title": "Before entering scene “{name}” — what this will change", "scenes.preview.loading": "Working out what changes…", "scenes.preview.failed": "Preview failed: {error}", "scenes.preview.enter": "Enter the scene", "scenes.preview.dismiss": "Don't show again", "scenes.preview.dismiss.title": "Close this card and stop showing it before entering scenes (bring it back with “Turn the reminder on” at the top of this page)", "scenes.preview.toggle.off": "Turn the reminder off", "scenes.preview.toggle.on": "Turn the reminder on", "scenes.preview.toggle.title": "Show a “what will change” card before entering a scene", "scenes.preview.nochange": "Nothing to change: the runtime already matches what this scene asks for.", "scenes.preview.noArchive": "This scene has no saved profile — MCP, skills and personas are all read as “none checked”, so entering disables every one of them.", "scenes.preview.exitFirst": "Leaves “{scene}” first: what it changed is restored from the before-entering snapshot.", "scenes.preview.exitNoSnapshot": "“{scene}” is active but its before-entering snapshot is gone: leaving only clears the mode flag, the runtime will not go back.", "scenes.preview.row.serversOn": "Enables {count} MCP server(s): {names}", "scenes.preview.row.serversOff": "Disables {count} MCP server(s): {names}", "scenes.preview.row.toolsOn": "Puts back {count} MCP tool(s): {names}", "scenes.preview.row.toolsOff": "Disables {count} MCP tool(s): {names}", "scenes.preview.row.skillsOn": "Enables {count} skill(s): {names}", "scenes.preview.row.skillsOff": "Disables {count} skill(s): {names}", "scenes.preview.row.personasOn": "Enables {count} persona(s): {names}", "scenes.preview.row.personasOff": "Disables {count} persona(s): {names}", "scenes.preview.enter.servers": "{count} MCP server(s) stay on: {names}", "scenes.preview.enter.memories": "{count} memory(ies) enabled: {names}", "scenes.preview.enter.personas": "{count} subagent(s) enabled: {names}", "scenes.preview.enter.personasNone": "No subagent stays enabled",
|
|
5713
5921
|
"scenes.preview.bind.prompt": "Global prompt: switch to “{name}”",
|
|
5714
5922
|
"scenes.preview.bind.promptSame": "Global prompt: unchanged (still “{name}”)",
|
|
@@ -6009,7 +6217,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6009
6217
|
* 原文:"the pre-step rejection that produced it discarded the claimed messages")。
|
|
6010
6218
|
* 没有这条横幅,用户看到的就是"消息没了,也没有任何解释"。
|
|
6011
6219
|
*
|
|
6012
|
-
* 出口写在文案里:解铃在「设置 →
|
|
6220
|
+
* 出口写在文案里:解铃在「设置 → 工具管理 → 兼容」的访问令牌框 —— 那条路全是只读 op,
|
|
6013
6221
|
* 令牌没验过照样打得开(见 request-gate.ts 的白名单)。
|
|
6014
6222
|
*/
|
|
6015
6223
|
let tokenBanner = null
|
|
@@ -6040,7 +6248,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6040
6248
|
tokenBannerText.className = 'dsm-token-banner-text'
|
|
6041
6249
|
tokenBanner.appendChild(tokenBannerText)
|
|
6042
6250
|
// 「填写令牌」:点开一个自包含的弹窗,填对自动关(用户 2026-10-12 提案)。
|
|
6043
|
-
// 此前这颗键是"跳到设置 →
|
|
6251
|
+
// 此前这颗键是"跳到设置 → 工具管理 → 兼容",但横幅是在**任何**页面都可能出现的,
|
|
6044
6252
|
// 从对话页点它时设置面板往往根本没开 —— 而宿主没有"打开设置"的公开服务,
|
|
6045
6253
|
// 于是那颗键要么没反应、要么得靠认领宿主 DOM 里自己那一行导航(又脆又绕)。
|
|
6046
6254
|
// 改成弹窗之后,这条键不再依赖用户当时在哪一页,也不再碰宿主 DOM。
|
|
@@ -6177,6 +6385,166 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6177
6385
|
// 「整串都是 `•`」,客户端再引入 `****` 会让那个判据失效。
|
|
6178
6386
|
|
|
6179
6387
|
// 顶部栏右端:GitHub 链接(issues 在仓库)+ 版本号(全界面唯一显示处)。
|
|
6388
|
+
|
|
6389
|
+
// ── 设置侧栏「工具管理」那一行的图标(2026-10-18)─────────────────────────
|
|
6390
|
+
//
|
|
6391
|
+
// 为什么需要它:宿主 ui-settings-general 的 SettingsPanel 用 `navIcon(row.id)`
|
|
6392
|
+
// 挑侧栏字形,那是一张封闭名单 —— account / models / agent-presets /
|
|
6393
|
+
// plugins / archived-sessions 各有专用图标,**其余 id 一律落到默认齿轮**。
|
|
6394
|
+
// `settings.section` 这个 slot 只投影 id / order / label 三项(宿主 bundle 里
|
|
6395
|
+
// SlotCore 的注册描述符与运行时 slot 清单都只有这三个字段),注册方没有图标可传,
|
|
6396
|
+
// 所以每一个第三方分区都戴着那颗齿轮,本插件也是。
|
|
6397
|
+
//
|
|
6398
|
+
// 做法(dshmarket / dsh-better-sidebar / dsh-skill-mcp-panel 三家都是这么干的,
|
|
6399
|
+
// 先例见 dshmarket 的 settings-nav-icon.ts):本插件的客户端 fiber 在场时,
|
|
6400
|
+
// 认领设置面板里**可见文字等于本分区当前 label** 的那一行,给它打一个标记属性,
|
|
6401
|
+
// 再用一条样式把宿主的齿轮 svg 藏起来、在 ::before 上画我们自己的图。
|
|
6402
|
+
//
|
|
6403
|
+
// 几条边界,逐条对应"会不会出事":
|
|
6404
|
+
// · 认领判据只有**文案全等**(isOwnNavRow)。空 label 匹配不到任何行 ——
|
|
6405
|
+
// 语言还没解析出来时不能把整条 nav 都标上。
|
|
6406
|
+
// · 只给标记加属性、注入一条样式,**不碰宿主的 DOM 结构与 React 树**。
|
|
6407
|
+
// · 样式与 observer 都长在 ctx.effect 里,fiber 卸载时一起撤(撤的时候顺手
|
|
6408
|
+
// 把标记擦掉,不会留下污染给下一个打开的面板)。
|
|
6409
|
+
// · 宿主重绘面板时 React 会把我们换掉的齿轮**改回来**,所以用 MutationObserver
|
|
6410
|
+
// 盯着 document.body 重新贴;这一点不是锦上添花,是这条路能成立的唯一原因。
|
|
6411
|
+
// · 宿主哪天给 settings.section 加了 icon 字段,这个模块(连同调用点)就可以删。
|
|
6412
|
+
//
|
|
6413
|
+
// 图形用 CSS `mask-image` 而不是 <img>:mask 只读 alpha,可见颜色取自元素的
|
|
6414
|
+
// `background-color: currentColor`,于是侧栏选中/悬停/主题切换时图标自动跟着变,
|
|
6415
|
+
// 与旁边那排宿主图标完全一致。<img> 做不到这一点(它是死颜色)。
|
|
6416
|
+
// 代价是 SVG 必须**纯黑**绘制 —— mask 只认 alpha,任何非黑填充都会变成实心块。
|
|
6417
|
+
//
|
|
6418
|
+
// ⚠️ 本片整段都在 `apply()` 体内(40 片那个 `apply(ctx) {` 之后的所有分片都是),
|
|
6419
|
+
// 所以 `installSettingsNavIcon(...)` 的调用必须写在**这些 const 之后** ——
|
|
6420
|
+
// 写在 40 片那个位置会撞 const 的 TDZ(`Cannot access 'NAV_ICON_MARKER'
|
|
6421
|
+
// before initialization`),宿主当场 boot 失败。2026-10-18 就是这么挂的。
|
|
6422
|
+
|
|
6423
|
+
/** 标记本插件自己那一行的属性名(前缀跟着本插件,避免与别家插件的标记撞名)。 */
|
|
6424
|
+
var NAV_ICON_MARKER = 'data-dsm-nav-icon'
|
|
6425
|
+
|
|
6426
|
+
/**
|
|
6427
|
+
* 设置面板的侧栏行。宿主把每个 settings.section 条目渲染成面板 <nav> 里的
|
|
6428
|
+
* 一个 <button>(SettingsPanel 在 dsh-client-ui-settings-general)。
|
|
6429
|
+
*/
|
|
6430
|
+
var NAV_ROW_SELECTOR = '[role="dialog"] nav button'
|
|
6431
|
+
|
|
6432
|
+
/**
|
|
6433
|
+
* 图标本体:三层叠放的方块(工具管理),取自 docs/images/icon.svg。
|
|
6434
|
+
*
|
|
6435
|
+
* 纯黑是刻意的(见上面说明):mask 只读 alpha。viewBox 与源文件一致(1024),
|
|
6436
|
+
* 渲染尺寸由 CSS 的 mask-size 决定,所以这里不写宽高。
|
|
6437
|
+
*/
|
|
6438
|
+
function navIconMaskSvg() {
|
|
6439
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="#000">'
|
|
6440
|
+
+ '<path d="M592 336h-416c-52.928 0-96 43.072-96 96v416c0 52.928 43.072 96 96 96h416c52.928 0 96-43.072 96-96v-416c0-52.928-43.072-96-96-96z m32 512a32 32 0 0 1-32 32h-416c-17.632 0-32-14.336-32-32v-416c0-17.632 14.368-32 32-32h416c17.664 0 32 14.368 32 32v416z"/>'
|
|
6441
|
+
+ '<path d="M720 208h-416a32 32 0 0 0 0 64h416c17.664 0 32 14.368 32 32v416a32 32 0 1 0 64 0v-416c0-52.928-43.072-96-96-96zM528 752h-288a32 32 0 1 0 0 64h288a32 32 0 1 0 0-64z"/>'
|
|
6442
|
+
+ '<path d="M848 80h-416a32 32 0 0 0 0 64h416c17.664 0 32 14.368 32 32v416a32 32 0 1 0 64 0v-416c0-52.928-43.072-96-96-96z"/>'
|
|
6443
|
+
+ '</svg>'
|
|
6444
|
+
}
|
|
6445
|
+
|
|
6446
|
+
/** mask 的 data URL(运行时编码,不手写转义)。 */
|
|
6447
|
+
function navIconMaskUrl(svg) {
|
|
6448
|
+
return 'data:image/svg+xml,' + encodeURIComponent(svg || navIconMaskSvg())
|
|
6449
|
+
}
|
|
6450
|
+
|
|
6451
|
+
/**
|
|
6452
|
+
* 这一行是不是本插件的。
|
|
6453
|
+
*
|
|
6454
|
+
* 纯函数,也是这套逻辑**唯一**的判定:可见文字等于宿主此刻正在投影的本分区
|
|
6455
|
+
* label。空 label 一律不匹配 —— 语言没解析出来时不能把整条 nav 都认下来。
|
|
6456
|
+
*/
|
|
6457
|
+
function isOwnNavRow(rowText, wantedLabel) {
|
|
6458
|
+
var wanted = String(wantedLabel == null ? '' : wantedLabel).trim()
|
|
6459
|
+
if (wanted.length === 0) return false
|
|
6460
|
+
return String(rowText == null ? '' : rowText).trim() === wanted
|
|
6461
|
+
}
|
|
6462
|
+
|
|
6463
|
+
/**
|
|
6464
|
+
* 标记行的样式:藏掉宿主的齿轮,在 ::before 上画我们的图。
|
|
6465
|
+
*
|
|
6466
|
+
* 16px 与宿主那排字形同尺寸(它的 navIcon 走 size: 16),位置由 flex 排在
|
|
6467
|
+
* 标签之前 —— 按钮本身是 flex 行,::before 作为第一个子元素天然就位。
|
|
6468
|
+
*/
|
|
6469
|
+
function navIconCss(maskUrl) {
|
|
6470
|
+
return [
|
|
6471
|
+
'[' + NAV_ICON_MARKER + '] > svg { display: none; }',
|
|
6472
|
+
'[' + NAV_ICON_MARKER + ']::before {',
|
|
6473
|
+
" content: '';",
|
|
6474
|
+
' flex: none;',
|
|
6475
|
+
' width: 16px;',
|
|
6476
|
+
' height: 16px;',
|
|
6477
|
+
' background-color: currentColor;',
|
|
6478
|
+
' -webkit-mask-image: url("' + maskUrl + '");',
|
|
6479
|
+
' mask-image: url("' + maskUrl + '");',
|
|
6480
|
+
' -webkit-mask-repeat: no-repeat;',
|
|
6481
|
+
' mask-repeat: no-repeat;',
|
|
6482
|
+
' -webkit-mask-position: center;',
|
|
6483
|
+
' mask-position: center;',
|
|
6484
|
+
' -webkit-mask-size: 16px 16px;',
|
|
6485
|
+
' mask-size: 16px 16px;',
|
|
6486
|
+
'}'
|
|
6487
|
+
].join('\n')
|
|
6488
|
+
}
|
|
6489
|
+
|
|
6490
|
+
/**
|
|
6491
|
+
* 装上侧栏图标。
|
|
6492
|
+
*
|
|
6493
|
+
* @param ctx 客户端上下文(effect 的归属者)。
|
|
6494
|
+
* @param resolveLabel 本分区当前 label —— 与 settings.section 注册时传的是
|
|
6495
|
+
* 同一个 thunk,所以切语言时两边永远一致(label 变了,判据跟着变,不会认错行)。
|
|
6496
|
+
*/
|
|
6497
|
+
function installSettingsNavIcon(ctx, resolveLabel) {
|
|
6498
|
+
if (typeof document === 'undefined') return
|
|
6499
|
+
ctx.effect(() => {
|
|
6500
|
+
const tag = document.createElement('style')
|
|
6501
|
+
tag.textContent = navIconCss(navIconMaskUrl())
|
|
6502
|
+
document.head.appendChild(tag)
|
|
6503
|
+
let disposed = false
|
|
6504
|
+
let scheduled = false
|
|
6505
|
+
const sync = () => {
|
|
6506
|
+
scheduled = false
|
|
6507
|
+
if (disposed) return
|
|
6508
|
+
const wanted = resolveLabel()
|
|
6509
|
+
const rows = document.querySelectorAll(NAV_ROW_SELECTOR)
|
|
6510
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
6511
|
+
const row = rows[i]
|
|
6512
|
+
if (isOwnNavRow(row.textContent, wanted)) row.setAttribute(NAV_ICON_MARKER, '')
|
|
6513
|
+
else row.removeAttribute(NAV_ICON_MARKER)
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6516
|
+
// 宿主每次重绘面板(打开/关闭、切语言、内容变化)都会重建那几行,
|
|
6517
|
+
// 重建后我们换掉的齿轮会被 React 改回去 —— 所以必须能重新贴。
|
|
6518
|
+
// queueMicrotask 合批:一次交互往往连触发多轮 mutation,不必每条都扫一遍。
|
|
6519
|
+
const schedule = () => {
|
|
6520
|
+
if (scheduled || disposed) return
|
|
6521
|
+
scheduled = true
|
|
6522
|
+
queueMicrotask(sync)
|
|
6523
|
+
}
|
|
6524
|
+
sync()
|
|
6525
|
+
const observer = new MutationObserver(schedule)
|
|
6526
|
+
observer.observe(document.body, { childList: true, subtree: true, characterData: true })
|
|
6527
|
+
return () => {
|
|
6528
|
+
disposed = true
|
|
6529
|
+
observer.disconnect()
|
|
6530
|
+
const marked = document.querySelectorAll('[' + NAV_ICON_MARKER + ']')
|
|
6531
|
+
for (let i = 0; i < marked.length; i += 1) marked[i].removeAttribute(NAV_ICON_MARKER)
|
|
6532
|
+
tag.remove()
|
|
6533
|
+
}
|
|
6534
|
+
}, 'dsh-plugin-tool-management: settings nav icon')
|
|
6535
|
+
}
|
|
6536
|
+
|
|
6537
|
+
// 调用点放在本片末尾(见上面 ⚠️ 那段):必须在这些 const 初始化之后。
|
|
6538
|
+
//
|
|
6539
|
+
// 整段包在 try 里:侧栏图标是**纯装饰**,取不到 DOM 或宿主换了面板结构时最坏
|
|
6540
|
+
// 应该只是"还是那颗齿轮",绝不能把 apply 带崩 —— 2026-10-18 那次 TDZ 就是
|
|
6541
|
+
// 一个装饰性功能让整个宿主 boot 失败("entry did not activate")。
|
|
6542
|
+
try {
|
|
6543
|
+
installSettingsNavIcon(ctx, function () { return t('nav.title') })
|
|
6544
|
+
} catch (e) {
|
|
6545
|
+
try { console.warn('[dsh-plugin-tool-management] 设置侧栏图标不可用,沿用宿主默认图标:' + errMsg(e)) } catch (e2) { /* 控制台不可用 */ }
|
|
6546
|
+
}
|
|
6547
|
+
|
|
6180
6548
|
function FeedbackLinks() {
|
|
6181
6549
|
return React.createElement('div', { className: 'dsm-feedback-links' },
|
|
6182
6550
|
React.createElement('a', { className: 'dsm-feedback-link', href: 'https://github.com/ouli-1242/dsh-plugin-tool-management', target: '_blank', rel: 'noreferrer', 'aria-label': t('link.project'), title: t('link.project') },
|
|
@@ -6207,14 +6575,6 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6207
6575
|
const [confirmRow, setConfirmRow] = React.useState(null)
|
|
6208
6576
|
const [reveal, setReveal] = React.useState(false)
|
|
6209
6577
|
const [settings, setSettings] = React.useState(null)
|
|
6210
|
-
const [noteDraft, setNoteDraft] = React.useState('')
|
|
6211
|
-
// 备注保存的**就地**回执(用户 2026-10-12:「mcp 备注在锁定情况下这个红色的提示词
|
|
6212
|
-
// 不应该显示在 mcp 页,应该显示在详细页」)。此前 saveNote 走的是共用的 `run()`,
|
|
6213
|
-
// 结果一律 setMsg —— 那是**页面级**提示条,而动作发生在弹窗里:锁定期间点「保存备注」
|
|
6214
|
-
// 被冻结闸拒掉,红字却出现在弹窗**后面**的页面上,关上弹窗才看得见,根本对不上号。
|
|
6215
|
-
// 备注这一个动作的回执只属于这个弹窗,所以单独一个槽(成功 / 失败 / 告警都走它),
|
|
6216
|
-
// 打开与关闭详情时清空,只在备注小节里渲染。
|
|
6217
|
-
const [noteNotice, setNoteNotice] = React.useState(null)
|
|
6218
6578
|
const [compactConfirm, setCompactConfirm] = React.useState(false)
|
|
6219
6579
|
// A1:粘贴 JSON 导入 / 导出两个弹窗(0.15.0)。
|
|
6220
6580
|
const [jsonImportOpen, setJsonImportOpen] = React.useState(false)
|
|
@@ -6378,6 +6738,11 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6378
6738
|
headers: kvToLines(row.headers),
|
|
6379
6739
|
env: kvToLines(row.env),
|
|
6380
6740
|
level: row.level === 'global' ? 'global' : 'project',
|
|
6741
|
+
// 备注也在编辑表单里改(用户 2026-10-18:「mcp 备注编辑功能应该在编辑页,
|
|
6742
|
+
// 详细页只能看」):详情页是只读视图,写动作一律回到这张表。
|
|
6743
|
+
// `origNote` 留一份原文用于判断"这次到底改没改备注"—— 没改就不多写一次侧车。
|
|
6744
|
+
note: row.notes || '',
|
|
6745
|
+
origNote: row.notes || '',
|
|
6381
6746
|
})
|
|
6382
6747
|
}
|
|
6383
6748
|
const closeForm = () => { setFormError(null); setFormModal(null) }
|
|
@@ -6421,7 +6786,17 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6421
6786
|
if (payload.mode === 'edit') args.id = payload.id
|
|
6422
6787
|
// 备注只在新增时带:服务端在写补丁的同一次锁里落进侧车(见 mcpmAdd)。
|
|
6423
6788
|
else args.note = payload.note
|
|
6424
|
-
|
|
6789
|
+
// 编辑态:备注在**保存之后**单独写一次(`mcpm-edit` 不碰侧车)。改过才写 ——
|
|
6790
|
+
// 没改就多一次写盘 + 刷新 updatedAt,没有意义。
|
|
6791
|
+
const noteChanged = payload.mode === 'edit' && String(payload.note || '').trim() !== String(payload.origNote || '').trim()
|
|
6792
|
+
run(payload.mode === 'edit' ? 'mcpm-edit' : 'mcpm-add', args, 'form', function () {
|
|
6793
|
+
closeForm()
|
|
6794
|
+
if (noteChanged) {
|
|
6795
|
+
apiCall('mcpm-note', { id: payload.id, note: payload.note }).then(function (res) {
|
|
6796
|
+
if (res && res.ok === false) setMsg({ kind: 'warn', text: mt('mcp.msg.noteFailed') })
|
|
6797
|
+
}).catch(function () { setMsg({ kind: 'warn', text: mt('mcp.msg.noteFailed') }) }).then(function () { refresh() })
|
|
6798
|
+
}
|
|
6799
|
+
})
|
|
6425
6800
|
}
|
|
6426
6801
|
|
|
6427
6802
|
// 「启动中」的乐观记账(用户 2026-10-15:呼吸灯要过一会才出现):翻开开关那一刻宿主
|
|
@@ -6457,7 +6832,13 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6457
6832
|
setState((prev) => Object.assign({}, prev, {
|
|
6458
6833
|
rows: (prev.rows || []).map((item) => (
|
|
6459
6834
|
item.id === row.id && item.level === row.level
|
|
6460
|
-
|
|
6835
|
+
// `live.enabled` 一起乐观翻掉(用户 2026-10-18:「关闭后绿灯应该立即消失变灰,
|
|
6836
|
+
// 现在有延迟」):列表里那盏绿灯读的是宿主上一轮轮询的 `live`,只改 `disabled`
|
|
6837
|
+
// 的话它得等下一次刷新(默认 5 秒)才灭 —— 点完开关的五秒里界面还在说"它在跑"。
|
|
6838
|
+
? Object.assign({}, item, {
|
|
6839
|
+
disabled: nextDisabled,
|
|
6840
|
+
live: item.live ? Object.assign({}, item.live, { enabled: !nextDisabled }) : item.live,
|
|
6841
|
+
})
|
|
6461
6842
|
: item
|
|
6462
6843
|
)),
|
|
6463
6844
|
}))
|
|
@@ -6510,23 +6891,8 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6510
6891
|
if (fresh) setDetail(Object.assign({}, detail, { row: fresh }))
|
|
6511
6892
|
})
|
|
6512
6893
|
}
|
|
6513
|
-
//
|
|
6514
|
-
//
|
|
6515
|
-
// 失败时**原样透出服务端那句**(锁定态就是冻结闸的「场景已锁定(…):先到场景页解锁
|
|
6516
|
-
// 再修改」)—— 它正是用户要看到的那句话,只是位置从页面挪进了弹窗。
|
|
6517
|
-
const saveNote = () => {
|
|
6518
|
-
const row = detail && detail.row
|
|
6519
|
-
if (!row) return
|
|
6520
|
-
setNoteNotice(null)
|
|
6521
|
-
setBusy('note')
|
|
6522
|
-
apiCall('mcpm-note', { id: row.id, note: noteDraft }).then((res) => {
|
|
6523
|
-
if (res && res.ok) {
|
|
6524
|
-
setDetail(Object.assign({}, detail, { row: Object.assign({}, row, { notes: noteDraft.trim() }) }))
|
|
6525
|
-
setNoteNotice(opMsg(res))
|
|
6526
|
-
refresh()
|
|
6527
|
-
} else setNoteNotice({ kind: 'err', text: (res && res.error) || mt('mcp.msg.failed') })
|
|
6528
|
-
}).catch((e) => setNoteNotice({ kind: 'err', text: errMsg(e) })).then(() => setBusy(null))
|
|
6529
|
-
}
|
|
6894
|
+
// 备注的写入随「编辑」一起提交(见 submitForm 的 noteChanged),详情页只读 ——
|
|
6895
|
+
// 原先这里那颗 saveNote / noteDraft 随「详细页只能看」一并删除。
|
|
6530
6896
|
const confirmRemove = () => {
|
|
6531
6897
|
const row = confirmRow
|
|
6532
6898
|
setConfirmRow(null)
|
|
@@ -6560,10 +6926,6 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6560
6926
|
// 请求序号(每次开窗 +1):两处详情弹窗共用同一份 `detail`,连点两台服务器时
|
|
6561
6927
|
// 先发的响应可能后到并覆盖后发的那份 —— 序号对不上就丢弃。
|
|
6562
6928
|
const seq = ++detailSeqRef.current
|
|
6563
|
-
setNoteDraft(row.notes || '')
|
|
6564
|
-
// 换一台服务器 = 换一份备注:上一台的保存回执必须一起清掉,否则新弹窗会顶着
|
|
6565
|
-
// 另一台服务器的红字(成功回执同理)。
|
|
6566
|
-
setNoteNotice(null)
|
|
6567
6929
|
setDetail({ row, loading: true, error: null, tools: [] })
|
|
6568
6930
|
apiCall('mcpm-tools', { serverName: row.serverName }).then((res) => {
|
|
6569
6931
|
if (seq !== detailSeqRef.current) return
|
|
@@ -6578,7 +6940,6 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6578
6940
|
const closeDetail = () => {
|
|
6579
6941
|
detailSeqRef.current += 1
|
|
6580
6942
|
setDetail(null)
|
|
6581
|
-
setNoteNotice(null)
|
|
6582
6943
|
}
|
|
6583
6944
|
|
|
6584
6945
|
const toggleTool = (row, tool) => {
|
|
@@ -6900,9 +7261,10 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6900
7261
|
React.createElement('textarea', { className: 'dsm-control dsm-textarea-sm', value: formModal.headers, placeholder: 'Authorization=Bearer xxx', onChange: setFormField('headers') }))
|
|
6901
7262
|
: null,
|
|
6902
7263
|
// 备注放在表单**最下面**:它是可选项,说的又是"这台怎么用",不该插在必填的连接参数中间。
|
|
6903
|
-
// 默认不填 =
|
|
6904
|
-
//
|
|
6905
|
-
|
|
7264
|
+
// 默认不填 = 侧车里不产生条目。新增与编辑**都出现**(用户 2026-10-18:「备注编辑功能
|
|
7265
|
+
// 应该在编辑页,详细页只能看」):详情页只读,写备注这一件事只在这里做。
|
|
7266
|
+
// 上限就是注入段的截断长度 `MCP_NOTE_MAX` —— 写多少模型就能读到多少。
|
|
7267
|
+
React.createElement('label', { className: 'dsm-field' },
|
|
6906
7268
|
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
6907
7269
|
React.createElement('span', { className: 'dsm-label' }, mt('mcp.field.note')),
|
|
6908
7270
|
React.createElement('span', { className: 'dsm-char-count' }, String(formModal.note || '').length + ' / ' + MCP_NOTE_MAX)),
|
|
@@ -6952,26 +7314,14 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
|
|
|
6952
7314
|
React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.status')),
|
|
6953
7315
|
React.createElement('div', { className: 'dsm-feedback' + (detailStatus.tone === 'bad' ? ' dsm-error' : ' dsm-warning') }, detailHint)) : null,
|
|
6954
7316
|
React.createElement('div', { className: 'dsm-detail-section' },
|
|
6955
|
-
//
|
|
6956
|
-
//
|
|
6957
|
-
//
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
React.createElement('
|
|
6962
|
-
|
|
6963
|
-
// (用户 2026-10-15:上一版把它与「刷新工具」都摆成标题行里的 quiet 键,
|
|
6964
|
-
// 两颗一模一样、还落在相邻两节的同一高度 —— 备注输完下意识就点到「刷新
|
|
6965
|
-
// 工具」。两个信号把两颗分开:位置贴着 textarea,提交动作跟着被提交的
|
|
6966
|
-
// 字段走;权重用实心主色,它才是备注区的提交,「刷新工具」保持 quiet
|
|
6967
|
-
// 的工具区动作)。
|
|
6968
|
-
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
6969
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null || noteDraft.trim() === String(detail.row.notes || ''), onClick: saveNote }, mt('mcp.detail.note.save'))),
|
|
6970
|
-
// 备注保存的回执就地渲染(见 noteNotice 的说明)。文案原样来自服务端:
|
|
6971
|
-
// 成功走 Notice 的 ok 档(右下角浮出的 toast,与全站成功提示同一副长相);
|
|
6972
|
-
// 被冻结闸拒掉是弹窗内的一条红色 feedback —— 这正是用户要看到的那句话,
|
|
6973
|
-
// 只是位置从页面挪进了弹窗。
|
|
6974
|
-
React.createElement(Notice, { kind: noteNotice && noteNotice.kind, text: noteNotice && noteNotice.text })),
|
|
7317
|
+
// 备注在详情页**只读**(用户 2026-10-18:「mcp 备注编辑功能应该在编辑页,详细页只能看」):
|
|
7318
|
+
// 详情是"看这台现在配了什么"的地方,要改就走 ⋯ →「编辑」那张表 ——
|
|
7319
|
+
// 写动作只有一个入口,不会出现"改了这里忘了那里"。
|
|
7320
|
+
// 没备注时**也渲染同一个框**(用户 2026-10-18:「没备注也应该有边框,只是显示灰色字
|
|
7321
|
+
// 当前没有备注」):有备注时是一块正文,没备注时只剩标题、这一节看着像没渲染出来。
|
|
7322
|
+
React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.note')),
|
|
7323
|
+
React.createElement('p', { className: 'dsm-detail-note' + (detail.row.notes ? '' : ' dsm-detail-note-empty') },
|
|
7324
|
+
detail.row.notes || mt('mcp.detail.note.empty'))),
|
|
6975
7325
|
React.createElement('div', { className: 'dsm-detail-section' },
|
|
6976
7326
|
React.createElement('div', { className: 'dsm-detail-title dsm-detail-title-row' },
|
|
6977
7327
|
mt('mcp.detail.tools', { count: detail.loading ? '…' : (detail.tools || []).length }),
|
|
@@ -8396,7 +8746,7 @@ function enabledMemoryCount(rules) {
|
|
|
8396
8746
|
/**
|
|
8397
8747
|
* 「填写令牌」弹窗(用户 2026-10-12 提案:横幅那颗键点开一个弹窗,填对自动关)。
|
|
8398
8748
|
*
|
|
8399
|
-
* 为什么是弹窗而不是"跳到设置 →
|
|
8749
|
+
* 为什么是弹窗而不是"跳到设置 → 工具管理 → 兼容":那条路要求设置面板已经挂载
|
|
8400
8750
|
* (`navigateToTab` 只在 ToolsSection 挂载时才有值),从对话页点它就得先想办法把宿主的
|
|
8401
8751
|
* 设置对话框打开 —— 宿主没有这个服务,只能去 DOM 里认领它自己的设置导航行,又脆又绕。
|
|
8402
8752
|
* 一个自包含的输入框把整条依赖都去掉了:不碰宿主 DOM、不需要跳页、也不必知道用户当时
|
|
@@ -9079,6 +9429,10 @@ function enabledMemoryCount(rules) {
|
|
|
9079
9429
|
const anyLocked = ysx[0], setAnyLocked = ysx[1]
|
|
9080
9430
|
const zsx = React.useState([])
|
|
9081
9431
|
const sceneList = zsx[0], setSceneList = zsx[1]
|
|
9432
|
+
// 技能来源表:只给回收站详情把 `<rootKey>/<name>` 里的根键翻成人看的名字用
|
|
9433
|
+
//(`custom-<uuid>` 那种串摆给人看没有意义,见 manifestInfoRows 的注释)。
|
|
9434
|
+
const rxs = React.useState([])
|
|
9435
|
+
const skillRoots = rxs[0], setSkillRoots = rxs[1]
|
|
9082
9436
|
React.useEffect(function () { load(); return undefined }, [])
|
|
9083
9437
|
const dataOf = function (id) { return (data && data[id]) || { loading: true, error: null, entries: [] } }
|
|
9084
9438
|
function setSlot(id, slot) {
|
|
@@ -9094,6 +9448,12 @@ function enabledMemoryCount(rules) {
|
|
|
9094
9448
|
})
|
|
9095
9449
|
// 锁定态(只读判断,失败不拦 —— 写操作到了服务端还有 frozen 这道闸);
|
|
9096
9450
|
// `sceneList` 只有记忆组用得上(每条要说清属于哪个场景)。
|
|
9451
|
+
// 技能来源表只给回收站详情翻根键用:读不到就退回原键,不因为这一趟失败影响列表。
|
|
9452
|
+
if (groupIds.indexOf('skills') >= 0) {
|
|
9453
|
+
apiCall('skill-state', {}).then(function (res) {
|
|
9454
|
+
if (res && res.ok) setSkillRoots(res.roots || [])
|
|
9455
|
+
}).catch(function () { })
|
|
9456
|
+
}
|
|
9097
9457
|
var needLock = groupIds.some(function (gid) { return GROUPS[gid].frozen === true || gid === 'memories' })
|
|
9098
9458
|
if (!needLock) return
|
|
9099
9459
|
apiCall('rules-list', {}).then(function (res) {
|
|
@@ -9275,7 +9635,7 @@ function enabledMemoryCount(rules) {
|
|
|
9275
9635
|
function openPreview(g, item) {
|
|
9276
9636
|
const id = String(idOfOf(g)(item))
|
|
9277
9637
|
const meta = g.previewMeta ? g.previewMeta(item) : null
|
|
9278
|
-
setPreview({ group: g.id, id: id, name: g.nameOf(item), meta: meta, loading: meta === null, error: null, files: [] })
|
|
9638
|
+
setPreview({ group: g.id, id: id, name: g.nameOf(item), meta: meta, loading: meta === null, error: null, files: [], manifest: null })
|
|
9279
9639
|
if (meta !== null) return
|
|
9280
9640
|
const patch = function (fields) {
|
|
9281
9641
|
setPreview(function (cur) {
|
|
@@ -9284,7 +9644,7 @@ function enabledMemoryCount(rules) {
|
|
|
9284
9644
|
})
|
|
9285
9645
|
}
|
|
9286
9646
|
apiCall('trash-preview', { domain: g.id, id: id }).then(function (res) {
|
|
9287
|
-
if (res && res.ok) patch({ loading: false, error: null, files: res.files || [] })
|
|
9647
|
+
if (res && res.ok) patch({ loading: false, error: null, files: res.files || [], manifest: res.manifest || null })
|
|
9288
9648
|
else patch({ loading: false, error: translateError(t, res), files: [] })
|
|
9289
9649
|
}).catch(function (e) { patch({ loading: false, error: errMsg(e), files: [] }) })
|
|
9290
9650
|
}
|
|
@@ -9498,21 +9858,59 @@ function enabledMemoryCount(rules) {
|
|
|
9498
9858
|
body = React.createElement('div', { className: 'dsm-empty' }, t('trash.preview.loading'))
|
|
9499
9859
|
} else {
|
|
9500
9860
|
const files = preview.files || []
|
|
9861
|
+
// 记忆 / 场景 / 技能这类条目:清单里写着"删掉的是什么"(场景档案勾了什么、记忆属于
|
|
9862
|
+
// 哪个场景、什么形态),文件列表只是"里面有哪些文件"。用户 2026-10-18:「记忆回收站
|
|
9863
|
+
// 的详细应该记忆在第一栏,附件在下面」—— 所以顺序固定为**条目信息在前、文件在后**,
|
|
9864
|
+
// 不是把两类混成一张表。
|
|
9865
|
+
const info = manifestInfoRows(t, preview.group, preview.manifest, skillRoots)
|
|
9866
|
+
// 技能条目套用「技能详情」弹窗的排版(用户 2026-10-18:「技能的回收站详细还是挤在一起,
|
|
9867
|
+
// 不能套用技能详细弹窗吗」):正文那份单独成节、其余文件是附件清单,不再一份文件一张
|
|
9868
|
+
// 带边框的卡 —— 那种画法把十个文件挤成十条 240px 的代码窗,什么也读不出来。
|
|
9869
|
+
const isSkill = preview.group === 'skills'
|
|
9870
|
+
const bodyFile = isSkill ? (files.filter(function (f) { return /(^|\/)SKILL\.md$/i.test(String(f.path || '')) })[0] || files[0]) : null
|
|
9871
|
+
const attachFiles = isSkill ? files.filter(function (f) { return f !== bodyFile }) : files
|
|
9501
9872
|
body = React.createElement('div', { className: 'dsm-preview' },
|
|
9873
|
+
info.length
|
|
9874
|
+
? React.createElement(React.Fragment, null,
|
|
9875
|
+
formSection(t('trash.preview.entry')),
|
|
9876
|
+
React.createElement('div', { className: 'dsm-fm' }, info.map(function (row) {
|
|
9877
|
+
return React.createElement('div', { key: row[0], className: 'dsm-fm-row' },
|
|
9878
|
+
React.createElement('div', { className: 'dsm-fm-key' }, row[0]),
|
|
9879
|
+
React.createElement('div', { className: 'dsm-fm-val' }, row[1]))
|
|
9880
|
+
})))
|
|
9881
|
+
: null,
|
|
9882
|
+
isSkill && attachFiles.length
|
|
9883
|
+
? React.createElement(React.Fragment, null,
|
|
9884
|
+
formSection(t('detail.attachments') + ' · ' + attachFiles.length),
|
|
9885
|
+
React.createElement('div', { className: 'dsm-attach-list dsm-attach-cap' }, attachFiles.map(function (f) {
|
|
9886
|
+
return React.createElement('div', { key: f.path, className: 'dsm-file', title: f.path },
|
|
9887
|
+
React.createElement('span', { className: 'dsm-file-name' }, f.path),
|
|
9888
|
+
React.createElement('span', { className: 'dsm-file-meta' }, fmtBytes(f.size)))
|
|
9889
|
+
})))
|
|
9890
|
+
: null,
|
|
9891
|
+
isSkill && bodyFile
|
|
9892
|
+
? React.createElement('div', { className: 'dsm-detail-section' },
|
|
9893
|
+
React.createElement('div', { className: 'dsm-detail-title' }, t('detail.body')),
|
|
9894
|
+
bodyFile.text === undefined
|
|
9895
|
+
? React.createElement('div', { className: 'dsm-preview-flag' }, t('trash.preview.binary'))
|
|
9896
|
+
: React.createElement('pre', { className: 'dsm-code dsm-code-fill' }, bodyFile.text))
|
|
9897
|
+
: null,
|
|
9502
9898
|
React.createElement('div', { className: 'dsm-preview-note' },
|
|
9503
9899
|
t('trash.preview.readonly') + ' · ' + t('trash.preview.files', { count: files.length })),
|
|
9504
|
-
|
|
9505
|
-
?
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
React.createElement('
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9900
|
+
isSkill
|
|
9901
|
+
? null
|
|
9902
|
+
: files.length
|
|
9903
|
+
? React.createElement('div', { className: 'dsm-preview-files' }, files.map(function (f, i) {
|
|
9904
|
+
return React.createElement('section', { key: f.path || i, className: 'dsm-preview-file' },
|
|
9905
|
+
React.createElement('div', { className: 'dsm-preview-file-head' },
|
|
9906
|
+
React.createElement('span', { className: 'dsm-preview-path', title: f.path }, f.path),
|
|
9907
|
+
React.createElement('span', { className: 'dsm-preview-size' }, fmtBytes(f.size))),
|
|
9908
|
+
f.text === undefined ? null : React.createElement('pre', { className: 'dsm-code' }, f.text),
|
|
9909
|
+
// 二进制 / 截断各自如实标一句:不标的话用户会以为"这条记录就这么点内容"。
|
|
9910
|
+
f.binary === true ? React.createElement('div', { className: 'dsm-preview-flag' }, t('trash.preview.binary')) : null,
|
|
9911
|
+
f.truncated === true ? React.createElement('div', { className: 'dsm-preview-flag' }, t('trash.preview.truncated')) : null)
|
|
9912
|
+
}))
|
|
9913
|
+
: React.createElement(EmptyState, { text: t('trash.preview.empty') }))
|
|
9516
9914
|
}
|
|
9517
9915
|
// 只读预览单开一档(见 10-css.js 的 .dsm-modal-preview):这里装的是代码正文,
|
|
9518
9916
|
// S 档 440 一行要折好几次。负载文件那一档再挂 fill(定高 + 代码区吃掉剩余高度)。
|
|
@@ -11394,12 +11792,15 @@ function enabledMemoryCount(rules) {
|
|
|
11394
11792
|
// 右侧那两颗按钮整块挡冒泡 —— 点「恢复」不该顺带把这条勾上。
|
|
11395
11793
|
var at = it.archivedAt || it.createdAt || 0
|
|
11396
11794
|
var metaBits = []
|
|
11397
|
-
if (it.cwd) metaBits.push(React.createElement('span', { key: 'cwd', className: 'dsm-listrow-meta-desc', title: it.cwd }, it.cwd))
|
|
11795
|
+
if (it.cwd) metaBits.push(React.createElement('span', { key: 'cwd', className: 'dsm-listrow-meta-desc dsm-listrow-meta-path', title: it.cwd }, it.cwd))
|
|
11398
11796
|
// 归档时间排在目录**后面且不可收缩**:长目录会被截,但"什么时候收进来的"必须留下
|
|
11399
11797
|
//(保留期默认 30 天,它其实就是"还剩几天")。宿主没给时间就不显示,不拿未知占位。
|
|
11400
11798
|
if (at) metaBits.push(React.createElement('span', { key: 'at', className: 'dsm-listrow-meta-info' }, new Date(at).toLocaleDateString()))
|
|
11401
11799
|
return React.createElement(ListRow, {
|
|
11402
11800
|
key: it.sessionId,
|
|
11801
|
+
// 行比别处松一档(用户 2026-10-18:「归档会话太拥挤了」):这一页的行夹着
|
|
11802
|
+
// 目录 + 日期两段文字,还常驻两颗按钮,48px 的通用行高塞下来标题和元信息贴在一起。
|
|
11803
|
+
className: 'dsm-hist-row',
|
|
11403
11804
|
leading: React.createElement(RowCheck, { checked: selected.has(it.sessionId), label: t('hist.select'), onChange: function () { toggleSelect(it.sessionId) } }),
|
|
11404
11805
|
onToggle: function () { toggleSelect(it.sessionId) },
|
|
11405
11806
|
name: it.title || ('Session ' + shortId(it.sessionId)),
|
|
@@ -11437,21 +11838,25 @@ function enabledMemoryCount(rules) {
|
|
|
11437
11838
|
React.createElement(RowCheck, { checked: allChecked, label: t('hist.selectGroup', { title: g.title }), onChange: function () { toggleGroup(g.key, groupIds) } }),
|
|
11438
11839
|
React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: function () { toggleGroupCollapse(g.key, baseOpen) } },
|
|
11439
11840
|
React.createElement('span', { className: 'dsm-source-title dsm-hist-group-title', title: g.title }, g.title),
|
|
11440
|
-
React.createElement('span', { className: 'dsm-count' }, String(g.items.length))
|
|
11841
|
+
React.createElement('span', { className: 'dsm-count' }, String(g.items.length)),
|
|
11842
|
+
// 目录挪进标题栏、落到第二行(靠 flex-wrap + width:100%):起点与标题左端对齐,
|
|
11843
|
+
// 不再被右侧那颗标签挤着走(用户 2026-10-18:「应该开头对齐……放在标题下面是不是更好」)。
|
|
11844
|
+
g.path ? React.createElement('span', { className: 'dsm-path', title: g.path }, g.path) : null),
|
|
11441
11845
|
// 目录已被删除(dirMissing)时:既不给「重新登记」入口,也不再并列「已移除」标签 ——
|
|
11442
11846
|
// 只留一枚醒目告警,明确告诉用户这个工作区的磁盘文件夹已经不存在了
|
|
11443
11847
|
// (用户 2026-10-12:「如果工作区已经被删除应该不能恢复……要怎么及时知道这个工作区
|
|
11444
11848
|
// 实际文件夹已经被删除了」)。宿主 registerWorkspace 的 realpath 本来就会拒绝
|
|
11445
11849
|
// 不存在的目录,这里把入口提前收掉,避免点了才报错。
|
|
11850
|
+
// 目录随标题走:放进 .dsm-source-head-main 里,靠 flex-wrap 落到第二行、
|
|
11851
|
+
// 与标题左端对齐(用户 2026-10-18:「应该开头对齐……放在标题下面是不是更好」)。
|
|
11446
11852
|
(detached || g.dirMissing) ? React.createElement('div', { className: 'dsm-hist-group-extra' },
|
|
11447
11853
|
g.dirMissing
|
|
11448
11854
|
// 「原目录不存在」:红色描边标签(用户 2026-10-12:「改成『原目录不存在』
|
|
11449
11855
|
// 也加一个边框,但是不能点击」)。边框是它唯一的视觉标记 —— 原先靠行首的
|
|
11450
11856
|
// ⚠ 符号,混在一堆文字里看不出这是一枚"状态标签"。它是个 span,本来就不可点。
|
|
11451
11857
|
? React.createElement('span', { className: 'dsm-hist-dir-missing', title: t('hist.group.dirMissing.hint') }, t('hist.group.dirMissing'))
|
|
11452
|
-
: React.createElement('span', { className: 'dsm-tag', title: t('hist.group.removed.hint') }, t('hist.group.removed')),
|
|
11453
|
-
detached && !g.dirMissing && g.canRegister ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, title: t('hist.btn.registerWs.hint'), onClick: function () { setModal({ type: 'register', path: g.path, title: g.title }) } }, t('hist.btn.registerWs')) : null) : null,
|
|
11454
|
-
g.path ? React.createElement('span', { className: 'dsm-path', title: g.path }, g.path) : null),
|
|
11858
|
+
: detached ? React.createElement('span', { className: 'dsm-tag', title: t('hist.group.removed.hint') }, t('hist.group.removed')) : null,
|
|
11859
|
+
detached && !g.dirMissing && g.canRegister ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, title: t('hist.btn.registerWs.hint'), onClick: function () { setModal({ type: 'register', path: g.path, title: g.title }) } }, t('hist.btn.registerWs')) : null) : null),
|
|
11455
11860
|
open ? React.createElement('div', { className: 'dsm-source-body' }, g.items.map(renderRow)) : null)
|
|
11456
11861
|
}
|
|
11457
11862
|
|
|
@@ -11685,8 +12090,9 @@ function enabledMemoryCount(rules) {
|
|
|
11685
12090
|
/**
|
|
11686
12091
|
* 场景描述最多存多少个字(新建/编辑表单的 `maxLength` 与字数计数都用它)。
|
|
11687
12092
|
*
|
|
11688
|
-
*
|
|
11689
|
-
*
|
|
12093
|
+
* 行内那一格**不靠这个数撑腰**:`.dsm-scene-tile-desc` 是 `nowrap` + 省略号,
|
|
12094
|
+
* 再长也只占一行、不会把行撑成纵向(2026-10-18 起描述与名字同一行)。显示层仍按它裁一次,
|
|
12095
|
+
* 兜住历史数据。
|
|
11690
12096
|
*/
|
|
11691
12097
|
var SCENE_DESC_MAX = 300
|
|
11692
12098
|
/** 记忆描述在勾选行里最多显示多少个字:记忆正文可能很长,行内只留一行,超长截断(全文进 title)。 */
|
|
@@ -11738,6 +12144,68 @@ function enabledMemoryCount(rules) {
|
|
|
11738
12144
|
return clipText(scene && scene.description, SCENE_DESC_MAX)
|
|
11739
12145
|
}
|
|
11740
12146
|
|
|
12147
|
+
/**
|
|
12148
|
+
* 回收站详情里「条目信息」那几行(用户 2026-10-18:「回收站的详细功能做的不够好,显示的内容
|
|
12149
|
+
* 不够全,例如场景没有显示勾选了什么,设置了什么」)。
|
|
12150
|
+
*
|
|
12151
|
+
* 数据来自 `trash-preview` 回传的 `manifest`(就是条目目录里那份 `manifest.json`),按域各取:
|
|
12152
|
+
* · scenes —— 记录(显示名 / 备注 / 锁定)+ 档案四段勾了多少条 + 绑的提示词与工具表;
|
|
12153
|
+
* · memories —— 属于哪个场景、形态(flat / bundle)、删除时间;
|
|
12154
|
+
* · skills / presets / quick / agents —— 名字 + 删除时间(它们的"内容"就是文件本身)。
|
|
12155
|
+
*
|
|
12156
|
+
* 只报**有值的**:段未定义 = 该域不碰(不是"勾了 0 个"),两者不能混成一句话。
|
|
12157
|
+
*/
|
|
12158
|
+
function manifestInfoRows(t, domain, manifest, skillRoots) {
|
|
12159
|
+
var rows = []
|
|
12160
|
+
var m = manifest && typeof manifest === 'object' ? manifest : null
|
|
12161
|
+
if (!m) return rows
|
|
12162
|
+
var deleted = m.deletedAt ? new Date(m.deletedAt).toLocaleString() : ''
|
|
12163
|
+
if (deleted) rows.push([t('trash.preview.field.deletedAt'), deleted])
|
|
12164
|
+
if (domain === 'memories') {
|
|
12165
|
+
if (m.group !== undefined) rows.push([t('trash.preview.field.scene'), sceneLabel([], String(m.group || ''))])
|
|
12166
|
+
if (m.form) rows.push([t('trash.preview.field.form'), t('memory.form.' + (m.form === 'bundle' ? 'bundle' : 'flat'))])
|
|
12167
|
+
return rows
|
|
12168
|
+
}
|
|
12169
|
+
if (domain !== 'scenes') return rows
|
|
12170
|
+
var data = (m.data && typeof m.data === 'object') ? m.data : {}
|
|
12171
|
+
var rec = (data.record && typeof data.record === 'object') ? data.record : {}
|
|
12172
|
+
var arc = (data.archive && typeof data.archive === 'object') ? data.archive : {}
|
|
12173
|
+
if (rec.label) rows.push([t('trash.preview.field.label'), String(rec.label)])
|
|
12174
|
+
if (rec.description) rows.push([t('trash.preview.field.desc'), String(rec.description)])
|
|
12175
|
+
if (rec.locked === true) rows.push([t('trash.preview.field.locked'), t('trash.preview.yes')])
|
|
12176
|
+
// 档案四段:只有**段已定义**才报(undefined = 这个域不动,与"勾了 0 个"是两件事)。
|
|
12177
|
+
if (arc.mcp && typeof arc.mcp === 'object') {
|
|
12178
|
+
var servers = Object.keys(arc.mcp)
|
|
12179
|
+
// 工具级收窄:`'*'` 是整台,数组是指定工具 —— 明细放在同一格里,别让人以为只是"勾了几台"。
|
|
12180
|
+
var detail = servers.map(function (k) {
|
|
12181
|
+
var v = arc.mcp[k]
|
|
12182
|
+
return Array.isArray(v) ? k + '(' + v.length + ')' : k
|
|
12183
|
+
}).join('、')
|
|
12184
|
+
rows.push([t('trash.preview.field.mcp'), detail || t('trash.preview.none')])
|
|
12185
|
+
}
|
|
12186
|
+
if (Array.isArray(arc.skills)) {
|
|
12187
|
+
// 技能 id 是 `<rootKey>/<name>`,而 rootKey 对「手动添加的目录」是一串
|
|
12188
|
+
// `custom-<uuid前16位>`(用户 2026-10-18:「场景详细页的 custom-d38da02b873bdb5c
|
|
12189
|
+
// 是什么鬼」)。那一串只是磁盘上的去重键,对人没有意义 —— 详情里把根键换成人看的
|
|
12190
|
+
// 名字,查不到的(来源已被移除)就退回"名字 + 已移除来源",不把 uuid 摆给人看。
|
|
12191
|
+
rows.push([t('trash.preview.field.skills'), arc.skills.map(function (id) {
|
|
12192
|
+
var s = String(id)
|
|
12193
|
+
var at = s.indexOf('/')
|
|
12194
|
+
if (at < 0) return s
|
|
12195
|
+
var rootKey = s.slice(0, at)
|
|
12196
|
+
var root = (skillRoots || []).filter(function (r) { return r.key === rootKey })[0]
|
|
12197
|
+
var rootName = root ? rootDisplayName(t, root) : (rootKey.indexOf('custom-') === 0 ? t('trash.preview.sourceGone') : rootKey)
|
|
12198
|
+
return rootName + ' / ' + s.slice(at + 1)
|
|
12199
|
+
}).join('、') || t('trash.preview.none')])
|
|
12200
|
+
}
|
|
12201
|
+
if (Array.isArray(arc.subagents)) rows.push([t('trash.preview.field.subagents'), arc.subagents.join('、') || t('trash.preview.none')])
|
|
12202
|
+
if (Array.isArray(arc.quickPrompts)) rows.push([t('trash.preview.field.quick'), arc.quickPrompts.join('、') || t('trash.preview.none')])
|
|
12203
|
+
if (arc.toolTablePreset) rows.push([t('trash.preview.field.toolTable'), String(arc.toolTablePreset)])
|
|
12204
|
+
if (rec.prompt) rows.push([t('trash.preview.field.prompt'), String(rec.prompt)])
|
|
12205
|
+
if (Array.isArray(data.memories)) rows.push([t('trash.preview.field.memories'), String(data.memories.length)])
|
|
12206
|
+
return rows
|
|
12207
|
+
}
|
|
12208
|
+
|
|
11741
12209
|
// ---------- 场景记忆页(场景 = scene-memory/ 下的一级目录;内容自动生效)----------
|
|
11742
12210
|
//
|
|
11743
12211
|
// 变更单 01 之后 Rules 与 Scenes 合并为本页:**场景(一级目录)是分组维度,
|
|
@@ -13485,7 +13953,9 @@ function enabledMemoryCount(rules) {
|
|
|
13485
13953
|
return parts.join(' · ')
|
|
13486
13954
|
}
|
|
13487
13955
|
// 当前模式场景被锁 → 「退出模式」按钮与开关一起禁用(服务端守卫兜底)。
|
|
13488
|
-
|
|
13956
|
+
// `modeSceneObj` 是那个场景的记录本身:横幅里那颗「锁定」要拿它去调 toggleSceneLock。
|
|
13957
|
+
var modeSceneObj = modeScene ? (data.scenes || []).filter(function (s) { return s.name === modeScene })[0] : null
|
|
13958
|
+
var modeSceneLocked = !!(modeSceneObj && modeSceneObj.locked === true)
|
|
13489
13959
|
var modeSummary = modeScene ? archiveSummary(data.archives[modeScene], modeScene) : ''
|
|
13490
13960
|
return React.createElement('section', { className: 'dsm-section' },
|
|
13491
13961
|
// 页头一行化(REDESIGN §3 + §6):新增(实底) · 开启提醒 · 回收站;统计内联。
|
|
@@ -13528,7 +13998,13 @@ function enabledMemoryCount(rules) {
|
|
|
13528
13998
|
t('scenes.mode.active', { name: sceneLabel(data.scenes, modeScene) || modeScene })),
|
|
13529
13999
|
React.createElement('div', { className: 'dsm-mode-sub' },
|
|
13530
14000
|
modeSummary ? t('scenes.mode.profile', { parts: modeSummary }) : t('scenes.mode.noProfile'))),
|
|
13531
|
-
|
|
14001
|
+
// 「锁定」搬到这条横幅里、挨着「退出模式」(用户 2026-10-18:「锁定改成只有开启后
|
|
14002
|
+
// 才出现,而且出现在退出模式旁边」):锁的是**当前正在用的这个场景**,卡片上每张
|
|
14003
|
+
// 都挂一颗、再按 active 置灰,读起来像"这张卡有锁功能但用不了"。只有进入之后才有
|
|
14004
|
+
// 锁定这回事,于是它只在这条横幅里出现。
|
|
14005
|
+
React.createElement('span', { className: 'dsm-mode-acts' },
|
|
14006
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-lock', disabled: busy, title: t('scenes.lock.hint'), onClick: function () { toggleSceneLock(modeSceneObj) } }, modeSceneLocked ? t('scenes.lock.unlock') : t('scenes.lock.lock')),
|
|
14007
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || modeSceneLocked, title: modeSceneLocked ? t('scenes.lock.blockedExit') : '', onClick: exitMode }, t('memory.mode.exit')))) : null,
|
|
13532
14008
|
React.createElement(Notice, { key: 'notice', kind: result && result.ok ? 'ok' : 'err', text: result && result.text }),
|
|
13533
14009
|
React.createElement(UndoBar, { key: 'undo', undo: undo, busy: busy, t: t, onUndo: undoMemories, onClose: dismissUndo }),
|
|
13534
14010
|
// 历史「全部启用」(active=null)遗留:单选模型下多名场景同时在场是歧义状态。
|
|
@@ -13581,30 +14057,35 @@ function enabledMemoryCount(rules) {
|
|
|
13581
14057
|
// 不一致就如实标出来,别让用户以为文件里就是它。
|
|
13582
14058
|
var promptMismatch = promptLive && !promptMissing && data.scenePrompt.duplicate !== true
|
|
13583
14059
|
return React.createElement('article', { key: 's:' + name, className: 'dsm-scene-tile' + (modeScene === name ? ' dsm-scene-tile-mode' : '') },
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
14060
|
+
// 左列:名字 + 描述(两行)。右侧控件是另一格,二者按卡片中线对齐。
|
|
14061
|
+
React.createElement('div', { className: 'dsm-scene-tile-main' },
|
|
14062
|
+
React.createElement('div', { className: 'dsm-scene-tile-head' },
|
|
14063
|
+
React.createElement('span', { className: 'dsm-scene-tile-name', title: label }, label),
|
|
14064
|
+
// 显示名与磁盘目录名不同时才标出真名,方便对文件核对。
|
|
14065
|
+
name === label ? null : React.createElement('span', { className: 'dsm-scene-tile-key' }, name),
|
|
14066
|
+
locked ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.shared')) : null,
|
|
14067
|
+
modeScene === name ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.mode.current')) : null,
|
|
14068
|
+
sceneLocked ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('scenes.lock.tag')) : null,
|
|
14069
|
+
!locked && scene.active === false ? React.createElement('span', { className: 'dsm-tag' }, t('memory.scene.off')) : null,
|
|
14070
|
+
// 「提示词生效中:X」「提示词:X」两枚标签已删(用户 2026-10-18):绑定关系在
|
|
14071
|
+
// 编辑器里一眼可改,挂在卡片上只是每行多一枚药丸。只留**出问题时**的两枚
|
|
14072
|
+
//(预设不见了 / 文件里不是它)—— 那才是需要人动手的信号。
|
|
14073
|
+
promptMissing ? React.createElement('span', { className: 'dsm-tag dsm-tag-off', title: t('scenes.prompt.tag.hint') }, t('scenes.prompt.missing', { id: boundPrompt })) : null,
|
|
14074
|
+
promptMismatch ? React.createElement('span', { className: 'dsm-tag dsm-tag-off', title: t('scenes.prompt.mismatch.hint', { id: boundPrompt }) }, t('agm.file.mismatch')) : null),
|
|
14075
|
+
// 描述固定第二行(用户 2026-10-18:「场景页的备注在设置页没显示,应该显示在第二行」):
|
|
14076
|
+
// 在编辑器里填的描述就是这一句,它不在卡头那一行跟名字抢宽度。没有描述也渲染,
|
|
14077
|
+
// 保持每张卡同样两行高,行与行之间对得齐。
|
|
14078
|
+
React.createElement('div', { className: 'dsm-scene-tile-desc', title: desc || '' }, desc || t('scenes.noDesc'))),
|
|
14079
|
+
// 右列:开关 + ⋯(用户 2026-10-18:「switch 按键和三个点按键没有上下居中」)。
|
|
14080
|
+
// 「锁定」已搬进当前模式横幅,卡片上不再出现 —— 锁的是"正在用的那个场景"。
|
|
14081
|
+
// 编辑 / 删除收进行尾一颗 ⋯:卡片底下那条只有两颗按钮的空 band 白占一截高度。
|
|
14082
|
+
// 锁定态「编辑」照旧置灰(改档案是写盘动作,与记忆 / 子智能体那两页的
|
|
14083
|
+
// "只读能看"不同,场景编辑器一打开就是那张要保存的表)。
|
|
14084
|
+
React.createElement('div', { className: 'dsm-scene-tile-side' },
|
|
13599
14085
|
locked ? null : React.createElement('span', {
|
|
13600
14086
|
className: 'dsm-scene-tile-switch',
|
|
13601
14087
|
title: blockedByOther ? t('scenes.enable.blocked', { name: sceneLabel(data.scenes, data.activeScene) || data.activeScene || '' }) : (sceneLocked && scene.active === true ? t('scenes.lock.blockedExit') : ''),
|
|
13602
14088
|
}, React.createElement(Switch, { on: scene.active === true, disabled: busy || blockedByOther || (sceneLocked && scene.active === true), label: t('memory.scene.enable') + ' ' + name, onClick: function () { toggleScene(scene) } })),
|
|
13603
|
-
locked ? null : React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-lock', disabled: busy || scene.active !== true, title: scene.active !== true ? t('scenes.lock.notActive') : t('scenes.lock.hint'), onClick: function () { toggleSceneLock(scene) } }, sceneLocked ? t('scenes.lock.unlock') : t('scenes.lock.lock')),
|
|
13604
|
-
// 编辑 / 删除收进卡头一颗 ⋯(用户 2026-10-16):卡片底下那条只有两颗按钮的
|
|
13605
|
-
// 空 band 白占一截高度,档案没内容时整张卡看着像坏了。
|
|
13606
|
-
// 锁定态「编辑」照旧置灰(改档案是写盘动作,与记忆 / 子智能体那两页的
|
|
13607
|
-
// "只读能看"不同,场景编辑器一打开就是那张要保存的表)。
|
|
13608
14089
|
React.createElement(RowMenu, {
|
|
13609
14090
|
key: 'menu',
|
|
13610
14091
|
label: t('row.menu', { name: label }),
|
|
@@ -13613,10 +14094,7 @@ function enabledMemoryCount(rules) {
|
|
|
13613
14094
|
].concat(locked ? [] : [
|
|
13614
14095
|
{ key: 'delete', label: t('scenes.row.delete'), danger: true, disabled: busy || sceneLocked, title: sceneLocked ? t('scenes.lock.blockedEdit') : undefined, onClick: function () { setModal({ type: 'scene-delete', name: name }) } },
|
|
13615
14096
|
]),
|
|
13616
|
-
}))
|
|
13617
|
-
// 描述行**只有描述**(数量都收进上面的模式条了);全文放 title,卡片本身永远一行。
|
|
13618
|
-
// 它是卡片的最后一行,所以下边距由它承担(原来那条 foot 一并删掉,见上)。
|
|
13619
|
-
React.createElement('p', { className: 'dsm-scene-tile-desc', title: desc || '' }, desc || t('scenes.noDesc')))
|
|
14097
|
+
})))
|
|
13620
14098
|
})),
|
|
13621
14099
|
modal && modal.type === 'scene-delete' ? React.createElement(Modal, { key: 'sdel', className: 'dsm-modal-sm', title: t('memory.deleteScene.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
13622
14100
|
React.createElement('p', { className: 'dsm-help' }, t('memory.deleteScene.desc', { name: modal.name })),
|
|
@@ -15598,7 +16076,7 @@ function enabledMemoryCount(rules) {
|
|
|
15598
16076
|
}
|
|
15599
16077
|
// ── 斜杠命令「工具」段(对话输入框里直接管六个域)────────────────────────────
|
|
15600
16078
|
//
|
|
15601
|
-
// 要解决的问题:切个场景、临时停一个 MCP,都得离开当前对话去「设置 →
|
|
16079
|
+
// 要解决的问题:切个场景、临时停一个 MCP,都得离开当前对话去「设置 → 工具管理」。这一段把
|
|
15602
16080
|
// 这些**可逆**动作搬进宿主的 `/` 菜单:打一个 `/` → 出现「工具」段 → 点一行原地进入该域
|
|
15603
16081
|
// → 点一条直接启停,**菜单停在原地不关**,接着点下一条也行。
|
|
15604
16082
|
//
|
|
@@ -16197,8 +16675,11 @@ function enabledMemoryCount(rules) {
|
|
|
16197
16675
|
// 中英随宿主语言。源名本身(tool-management)因此不会出现在界面上,只当唯一性用。
|
|
16198
16676
|
showGroupTitle: false,
|
|
16199
16677
|
async candidates(session, req) {
|
|
16200
|
-
//
|
|
16201
|
-
|
|
16678
|
+
// 行内也认(用户 2026-10-18:「输入框输入了文字,空格后输入斜杠命令没有工具和
|
|
16679
|
+
// 快捷提示词」):宿主自己的 `boundaryOk` 只把「紧跟在文字后面的 `/`」当成普通
|
|
16680
|
+
// 字符,前面是空格的 `/` 本来就是合法触发(`position` 报 `inline`)。我们此前一律
|
|
16681
|
+
// 只收 `leading`,于是「先打一段话、空格、再打 /」永远弹不出这两段。
|
|
16682
|
+
// 真正的"半路打出来的 /"(`https://`、`a/b`)宿主已经挡掉,这里不必再拦一遍。
|
|
16202
16683
|
const domains = domainsOf()
|
|
16203
16684
|
const query = req.query || ''
|
|
16204
16685
|
const at = query.indexOf('/')
|
|
@@ -16328,7 +16809,7 @@ function enabledMemoryCount(rules) {
|
|
|
16328
16809
|
order: 3,
|
|
16329
16810
|
showGroupTitle: false,
|
|
16330
16811
|
async candidates(session, req) {
|
|
16331
|
-
|
|
16812
|
+
// 同「工具」段:行内也认(前面是空格的 `/` 本来就是合法触发)。
|
|
16332
16813
|
const query = String(req.query || '')
|
|
16333
16814
|
// 只列一层:带 `/` 的草稿(`/tool-skill/…`)不是这一段的 token。
|
|
16334
16815
|
if (query.indexOf('/') >= 0) return []
|
|
@@ -16395,6 +16876,95 @@ function enabledMemoryCount(rules) {
|
|
|
16395
16876
|
})
|
|
16396
16877
|
}
|
|
16397
16878
|
|
|
16879
|
+
|
|
16880
|
+
// ── 侧栏入口(2026-10-18)─────────────────────────────────────────────────
|
|
16881
|
+
//
|
|
16882
|
+
// 宿主的侧栏全局入口是**两半**:`main` 里那个键控面板(选中后占中栏)与
|
|
16883
|
+
// `sidebar.panellist` 里那一行(图标 + 名字)。两半必须同 id,点行才有地方去。
|
|
16884
|
+
//
|
|
16885
|
+
// 为什么是"主面板"而不是"打开设置":设置对话框的开合状态是宿主私有的,没有公开
|
|
16886
|
+
// 服务可调(同生态的 dshmarket 在 sidebar-entry.ts 里写明同一件事 —— 主面板是公开
|
|
16887
|
+
// 契约里唯一的一键目的地)。所以这一行打开的是本插件页面的**另一份挂载**,内容
|
|
16888
|
+
// 与设置面板里那一页同源(都渲染 ToolsSection),不是复制一套界面。
|
|
16889
|
+
//
|
|
16890
|
+
// 开关在兼容页页头,状态与侧车在 factory 作用域(见 20 片 sidebarEntryState):
|
|
16891
|
+
// 关掉时两半一起撤,撤销前先把中栏从这一页带出去 —— 否则选中项指向一个已注销的
|
|
16892
|
+
// key,中栏会空着。
|
|
16893
|
+
const SIDEBAR_ENTRY_ID = 'dsm-tools'
|
|
16894
|
+
// 排在官方那几行之后(插件 0 / 市场 1 / 自动化任务 10):本插件是"设置"这一类,
|
|
16895
|
+
// 摆在那三行下面比插在插件与市场之间更像话。
|
|
16896
|
+
const SIDEBAR_ENTRY_ORDER = 20
|
|
16897
|
+
|
|
16898
|
+
/** 侧栏入口打开的那一页:外层只负责滚动与留白,内容仍是 ToolsSection 本身。 */
|
|
16899
|
+
function SidebarToolsPage() {
|
|
16900
|
+
return React.createElement('div', { className: 'dsm-panel' }, React.createElement(ToolsSection))
|
|
16901
|
+
}
|
|
16902
|
+
|
|
16903
|
+
/**
|
|
16904
|
+
* 侧栏那一行的图标(props:size / active,宿主给)。
|
|
16905
|
+
*
|
|
16906
|
+
* 与设置侧栏那颗图形同一份(40b 的 mask 版给宿主的封闭名单用),这里直接画 ——
|
|
16907
|
+
* 用 currentColor,宿主切换选中 / 悬停 / 主题时自动跟着变。
|
|
16908
|
+
*/
|
|
16909
|
+
function SidebarEntryIcon(props) {
|
|
16910
|
+
const size = (props && props.size) || 16
|
|
16911
|
+
return React.createElement('svg', { viewBox: '0 0 1024 1024', width: size, height: size, 'aria-hidden': 'true', focusable: 'false' },
|
|
16912
|
+
React.createElement('path', { fill: 'currentColor', d: 'M592 336h-416c-52.928 0-96 43.072-96 96v416c0 52.928 43.072 96 96 96h416c52.928 0 96-43.072 96-96v-416c0-52.928-43.072-96-96-96z m32 512a32 32 0 0 1-32 32h-416c-17.632 0-32-14.336-32-32v-416c0-17.632 14.368-32 32-32h416c17.664 0 32 14.368 32 32v416z' }),
|
|
16913
|
+
React.createElement('path', { fill: 'currentColor', d: 'M720 208h-416a32 32 0 0 0 0 64h416c17.664 0 32 14.368 32 32v416a32 32 0 1 0 64 0v-416c0-52.928-43.072-96-96-96zM528 752h-288a32 32 0 1 0 0 64h288a32 32 0 1 0 0-64z' }),
|
|
16914
|
+
React.createElement('path', { fill: 'currentColor', d: 'M848 80h-416a32 32 0 0 0 0 64h416c17.664 0 32 14.368 32 32v416a32 32 0 1 0 64 0v-416c0-52.928-43.072-96-96-96z' }))
|
|
16915
|
+
}
|
|
16916
|
+
|
|
16917
|
+
/**
|
|
16918
|
+
* 一块注册 + 一个 `apply`:开关真翻的那一刻就注册 / 注销,不必等下一次打开面板。
|
|
16919
|
+
*
|
|
16920
|
+
* @param leavePanel 面板那一半传 true:注销前先把中栏带出去(见文件内那段注释)。
|
|
16921
|
+
*/
|
|
16922
|
+
const sidebarEntryGate = function (options, component, leavePanel) {
|
|
16923
|
+
let dispose = null
|
|
16924
|
+
const apply = function () {
|
|
16925
|
+
const want = sidebarEntrySnapshot().enabled !== false
|
|
16926
|
+
if (want && dispose === null) {
|
|
16927
|
+
const off = slots.register(options, component)
|
|
16928
|
+
dispose = typeof off === 'function' ? off : function () {}
|
|
16929
|
+
return
|
|
16930
|
+
}
|
|
16931
|
+
if (want || dispose === null) return
|
|
16932
|
+
const stop = dispose
|
|
16933
|
+
dispose = null
|
|
16934
|
+
if (leavePanel) {
|
|
16935
|
+
const layout = serviceOf('layout')
|
|
16936
|
+
try {
|
|
16937
|
+
if (layout && layout.panelInfo && layout.panelInfo.getSnapshot().activePanelId === SIDEBAR_ENTRY_ID) layout.selectPanel(null)
|
|
16938
|
+
} catch (e) { /* 布局服务不可用 → 交给宿主的 retainMainPanels */ }
|
|
16939
|
+
}
|
|
16940
|
+
stop()
|
|
16941
|
+
}
|
|
16942
|
+
apply()
|
|
16943
|
+
const unsubscribe = onSidebarEntryChange(apply)
|
|
16944
|
+
return function () {
|
|
16945
|
+
unsubscribe()
|
|
16946
|
+
if (dispose !== null) { const stop = dispose; dispose = null; stop() }
|
|
16947
|
+
}
|
|
16948
|
+
}
|
|
16949
|
+
|
|
16950
|
+
// 两个 slot 各自注入:宿主没声明侧栏(旧版本 / 精简组合)时这一整段等于不存在,
|
|
16951
|
+
// 设置面板里那一页照旧能用。
|
|
16952
|
+
slots.inject('main', function () {
|
|
16953
|
+
return sidebarEntryGate({ name: 'main', key: SIDEBAR_ENTRY_ID }, SidebarToolsPage, true)
|
|
16954
|
+
})
|
|
16955
|
+
slots.inject('sidebar.panellist', function () {
|
|
16956
|
+
return sidebarEntryGate({
|
|
16957
|
+
name: 'sidebar.panellist', id: SIDEBAR_ENTRY_ID, order: SIDEBAR_ENTRY_ORDER,
|
|
16958
|
+
label: function () { return t('nav.title') }, locale: NS,
|
|
16959
|
+
}, SidebarEntryIcon, false)
|
|
16960
|
+
})
|
|
16961
|
+
// 读一次存下来的开关(读侧免令牌,见 op-registry 的 sidebar-settings)。读不到就
|
|
16962
|
+
// 保持默认(开);这一次跑在注册之后,所以关着的人会先看到一行再消失 —— 与
|
|
16963
|
+
// dshmarket 同一条时序,换的是"不必等服务器往返就能先给界面"。
|
|
16964
|
+
apiCall('sidebar-settings', {}).then(function (r) {
|
|
16965
|
+
if (r && r.ok && r.settings) setSidebarEntryState({ enabled: r.settings.enabled !== false })
|
|
16966
|
+
}).catch(function () { /* 读不到 → 保持默认(开) */ })
|
|
16967
|
+
|
|
16398
16968
|
|
|
16399
16969
|
// 页面组件填进 factory 作用域那个占位对象(导出对象求值时已指向它)。
|
|
16400
16970
|
// 不要改回 `module.exports.X = ...`:apply 可能在本函数开头提前返回。
|