dsh-model-switch 0.4.41 → 0.4.42
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 +12 -0
- package/README.md +2 -0
- package/README.zh.md +2 -0
- package/cordis.patch.yml +7 -0
- package/lib/{auto-review-BDx3LjVL.js → auto-review-QDEFUjT2.js} +2 -332
- package/lib/auto-review.js +1 -1
- package/lib/automode-compat.js +266 -0
- package/lib/client.js +23 -1
- package/lib/index.js +5 -2
- package/lib/tool-allowlist-DlzB5GlQ.js +332 -0
- package/lib/types/automode-compat.d.ts +152 -0
- package/lib/types/capabilities-rpc.d.ts +8 -0
- package/lib/types/client/locales.d.ts +3 -0
- package/lib/types/client-contract.d.ts +7 -0
- package/package.json +5 -1
- package/lib/session-route-BCQFzqRP.js +0 -172
- package/lib/session-route-BjNPlliu.js +0 -172
package/lib/client.js
CHANGED
|
@@ -444,10 +444,22 @@ window.__ModuleLoader__.load({
|
|
|
444
444
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
445
445
|
const status = raw;
|
|
446
446
|
const count = (input) => typeof input === "number" && Number.isFinite(input) && input >= 0 ? input : 0;
|
|
447
|
+
const route = (input) => typeof input === "string" && input.trim() !== "" ? input.trim() : void 0;
|
|
448
|
+
const automode = status.automode !== null && typeof status.automode === "object" && !Array.isArray(status.automode) ? (() => {
|
|
449
|
+
const row = status.automode;
|
|
450
|
+
if (row.installed !== true) return void 0;
|
|
451
|
+
return {
|
|
452
|
+
route: route(row.route),
|
|
453
|
+
routeSkipped: route(row.routeSkipped),
|
|
454
|
+
added: count(row.added),
|
|
455
|
+
skipped: count(row.skipped)
|
|
456
|
+
};
|
|
457
|
+
})() : void 0;
|
|
447
458
|
return {
|
|
448
459
|
held: status.held === true,
|
|
449
460
|
allowedCalls: count(status.allowedCalls),
|
|
450
|
-
skippedReadOnly: count(status.skippedReadOnly)
|
|
461
|
+
skippedReadOnly: count(status.skippedReadOnly),
|
|
462
|
+
automode
|
|
451
463
|
};
|
|
452
464
|
}
|
|
453
465
|
/**
|
|
@@ -2603,6 +2615,10 @@ window.__ModuleLoader__.load({
|
|
|
2603
2615
|
className: ModelSwitchSettings_module_css_default.hint,
|
|
2604
2616
|
children: props.t("reviewAllowlistSkipped").replace("{count}", String(reviewStatus.skippedReadOnly))
|
|
2605
2617
|
}),
|
|
2618
|
+
reviewStatus?.automode === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2619
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
2620
|
+
children: props.t("reviewAutomodeLinked").replace("{route}", reviewStatus.automode.route ?? (reviewStatus.automode.routeSkipped === "user-set" ? props.t("reviewAutomodeRouteOwn") : props.t("reviewAutomodeRouteUnset"))).replace("{added}", String(reviewStatus.automode.added)).replace("{skipped}", String(reviewStatus.automode.skipped))
|
|
2621
|
+
}),
|
|
2606
2622
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2607
2623
|
className: ModelSwitchSettings_module_css_default.allowList,
|
|
2608
2624
|
children: allowlistIncluded.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
@@ -3049,6 +3065,9 @@ window.__ModuleLoader__.load({
|
|
|
3049
3065
|
reviewAllowlistAvailable: "本机存在但未放行的工具:",
|
|
3050
3066
|
reviewAllowlistRepo: "仓库",
|
|
3051
3067
|
reviewAllowlistSkipped: "本 Host 进程已为纯读调用跳过 {count} 次审查。",
|
|
3068
|
+
reviewAutomodeLinked: "Auto mode 已接入:审查路由 {route},直通 +{added} 个纯读工具({skipped} 条通配未同步)。",
|
|
3069
|
+
reviewAutomodeRouteOwn: "沿用其自身配置",
|
|
3070
|
+
reviewAutomodeRouteUnset: "未配置(沿用会话模型)",
|
|
3052
3071
|
"reason.central-subagent-routing": "Alpha.4 没有全局子代理启动路由接口。",
|
|
3053
3072
|
"reason.packaged-preset-roots": "Alpha.4 不支持插件提供额外 preset root。",
|
|
3054
3073
|
"reason.tool-owner-suppression": "Alpha.4 没有工具所有者或来源抑制接口。",
|
|
@@ -3129,6 +3148,9 @@ window.__ModuleLoader__.load({
|
|
|
3129
3148
|
reviewAllowlistAvailable: "Present on this machine but not allowed:",
|
|
3130
3149
|
reviewAllowlistRepo: "repo",
|
|
3131
3150
|
reviewAllowlistSkipped: "This Host process has skipped the reviewer {count} times.",
|
|
3151
|
+
reviewAutomodeLinked: "Auto mode linked: reviewer route {route}, passes +{added} read-only tools ({skipped} pattern entries not synced).",
|
|
3152
|
+
reviewAutomodeRouteOwn: "its own settings",
|
|
3153
|
+
reviewAutomodeRouteUnset: "unset (follows the session model)",
|
|
3132
3154
|
"reason.central-subagent-routing": "Alpha.4 exposes no global Subagent start-routing seam.",
|
|
3133
3155
|
"reason.packaged-preset-roots": "Alpha.4 exposes no plugin-owned preset root.",
|
|
3134
3156
|
"reason.tool-owner-suppression": "Alpha.4 exposes no tool owner or provenance suppression seam.",
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { o as normalizeProviderGroups } from "./session-route-D1zoZgf3.js";
|
|
2
|
-
import {
|
|
2
|
+
import { r as resolveToolSource, t as DEFAULT_READ_ONLY_TOOLS } from "./tool-allowlist-DlzB5GlQ.js";
|
|
3
|
+
import { i as autoReviewStatus } from "./auto-review-QDEFUjT2.js";
|
|
3
4
|
import { ModelSwitchAdapterRegistry } from "./adapter-registry.js";
|
|
5
|
+
import { automodeCompatView } from "./automode-compat.js";
|
|
4
6
|
import { createRequire } from "node:module";
|
|
5
7
|
import z from "@deepseek-ai/schemastery";
|
|
6
8
|
import { Service } from "@deepseek-ai/cordis";
|
|
@@ -526,7 +528,8 @@ function installCapabilitiesRpc(ctx, registry, snapshot) {
|
|
|
526
528
|
}), () => ({
|
|
527
529
|
held: autoReviewStatus.held,
|
|
528
530
|
allowedCalls: autoReviewStatus.allowedCalls,
|
|
529
|
-
skippedReadOnly: autoReviewStatus.skippedReadOnly
|
|
531
|
+
skippedReadOnly: autoReviewStatus.skippedReadOnly,
|
|
532
|
+
automode: automodeCompatView()
|
|
530
533
|
}));
|
|
531
534
|
const dispose = scope.connection.fetch.register({
|
|
532
535
|
path: "/api/plugin-rpc/model-switch",
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/** Catalog lookup by id. */
|
|
2
|
+
const SOURCE_BY_ID = new Map([
|
|
3
|
+
{
|
|
4
|
+
id: "core",
|
|
5
|
+
kind: "core",
|
|
6
|
+
label: "DSH 核心",
|
|
7
|
+
repo: "https://github.com/deepseek-ai/deepseek-harness"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: "dsh-ops",
|
|
11
|
+
kind: "plugin",
|
|
12
|
+
label: "插件 · dsh-ops",
|
|
13
|
+
repo: "https://github.com/T-Auto/dsh-ops"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "dsh-browser-cdp",
|
|
17
|
+
kind: "plugin",
|
|
18
|
+
label: "插件 · dsh-browser-cdp",
|
|
19
|
+
repo: "https://github.com/drscrewdriver/dsh-browser-cdp"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "dsh-auto-memory",
|
|
23
|
+
kind: "plugin",
|
|
24
|
+
label: "插件 · dsh-auto-memory",
|
|
25
|
+
repo: "https://github.com/AskTheWay/dsh-auto-memory"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "dsh-model-switch",
|
|
29
|
+
kind: "plugin",
|
|
30
|
+
label: "插件 · dsh-model-switch",
|
|
31
|
+
repo: "https://github.com/vandoor7-droid/dsh-model-switch"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "mcp:wincu",
|
|
35
|
+
kind: "mcp",
|
|
36
|
+
label: "MCP · wincu(dsh-computer-use-win)",
|
|
37
|
+
repo: "https://github.com/Yu-tao-Li/dsh-computer-use-win"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "qqbot-extension",
|
|
41
|
+
kind: "extension",
|
|
42
|
+
label: "QQ 机器人扩展(框架 @zaofan/dsh-qqbot)",
|
|
43
|
+
repo: "https://github.com/zaofan-make/dsh-qqbot"
|
|
44
|
+
}
|
|
45
|
+
].map((entry) => [entry.id, entry]));
|
|
46
|
+
/**
|
|
47
|
+
* Tool names the released Host owns, calibrated against dsh 0.2.1-rc.2.
|
|
48
|
+
*
|
|
49
|
+
* A name outside the catalog would otherwise be reported as core, which is the
|
|
50
|
+
* one guess a reader could act on wrongly; it resolves to `unknown` instead.
|
|
51
|
+
*/
|
|
52
|
+
const CORE_TOOLS = /* @__PURE__ */ new Set([
|
|
53
|
+
"ask_user_question",
|
|
54
|
+
"create_goal",
|
|
55
|
+
"create_skill",
|
|
56
|
+
"create_worktree",
|
|
57
|
+
"edit",
|
|
58
|
+
"exit_plan_mode",
|
|
59
|
+
"get_goal",
|
|
60
|
+
"glob",
|
|
61
|
+
"grep",
|
|
62
|
+
"interrupt_agent",
|
|
63
|
+
"job_kill",
|
|
64
|
+
"job_list",
|
|
65
|
+
"job_output",
|
|
66
|
+
"list_agents",
|
|
67
|
+
"list_mcp_resource_templates",
|
|
68
|
+
"list_mcp_resources",
|
|
69
|
+
"load_workspace_dependencies",
|
|
70
|
+
"present",
|
|
71
|
+
"pwsh",
|
|
72
|
+
"ralph",
|
|
73
|
+
"read",
|
|
74
|
+
"read_image",
|
|
75
|
+
"read_mcp_resource",
|
|
76
|
+
"schedule_create",
|
|
77
|
+
"schedule_delete",
|
|
78
|
+
"schedule_list",
|
|
79
|
+
"schedule_update",
|
|
80
|
+
"send_message",
|
|
81
|
+
"session_event_read",
|
|
82
|
+
"session_event_search",
|
|
83
|
+
"session_event_trace",
|
|
84
|
+
"session_search",
|
|
85
|
+
"session_trace",
|
|
86
|
+
"sidebar_open",
|
|
87
|
+
"skill",
|
|
88
|
+
"spawn_teammate",
|
|
89
|
+
"subagent",
|
|
90
|
+
"subagent_fork",
|
|
91
|
+
"team_task_create",
|
|
92
|
+
"team_task_get",
|
|
93
|
+
"team_task_list",
|
|
94
|
+
"team_task_update",
|
|
95
|
+
"terminal_close",
|
|
96
|
+
"terminal_list",
|
|
97
|
+
"terminal_open",
|
|
98
|
+
"terminal_read",
|
|
99
|
+
"terminal_send",
|
|
100
|
+
"terminal_signal",
|
|
101
|
+
"todo_write",
|
|
102
|
+
"update_goal",
|
|
103
|
+
"wait_agent",
|
|
104
|
+
"web_fetch",
|
|
105
|
+
"web_search",
|
|
106
|
+
"web_search_pro",
|
|
107
|
+
"workflow",
|
|
108
|
+
"working_directory",
|
|
109
|
+
"write"
|
|
110
|
+
]);
|
|
111
|
+
/** MCP server name → catalog id, for the servers this allowlist knows. */
|
|
112
|
+
const MCP_SERVERS = /* @__PURE__ */ new Map([["wincu", "mcp:wincu"]]);
|
|
113
|
+
/** Plugin identity by tool-name prefix (checked before the core catalog). */
|
|
114
|
+
const PLUGIN_PREFIXES = [
|
|
115
|
+
["bcdp_", "dsh-browser-cdp"],
|
|
116
|
+
["browser_window_", "dsh-browser-cdp"],
|
|
117
|
+
["ops_", "dsh-ops"],
|
|
118
|
+
["memory_", "dsh-auto-memory"]
|
|
119
|
+
];
|
|
120
|
+
/** Single plugin tools that carry no prefix. */
|
|
121
|
+
const PLUGIN_TOOLS = /* @__PURE__ */ new Map([["generate_image", "dsh-model-switch"]]);
|
|
122
|
+
/** `<base>__<owner>` — the extension-tool naming the QQ bot framework uses. */
|
|
123
|
+
const EXTENSION_TOOL = /^([A-Za-z0-9_-]+)__(.+)$/;
|
|
124
|
+
/**
|
|
125
|
+
* Default allowlist: every pure-read tool this deployment exposes plus the
|
|
126
|
+
* no-side-effect and autonomy entries the user explicitly released, calibrated
|
|
127
|
+
* on a live desktop profile (105 tools) at dsh 0.2.1-rc.2.
|
|
128
|
+
*
|
|
129
|
+
* Grouped by origin so a reader can trace each entry through {@link SOURCE_CATALOG}.
|
|
130
|
+
* Entries ending in `*` are patterns: the QQ bot extension suffixes its tools with
|
|
131
|
+
* the bot's own name, so `sessions_list__*` keeps working when the persona is
|
|
132
|
+
* renamed.
|
|
133
|
+
*
|
|
134
|
+
* Everything here follows one rule: releasing it must not let a side effect escape
|
|
135
|
+
* the reviewer. Calls that merely dispatch, ask, present, wait or navigate qualify
|
|
136
|
+
* because the work they trigger still runs under the same auto preset. Delegation
|
|
137
|
+
* tools (`subagent`, `subagent_fork`, `spawn_teammate`) deliberately stay out: a
|
|
138
|
+
* child session seeds its preset from `permissionPresets.current(parent.session)`,
|
|
139
|
+
* and this bypass answers `'custom'` for an allowlisted call in flight — releasing
|
|
140
|
+
* them would hand the child no `auto` preset at all, i.e. no review.
|
|
141
|
+
*/
|
|
142
|
+
const DEFAULT_READ_ONLY_TOOLS = [
|
|
143
|
+
"read",
|
|
144
|
+
"grep",
|
|
145
|
+
"glob",
|
|
146
|
+
"read_image",
|
|
147
|
+
"skill",
|
|
148
|
+
"web_search",
|
|
149
|
+
"web_search_pro",
|
|
150
|
+
"web_fetch",
|
|
151
|
+
"load_workspace_dependencies",
|
|
152
|
+
"ask_user_question",
|
|
153
|
+
"present",
|
|
154
|
+
"sidebar_open",
|
|
155
|
+
"exit_plan_mode",
|
|
156
|
+
"job_list",
|
|
157
|
+
"job_output",
|
|
158
|
+
"terminal_list",
|
|
159
|
+
"terminal_read",
|
|
160
|
+
"list_agents",
|
|
161
|
+
"team_task_list",
|
|
162
|
+
"team_task_get",
|
|
163
|
+
"get_goal",
|
|
164
|
+
"schedule_list",
|
|
165
|
+
"wait_agent",
|
|
166
|
+
"send_message",
|
|
167
|
+
"team_task_create",
|
|
168
|
+
"list_mcp_resources",
|
|
169
|
+
"list_mcp_resource_templates",
|
|
170
|
+
"read_mcp_resource",
|
|
171
|
+
"session_search",
|
|
172
|
+
"session_event_search",
|
|
173
|
+
"session_event_read",
|
|
174
|
+
"session_event_trace",
|
|
175
|
+
"session_trace",
|
|
176
|
+
"create_goal",
|
|
177
|
+
"schedule_create",
|
|
178
|
+
"terminal_open",
|
|
179
|
+
"ops_read_files",
|
|
180
|
+
"ops_discover_tools",
|
|
181
|
+
"bcdp_status",
|
|
182
|
+
"bcdp_help",
|
|
183
|
+
"bcdp_doctor",
|
|
184
|
+
"bcdp_snapshot",
|
|
185
|
+
"browser_window_status",
|
|
186
|
+
"memory_list",
|
|
187
|
+
"memory_read",
|
|
188
|
+
"mcp__wincu__windows_computer_use_accessibility_tree",
|
|
189
|
+
"mcp__wincu__windows_computer_use_element_info",
|
|
190
|
+
"mcp__wincu__windows_computer_use_find",
|
|
191
|
+
"mcp__wincu__windows_computer_use_health",
|
|
192
|
+
"mcp__wincu__windows_computer_use_list_windows",
|
|
193
|
+
"mcp__wincu__windows_computer_use_ocr",
|
|
194
|
+
"mcp__wincu__windows_computer_use_screenshot",
|
|
195
|
+
"mcp__wincu__windows_computer_use_snapshot",
|
|
196
|
+
"mcp__wincu__windows_computer_use_tools",
|
|
197
|
+
"mcp__wincu__windows_computer_use_wait",
|
|
198
|
+
"mcp__wincu__windows_computer_use_wait_for",
|
|
199
|
+
"sessions_list__*",
|
|
200
|
+
"bing_search__*"
|
|
201
|
+
];
|
|
202
|
+
/**
|
|
203
|
+
* Validate one allowlist entry.
|
|
204
|
+
*
|
|
205
|
+
* `*` is a suffix wildcard: the entry must keep at least one literal leading
|
|
206
|
+
* character, so a stray `*` can never turn the list into "allow everything".
|
|
207
|
+
*
|
|
208
|
+
* @param entry - the raw entry as typed in settings.
|
|
209
|
+
* @returns `ok`, or the specific problem.
|
|
210
|
+
*/
|
|
211
|
+
function validateAllowEntry(entry) {
|
|
212
|
+
const trimmed = entry.trim();
|
|
213
|
+
if (trimmed === "") return "empty";
|
|
214
|
+
if (/^\*+$/.test(trimmed)) return "bare-wildcard";
|
|
215
|
+
if (trimmed.startsWith("*")) return "leading-wildcard";
|
|
216
|
+
return "ok";
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Whether a tool name matches one allowlist entry.
|
|
220
|
+
*
|
|
221
|
+
* Exact names match exactly; an entry may end in `*`, which matches any
|
|
222
|
+
* remaining characters (including none). Invalid entries never match.
|
|
223
|
+
*
|
|
224
|
+
* @param name - the tool name from `exec.name`.
|
|
225
|
+
* @param entry - one allowlist entry.
|
|
226
|
+
* @returns true when the call may skip review.
|
|
227
|
+
*/
|
|
228
|
+
function matchesAllowPattern(name, entry) {
|
|
229
|
+
if (name === "" || validateAllowEntry(entry) !== "ok") return false;
|
|
230
|
+
const trimmed = entry.trim();
|
|
231
|
+
const star = trimmed.indexOf("*");
|
|
232
|
+
if (star === -1) return name === trimmed;
|
|
233
|
+
const prefix = trimmed.slice(0, star);
|
|
234
|
+
const suffix = trimmed.slice(star + 1);
|
|
235
|
+
if (!name.startsWith(prefix) || !name.endsWith(suffix)) return false;
|
|
236
|
+
return name.length >= prefix.length + suffix.length;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Whether a tool name is covered by an allowlist.
|
|
240
|
+
* @param name - the tool name under review.
|
|
241
|
+
* @param allowlist - configured entries, patterns included.
|
|
242
|
+
* @returns true when at least one entry matches.
|
|
243
|
+
*/
|
|
244
|
+
function isAllowedByList(name, allowlist) {
|
|
245
|
+
return allowlist.some((entry) => matchesAllowPattern(name, entry));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Resolve the origin of one tool name for display.
|
|
249
|
+
*
|
|
250
|
+
* Resolution order: MCP bridge → `<tool>__<owner>` extension → plugin prefix →
|
|
251
|
+
* plugin table → core catalog → unknown. Only the first two are structural; the
|
|
252
|
+
* rest are calibrated names, so an unlisted tool is reported as unknown rather
|
|
253
|
+
* than guessed into a package.
|
|
254
|
+
*
|
|
255
|
+
* @param name - the tool name.
|
|
256
|
+
* @returns The origin, with its repository when known.
|
|
257
|
+
*/
|
|
258
|
+
function resolveToolSource(name) {
|
|
259
|
+
const trimmed = name.trim().replace(/\*+$/, "");
|
|
260
|
+
const mcp = /^mcp__([A-Za-z0-9_-]+)__/.exec(trimmed);
|
|
261
|
+
if (mcp !== null) {
|
|
262
|
+
const server = mcp[1];
|
|
263
|
+
const entry = SOURCE_BY_ID.get(MCP_SERVERS.get(server) ?? "");
|
|
264
|
+
if (entry !== void 0) return {
|
|
265
|
+
kind: "mcp",
|
|
266
|
+
label: entry.label,
|
|
267
|
+
repo: entry.repo,
|
|
268
|
+
owner: server
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
kind: "mcp",
|
|
272
|
+
label: `MCP · ${server}`,
|
|
273
|
+
owner: server
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const extension = EXTENSION_TOOL.exec(trimmed);
|
|
277
|
+
if (extension !== null && !trimmed.startsWith("mcp__")) {
|
|
278
|
+
const entry = SOURCE_BY_ID.get("qqbot-extension");
|
|
279
|
+
const base = extension[1];
|
|
280
|
+
const owner = extension[2];
|
|
281
|
+
return {
|
|
282
|
+
kind: "extension",
|
|
283
|
+
label: `扩展 · ${base}__${owner}`,
|
|
284
|
+
...entry === void 0 ? {} : { repo: entry.repo },
|
|
285
|
+
owner
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (trimmed.endsWith("__")) {
|
|
289
|
+
const entry = SOURCE_BY_ID.get("qqbot-extension");
|
|
290
|
+
return {
|
|
291
|
+
kind: "extension",
|
|
292
|
+
label: `扩展 · ${trimmed.slice(0, -2)}__*`,
|
|
293
|
+
...entry === void 0 ? {} : { repo: entry.repo },
|
|
294
|
+
owner: "*"
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
for (const [prefix, id] of PLUGIN_PREFIXES) {
|
|
298
|
+
if (!trimmed.startsWith(prefix)) continue;
|
|
299
|
+
const entry = SOURCE_BY_ID.get(id);
|
|
300
|
+
if (entry !== void 0) return {
|
|
301
|
+
kind: "plugin",
|
|
302
|
+
label: entry.label,
|
|
303
|
+
repo: entry.repo,
|
|
304
|
+
owner: id
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const plugin = PLUGIN_TOOLS.get(trimmed);
|
|
308
|
+
if (plugin !== void 0) {
|
|
309
|
+
const entry = SOURCE_BY_ID.get(plugin);
|
|
310
|
+
if (entry !== void 0) return {
|
|
311
|
+
kind: "plugin",
|
|
312
|
+
label: entry.label,
|
|
313
|
+
repo: entry.repo,
|
|
314
|
+
owner: plugin
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (CORE_TOOLS.has(trimmed)) {
|
|
318
|
+
const entry = SOURCE_BY_ID.get("core");
|
|
319
|
+
return {
|
|
320
|
+
kind: "core",
|
|
321
|
+
label: entry?.label ?? "DSH 核心",
|
|
322
|
+
...entry === void 0 ? {} : { repo: entry.repo },
|
|
323
|
+
owner: "core"
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
kind: "unknown",
|
|
328
|
+
label: "未知来源"
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
export { isAllowedByList as n, resolveToolSource as r, DEFAULT_READ_ONLY_TOOLS as t };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapt the third-party `@log.li/dsh-automode` row to this plugin's routes.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* `dsh-automode` owns a completely different decision pipeline: its own
|
|
7
|
+
* `auto-mode` permission preset, a `tools/pre-execute` gate, and a two-stage
|
|
8
|
+
* classifier that calls `ctx.llm.stream()` with
|
|
9
|
+
* `classifier.provider` / `classifier.model` (`resolveRoute`, falling back to
|
|
10
|
+
* the session header when both are empty). It reads the pure-read allowance from
|
|
11
|
+
* `config.readOnlyTools` with an exact `includes()` match
|
|
12
|
+
* (`src/pre-execute.ts`), so a call that hits it is allowed before the
|
|
13
|
+
* classifier runs — still after the plugin's own hard-deny regex band.
|
|
14
|
+
*
|
|
15
|
+
* Both knobs live in that row's own config. There is no public seam to inject a
|
|
16
|
+
* route or a tool list from outside, and the user asked for the adaptation to
|
|
17
|
+
* happen inside the plugin layer: no profile editing, no second plugin. This
|
|
18
|
+
* module is that adapter. It rewrites the automode row's **runtime** option
|
|
19
|
+
* object right before the loader mounts it and on every remount:
|
|
20
|
+
*
|
|
21
|
+
* - `classifier.provider` / `classifier.model` are filled from this plugin's
|
|
22
|
+
* Auto-review route **only when the row leaves them empty**; an explicit route
|
|
23
|
+
* in the profile always wins.
|
|
24
|
+
* - `readOnlyTools` is the union of the row's own list and this plugin's
|
|
25
|
+
* allowlist — but only while the "skip pure-read tools" switch is on. Entries
|
|
26
|
+
* that carry a `*` cannot transfer (the third-party check is exact-match) and
|
|
27
|
+
* are reported instead of silently dropped.
|
|
28
|
+
*
|
|
29
|
+
* Nothing is persisted: `entry.options` is the loader's live options object, and
|
|
30
|
+
* `Entry.update()` (which writes the profile layer) is deliberately never called
|
|
31
|
+
* here. Restarting the Host re-applies the same in-memory view.
|
|
32
|
+
*
|
|
33
|
+
* Every failure is contained. If the row shape or the loader internals change,
|
|
34
|
+
* this adapter logs one warning and the rest of the plugin keeps working.
|
|
35
|
+
*
|
|
36
|
+
* @module
|
|
37
|
+
*/
|
|
38
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
39
|
+
import type { Loader } from '@deepseek-ai/cordis-plugin-loader';
|
|
40
|
+
import type { ModelSwitchSettings } from './host-settings.js';
|
|
41
|
+
/** Cordis plugin identity; matches the `cordis.patch.yml` insert row id. */
|
|
42
|
+
export declare const name = "model-switch-automode-compat";
|
|
43
|
+
/** The loader is the only seam this row needs besides this plugin's settings. */
|
|
44
|
+
export declare const inject: string[];
|
|
45
|
+
/** Third-party package this row adapts (strict package identity, never a prefix). */
|
|
46
|
+
export declare const AUTOMODE_PACKAGE = "@log.li/dsh-automode";
|
|
47
|
+
/**
|
|
48
|
+
* Why the route was not injected.
|
|
49
|
+
*
|
|
50
|
+
* - `user-set`: the row already names a provider and a model.
|
|
51
|
+
* - `no-route`: this plugin has no Auto-review route configured right now.
|
|
52
|
+
*/
|
|
53
|
+
export type AutomodeRouteSkip = 'user-set' | 'no-route';
|
|
54
|
+
/** Result of adapting one row's config object. */
|
|
55
|
+
export interface AutomodeCompatApplied {
|
|
56
|
+
/** `provider/model` written into the row, when it was empty before. */
|
|
57
|
+
readonly route?: string | undefined;
|
|
58
|
+
/** Set when the route was left alone. */
|
|
59
|
+
readonly routeSkipped?: AutomodeRouteSkip | undefined;
|
|
60
|
+
/** Allowlist entries merged into `readOnlyTools`. */
|
|
61
|
+
readonly added: readonly string[];
|
|
62
|
+
/** Allowlist entries that cannot transfer because they are pattern-shaped. */
|
|
63
|
+
readonly skipped: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
/** One adaptation result: the config to install plus what changed. */
|
|
66
|
+
export interface AutomodeCompatPlan {
|
|
67
|
+
/** The config object to assign to the row. */
|
|
68
|
+
readonly config: Record<string, unknown>;
|
|
69
|
+
/** Whether `config` differs from the input (false means "leave it alone"). */
|
|
70
|
+
readonly changed: boolean;
|
|
71
|
+
/** What was applied, for the status line and the log. */
|
|
72
|
+
readonly applied: AutomodeCompatApplied;
|
|
73
|
+
}
|
|
74
|
+
/** Live adapter state, surfaced next to the allowlist editor. */
|
|
75
|
+
export declare const automodeCompatStatus: {
|
|
76
|
+
/** A `@log.li/dsh-automode` row exists in this Host. */
|
|
77
|
+
installed: boolean;
|
|
78
|
+
/** `provider/model` this adapter wrote, when it wrote one. */
|
|
79
|
+
route: string | undefined;
|
|
80
|
+
/** Why the route was skipped, when it was. */
|
|
81
|
+
routeSkipped: AutomodeRouteSkip | undefined;
|
|
82
|
+
/** Tools merged into the row's `readOnlyTools`. */
|
|
83
|
+
added: string[];
|
|
84
|
+
/** Pattern-shaped entries that cannot transfer. */
|
|
85
|
+
skipped: string[];
|
|
86
|
+
/** How many rows were rewritten (remounts included). */
|
|
87
|
+
applied: number;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Whether one loader entry is the third-party auto-mode row.
|
|
91
|
+
* @param entry - anything the loader hands over (an entry, or an event payload).
|
|
92
|
+
* @returns true only for the exact package name.
|
|
93
|
+
*/
|
|
94
|
+
export declare function isAutomodeEntry(entry: unknown): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Decide the config one automode row should run with.
|
|
97
|
+
*
|
|
98
|
+
* Pure and side-effect free so the policy is unit-testable.
|
|
99
|
+
*
|
|
100
|
+
* @param config - the row's current config (any JSON-ish value).
|
|
101
|
+
* @param route - this plugin's Auto-review route; empty strings mean "unset".
|
|
102
|
+
* @param allowlist - tool names to merge into `readOnlyTools`; pass an empty
|
|
103
|
+
* list while the skip switch is off.
|
|
104
|
+
* @param adoptedRoute - a `provider/model` this adapter wrote on an earlier
|
|
105
|
+
* pass; seeing it again means the row is still ours to rewrite.
|
|
106
|
+
* @returns the config to install, whether it changed, and what was applied.
|
|
107
|
+
*/
|
|
108
|
+
export declare function planAutomodeCompat(config: unknown, route: {
|
|
109
|
+
readonly provider?: string;
|
|
110
|
+
readonly model?: string;
|
|
111
|
+
}, allowlist: readonly string[], adoptedRoute?: string): AutomodeCompatPlan;
|
|
112
|
+
/** One-line human summary of a plan, for the startup log. */
|
|
113
|
+
export declare function describeAutomodePlan(plan: AutomodeCompatPlan): string;
|
|
114
|
+
/** Shape the settings card renders for the third-party row, or undefined when absent. */
|
|
115
|
+
export interface AutomodeCompatView {
|
|
116
|
+
readonly installed: boolean;
|
|
117
|
+
/** `provider/model` written into the row, when one was. */
|
|
118
|
+
readonly route?: string | undefined;
|
|
119
|
+
/** Why no route was written, when none was. */
|
|
120
|
+
readonly routeSkipped?: AutomodeRouteSkip | undefined;
|
|
121
|
+
/** How many tools were merged in. */
|
|
122
|
+
readonly added: number;
|
|
123
|
+
/** How many pattern-shaped entries could not transfer. */
|
|
124
|
+
readonly skipped: number;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Snapshot for the RPC/UI layer.
|
|
128
|
+
* @returns the view, or undefined when this Host has no automode row.
|
|
129
|
+
*/
|
|
130
|
+
export declare function automodeCompatView(): AutomodeCompatView | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Install the adapter: adapt live rows now, and every row the loader mounts later.
|
|
133
|
+
*
|
|
134
|
+
* The loader applies the profile patch layer and mounts the entry's plugin right
|
|
135
|
+
* after the `loader/patch-context` waterfall returns, so rewriting the entry's
|
|
136
|
+
* `options.config` from that waterfall is what makes the adapted values the ones
|
|
137
|
+
* the plugin activates with. No profile write happens here.
|
|
138
|
+
*
|
|
139
|
+
* @param ctx - this row's context.
|
|
140
|
+
* @param loader - the loader service, when the Host provides it.
|
|
141
|
+
* @param settings - reads the current plugin settings.
|
|
142
|
+
* @returns a disposer that detaches the listeners, plus a replay for live rows.
|
|
143
|
+
*/
|
|
144
|
+
export declare function installAutomodeCompat(ctx: Context, loader: Loader | undefined, settings: () => ModelSwitchSettings): {
|
|
145
|
+
dispose: () => void;
|
|
146
|
+
replay: () => boolean;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Row entry point: adapt the third-party row on activation and on remount.
|
|
150
|
+
* @param ctx - this row's context.
|
|
151
|
+
*/
|
|
152
|
+
export declare function apply(ctx: Context): void;
|
|
@@ -8,6 +8,14 @@ export interface ReviewStatus {
|
|
|
8
8
|
readonly held: boolean;
|
|
9
9
|
readonly allowedCalls: number;
|
|
10
10
|
readonly skippedReadOnly: number;
|
|
11
|
+
/** Third-party `@log.li/dsh-automode` row, when this Host mounts one. */
|
|
12
|
+
readonly automode?: {
|
|
13
|
+
readonly installed: boolean;
|
|
14
|
+
readonly route?: string | undefined;
|
|
15
|
+
readonly routeSkipped?: string | undefined;
|
|
16
|
+
readonly added: number;
|
|
17
|
+
readonly skipped: number;
|
|
18
|
+
} | undefined;
|
|
11
19
|
}
|
|
12
20
|
export declare const CAPABILITIES_RPC_METHOD = "plugin-rpc/model-switch";
|
|
13
21
|
export interface CapabilitiesSnapshot {
|
|
@@ -71,6 +71,9 @@ export declare const zh: {
|
|
|
71
71
|
readonly reviewAllowlistAvailable: "本机存在但未放行的工具:";
|
|
72
72
|
readonly reviewAllowlistRepo: "仓库";
|
|
73
73
|
readonly reviewAllowlistSkipped: "本 Host 进程已为纯读调用跳过 {count} 次审查。";
|
|
74
|
+
readonly reviewAutomodeLinked: "Auto mode 已接入:审查路由 {route},直通 +{added} 个纯读工具({skipped} 条通配未同步)。";
|
|
75
|
+
readonly reviewAutomodeRouteOwn: "沿用其自身配置";
|
|
76
|
+
readonly reviewAutomodeRouteUnset: "未配置(沿用会话模型)";
|
|
74
77
|
readonly 'reason.central-subagent-routing': "Alpha.4 没有全局子代理启动路由接口。";
|
|
75
78
|
readonly 'reason.packaged-preset-roots': "Alpha.4 不支持插件提供额外 preset root。";
|
|
76
79
|
readonly 'reason.tool-owner-suppression': "Alpha.4 没有工具所有者或来源抑制接口。";
|
|
@@ -94,6 +94,13 @@ export interface ReviewStatusView {
|
|
|
94
94
|
readonly held: boolean;
|
|
95
95
|
readonly allowedCalls: number;
|
|
96
96
|
readonly skippedReadOnly: number;
|
|
97
|
+
/** Third-party `@log.li/dsh-automode` row this Host adapted, when present. */
|
|
98
|
+
readonly automode?: {
|
|
99
|
+
readonly route?: string | undefined;
|
|
100
|
+
readonly routeSkipped?: string | undefined;
|
|
101
|
+
readonly added: number;
|
|
102
|
+
readonly skipped: number;
|
|
103
|
+
} | undefined;
|
|
97
104
|
}
|
|
98
105
|
/**
|
|
99
106
|
* Decode the reviewer counters from a `tools` RPC payload.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-switch",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.42",
|
|
4
4
|
"description": "Explicit model routing plus composer/Plan picker for DeepSeek Harness",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
"types": "./lib/types/adapter-registry.d.ts",
|
|
59
59
|
"default": "./lib/adapter-registry.js"
|
|
60
60
|
},
|
|
61
|
+
"./automode-compat": {
|
|
62
|
+
"types": "./lib/types/automode-compat.d.ts",
|
|
63
|
+
"default": "./lib/automode-compat.js"
|
|
64
|
+
},
|
|
61
65
|
"./package.json": "./package.json"
|
|
62
66
|
},
|
|
63
67
|
"files": [
|