dsh-plugin-tool-management 0.13.0 → 0.15.0
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 +560 -474
- package/README.md +91 -123
- package/README_EN.md +99 -126
- package/docs/images/1-EN.png +0 -0
- package/docs/images/1.png +0 -0
- package/docs/images/2-EN.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3-EN.png +0 -0
- package/docs/images/3.png +0 -0
- package/docs/images/4-EN.png +0 -0
- package/docs/images/4.png +0 -0
- package/docs/images/5-EN.png +0 -0
- package/docs/images/5.png +0 -0
- package/docs/images/6-EN.png +0 -0
- package/docs/images/6.png +0 -0
- package/docs/images/7-EN.png +0 -0
- package/docs/images/7.png +0 -0
- package/docs/images/8-EN.png +0 -0
- package/docs/images/8.png +0 -0
- package/docs/update.md +391 -337
- package/lib/audit-log.js +157 -0
- package/lib/client.js +4204 -2454
- package/lib/compat/patch-dialect.js +3 -1
- package/lib/compat/preset-reach.js +43 -9
- package/lib/compat/probe.js +149 -7
- package/lib/context-inject.js +91 -35
- package/lib/index.js +479 -114
- package/lib/mcp/loader-token.js +53 -28
- package/lib/mcp/manager.js +149 -21
- package/lib/mcp/secret-guard.js +21 -0
- package/lib/mcp/state-section.js +13 -11
- package/lib/memories/archive-engine.js +70 -2
- package/lib/memories/archive.js +8 -1
- package/lib/memories/constants.js +72 -8
- package/lib/memories/index-io.js +7 -1
- package/lib/memories/projection.js +67 -31
- package/lib/memories/service.js +17 -2
- package/lib/memories/snapshot.js +113 -22
- package/lib/op-registry.js +26 -6
- package/lib/ops/candidates.js +11 -0
- package/lib/ops/compat.js +113 -31
- package/lib/ops/scene-records.js +34 -0
- package/lib/ops/scene-sync.js +12 -1
- package/lib/ops/sessions.js +24 -9
- package/lib/ops/snapshot.js +596 -0
- package/lib/prompts/service.js +25 -2
- package/lib/scenes/candidates.js +66 -5
- package/lib/sessions/bridge.js +5 -0
- package/lib/sessions/workspace.js +17 -5
- package/lib/skills/core.js +105 -3
- package/lib/skills/service.js +34 -2
- package/lib/subagents/service.js +37 -4
- package/lib/subagents/tools.js +11 -4
- package/lib/tools/deps.js +16 -1
- package/lib/tools/mcp.js +291 -50
- package/lib/tools/memory.js +131 -80
- package/lib/tools/prompt.js +27 -13
- package/lib/tools/scene.js +351 -0
- package/lib/tools/skills.js +93 -17
- package/lib/tools/subagent.js +56 -45
- package/lib/tools/table.js +208 -15
- package/package.json +24 -20
package/lib/op-registry.js
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// "会不会改宿主或外部系统的状态",而上面那些写的是插件自己的侧车(丢了可重建)。
|
|
32
32
|
// 想要它们也带令牌就显式改成 write,别靠"读操作带副作用"这句话推。
|
|
33
33
|
export const OP_REGISTRY = Object.freeze({
|
|
34
|
-
// ── MCP 域(
|
|
34
|
+
// ── MCP 域(17)────────────────────────────────────────────────────────────
|
|
35
35
|
'mcpm-list': { annotatesLock: true },
|
|
36
36
|
// reveal 返回**未打码**的凭据。它是读操作,但按写门禁 —— 局域网暴露的端口上,令牌对
|
|
37
37
|
// 明文凭据必须是最后一道防线,而不只对着写操作。
|
|
@@ -55,7 +55,10 @@ export const OP_REGISTRY = Object.freeze({
|
|
|
55
55
|
// 会停在启用态 —— 是写不是读。(0.6.0 / 0.7.0 各有漏列前科,见文件头。)
|
|
56
56
|
'mcpm-tools-refresh': { write: true },
|
|
57
57
|
'mcpm-tools': { readonly: true },
|
|
58
|
-
//
|
|
58
|
+
// 配置体检:只读补丁文件 + 一次 PATH 查询(`where`/`which`,不执行配置里的命令)。
|
|
59
|
+
// 返回的是检查项 id,不回任何字段值 —— 与 `mcpm-list` 同档,不带令牌。
|
|
60
|
+
'mcpm-inspect': { readonly: true },
|
|
61
|
+
// ── 技能域(22,含内联的 skill-open)───────────────────────────────────────
|
|
59
62
|
'skill-state': { annotatesLock: true },
|
|
60
63
|
'skill-detail': { readonly: true },
|
|
61
64
|
'skill-browse': { readonly: true },
|
|
@@ -70,6 +73,9 @@ export const OP_REGISTRY = Object.freeze({
|
|
|
70
73
|
'skill-prefer': { serviceWrite: true, frozen: true },
|
|
71
74
|
'skill-unprefer': { serviceWrite: true, frozen: true },
|
|
72
75
|
'skill-create': { serviceWrite: true, frozen: true },
|
|
76
|
+
// 改写 hub 里已存在的那一份技能(`skill_manager_save` 的改分支)。与 create 同规格:
|
|
77
|
+
// 都是往 hub 落文件。它**不动**官方根里的技能 —— 那层边界在 service 的胜出者判定里。
|
|
78
|
+
'skill-update': { serviceWrite: true, frozen: true },
|
|
73
79
|
'skill-import': { serviceWrite: true, frozen: true },
|
|
74
80
|
'skill-upload': { serviceWrite: true, frozen: true },
|
|
75
81
|
'skill-delete': { serviceWrite: true, frozen: true },
|
|
@@ -161,15 +167,26 @@ export const OP_REGISTRY = Object.freeze({
|
|
|
161
167
|
'bundle-export': { write: true },
|
|
162
168
|
// 新增一条宿主工作区登记 —— 改的是宿主侧的登记,不是插件自己的侧车。
|
|
163
169
|
'history-workspace-register': { write: true },
|
|
164
|
-
// ── 兼容 / 注入 /
|
|
170
|
+
// ── 兼容 / 注入 / 备份 / 流水 / 迁移快照(11)────────────────────────────────
|
|
165
171
|
'compat-status': { readonly: true },
|
|
172
|
+
// 「最近改动」流水的读侧:只读 hub 里的 `audit.jsonl`(内容只有对象名,见 audit-log.ts 第 ① 条)。
|
|
173
|
+
'audit-list': { readonly: true },
|
|
174
|
+
// 清空流水:删的是插件自己的文件,但它是**一次改动**(清完还会留下一条 `audit-clear`),按写门禁。
|
|
175
|
+
'audit-clear': { write: true },
|
|
176
|
+
// 整机迁移快照(0.15.0 C2)。export 标 sensitive 与 `mcpm-export` 同档:`includeSecrets` 时
|
|
177
|
+
// 它交出的就是明文凭据;不配令牌时不设防(判据见 http-fence.ts 的 secretOpRejection)。
|
|
178
|
+
'snapshot-export': { write: true, sensitive: true },
|
|
179
|
+
'snapshot-preview': { readonly: true },
|
|
180
|
+
// 逐域调各域自己的 import op —— 那些 op 本来就被场景冻结挡着,这里不重复标 frozen,
|
|
181
|
+
// 免得两道锁口径分家(预览与执行都走同一套门禁)。
|
|
182
|
+
'snapshot-import': { write: true },
|
|
166
183
|
'feature-overview': { readonly: true },
|
|
167
184
|
'preset-reach': { readonly: true },
|
|
168
|
-
//
|
|
185
|
+
// 全部注入域的当前正文**全文**都从这里出去。今天不带令牌就能读,是这批只读里披露面最大的一条;
|
|
169
186
|
// 要不要上门禁是产品决定,登记在这里是为了让它可数。
|
|
170
187
|
'injection-live': { readonly: true },
|
|
171
188
|
'backups-list': { readonly: true },
|
|
172
|
-
//
|
|
189
|
+
// 注入设置(各域开关 / 压制型预设口径)写侧车。它是"配置"不是"宿主状态",但改的是
|
|
173
190
|
// 投递语义,按写操作门禁。
|
|
174
191
|
'inject-settings': { write: true },
|
|
175
192
|
// 模型工具表(哪些工具根本不发给模型)也写侧车,改的是每轮请求的内容 —— 同上按写门禁。
|
|
@@ -180,8 +197,11 @@ export const OP_REGISTRY = Object.freeze({
|
|
|
180
197
|
'scene-settings': { write: true },
|
|
181
198
|
// 清理 patch 备份:删磁盘文件(备份里含明文凭据副本),按写操作门禁。
|
|
182
199
|
'backups-clean': { write: true },
|
|
183
|
-
// ── 场景候选源(
|
|
200
|
+
// ── 场景候选源(4)与内联(1)──────────────────────────────────────────────
|
|
184
201
|
'model-candidates': { readonly: true },
|
|
202
|
+
// 思考强度档位:要问 adapter(`llm.resolveModelInfo`,异步、可能联网),但只读不写任何状态
|
|
203
|
+
// —— 与 model-candidates 同一档。它不进 scene-inventory,是**按需**拉取的。
|
|
204
|
+
'model-reasoning': { readonly: true },
|
|
185
205
|
'scene-inventory': { readonly: true },
|
|
186
206
|
// 跨域悬空引用体检:只读对账(索引走 readIndexSync,权威集合各读一次)。
|
|
187
207
|
'state-doctor': { readonly: true },
|
package/lib/ops/candidates.js
CHANGED
|
@@ -16,6 +16,17 @@ export function buildCandidateOps(deps) {
|
|
|
16
16
|
}),
|
|
17
17
|
// model-candidates:宿主 LLM 目录里的 (provider, model) 对(不发网络请求)。
|
|
18
18
|
'model-candidates': async () => ({ ok: true, ...(await deps.modelCandidates()) }),
|
|
19
|
+
// model-reasoning:某个 (provider, model) 支持的思考强度档位(人设表单的「思考强度」下拉)。
|
|
20
|
+
// 为什么单独一个 op:档位清单要问 adapter(`llm.resolveModelInfo`,异步、可能联网),而
|
|
21
|
+
// model-candidates 是"不发网络请求"的本地目录 —— 捆在一次调用里会让"打开人设表单"这件事
|
|
22
|
+
// 忽然变成可能联网的操作。这里只做参数归一与转发,判定留给调用方(界面按 ok 分两态显示)。
|
|
23
|
+
'model-reasoning': async (args) => {
|
|
24
|
+
const provider = String((args && args.provider) || '').trim();
|
|
25
|
+
const model = String((args && args.model) || '').trim();
|
|
26
|
+
if (!provider || !model)
|
|
27
|
+
return { ok: false, error: 'provider 与 model 都要给(档位跟模型走)' };
|
|
28
|
+
return deps.modelReasoning(provider, model);
|
|
29
|
+
},
|
|
19
30
|
// 场景档案勾选器数据源 v2:全部 MCP 服务器(含未运行)+ 技能全集 + 人设清单。
|
|
20
31
|
'scene-inventory': async () => {
|
|
21
32
|
const [rowsR, tools, skills, subs] = await Promise.all([deps.mcpmListView(), deps.toolStates(), deps.skillRows(), deps.subagentList()]);
|
package/lib/ops/compat.js
CHANGED
|
@@ -129,15 +129,35 @@ export function buildCompatOps(deps) {
|
|
|
129
129
|
const domainsOn = INJECT_DOMAIN_KEYS.filter((key) => settings.domains[key] !== false);
|
|
130
130
|
const token = await deps.readTokenState();
|
|
131
131
|
const lockedScenes = await deps.lockedSceneNames();
|
|
132
|
-
const disabledTools = deps.
|
|
132
|
+
const disabledTools = deps.disabledToolSummary();
|
|
133
133
|
const routeOf = (operation) => assessment === undefined ? undefined : routeFor(assessment, operation);
|
|
134
|
-
/** 一行一个功能点:状态三层合成,`detail` 写明依据。 */
|
|
135
134
|
const rows = [];
|
|
136
|
-
const push = (
|
|
137
|
-
|
|
135
|
+
const push = (row) => rows.push(row);
|
|
136
|
+
/** note 降级行的公共字段:透传 note 的 detailKey / params,没有结构化键就只带原文。 */
|
|
137
|
+
const noteFields = (note) => note === undefined ? {}
|
|
138
|
+
: { ...(note.detailKey === undefined ? {} : { detailKey: note.detailKey }), ...(note.params === undefined ? {} : { params: note.params }) };
|
|
139
|
+
/** 路由判定行的公共字段:可用带 via;不可用带缺失能力的 id + 原始标签(客户端出各语言标签)。 */
|
|
140
|
+
const routeFields = (decision) => {
|
|
141
|
+
if (decision === undefined)
|
|
142
|
+
return {};
|
|
143
|
+
return decision.via === 'none'
|
|
144
|
+
? { refusals: decision.refusals.map((item) => ({ id: item.id, label: item.label })) }
|
|
145
|
+
: { via: decision.via };
|
|
146
|
+
};
|
|
147
|
+
const routeDetail = (decision) => decision === undefined ? '能力探测不可用(归档服务未挂载)'
|
|
148
|
+
: decision.via === 'none' ? '宿主缺少该路径所需能力:' + decision.refusals.map((item) => item.label).join('、')
|
|
149
|
+
: `可用(路径:${decision.via})`;
|
|
150
|
+
// 前置项:插件挂载(这一行本身说明 inject 都解析了 —— 否则 apply 根本不会跑);
|
|
138
151
|
// 启动期那段 `!!js` 表达式若抛错,插件同样不会挂上(它的 try/catch 就是为了不抛)。
|
|
139
152
|
const mountNote = notes.get('cordis-original-symbol');
|
|
140
|
-
push(
|
|
153
|
+
push({
|
|
154
|
+
key: 'mount', label: '插件挂载', tab: 'compat',
|
|
155
|
+
state: mountNote === undefined ? 'ok' : 'degraded',
|
|
156
|
+
detail: mountNote === undefined
|
|
157
|
+
? `${deps.injectServiceCount} 个 inject 服务全部解析,插件已挂载(详见 doctor 的挂载心跳)`
|
|
158
|
+
: mountNote.detail,
|
|
159
|
+
...(mountNote === undefined ? { params: { count: deps.injectServiceCount } } : noteFields(mountNote)),
|
|
160
|
+
});
|
|
141
161
|
// 装配层:有上报就是降级,附上报里的原因;没有就是正常。
|
|
142
162
|
const assemblyRows = [
|
|
143
163
|
['patch-write-guard', '宿主配置写入(补丁校验)', 'mcp'],
|
|
@@ -149,44 +169,106 @@ export function buildCompatOps(deps) {
|
|
|
149
169
|
];
|
|
150
170
|
for (const [noteId, label, tab] of assemblyRows) {
|
|
151
171
|
const note = notes.get(noteId);
|
|
152
|
-
push(
|
|
172
|
+
push({
|
|
173
|
+
key: noteId, label, tab, state: note === undefined ? 'ok' : 'degraded',
|
|
174
|
+
detail: note === undefined ? '装配正常,无降级上报' : note.detail,
|
|
175
|
+
...noteFields(note),
|
|
176
|
+
});
|
|
153
177
|
}
|
|
154
178
|
// 宿主能力层:删除 / 归档 / 恢复 / 批量各自由路由判定回答(与服务端执行同源)。
|
|
155
179
|
const deleteRoute = routeOf('delete');
|
|
156
|
-
push(
|
|
157
|
-
:
|
|
158
|
-
|
|
180
|
+
push({
|
|
181
|
+
key: 'session-delete', label: '会话删除', tab: 'sessions',
|
|
182
|
+
state: deleteRoute === undefined ? 'unknown' : deleteRoute.via === 'none' ? 'unavailable' : 'ok',
|
|
183
|
+
detail: routeDetail(deleteRoute), ...routeFields(deleteRoute),
|
|
184
|
+
});
|
|
159
185
|
for (const [operation, label] of [['archive', '归档'], ['unarchive', '恢复'], ['batch', '批量操作'], ['list', '历史列表']]) {
|
|
160
186
|
const decision = routeOf(operation);
|
|
161
|
-
push(
|
|
162
|
-
:
|
|
163
|
-
|
|
187
|
+
push({
|
|
188
|
+
key: 'session-' + operation, label: `会话${label}`, tab: 'sessions',
|
|
189
|
+
state: decision === undefined ? 'unknown' : decision.via === 'none' ? 'unavailable' : 'ok',
|
|
190
|
+
detail: routeDetail(decision), ...routeFields(decision),
|
|
191
|
+
});
|
|
164
192
|
}
|
|
165
193
|
// 用户配置层:注入域 / 令牌 / 场景锁定 / 停用工具数。
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
push(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
// 分母必须取自 INJECT_DOMAIN_KEYS:0.14.0 场景与记忆分家后域从 5 个变 6 个,
|
|
195
|
+
// 写死的「/5」把 6/6 报成 6/5(2026-09-28 用户实测)。
|
|
196
|
+
const domainTotal = INJECT_DOMAIN_KEYS.length;
|
|
197
|
+
push({
|
|
198
|
+
key: 'injection-domains', label: '注入域开关', tab: 'compat',
|
|
199
|
+
state: domainsOn.length === 0 ? 'disabled' : 'ok',
|
|
200
|
+
detail: domainsOn.length === 0 ? `${domainTotal} 个注入域全部关闭(用户设置)` : `${domainsOn.length}/${domainTotal} 个域开启:${domainsOn.join('、')}`,
|
|
201
|
+
domains: domainsOn, count: domainsOn.length, total: domainTotal,
|
|
202
|
+
});
|
|
203
|
+
push({
|
|
204
|
+
key: 'token', label: '访问令牌', tab: 'compat',
|
|
205
|
+
state: !token.active ? 'disabled' : token.accepted ? 'ok' : 'locked',
|
|
206
|
+
detail: !token.active ? '未启用(宿主没配令牌,或令牌功能被关掉)'
|
|
207
|
+
: token.accepted ? '已生效且本次启动已通过验证' : '已生效,本次启动尚未验证:写操作与对话会被拦住,到本页下方填写令牌',
|
|
208
|
+
});
|
|
209
|
+
push({
|
|
210
|
+
key: 'scene-lock', label: '场景锁定', tab: 'scenes',
|
|
211
|
+
state: lockedScenes.length === 0 ? 'ok' : 'locked-scene',
|
|
212
|
+
detail: lockedScenes.length === 0 ? '无锁定场景' : `锁定中:${lockedScenes.join('、')}(写门禁按锁定场景生效)`,
|
|
213
|
+
scenes: lockedScenes,
|
|
214
|
+
});
|
|
215
|
+
// 停用是用户配置或场景收窄的**事实**,不是故障:功能本身的好坏由上面「工具表可见性」
|
|
216
|
+
// 装配行报(`mcp-tool-visibility` 降级 = 拦截/摘除没装上),这一行只如实陈述停用构成,
|
|
217
|
+
// 状态恒为 ok —— 否则"只开了几台 MCP"的用户会一直看到琥珀,以为出了问题
|
|
218
|
+
// (2026-09-28 用户裁定:琥珀只留给功能出问题)。
|
|
219
|
+
// 「N 个工具」也说不清整台停用:一条 `*` 通配背后是那台服务器的全部工具,
|
|
220
|
+
// 按条目数报会把 6 台整台停用读成"6 个工具"。两个口径分开说:台数只数 `*`,工具数只数单独停用的条目。
|
|
221
|
+
const disabledPart = disabledTools.servers > 0 && disabledTools.tools > 0
|
|
222
|
+
? `${disabledTools.servers} 台服务器整台停用 + ${disabledTools.tools} 个工具单独停用`
|
|
223
|
+
: disabledTools.servers > 0
|
|
224
|
+
? `${disabledTools.servers} 台服务器整台停用`
|
|
225
|
+
: disabledTools.tools > 0
|
|
226
|
+
? `${disabledTools.tools} 个工具单独停用`
|
|
227
|
+
: '没有停用的工具';
|
|
228
|
+
push({
|
|
229
|
+
key: 'mcp-tools', label: 'MCP 工具停用', tab: 'mcp', state: 'ok',
|
|
230
|
+
detail: disabledPart + (disabledTools.servers + disabledTools.tools === 0 ? '' : '(执行拦截 + 可见性摘除)'),
|
|
231
|
+
servers: disabledTools.servers, tools: disabledTools.tools,
|
|
232
|
+
});
|
|
233
|
+
// 工具表按每个请求付钱:这一行回答"这一轮实际发出去多少"。关掉(无论出厂默认还是
|
|
234
|
+
// 用户自己关)都是**配置事实**而非故障 —— 状态恒为 ok,琥珀只留给功能出问题
|
|
235
|
+
// (口径与 MCP 工具停用一致,2026-09-28 用户裁定)。出厂默认关掉的那几条单说:
|
|
236
|
+
// 那是插件替用户做的一个可逆选择,与用户自己关的分开计数。
|
|
237
|
+
// 说明只保留一句关了多少:省多少 tok、去哪开关、面板不受影响、skill_manager_read
|
|
238
|
+
// 让位这些次级信息都不上功能总览(正常行一行读完,2026-09-28 用户反馈);
|
|
239
|
+
// 逐会话的让位差额(index.ts 的 CARRIER_DUPLICATES)在别处自有交代。
|
|
175
240
|
const table = deps.toolTableReport();
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
? `${table.totalCount}
|
|
179
|
-
:
|
|
180
|
-
|
|
241
|
+
const userOff = table.hiddenCount - table.defaultHiddenCount;
|
|
242
|
+
const offPart = table.hiddenCount === 0
|
|
243
|
+
? `${table.totalCount} 个工具全部下发`
|
|
244
|
+
: (userOff === 0
|
|
245
|
+
? `出厂默认关掉 ${table.defaultHiddenCount}/${table.totalCount} 个`
|
|
246
|
+
: `关掉 ${table.hiddenCount}/${table.totalCount} 个(含出厂默认 ${table.defaultHiddenCount} 个)`);
|
|
247
|
+
push({
|
|
248
|
+
key: 'tool-table', label: '模型工具表', tab: 'compat', state: 'ok', detail: offPart,
|
|
249
|
+
hidden: table.hiddenCount, total: table.totalCount, defaults: table.defaultHiddenCount,
|
|
250
|
+
});
|
|
251
|
+
const nativeNote = notes.get('workspace.delete-native');
|
|
252
|
+
push({
|
|
253
|
+
key: 'native-delete', label: '宿主原生删除入口', tab: 'compat',
|
|
254
|
+
state: nativeNote ? 'partial' : 'ok',
|
|
255
|
+
detail: nativeNote?.detail ?? '宿主未提供原生删除入口(本插件自有完整序列)',
|
|
256
|
+
...noteFields(nativeNote),
|
|
257
|
+
});
|
|
181
258
|
// 身份 / 版本:与 compat-status 同一份数据。
|
|
182
259
|
const identity = assessment?.identity;
|
|
183
260
|
const identityIssue = identity !== undefined && (identity.blockers.length > 0 || Object.values(identity.sameAsHost).some((same) => same === false));
|
|
184
|
-
push(
|
|
185
|
-
:
|
|
186
|
-
|
|
261
|
+
push({
|
|
262
|
+
key: 'host-identity', label: '宿主身份与模块', tab: 'compat',
|
|
263
|
+
state: assessment === undefined ? 'unknown' : identityIssue ? 'degraded' : 'ok',
|
|
264
|
+
detail: assessment === undefined ? '能力探测不可用(归档服务未挂载)'
|
|
265
|
+
: identityIssue ? '存在阻塞项:' + (identity?.blockers ?? []).join(';')
|
|
266
|
+
: `模块与宿主同源(宿主 DSH ${identity?.version ?? '?'})`,
|
|
267
|
+
...(assessment === undefined ? {} : { version: identity?.version ?? '?', blockers: identity?.blockers ?? [] }),
|
|
268
|
+
});
|
|
187
269
|
// 无独立装配点的域:没有上报就是正常(如实说明依据是"没有降级上报")。
|
|
188
270
|
for (const [key, label, tab] of [['memory', '记忆', 'memory'], ['prompts', '提示词', 'prompts'], ['subagents', '子智能体', 'subagents'], ['scenes', '场景档案', 'scenes']]) {
|
|
189
|
-
push(key, label, tab, 'ok', '无降级上报(装配失败会出现在这里)');
|
|
271
|
+
push({ key, label, tab, state: 'ok', detail: '无降级上报(装配失败会出现在这里)' });
|
|
190
272
|
}
|
|
191
273
|
return { ok: true, rows, generatedAt: Date.now() };
|
|
192
274
|
}
|
|
@@ -222,7 +304,7 @@ export function buildCompatOps(deps) {
|
|
|
222
304
|
...(r.failed.length ? { failedCount: r.failed.length, failedNames: r.failed } : {}),
|
|
223
305
|
};
|
|
224
306
|
},
|
|
225
|
-
//
|
|
307
|
+
// 注入实况(只读):最近活跃会话里模型**真正看到**的各域文本 + 那一段对话的投递统计。
|
|
226
308
|
// 回答"勾了开关到底送没送到"——界面配置与实际注入不一致时,这里一眼可见。
|
|
227
309
|
'injection-live': async () => {
|
|
228
310
|
const live = deps.getContextInjectorLive();
|
package/lib/ops/scene-records.js
CHANGED
|
@@ -108,6 +108,18 @@ export function buildSceneRecordOps(rc) {
|
|
|
108
108
|
if (name === GLOBAL_SCENE)
|
|
109
109
|
next.label = GLOBAL_SCENE_LABEL;
|
|
110
110
|
index.scenes[name] = next;
|
|
111
|
+
// 模型工具表方案绑定(0.15.0 用户裁定:入口改到「修改场景」表单):绑定存**档案**,
|
|
112
|
+
// 只在真的绑了东西时才建档案存根 —— 不绑就保持「没有档案」的原语义
|
|
113
|
+
//(没有档案与有空档案在进入场景的预览提示上不等价)。
|
|
114
|
+
const toolTablePreset = args && args.toolTablePreset !== undefined ? String(args.toolTablePreset).trim() : '';
|
|
115
|
+
if (toolTablePreset.length > 64) {
|
|
116
|
+
return fail('error.rules.invalidGroup', `工具表方案名过长(≤64 字符)`);
|
|
117
|
+
}
|
|
118
|
+
if (toolTablePreset !== '') {
|
|
119
|
+
if (!index.archives)
|
|
120
|
+
index.archives = {};
|
|
121
|
+
index.archives[name] = { ...(index.archives[name] || {}), toolTablePreset };
|
|
122
|
+
}
|
|
111
123
|
await writeIndex(stateDir, index);
|
|
112
124
|
invalidateSnapshot();
|
|
113
125
|
return { ok: true, scene: sceneRecordOf(name, next), ...(collapsed ? { collapsedActive: true } : {}) };
|
|
@@ -158,6 +170,28 @@ export function buildSceneRecordOps(rc) {
|
|
|
158
170
|
else
|
|
159
171
|
next.prompt = prompt;
|
|
160
172
|
}
|
|
173
|
+
if (args && args.toolTablePreset !== undefined) {
|
|
174
|
+
// 模型工具表方案绑定(0.15.0 用户裁定:入口改到「修改场景」表单):绑定存**档案**,
|
|
175
|
+
// 记录与档案同在一份索引 JSON,这一次读-改-写顺带落掉。''= 解绑(删字段;档案因此
|
|
176
|
+
// 变空时整条删掉,与档案保存「空档案不落条目」的约定一致);不传这个参数 = 完全不碰
|
|
177
|
+
// 这一域(旧调用方兼容)。绑了不存在的方案在这里不硬失败 —— 引擎在进出场景时按
|
|
178
|
+
// stale 丢弃并报告,和档案保存对悬空绑定的处理同口径。
|
|
179
|
+
const toolTablePreset = String(args.toolTablePreset).trim();
|
|
180
|
+
if (toolTablePreset.length > 64) {
|
|
181
|
+
return fail('error.rules.invalidGroup', `工具表方案名过长(≤64 字符)`);
|
|
182
|
+
}
|
|
183
|
+
if (!index.archives)
|
|
184
|
+
index.archives = {};
|
|
185
|
+
const archive = { ...((index.archives && index.archives[name]) || {}) };
|
|
186
|
+
if (toolTablePreset === '')
|
|
187
|
+
delete archive.toolTablePreset;
|
|
188
|
+
else
|
|
189
|
+
archive.toolTablePreset = toolTablePreset;
|
|
190
|
+
if (Object.keys(archive).length > 0)
|
|
191
|
+
index.archives[name] = archive;
|
|
192
|
+
else
|
|
193
|
+
delete index.archives[name];
|
|
194
|
+
}
|
|
161
195
|
if (args && args.label !== undefined && name !== GLOBAL_SCENE) {
|
|
162
196
|
const label = String(args.label).trim();
|
|
163
197
|
if (label === '')
|
package/lib/ops/scene-sync.js
CHANGED
|
@@ -10,7 +10,18 @@ export function buildSceneSyncOps(deps) {
|
|
|
10
10
|
// 的 `agentsMd` 字段(`applied` / `restored` / `unchanged` / `error`)。
|
|
11
11
|
// 组装时放在 memoriesService.ops 的 spread **之后**,显式覆盖同名 op。
|
|
12
12
|
'rules-set-active': async (args) => deps.withAgentsMdSync(await deps.rulesOps['rules-set-active'](args)),
|
|
13
|
-
'rules-update-scene': async (args) =>
|
|
13
|
+
'rules-update-scene': async (args) => {
|
|
14
|
+
const res = await deps.rulesOps['rules-update-scene'](args);
|
|
15
|
+
// 工具表方案绑定改动(0.15.0):改的是**当前启用场景**且这次真的带了这一参数时即时生效;
|
|
16
|
+
// 改名在场景处于模式中时会被拦(rulesUpdateScene 的 sceneInMode),所以这里 args.name 就是生效场景的名字。
|
|
17
|
+
if (res && res.ok !== false && deps.onToolTablePresetChanged && args && args.toolTablePreset !== undefined) {
|
|
18
|
+
try {
|
|
19
|
+
await deps.onToolTablePresetChanged(String(args.name || ''), String(args.toolTablePreset));
|
|
20
|
+
}
|
|
21
|
+
catch { /* 即时生效失败不拦保存;下次进/退场景会对齐 */ }
|
|
22
|
+
}
|
|
23
|
+
return deps.withAgentsMdSync(res);
|
|
24
|
+
},
|
|
14
25
|
'rules-create-scene': async (args) => deps.withAgentsMdSync(await deps.rulesOps['rules-create-scene'](args)),
|
|
15
26
|
'rules-remove-scene': async (args) => deps.withAgentsMdSync(await deps.rulesOps['rules-remove-scene'](args)),
|
|
16
27
|
};
|
package/lib/ops/sessions.js
CHANGED
|
@@ -54,7 +54,27 @@ export function buildSessionOps(deps) {
|
|
|
54
54
|
? (await registry.archivedSessionDetails()).items
|
|
55
55
|
: (await registry.archivedSessionMetadata()).items;
|
|
56
56
|
const archived = new Set(archivedItems.map((i) => i.sessionId));
|
|
57
|
+
// 官方契约(dsh-session-projection-cache 源码):cachedSnapshot(meta, keys?) 两参,
|
|
58
|
+
// keys 缺省 = 全部 wire 键;keys 传非可迭代值会在 viewCheckpoint 里 new Set 抛错。
|
|
59
|
+
// 旧格式代际的缓存记录过不了 cachedSnapshot 的身份校验,官方另有只认标题的
|
|
60
|
+
// cachedPredecessorTitle 提示面(api-session-controller 的列表路径正是两者相或)。
|
|
57
61
|
const cache = deps.host('sessionProjectionCache');
|
|
62
|
+
const titleOf = (header) => {
|
|
63
|
+
if (!cache)
|
|
64
|
+
return undefined;
|
|
65
|
+
try {
|
|
66
|
+
const snap = typeof cache.cachedSnapshot === 'function' ? cache.cachedSnapshot(header, ['title']) : undefined;
|
|
67
|
+
const current = snap && snap.values ? snap.values.title : undefined;
|
|
68
|
+
if (typeof current === 'string')
|
|
69
|
+
return current;
|
|
70
|
+
const legacy = typeof cache.cachedPredecessorTitle === 'function' ? cache.cachedPredecessorTitle(header) : undefined;
|
|
71
|
+
const legacyTitle = legacy && legacy.values ? legacy.values.title : undefined;
|
|
72
|
+
return typeof legacyTitle === 'string' ? legacyTitle : undefined;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
58
78
|
const items = [];
|
|
59
79
|
for (const h of headers) {
|
|
60
80
|
const sessionId = h && typeof h.id === 'string' ? h.id : undefined;
|
|
@@ -71,14 +91,9 @@ export function buildSessionOps(deps) {
|
|
|
71
91
|
item.cwd = h.cwd;
|
|
72
92
|
if (typeof h.createdAt === 'number' && Number.isFinite(h.createdAt))
|
|
73
93
|
item.createdAt = h.createdAt;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (snap && snap.values && typeof snap.values.title === 'string')
|
|
78
|
-
item.title = snap.values.title;
|
|
79
|
-
}
|
|
80
|
-
catch { /* title best-effort */ }
|
|
81
|
-
}
|
|
94
|
+
const title = titleOf(h);
|
|
95
|
+
if (title !== undefined)
|
|
96
|
+
item.title = title;
|
|
82
97
|
// 工作区目录可能已被删除/移动(孤儿会话):best-effort 标记,供导出选择时识别。
|
|
83
98
|
if (item.cwd) {
|
|
84
99
|
try {
|
|
@@ -388,7 +403,7 @@ export function buildSessionOps(deps) {
|
|
|
388
403
|
continue;
|
|
389
404
|
}
|
|
390
405
|
entries.push({ zip: `${id}/AGENTS.md`, abs: docAbs });
|
|
391
|
-
//
|
|
406
|
+
// 描述侧车(预设那句说明)随预设一起走:导出再导入不该把它丢掉。
|
|
392
407
|
const metaAbs = join(deps.promptsDir, id, 'meta.json');
|
|
393
408
|
try {
|
|
394
409
|
if ((await stat(metaAbs)).isFile())
|