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
|
@@ -48,11 +48,34 @@ export const DOMAIN_FRAME = {
|
|
|
48
48
|
// 这是它做判断时要用的事实,不是反推。
|
|
49
49
|
// - 代价 ≈24 tok/轮,只在这一域。
|
|
50
50
|
title: 'Current scene',
|
|
51
|
-
how: "A scene is the user's working mode: the memories, prompt, tools and personas you are given are already filtered to
|
|
51
|
+
how: "A scene is the user's working mode: the memories, prompt, tools and personas you are given are already filtered to it.",
|
|
52
52
|
supersede: SUPERSEDE_NOTE,
|
|
53
53
|
},
|
|
54
54
|
memory: {
|
|
55
55
|
title: 'Current memories',
|
|
56
|
+
// 2026-10-18(审查 #7):记忆段截断时,折叠行 `- ... ({rest} more not listed)` 给了计数
|
|
57
|
+
// 但没给出口——被截掉的条目连名字都没列出来,模型既调不了 `memory_manager_read`(不知道
|
|
58
|
+
// 传什么名字),也不知道该用 `memory_manager_list` 去发现。另外三个域的溢出行都点名了
|
|
59
|
+
// 自己的 list 工具,唯独记忆段没有。
|
|
60
|
+
//
|
|
61
|
+
// 修法选了「frame `how` 条件点名」而不是「把 listToolVisible 管道接到记忆服务」:记忆 SWR
|
|
62
|
+
// 缓存是指纹驱动的(缓存渲染结果,不是缓存数据按 agent 重渲染),重构成技能目录那种方式
|
|
63
|
+
// 要动 4–5 个文件。而 `how` 已经能看 `{ text }`——MCP 就是这么按正文内容决定发不发的。
|
|
64
|
+
// `text.includes('more not listed')` 只在折叠行出现时命中(rest > 0);rest = 0(清单列全)
|
|
65
|
+
// 时折叠行不出现,how 不发——那些条目有名字,模型调 `memory_manager_read` 就行。
|
|
66
|
+
// 工具被关掉(兼容页 / 人设 toolsDeny)时不点名——与另外三域同一纪律。
|
|
67
|
+
how: ({ text, toolHidden }) => {
|
|
68
|
+
// 折叠行 `more not listed` 和截断标记 `(Truncated:` 两种信号都要覆盖:
|
|
69
|
+
// - 折叠行 = 清单部分列了 + 剩余没列 → 没名的那些需要 list 工具发现
|
|
70
|
+
// - 标记 = 连清单都没放进来 → 全部没名,更需要 list 工具
|
|
71
|
+
// - 清单列全(`## Not injected` + 所有名字,rest = 0)→ 两种信号都没有 → 不发 how:
|
|
72
|
+
// 模型用 `memory_manager_read` 按名字读就行
|
|
73
|
+
if (!text.includes('more not listed') && !text.includes('(Truncated:'))
|
|
74
|
+
return undefined;
|
|
75
|
+
if (toolHidden('memory_manager_list'))
|
|
76
|
+
return undefined;
|
|
77
|
+
return 'Not all memories fit here — see the rest with `memory_manager_list`.';
|
|
78
|
+
},
|
|
56
79
|
supersede: SUPERSEDE_NOTE,
|
|
57
80
|
},
|
|
58
81
|
mcp: {
|
|
@@ -154,23 +177,31 @@ export const DOMAIN_FRAME = {
|
|
|
154
177
|
// 说明**这些规则是要遵循的**。记忆域有授权语、场景域有定义,唯独效力最强的这一域空着 ——
|
|
155
178
|
// 而官方那条行给的是**降权**措辞("可作参考"),我们当初刻意没抄,于是等于什么都没说。
|
|
156
179
|
// 现在补一句正面的:要遵循,且用户当场说的优先。
|
|
157
|
-
|
|
180
|
+
// 措辞用 "right now" 不用 "for this machine"(2026-10-18 审查):场景预设接管时,正文是
|
|
181
|
+
// 场景绑定的那份 AGENTS.md,不是机器全局的 —— "for this machine" 在那个态下是假话。
|
|
182
|
+
// "right now" 同时覆盖全局 AGENTS.md 与场景预设两种来源,由正文首行 `Instructions from:`
|
|
183
|
+
// 给出具体出处。
|
|
184
|
+
how: 'These are the instructions in force right now; follow them unless the user says otherwise in this conversation.',
|
|
158
185
|
supersede: SUPERSEDE_NOTE,
|
|
159
186
|
},
|
|
160
187
|
};
|
|
161
188
|
/**
|
|
162
|
-
* `how`
|
|
189
|
+
* `how` 行里**点名过的工具**(skills / subagents / memory 三个域会点名)。
|
|
163
190
|
*
|
|
164
191
|
* 为什么要有这份名单:一条 `how` 行是否成立,取决于它点名的工具在**这个 agent 手里**还在不在。
|
|
165
|
-
* 名单放在这里而不是调用方 ——
|
|
192
|
+
* 名单放在这里而不是调用方 —— 与上面那几条 `how` 相邻,改一处不会漏另一处。
|
|
166
193
|
*
|
|
167
194
|
* "不在手里"有两条路,都要算:
|
|
168
195
|
* ① 兼容页「模型工具表」把它关掉(用户全局开关,名单在 index.ts 的 `hiddenTools` 里);
|
|
169
196
|
* ② **人设的工具限制**把它砍掉(`decideToolFilter` → 官方 `tools.restrict`,只作用于那份
|
|
170
197
|
* 人设启动的子代理)。②此前没人管:目录照注、`how` 行照样点名一个模型手里没有的工具,
|
|
171
198
|
* 等于诱导它去调一个不存在的名字(2026-10-07 修)。
|
|
199
|
+
*
|
|
200
|
+
* `memory_manager_list` 2026-10-18 加入(审查 #7):记忆段截断折叠行不点名查询工具时,
|
|
201
|
+
* 被截掉的条目无名、模型无法发现它们。`how` 在折叠行出现时点名它,与技能 / 子智能体 /
|
|
202
|
+
* MCP 三个域的溢出行同构。
|
|
172
203
|
*/
|
|
173
|
-
export const HOW_NAMED_TOOLS = ['skill_manager_read', 'subagent_manager_run'];
|
|
204
|
+
export const HOW_NAMED_TOOLS = ['skill_manager_read', 'subagent_manager_run', 'memory_manager_list'];
|
|
174
205
|
/** 域声明里没登记的 key(理论上到不了这里):给一个不出错的通用框架。 */
|
|
175
206
|
export const fallbackFrame = (label) => ({
|
|
176
207
|
title: `Current ${label}`,
|
package/lib/context-inject.js
CHANGED
|
@@ -281,7 +281,7 @@ export function createContextInjector(deps) {
|
|
|
281
281
|
// 的可见提示由兼容页那条「去填令牌」横幅承担。
|
|
282
282
|
try {
|
|
283
283
|
if (deps.tokenGateActive && deps.tokenGateActive()) {
|
|
284
|
-
log('
|
|
284
|
+
log('令牌未验证:本轮对话被拒绝(在「工具管理 → 兼容」页填入访问令牌后恢复)');
|
|
285
285
|
return { kind: 'reject' };
|
|
286
286
|
}
|
|
287
287
|
}
|
package/lib/index.js
CHANGED
|
@@ -398,14 +398,19 @@ export default {
|
|
|
398
398
|
// 退出回放时也不该去动它们,否则会改写用户对副本的显式策略记录。
|
|
399
399
|
currentSkills: async () => (await skillScan()).states,
|
|
400
400
|
applySkills: async (target) => {
|
|
401
|
-
//
|
|
401
|
+
// 四道过滤,缺一不可:
|
|
402
402
|
// 1. 被同名覆盖的副本直接跳过——它们本来就不生效,写策略只会污染用户的记录。
|
|
403
403
|
// 2. 结构不完整的技能也跳过——core 对它们一律拒绝启停(error.skill.notLoadable),
|
|
404
404
|
// 不跳过就会抛成"应用失败",**整个场景进不去**(老档案里的残留键能踩到)。
|
|
405
|
-
// 3.
|
|
405
|
+
// 3. **已经不存在的技能也跳过**(用户 2026-10-18 报「启动的场景里有被删除的内容,
|
|
406
|
+
// 就导致没办法退出」):快照 / 档案里记的是"进场景那一刻还在"的键,场景期间那条
|
|
407
|
+
// 技能被删掉之后,退出时按快照回放必然报 `技能不存在`,于是整次退出被这一条打断、
|
|
408
|
+
// mode 停在原地 —— 用户被困在场景里。没有可写的对象,跳过才是对的。
|
|
409
|
+
// 4. **已经处于目标状态的技能不再写**。`applySkills` 写的是显式策略记录,而目标
|
|
406
410
|
// 状态是生效状态,两者不等价:对已经生效为「停用」的技能再 disable 一次,
|
|
407
411
|
// 只是把「没有记录」固化成「显式停用」,场景模式进出一次就会改写 state.json。
|
|
408
412
|
const { states, shadowed, unloadable } = await skillScan();
|
|
413
|
+
const skipped = [];
|
|
409
414
|
for (const [key, on] of Object.entries(target)) {
|
|
410
415
|
const i = key.indexOf('/');
|
|
411
416
|
if (i <= 0)
|
|
@@ -417,13 +422,29 @@ export default {
|
|
|
417
422
|
// 继续往下走会抛成"应用失败",**整个场景就进不去了**(老档案里的残留键能踩到)。
|
|
418
423
|
if (unloadable.has(key))
|
|
419
424
|
continue;
|
|
425
|
+
// 这次扫描里根本没有这个键 = 技能已被删除(或所在目录已消失):没有可写的对象。
|
|
426
|
+
if (!(key in states)) {
|
|
427
|
+
skipped.push(`${key}(已不存在)`);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
420
430
|
if (states[key] === on)
|
|
421
431
|
continue;
|
|
422
432
|
const root = key.slice(0, i);
|
|
423
433
|
const name = key.slice(i + 1);
|
|
424
434
|
const r = await skillsService.ops[on ? 'skill-enable' : 'skill-disable']({ root, name });
|
|
425
|
-
if (r && r.ok === false)
|
|
435
|
+
if (r && r.ok === false) {
|
|
436
|
+
// 结构性失败(这一条现在解不开)只记警告并跳过 —— 与 applySkillSourceSwitches
|
|
437
|
+
// 同一条口径:拿它中断整个进入 / 退出只会把用户困在场景里,而这一行与本次改动无关。
|
|
438
|
+
const code = String((r && r.code) || '');
|
|
439
|
+
if (code === 'error.skill.notFound' || code === 'error.skill.notLoadable' || code === 'error.skill.noFrontmatter') {
|
|
440
|
+
skipped.push(`${key}(${r.error})`);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
426
443
|
throw new Error(`技能 ${key} 应用失败: ${r.error}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (skipped.length) {
|
|
447
|
+
ctx.logger?.warn?.(`scene: 跳过无法应用的技能 —— ${skipped.join('、')}`);
|
|
427
448
|
}
|
|
428
449
|
},
|
|
429
450
|
// ── 服务器级 / 来源级启停(P5)────────────────────────────────────────────
|
|
@@ -705,7 +726,7 @@ export default {
|
|
|
705
726
|
// 三份界面设置侧车(inject / scene / slash)拆到 ./settings-sidecars.ts:读走 3 秒 TTL 缓存,
|
|
706
727
|
// 写共用上面那条串行写锁。工厂只在这里调用一次,三份缓存仍是「每次 apply 一份」。
|
|
707
728
|
// 传的是 apply 作用域里那几个函数声明的引用(提升已初始化),不是快照值。
|
|
708
|
-
const { readInjectSettings, injectSettingsSync, injectNoticeOptions, injectSettingsOp, readSceneSettings, sceneSettingsOp, readSlashSettings, slashSettingsOp, } = createSettingsSidecars({
|
|
729
|
+
const { readInjectSettings, injectSettingsSync, injectNoticeOptions, injectSettingsOp, readSceneSettings, sceneSettingsOp, readSlashSettings, slashSettingsOp, readSidebarSettings, sidebarSettingsOp, } = createSettingsSidecars({
|
|
709
730
|
ensurePaths,
|
|
710
731
|
readJsonFile,
|
|
711
732
|
writeJsonFile,
|
|
@@ -769,7 +790,7 @@ export default {
|
|
|
769
790
|
// 关掉或没配都是空串)而本次启动还没有人验过 ⇒ 这一步不放行,宿主把 turn 收成
|
|
770
791
|
// `blocked`。这是"没输入令牌就没法对话"的唯一真正落实 —— 客户端那条输入框锁定在
|
|
771
792
|
// 本宿主上无路可走(`ctx.conversation.blocks` 不存在,见 ContextInjectorDeps 注释)。
|
|
772
|
-
// 只读 op
|
|
793
|
+
// 只读 op 不受影响,所以「工具管理 → 兼容」页照常能打开、能填令牌 —— 解铃就在那里。
|
|
773
794
|
tokenGateActive: () => TOKEN !== '' && !acceptedThisBoot(),
|
|
774
795
|
});
|
|
775
796
|
contextInjectorLive = () => injector.live();
|
|
@@ -1675,6 +1696,9 @@ export default {
|
|
|
1675
1696
|
// 斜杠命令入口的开关(读 / 写):客户端 boot 时读一次决定挂不挂 `/` 菜单里的「工具」段,
|
|
1676
1697
|
// 兼容页那两颗开关写一次。同 `scene-settings`:按写门禁但不冻结(见 slash-settings.ts)。
|
|
1677
1698
|
'slash-settings': (args) => slashSettingsOp(args),
|
|
1699
|
+
// 侧栏入口的开关(读 / 写):客户端 boot 时读一次决定注不注册侧栏那一行,兼容页那颗
|
|
1700
|
+
// 按钮写一次。同 `slash-settings`:按写门禁但不冻结(见 sidebar-settings.ts)。
|
|
1701
|
+
'sidebar-settings': (args) => sidebarSettingsOp(args),
|
|
1678
1702
|
// state-doctor:跨域**悬空引用**体检(只读,实现与理由见 src/ops/state-doctor.ts)。
|
|
1679
1703
|
// 这里只做"把权威集合取来"这一件事 —— 某一域读失败就传 null,体检会把它记进
|
|
1680
1704
|
// `skipped` 而不是当成"该域没有悬空项"(读不到 ≠ 不存在,报成后者就是骗人)。
|
package/lib/memories/snapshot.js
CHANGED
|
@@ -412,12 +412,12 @@ export function renderSceneMemory(probe, index, maxBytes) {
|
|
|
412
412
|
return byteLen(marker) <= space ? marker : '';
|
|
413
413
|
const rest = missed.length - lines.length;
|
|
414
414
|
const fold = `- ... (${rest} more not listed)\n`;
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
415
|
+
// 标记与折叠行的关系(2026-10-18 审查收口):
|
|
416
|
+
// - 清单列全(rest === 0)→ 不发标记也不发折叠行:`## Not injected` 标题已说清。
|
|
417
|
+
// - 清单不全 + 折叠行放得下 → 只发折叠行(给了具体计数),不发标记(重复)。
|
|
418
|
+
// - 清单不全 + 折叠行放不下 → 只发标记(折叠行是更具体的版本,但放不下时标记是唯一信号)。
|
|
419
419
|
const folded = rest > 0 && used + byteLen(fold) <= space;
|
|
420
|
-
const tail = `${head}${lines.join('')}${folded ? fold : ''}${rest > 0 ? marker : ''}`;
|
|
420
|
+
const tail = `${head}${lines.join('')}${folded ? fold : ''}${rest > 0 && !folded ? marker : ''}`;
|
|
421
421
|
return tail;
|
|
422
422
|
};
|
|
423
423
|
// ── ① 贪心:放得下就放,越界**跳过**(而不是整体停止)──────────────────
|
|
@@ -455,10 +455,18 @@ export function renderSceneMemory(probe, index, maxBytes) {
|
|
|
455
455
|
tail = renderTail(missed, maxBytes - byteLen(body), truncatedNow);
|
|
456
456
|
}
|
|
457
457
|
let text = `${body}${tail}`.replace(/^\n+/, '');
|
|
458
|
-
// ③
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
|
|
458
|
+
// ③ 兜底:有记忆没注入时绝不能静默消失。
|
|
459
|
+
// - 原来(2026-09-? 初版)只兜 text === '' 的情形:body 非空、tail 放不下时,
|
|
460
|
+
// 模型看到的是一段看起来完整的记忆段 —— 既没有 `## Not injected` 也没有标记,
|
|
461
|
+
// 被截掉的那些条目就**完全无痕**了。这是 2026-10-18 审查发现的 P1:body 的字节
|
|
462
|
+
// 核算(`used`)与 `byteLen(body)` 有几字节的误差(渲染时条目间的 `\n` / `\n\n`
|
|
463
|
+
// 没全进 `used`),于是 reserve 留的余量偶尔不够,标记放不下,3 兜底又不触发。
|
|
464
|
+
// - 现在的判据:只要 missed 非空或发现阶段截断,而 text 里既没有标记也没有
|
|
465
|
+
// `## Not injected` 标题,就强制追加标记 —— 宁可超预算几字节(原 ③ 注释的原则),
|
|
466
|
+
// 也不能让截断无痕。两样有一样就不追加(信号已经在)。
|
|
467
|
+
if ((missed.length > 0 || probe.truncated) && !text.includes(TRUNCATION_MARKER) && !text.includes(DROPPED_HEADING)) {
|
|
468
|
+
text = text === '' ? `${TRUNCATION_MARKER}\n` : `${text.replace(/\n+$/, '')}\n${TRUNCATION_MARKER}\n`;
|
|
469
|
+
}
|
|
462
470
|
const scenes = [];
|
|
463
471
|
for (const c of selected)
|
|
464
472
|
if (!scenes.includes(c.scene))
|
package/lib/op-registry.js
CHANGED
|
@@ -243,6 +243,9 @@ export const OP_REGISTRY = Object.freeze({
|
|
|
243
243
|
// 客户端 boot 时必问一次决定挂不挂 `/` 菜单,那一次不能要令牌,否则没配令牌的宿主上这个功能
|
|
244
244
|
// 永远出不来。
|
|
245
245
|
'slash-settings': { write: true, writeWhen: (a) => a.set === true },
|
|
246
|
+
// 侧栏入口的开关(侧车 `sidebar-settings.json`):同 `slash-settings` —— 写侧车按写门禁、
|
|
247
|
+
// 不冻结;读侧是纯读(客户端 boot 时必问一次,那一次不能要令牌)。
|
|
248
|
+
'sidebar-settings': { write: true, writeWhen: (a) => a.set === true },
|
|
246
249
|
// 清理 patch 备份:删磁盘文件(备份里含明文凭据副本),按写操作门禁。
|
|
247
250
|
'backups-clean': { write: true },
|
|
248
251
|
// ── 场景候选源(4)与内联(1)──────────────────────────────────────────────
|
package/lib/ops/trash.js
CHANGED
|
@@ -269,7 +269,18 @@ export function buildTrashOps(rc) {
|
|
|
269
269
|
catch {
|
|
270
270
|
return fail('error.rules.notFound', `回收站条目不存在:${id}`);
|
|
271
271
|
}
|
|
272
|
-
|
|
272
|
+
// 清单也一并回给界面(用户 2026-10-18:「回收站的详细功能做的不够好,显示的内容不够全,
|
|
273
|
+
// 例如场景没有显示勾选了什么」):场景的档案、记忆的场景 / 形态都在 `manifest.json` 里,
|
|
274
|
+
// 只回文件列表的话,详情页能看到的就只剩"有几个文件"。
|
|
275
|
+
// 读不到 / 坏了就回 null —— 详情页按"只有文件"那一档渲染,不因为清单缺了就整页报错。
|
|
276
|
+
let manifest = null;
|
|
277
|
+
try {
|
|
278
|
+
manifest = JSON.parse(await readFile(join(dir, 'manifest.json'), 'utf8'));
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
manifest = null;
|
|
282
|
+
}
|
|
283
|
+
return { ok: true, id, domain, manifest, files: await readTrashPayloadFiles(dir) };
|
|
273
284
|
}
|
|
274
285
|
return { rulesRemove, rulesRestore, rulesTrashList, rulesTrashRemove, trashPreview };
|
|
275
286
|
}
|
package/lib/settings-sidecars.js
CHANGED
|
@@ -10,6 +10,7 @@ import { hubPath } from './hub.js';
|
|
|
10
10
|
import { DEFAULT_INJECT_SETTINGS, normalizeInjectSettings } from './context-inject.js';
|
|
11
11
|
import { normalizeSceneSettings } from './scene-settings.js';
|
|
12
12
|
import { normalizeSlashSettings } from './slash-settings.js';
|
|
13
|
+
import { normalizeSidebarSettings } from './sidebar-settings.js';
|
|
13
14
|
export function createSettingsSidecars(deps) {
|
|
14
15
|
// 注入设置(侧车 `inject-settings.json`,界面在「兼容」页):
|
|
15
16
|
// - underSuppressingPresets:压制型预设(persona complete / 关运行时上下文,如极简)
|
|
@@ -131,6 +132,40 @@ export function createSettingsSidecars(deps) {
|
|
|
131
132
|
return { ok: true, settings: next };
|
|
132
133
|
});
|
|
133
134
|
}
|
|
135
|
+
// 侧栏入口的开关(侧车 `sidebar-settings.json`):只一项 —— 侧栏里挂不挂本插件那一行。
|
|
136
|
+
// 与 `slash-settings` 同规格:入口开关,不冻结;读侧不带 `set` 是纯读(客户端 boot 时
|
|
137
|
+
// 必问一次决定注不注册,那一次不能要令牌,否则没配令牌的宿主上这个入口永远出不来)。
|
|
138
|
+
const SIDEBAR_SETTINGS_FILE = 'sidebar-settings.json';
|
|
139
|
+
const SIDEBAR_SETTINGS_TTL_MS = 3000;
|
|
140
|
+
let sidebarSettingsCache = null;
|
|
141
|
+
async function readSidebarSettings(force = false) {
|
|
142
|
+
if (sidebarSettingsCache && !force && Date.now() - sidebarSettingsCache.at < SIDEBAR_SETTINGS_TTL_MS)
|
|
143
|
+
return sidebarSettingsCache.value;
|
|
144
|
+
await deps.ensurePaths();
|
|
145
|
+
const raw = await deps.readJsonFile(hubPath(SIDEBAR_SETTINGS_FILE));
|
|
146
|
+
const value = normalizeSidebarSettings(raw);
|
|
147
|
+
sidebarSettingsCache = { at: Date.now(), value };
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
async function sidebarSettingsOp(args) {
|
|
151
|
+
const current = await readSidebarSettings();
|
|
152
|
+
if (!args || args.set !== true)
|
|
153
|
+
return { ok: true, settings: current };
|
|
154
|
+
const next = normalizeSidebarSettings({
|
|
155
|
+
enabled: typeof args.enabled === 'boolean' ? args.enabled : current.enabled,
|
|
156
|
+
});
|
|
157
|
+
await deps.ensurePaths();
|
|
158
|
+
return deps.withWriteLock(async () => {
|
|
159
|
+
try {
|
|
160
|
+
await deps.writeJsonFile(hubPath(SIDEBAR_SETTINGS_FILE), next);
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
return { ok: false, error: '设置保存失败: ' + deps.message(e) };
|
|
164
|
+
}
|
|
165
|
+
sidebarSettingsCache = { at: Date.now(), value: next };
|
|
166
|
+
return { ok: true, settings: next };
|
|
167
|
+
});
|
|
168
|
+
}
|
|
134
169
|
return {
|
|
135
170
|
readInjectSettings,
|
|
136
171
|
injectSettingsSync,
|
|
@@ -140,5 +175,7 @@ export function createSettingsSidecars(deps) {
|
|
|
140
175
|
sceneSettingsOp,
|
|
141
176
|
readSlashSettings,
|
|
142
177
|
slashSettingsOp,
|
|
178
|
+
readSidebarSettings,
|
|
179
|
+
sidebarSettingsOp,
|
|
143
180
|
};
|
|
144
181
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// 侧栏入口的开关(侧车 `sidebar-settings.json`,按钮在「兼容」页页头)。
|
|
2
|
+
//
|
|
3
|
+
// 与 `slash-settings.ts` 同一档:它是个**入口开关**,不是管理动作 —— 所以不进场景冻结
|
|
4
|
+
// (锁着场景的人照样能把它关掉),也不参与任何门禁判定。
|
|
5
|
+
//
|
|
6
|
+
// 为什么存服务端而不是 localStorage:与其余入口开关同一个家,换浏览器也在;存本地就
|
|
7
|
+
// 只有那个浏览器认,而这条开关的用途正是"这台机器上我不想在侧栏看到它"。
|
|
8
|
+
export const DEFAULT_SIDEBAR_SETTINGS = { enabled: true };
|
|
9
|
+
/** 把任意输入夹成合法设置:读不到 / 形状不对一律按默认开启。 */
|
|
10
|
+
export function normalizeSidebarSettings(raw) {
|
|
11
|
+
const obj = (raw && typeof raw === 'object' ? raw : {});
|
|
12
|
+
return { enabled: typeof obj.enabled === 'boolean' ? obj.enabled : DEFAULT_SIDEBAR_SETTINGS.enabled };
|
|
13
|
+
}
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-tool-management",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "一个设置面板,统一管理 MCP、技能、记忆、子智能体、提示词和归档会话;可自定义搭配场景,一个场景即可切换整个环境;Agent也能帮你操作这个插件。",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
".": "./lib/index.js",
|
|
13
13
|
"./client": "./lib/client.js",
|
|
14
14
|
"./workspace": "./lib/sessions/workspace.js",
|
|
15
|
+
"./locale/*.json": "./locale/*.json",
|
|
15
16
|
"./package.json": "./package.json"
|
|
16
17
|
},
|
|
17
18
|
"files": [
|
|
18
19
|
"lib",
|
|
20
|
+
"locale",
|
|
19
21
|
"cordis.patch.yml",
|
|
20
22
|
"README.md",
|
|
21
23
|
"README_EN.md",
|
|
@@ -67,6 +69,7 @@
|
|
|
67
69
|
]
|
|
68
70
|
}
|
|
69
71
|
},
|
|
72
|
+
"icon": "./docs/images/icon.png",
|
|
70
73
|
"peerDependencies": {
|
|
71
74
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
72
75
|
"@deepseek-ai/dsh-agent-preset-registry": ">=0.1.7-rc.2 <0.2.0-rc.1 || >=0.2.0-rc.1 <0.2.1-rc.2 || >=0.2.1-rc.2 <0.2.2",
|