dsh-model-switch 0.4.38 → 0.4.40
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 +37 -0
- package/lib/{auto-review-CvmQaRVf.js → auto-review-BDx3LjVL.js} +385 -10
- package/lib/auto-review.js +1 -1
- package/lib/client.js +728 -4
- package/lib/index.js +86 -3
- package/lib/types/auto-review.d.ts +39 -6
- package/lib/types/capabilities-rpc.d.ts +10 -1
- package/lib/types/client/ModelSwitchSettings.d.ts +11 -1
- package/lib/types/client/locales.d.ts +18 -0
- package/lib/types/client/picker/PlanReviewCard.d.ts +8 -0
- package/lib/types/client-contract.d.ts +54 -0
- package/lib/types/host-settings.d.ts +17 -0
- package/lib/types/picker/discuss-plan.d.ts +43 -0
- package/lib/types/tool-allowlist.d.ts +122 -0
- package/lib/types/tool-catalog.d.ts +37 -0
- package/package.json +1 -1
- package/lib/auto-review-9SreBh-0.js +0 -855
- package/lib/auto-review-BLtwr83V.js +0 -738
- package/lib/auto-review-BoF2_YtP.js +0 -745
- package/lib/auto-review-CMHNfRuV.js +0 -1015
- package/lib/auto-review-D-0A5Amx.js +0 -859
- package/lib/auto-review-oan7iXY7.js +0 -987
package/lib/client.js
CHANGED
|
@@ -8,6 +8,337 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
9
|
let react_dom = require("react-dom");
|
|
10
10
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
11
|
+
/** Catalog lookup by id. */
|
|
12
|
+
const SOURCE_BY_ID = new Map([
|
|
13
|
+
{
|
|
14
|
+
id: "core",
|
|
15
|
+
kind: "core",
|
|
16
|
+
label: "DSH 核心",
|
|
17
|
+
repo: "https://github.com/deepseek-ai/deepseek-harness"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "dsh-ops",
|
|
21
|
+
kind: "plugin",
|
|
22
|
+
label: "插件 · dsh-ops",
|
|
23
|
+
repo: "https://github.com/T-Auto/dsh-ops"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "dsh-browser-cdp",
|
|
27
|
+
kind: "plugin",
|
|
28
|
+
label: "插件 · dsh-browser-cdp",
|
|
29
|
+
repo: "https://github.com/drscrewdriver/dsh-browser-cdp"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "dsh-auto-memory",
|
|
33
|
+
kind: "plugin",
|
|
34
|
+
label: "插件 · dsh-auto-memory",
|
|
35
|
+
repo: "https://github.com/AskTheWay/dsh-auto-memory"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "dsh-model-switch",
|
|
39
|
+
kind: "plugin",
|
|
40
|
+
label: "插件 · dsh-model-switch",
|
|
41
|
+
repo: "https://github.com/vandoor7-droid/dsh-model-switch"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "mcp:wincu",
|
|
45
|
+
kind: "mcp",
|
|
46
|
+
label: "MCP · wincu(dsh-computer-use-win)",
|
|
47
|
+
repo: "https://github.com/Yu-tao-Li/dsh-computer-use-win"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "qqbot-extension",
|
|
51
|
+
kind: "extension",
|
|
52
|
+
label: "QQ 机器人扩展(框架 @zaofan/dsh-qqbot)",
|
|
53
|
+
repo: "https://github.com/zaofan-make/dsh-qqbot"
|
|
54
|
+
}
|
|
55
|
+
].map((entry) => [entry.id, entry]));
|
|
56
|
+
/**
|
|
57
|
+
* Tool names the released Host owns, calibrated against dsh 0.2.1-rc.2.
|
|
58
|
+
*
|
|
59
|
+
* A name outside the catalog would otherwise be reported as core, which is the
|
|
60
|
+
* one guess a reader could act on wrongly; it resolves to `unknown` instead.
|
|
61
|
+
*/
|
|
62
|
+
const CORE_TOOLS = /* @__PURE__ */ new Set([
|
|
63
|
+
"ask_user_question",
|
|
64
|
+
"create_goal",
|
|
65
|
+
"create_skill",
|
|
66
|
+
"create_worktree",
|
|
67
|
+
"edit",
|
|
68
|
+
"exit_plan_mode",
|
|
69
|
+
"get_goal",
|
|
70
|
+
"glob",
|
|
71
|
+
"grep",
|
|
72
|
+
"interrupt_agent",
|
|
73
|
+
"job_kill",
|
|
74
|
+
"job_list",
|
|
75
|
+
"job_output",
|
|
76
|
+
"list_agents",
|
|
77
|
+
"list_mcp_resource_templates",
|
|
78
|
+
"list_mcp_resources",
|
|
79
|
+
"load_workspace_dependencies",
|
|
80
|
+
"present",
|
|
81
|
+
"pwsh",
|
|
82
|
+
"ralph",
|
|
83
|
+
"read",
|
|
84
|
+
"read_image",
|
|
85
|
+
"read_mcp_resource",
|
|
86
|
+
"schedule_create",
|
|
87
|
+
"schedule_delete",
|
|
88
|
+
"schedule_list",
|
|
89
|
+
"schedule_update",
|
|
90
|
+
"send_message",
|
|
91
|
+
"session_event_read",
|
|
92
|
+
"session_event_search",
|
|
93
|
+
"session_event_trace",
|
|
94
|
+
"session_search",
|
|
95
|
+
"session_trace",
|
|
96
|
+
"sidebar_open",
|
|
97
|
+
"skill",
|
|
98
|
+
"spawn_teammate",
|
|
99
|
+
"subagent",
|
|
100
|
+
"subagent_fork",
|
|
101
|
+
"team_task_create",
|
|
102
|
+
"team_task_get",
|
|
103
|
+
"team_task_list",
|
|
104
|
+
"team_task_update",
|
|
105
|
+
"terminal_close",
|
|
106
|
+
"terminal_list",
|
|
107
|
+
"terminal_open",
|
|
108
|
+
"terminal_read",
|
|
109
|
+
"terminal_send",
|
|
110
|
+
"terminal_signal",
|
|
111
|
+
"todo_write",
|
|
112
|
+
"update_goal",
|
|
113
|
+
"wait_agent",
|
|
114
|
+
"web_fetch",
|
|
115
|
+
"web_search",
|
|
116
|
+
"web_search_pro",
|
|
117
|
+
"workflow",
|
|
118
|
+
"working_directory",
|
|
119
|
+
"write"
|
|
120
|
+
]);
|
|
121
|
+
/** MCP server name → catalog id, for the servers this allowlist knows. */
|
|
122
|
+
const MCP_SERVERS = /* @__PURE__ */ new Map([["wincu", "mcp:wincu"]]);
|
|
123
|
+
/** Plugin identity by tool-name prefix (checked before the core catalog). */
|
|
124
|
+
const PLUGIN_PREFIXES = [
|
|
125
|
+
["bcdp_", "dsh-browser-cdp"],
|
|
126
|
+
["browser_window_", "dsh-browser-cdp"],
|
|
127
|
+
["ops_", "dsh-ops"],
|
|
128
|
+
["memory_", "dsh-auto-memory"]
|
|
129
|
+
];
|
|
130
|
+
/** Single plugin tools that carry no prefix. */
|
|
131
|
+
const PLUGIN_TOOLS = /* @__PURE__ */ new Map([["generate_image", "dsh-model-switch"]]);
|
|
132
|
+
/** `<base>__<owner>` — the extension-tool naming the QQ bot framework uses. */
|
|
133
|
+
const EXTENSION_TOOL = /^([A-Za-z0-9_-]+)__(.+)$/;
|
|
134
|
+
/**
|
|
135
|
+
* Default allowlist: every pure-read tool this deployment exposes plus the
|
|
136
|
+
* no-side-effect and autonomy entries the user explicitly released, calibrated
|
|
137
|
+
* on a live desktop profile (105 tools) at dsh 0.2.1-rc.2.
|
|
138
|
+
*
|
|
139
|
+
* Grouped by origin so a reader can trace each entry through {@link SOURCE_CATALOG}.
|
|
140
|
+
* Entries ending in `*` are patterns: the QQ bot extension suffixes its tools with
|
|
141
|
+
* the bot's own name, so `sessions_list__*` keeps working when the persona is
|
|
142
|
+
* renamed.
|
|
143
|
+
*
|
|
144
|
+
* Everything here follows one rule: releasing it must not let a side effect escape
|
|
145
|
+
* the reviewer. Calls that merely dispatch, ask, present, wait or navigate qualify
|
|
146
|
+
* because the work they trigger still runs under the same auto preset. Delegation
|
|
147
|
+
* tools (`subagent`, `subagent_fork`, `spawn_teammate`) deliberately stay out: a
|
|
148
|
+
* child session seeds its preset from `permissionPresets.current(parent.session)`,
|
|
149
|
+
* and this bypass answers `'custom'` for an allowlisted call in flight — releasing
|
|
150
|
+
* them would hand the child no `auto` preset at all, i.e. no review.
|
|
151
|
+
*/
|
|
152
|
+
const DEFAULT_READ_ONLY_TOOLS = [
|
|
153
|
+
"read",
|
|
154
|
+
"grep",
|
|
155
|
+
"glob",
|
|
156
|
+
"read_image",
|
|
157
|
+
"skill",
|
|
158
|
+
"web_search",
|
|
159
|
+
"web_search_pro",
|
|
160
|
+
"web_fetch",
|
|
161
|
+
"load_workspace_dependencies",
|
|
162
|
+
"ask_user_question",
|
|
163
|
+
"present",
|
|
164
|
+
"sidebar_open",
|
|
165
|
+
"exit_plan_mode",
|
|
166
|
+
"job_list",
|
|
167
|
+
"job_output",
|
|
168
|
+
"terminal_list",
|
|
169
|
+
"terminal_read",
|
|
170
|
+
"list_agents",
|
|
171
|
+
"team_task_list",
|
|
172
|
+
"team_task_get",
|
|
173
|
+
"get_goal",
|
|
174
|
+
"schedule_list",
|
|
175
|
+
"wait_agent",
|
|
176
|
+
"send_message",
|
|
177
|
+
"team_task_create",
|
|
178
|
+
"list_mcp_resources",
|
|
179
|
+
"list_mcp_resource_templates",
|
|
180
|
+
"read_mcp_resource",
|
|
181
|
+
"session_search",
|
|
182
|
+
"session_event_search",
|
|
183
|
+
"session_event_read",
|
|
184
|
+
"session_event_trace",
|
|
185
|
+
"session_trace",
|
|
186
|
+
"create_goal",
|
|
187
|
+
"schedule_create",
|
|
188
|
+
"terminal_open",
|
|
189
|
+
"ops_read_files",
|
|
190
|
+
"ops_discover_tools",
|
|
191
|
+
"bcdp_status",
|
|
192
|
+
"bcdp_help",
|
|
193
|
+
"bcdp_doctor",
|
|
194
|
+
"bcdp_snapshot",
|
|
195
|
+
"browser_window_status",
|
|
196
|
+
"memory_list",
|
|
197
|
+
"memory_read",
|
|
198
|
+
"mcp__wincu__windows_computer_use_accessibility_tree",
|
|
199
|
+
"mcp__wincu__windows_computer_use_element_info",
|
|
200
|
+
"mcp__wincu__windows_computer_use_find",
|
|
201
|
+
"mcp__wincu__windows_computer_use_health",
|
|
202
|
+
"mcp__wincu__windows_computer_use_list_windows",
|
|
203
|
+
"mcp__wincu__windows_computer_use_ocr",
|
|
204
|
+
"mcp__wincu__windows_computer_use_screenshot",
|
|
205
|
+
"mcp__wincu__windows_computer_use_snapshot",
|
|
206
|
+
"mcp__wincu__windows_computer_use_tools",
|
|
207
|
+
"mcp__wincu__windows_computer_use_wait",
|
|
208
|
+
"mcp__wincu__windows_computer_use_wait_for",
|
|
209
|
+
"sessions_list__*",
|
|
210
|
+
"bing_search__*"
|
|
211
|
+
];
|
|
212
|
+
/**
|
|
213
|
+
* Validate one allowlist entry.
|
|
214
|
+
*
|
|
215
|
+
* `*` is a suffix wildcard: the entry must keep at least one literal leading
|
|
216
|
+
* character, so a stray `*` can never turn the list into "allow everything".
|
|
217
|
+
*
|
|
218
|
+
* @param entry - the raw entry as typed in settings.
|
|
219
|
+
* @returns `ok`, or the specific problem.
|
|
220
|
+
*/
|
|
221
|
+
function validateAllowEntry(entry) {
|
|
222
|
+
const trimmed = entry.trim();
|
|
223
|
+
if (trimmed === "") return "empty";
|
|
224
|
+
if (/^\*+$/.test(trimmed)) return "bare-wildcard";
|
|
225
|
+
if (trimmed.startsWith("*")) return "leading-wildcard";
|
|
226
|
+
return "ok";
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Whether a tool name matches one allowlist entry.
|
|
230
|
+
*
|
|
231
|
+
* Exact names match exactly; an entry may end in `*`, which matches any
|
|
232
|
+
* remaining characters (including none). Invalid entries never match.
|
|
233
|
+
*
|
|
234
|
+
* @param name - the tool name from `exec.name`.
|
|
235
|
+
* @param entry - one allowlist entry.
|
|
236
|
+
* @returns true when the call may skip review.
|
|
237
|
+
*/
|
|
238
|
+
function matchesAllowPattern(name, entry) {
|
|
239
|
+
if (name === "" || validateAllowEntry(entry) !== "ok") return false;
|
|
240
|
+
const trimmed = entry.trim();
|
|
241
|
+
const star = trimmed.indexOf("*");
|
|
242
|
+
if (star === -1) return name === trimmed;
|
|
243
|
+
const prefix = trimmed.slice(0, star);
|
|
244
|
+
const suffix = trimmed.slice(star + 1);
|
|
245
|
+
if (!name.startsWith(prefix) || !name.endsWith(suffix)) return false;
|
|
246
|
+
return name.length >= prefix.length + suffix.length;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Whether a tool name is covered by an allowlist.
|
|
250
|
+
* @param name - the tool name under review.
|
|
251
|
+
* @param allowlist - configured entries, patterns included.
|
|
252
|
+
* @returns true when at least one entry matches.
|
|
253
|
+
*/
|
|
254
|
+
function isAllowedByList(name, allowlist) {
|
|
255
|
+
return allowlist.some((entry) => matchesAllowPattern(name, entry));
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Resolve the origin of one tool name for display.
|
|
259
|
+
*
|
|
260
|
+
* Resolution order: MCP bridge → `<tool>__<owner>` extension → plugin prefix →
|
|
261
|
+
* plugin table → core catalog → unknown. Only the first two are structural; the
|
|
262
|
+
* rest are calibrated names, so an unlisted tool is reported as unknown rather
|
|
263
|
+
* than guessed into a package.
|
|
264
|
+
*
|
|
265
|
+
* @param name - the tool name.
|
|
266
|
+
* @returns The origin, with its repository when known.
|
|
267
|
+
*/
|
|
268
|
+
function resolveToolSource(name) {
|
|
269
|
+
const trimmed = name.trim().replace(/\*+$/, "");
|
|
270
|
+
const mcp = /^mcp__([A-Za-z0-9_-]+)__/.exec(trimmed);
|
|
271
|
+
if (mcp !== null) {
|
|
272
|
+
const server = mcp[1];
|
|
273
|
+
const entry = SOURCE_BY_ID.get(MCP_SERVERS.get(server) ?? "");
|
|
274
|
+
if (entry !== void 0) return {
|
|
275
|
+
kind: "mcp",
|
|
276
|
+
label: entry.label,
|
|
277
|
+
repo: entry.repo,
|
|
278
|
+
owner: server
|
|
279
|
+
};
|
|
280
|
+
return {
|
|
281
|
+
kind: "mcp",
|
|
282
|
+
label: `MCP · ${server}`,
|
|
283
|
+
owner: server
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const extension = EXTENSION_TOOL.exec(trimmed);
|
|
287
|
+
if (extension !== null && !trimmed.startsWith("mcp__")) {
|
|
288
|
+
const entry = SOURCE_BY_ID.get("qqbot-extension");
|
|
289
|
+
const base = extension[1];
|
|
290
|
+
const owner = extension[2];
|
|
291
|
+
return {
|
|
292
|
+
kind: "extension",
|
|
293
|
+
label: `扩展 · ${base}__${owner}`,
|
|
294
|
+
...entry === void 0 ? {} : { repo: entry.repo },
|
|
295
|
+
owner
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
if (trimmed.endsWith("__")) {
|
|
299
|
+
const entry = SOURCE_BY_ID.get("qqbot-extension");
|
|
300
|
+
return {
|
|
301
|
+
kind: "extension",
|
|
302
|
+
label: `扩展 · ${trimmed.slice(0, -2)}__*`,
|
|
303
|
+
...entry === void 0 ? {} : { repo: entry.repo },
|
|
304
|
+
owner: "*"
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
for (const [prefix, id] of PLUGIN_PREFIXES) {
|
|
308
|
+
if (!trimmed.startsWith(prefix)) continue;
|
|
309
|
+
const entry = SOURCE_BY_ID.get(id);
|
|
310
|
+
if (entry !== void 0) return {
|
|
311
|
+
kind: "plugin",
|
|
312
|
+
label: entry.label,
|
|
313
|
+
repo: entry.repo,
|
|
314
|
+
owner: id
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const plugin = PLUGIN_TOOLS.get(trimmed);
|
|
318
|
+
if (plugin !== void 0) {
|
|
319
|
+
const entry = SOURCE_BY_ID.get(plugin);
|
|
320
|
+
if (entry !== void 0) return {
|
|
321
|
+
kind: "plugin",
|
|
322
|
+
label: entry.label,
|
|
323
|
+
repo: entry.repo,
|
|
324
|
+
owner: plugin
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (CORE_TOOLS.has(trimmed)) {
|
|
328
|
+
const entry = SOURCE_BY_ID.get("core");
|
|
329
|
+
return {
|
|
330
|
+
kind: "core",
|
|
331
|
+
label: entry?.label ?? "DSH 核心",
|
|
332
|
+
...entry === void 0 ? {} : { repo: entry.repo },
|
|
333
|
+
owner: "core"
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
kind: "unknown",
|
|
338
|
+
label: "未知来源"
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
//#endregion
|
|
11
342
|
//#region src/client-contract.ts
|
|
12
343
|
const MODEL_SWITCH_CONFIG_ID = "model-switch";
|
|
13
344
|
const PROVIDERS_CONFIG_ID = "llm-providers-ui";
|
|
@@ -34,6 +365,22 @@ window.__ModuleLoader__.load({
|
|
|
34
365
|
provider: "autoReviewProvider",
|
|
35
366
|
model: "autoReviewModel"
|
|
36
367
|
});
|
|
368
|
+
Object.freeze({
|
|
369
|
+
enabled: "autoReviewSkipReadOnly",
|
|
370
|
+
tools: "autoReviewAllowlist"
|
|
371
|
+
});
|
|
372
|
+
/**
|
|
373
|
+
* Derive the allowlist editor's value.
|
|
374
|
+
*
|
|
375
|
+
* An absent list means "never configured" and shows the shipped defaults; an
|
|
376
|
+
* explicitly emptied list stays empty, so clearing it is not undone by a reload.
|
|
377
|
+
*/
|
|
378
|
+
function deriveAutoReviewAllowlist(settings) {
|
|
379
|
+
return {
|
|
380
|
+
enabled: settings.autoReviewSkipReadOnly === true,
|
|
381
|
+
tools: Array.isArray(settings.autoReviewAllowlist) ? [...settings.autoReviewAllowlist] : [...DEFAULT_READ_ONLY_TOOLS]
|
|
382
|
+
};
|
|
383
|
+
}
|
|
37
384
|
/** Compaction summarization route for `/compact` and automatic compaction; both unset means the official default (the conversation target). */
|
|
38
385
|
const COMPACTION_SETTINGS_FIELDS = Object.freeze({
|
|
39
386
|
provider: "compactProvider",
|
|
@@ -86,6 +433,52 @@ window.__ModuleLoader__.load({
|
|
|
86
433
|
function routePairConfigured(route) {
|
|
87
434
|
return route !== void 0 && (route.provider ?? "").trim() !== "" && (route.model ?? "").trim() !== "";
|
|
88
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Decode the reviewer counters from a `tools` RPC payload.
|
|
438
|
+
* @param value - the RPC result value.
|
|
439
|
+
* @returns The counters, or undefined when the Host does not report them.
|
|
440
|
+
*/
|
|
441
|
+
function decodeReviewStatus(value) {
|
|
442
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
443
|
+
const raw = value.status;
|
|
444
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
445
|
+
const status = raw;
|
|
446
|
+
const count = (input) => typeof input === "number" && Number.isFinite(input) && input >= 0 ? input : 0;
|
|
447
|
+
return {
|
|
448
|
+
held: status.held === true,
|
|
449
|
+
allowedCalls: count(status.allowedCalls),
|
|
450
|
+
skippedReadOnly: count(status.skippedReadOnly)
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Decode the `tools` RPC payload.
|
|
455
|
+
*
|
|
456
|
+
* Malformed entries are skipped rather than failing the page: the catalog is a
|
|
457
|
+
* convenience view over the Host's tools, and a partial list is still useful.
|
|
458
|
+
*
|
|
459
|
+
* @param value - the RPC result value.
|
|
460
|
+
* @returns The rows, or undefined when the payload is not a catalog at all.
|
|
461
|
+
*/
|
|
462
|
+
function decodeToolCatalog(value) {
|
|
463
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
464
|
+
const listed = value.tools;
|
|
465
|
+
if (!Array.isArray(listed)) return void 0;
|
|
466
|
+
const rows = [];
|
|
467
|
+
for (const entry of listed) {
|
|
468
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
469
|
+
const raw = entry;
|
|
470
|
+
if (typeof raw.name !== "string" || raw.name === "") continue;
|
|
471
|
+
const source = raw.source !== null && typeof raw.source === "object" && !Array.isArray(raw.source) ? raw.source : {};
|
|
472
|
+
rows.push({
|
|
473
|
+
name: raw.name,
|
|
474
|
+
description: typeof raw.description === "string" ? raw.description : "",
|
|
475
|
+
sourceLabel: typeof source.label === "string" ? source.label : "未知来源",
|
|
476
|
+
...typeof source.repo === "string" ? { repo: source.repo } : {},
|
|
477
|
+
recommended: isAllowedByList(raw.name, DEFAULT_READ_ONLY_TOOLS)
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
return rows;
|
|
481
|
+
}
|
|
89
482
|
//#endregion
|
|
90
483
|
//#region src/client/derived-config-form.ts
|
|
91
484
|
function deriveConfigForm(source, project, fields) {
|
|
@@ -666,7 +1059,7 @@ window.__ModuleLoader__.load({
|
|
|
666
1059
|
}
|
|
667
1060
|
//#endregion
|
|
668
1061
|
//#region \0dsh-css:src/client/ModelSwitchSettings.module.css.mjs
|
|
669
|
-
const css$2 = ".djvrPG_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex;container-type:inline-size}.djvrPG_title{margin:0;font-size:16px;font-weight:500;line-height:24px}.djvrPG_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.djvrPG_saved{color:var(--dsw-alias-state-success-primary);align-items:center;gap:6px;margin:0;font-size:12px;line-height:18px;display:flex}.djvrPG_savedDot{background:currentColor;border-radius:50%;width:7px;height:7px}.djvrPG_group{flex-direction:column;gap:8px;margin-top:8px;display:flex}.djvrPG_groupLabel{color:var(--dsw-alias-label-tertiary);margin:0 2px;font-size:12px;font-weight:500;line-height:18px}.djvrPG_routeCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);border-radius:10px;list-style:none;overflow:hidden}.djvrPG_routeCardUnavailable{opacity:.72}.djvrPG_routeHeader{width:100%;min-height:68px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;justify-content:space-between;align-items:center;gap:16px;padding:12px 14px;display:flex}.djvrPG_routeHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.djvrPG_routeHeader:disabled{cursor:default}.djvrPG_routeIcon{width:18px;height:18px;color:var(--dsw-alias-label-primary);flex:none;display:block}.djvrPG_routeCopy{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.djvrPG_routeName{color:var(--dsw-alias-label-primary);align-items:center;gap:8px;font-size:14px;font-weight:600;line-height:20px;display:flex}.djvrPG_routeSummary{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:18px;overflow:hidden}.djvrPG_badge{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.djvrPG_badgeWarn{color:var(--dsw-alias-state-warn-label)}.djvrPG_chevron{width:18px;height:18px;color:var(--dsw-alias-label-primary);flex:none;transition:transform .16s}.djvrPG_routeCardOpen .djvrPG_chevron{transform:rotate(180deg)}.djvrPG_cardBody{border-top:1px solid var(--dsw-alias-border-l2);padding:16px 14px 18px}.djvrPG_formGrid{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;display:grid}.djvrPG_field{flex-direction:column;gap:6px;min-width:0;display:flex}.djvrPG_fieldFull{grid-column:1/-1}.djvrPG_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.djvrPG_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);appearance:none;background-color:var(--dsw-alias-bg-layer-1);width:100%;height:32px;color:var(--dsw-alias-label-primary);font:inherit;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");background-position:right 10px center;background-repeat:no-repeat;border-radius:8px;padding:0 32px 0 10px;font-size:14px;line-height:22px}.djvrPG_textInput{background-image:none;padding-right:10px}.djvrPG_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.djvrPG_input:disabled{opacity:.6;cursor:default}.djvrPG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.djvrPG_warning{color:var(--dsw-alias-state-warn-label)}.djvrPG_message{flex:1;min-width:0}.djvrPG_cardFooter{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;margin-top:14px;padding:12px 0 0;display:flex}.djvrPG_button{box-sizing:border-box;height:36px;font:inherit;cursor:pointer;border-radius:18px;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.djvrPG_secondaryButton{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0}.djvrPG_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.djvrPG_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border:0}.djvrPG_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.djvrPG_button:disabled{opacity:.4;cursor:default}.djvrPG_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}@container (width<=420px){.djvrPG_formGrid{grid-template-columns:1fr}.djvrPG_cardFooter .djvrPG_button{flex:1}}.djvrPG_routeHeaderRow{align-items:center;gap:8px;padding-right:14px;display:flex}.djvrPG_routeHeaderRow .djvrPG_routeHeader{flex:1;min-width:0}.djvrPG_rowCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);border-radius:10px;align-items:flex-start;gap:16px;padding:14px 16px;display:flex}.djvrPG_rowCopy{flex:1;gap:4px;min-width:0;display:grid}.djvrPG_rowTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:20px}.djvrPG_switch{background:var(--dsw-alias-bg-layer-1);width:40px;height:24px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2);cursor:pointer;border:0;border-radius:999px;flex:none;padding:0;position:relative}.djvrPG_switchOn{background:var(--dsw-alias-button-primary-fill);box-shadow:none}.djvrPG_switch i{background:var(--dsw-alias-label-primary-foreground);pointer-events:none;border-radius:50%;width:18px;height:18px;transition:left .16s;position:absolute;top:3px;left:3px}.djvrPG_switchOn i{left:19px}.djvrPG_switch:disabled{opacity:.4;cursor:default}.djvrPG_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}@media (prefers-reduced-motion:reduce){.djvrPG_chevron,.djvrPG_switch i{transition:none}}";
|
|
1062
|
+
const css$2 = ".djvrPG_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex;container-type:inline-size}.djvrPG_title{margin:0;font-size:16px;font-weight:500;line-height:24px}.djvrPG_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.djvrPG_saved{color:var(--dsw-alias-state-success-primary);align-items:center;gap:6px;margin:0;font-size:12px;line-height:18px;display:flex}.djvrPG_savedDot{background:currentColor;border-radius:50%;width:7px;height:7px}.djvrPG_group{flex-direction:column;gap:8px;margin-top:8px;display:flex}.djvrPG_groupLabel{color:var(--dsw-alias-label-tertiary);margin:0 2px;font-size:12px;font-weight:500;line-height:18px}.djvrPG_routeCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);border-radius:10px;list-style:none;overflow:hidden}.djvrPG_routeCardUnavailable{opacity:.72}.djvrPG_routeHeader{width:100%;min-height:68px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;justify-content:space-between;align-items:center;gap:16px;padding:12px 14px;display:flex}.djvrPG_routeHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.djvrPG_routeHeader:disabled{cursor:default}.djvrPG_routeIcon{width:18px;height:18px;color:var(--dsw-alias-label-primary);flex:none;display:block}.djvrPG_routeCopy{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.djvrPG_routeName{color:var(--dsw-alias-label-primary);align-items:center;gap:8px;font-size:14px;font-weight:600;line-height:20px;display:flex}.djvrPG_routeSummary{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:18px;overflow:hidden}.djvrPG_badge{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.djvrPG_badgeWarn{color:var(--dsw-alias-state-warn-label)}.djvrPG_chevron{width:18px;height:18px;color:var(--dsw-alias-label-primary);flex:none;transition:transform .16s}.djvrPG_routeCardOpen .djvrPG_chevron{transform:rotate(180deg)}.djvrPG_cardBody{border-top:1px solid var(--dsw-alias-border-l2);padding:16px 14px 18px}.djvrPG_formGrid{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;display:grid}.djvrPG_field{flex-direction:column;gap:6px;min-width:0;display:flex}.djvrPG_fieldFull{grid-column:1/-1}.djvrPG_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.djvrPG_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);appearance:none;background-color:var(--dsw-alias-bg-layer-1);width:100%;height:32px;color:var(--dsw-alias-label-primary);font:inherit;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");background-position:right 10px center;background-repeat:no-repeat;border-radius:8px;padding:0 32px 0 10px;font-size:14px;line-height:22px}.djvrPG_textInput{background-image:none;padding-right:10px}.djvrPG_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.djvrPG_input:disabled{opacity:.6;cursor:default}.djvrPG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.djvrPG_warning{color:var(--dsw-alias-state-warn-label)}.djvrPG_message{flex:1;min-width:0}.djvrPG_cardFooter{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;margin-top:14px;padding:12px 0 0;display:flex}.djvrPG_button{box-sizing:border-box;height:36px;font:inherit;cursor:pointer;border-radius:18px;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.djvrPG_secondaryButton{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0}.djvrPG_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.djvrPG_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border:0}.djvrPG_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.djvrPG_button:disabled{opacity:.4;cursor:default}.djvrPG_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}@container (width<=420px){.djvrPG_formGrid{grid-template-columns:1fr}.djvrPG_cardFooter .djvrPG_button{flex:1}}.djvrPG_routeHeaderRow{align-items:center;gap:8px;padding-right:14px;display:flex}.djvrPG_routeHeaderRow .djvrPG_routeHeader{flex:1;min-width:0}.djvrPG_rowCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);border-radius:10px;align-items:flex-start;gap:16px;padding:14px 16px;display:flex}.djvrPG_rowCopy{flex:1;gap:4px;min-width:0;display:grid}.djvrPG_rowTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:20px}.djvrPG_switch{background:var(--dsw-alias-bg-layer-1);width:40px;height:24px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2);cursor:pointer;border:0;border-radius:999px;flex:none;padding:0;position:relative}.djvrPG_switchOn{background:var(--dsw-alias-button-primary-fill);box-shadow:none}.djvrPG_switch i{background:var(--dsw-alias-label-primary-foreground);pointer-events:none;border-radius:50%;width:18px;height:18px;transition:left .16s;position:absolute;top:3px;left:3px}.djvrPG_switchOn i{left:19px}.djvrPG_switch:disabled{opacity:.4;cursor:default}.djvrPG_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}@media (prefers-reduced-motion:reduce){.djvrPG_chevron,.djvrPG_switch i{transition:none}}.djvrPG_allowToolbar{flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px;display:flex}.djvrPG_allowToolbar .djvrPG_input{flex:220px;width:auto;padding-right:10px}.djvrPG_allowList{flex-direction:column;gap:6px;margin:0 0 12px;padding:0;list-style:none;display:flex}.djvrPG_allowRow{border:1px solid var(--dsw-alias-border-l2);background-color:var(--dsw-alias-bg-layer-1);border-radius:8px;align-items:center;gap:10px;padding:6px 10px;display:flex}.djvrPG_allowLabel{cursor:default;flex:auto;align-items:center;gap:8px;min-width:0;display:flex}.djvrPG_allowLabel input{width:14px;height:14px;accent-color:var(--dsw-alias-brand-primary);flex:none}.djvrPG_allowName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}.djvrPG_allowSource{color:var(--dsw-alias-label-secondary);flex:none;align-items:center;gap:4px;font-size:12px;line-height:18px;display:flex}.djvrPG_allowDot{opacity:.6}.djvrPG_allowRepo{color:var(--dsw-alias-brand-primary);text-decoration:none}.djvrPG_allowRepo:hover{text-decoration:underline}";
|
|
670
1063
|
const tagId$2 = "dsh-model-switch/ModelSwitchSettings.module.css";
|
|
671
1064
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
672
1065
|
const tag = document.createElement("style");
|
|
@@ -676,6 +1069,14 @@ window.__ModuleLoader__.load({
|
|
|
676
1069
|
document.head.appendChild(tag);
|
|
677
1070
|
}
|
|
678
1071
|
var ModelSwitchSettings_module_css_default = {
|
|
1072
|
+
"allowDot": "djvrPG_allowDot",
|
|
1073
|
+
"allowLabel": "djvrPG_allowLabel",
|
|
1074
|
+
"allowList": "djvrPG_allowList",
|
|
1075
|
+
"allowName": "djvrPG_allowName",
|
|
1076
|
+
"allowRepo": "djvrPG_allowRepo",
|
|
1077
|
+
"allowRow": "djvrPG_allowRow",
|
|
1078
|
+
"allowSource": "djvrPG_allowSource",
|
|
1079
|
+
"allowToolbar": "djvrPG_allowToolbar",
|
|
679
1080
|
"badge": "djvrPG_badge",
|
|
680
1081
|
"badgeWarn": "djvrPG_badgeWarn",
|
|
681
1082
|
"button": "djvrPG_button",
|
|
@@ -1114,6 +1515,10 @@ window.__ModuleLoader__.load({
|
|
|
1114
1515
|
const [message, setMessage] = (0, react.useState)();
|
|
1115
1516
|
const [groupProvider, setGroupProvider] = (0, react.useState)("");
|
|
1116
1517
|
const [groupDraft, setGroupDraft] = (0, react.useState)(void 0);
|
|
1518
|
+
const [allowlistQuery, setAllowlistQuery] = (0, react.useState)("");
|
|
1519
|
+
const [catalogRows, setCatalogRows] = (0, react.useState)(void 0);
|
|
1520
|
+
const [catalogState, setCatalogState] = (0, react.useState)("idle");
|
|
1521
|
+
const [reviewStatus, setReviewStatus] = (0, react.useState)(void 0);
|
|
1117
1522
|
const loadSearchCapabilities = props.loadCapabilities;
|
|
1118
1523
|
const [searchSnapshot, setSearchSnapshot] = (0, react.useState)(void 0);
|
|
1119
1524
|
const [searchError, setSearchError] = (0, react.useState)(void 0);
|
|
@@ -1336,6 +1741,58 @@ window.__ModuleLoader__.load({
|
|
|
1336
1741
|
setBusy(void 0);
|
|
1337
1742
|
}
|
|
1338
1743
|
};
|
|
1744
|
+
/**
|
|
1745
|
+
* The Auto reviewer's read-only allowlist.
|
|
1746
|
+
*
|
|
1747
|
+
* An absent stored list means "never configured" and renders the shipped
|
|
1748
|
+
* defaults; an explicitly emptied list stays empty. Every write carries both
|
|
1749
|
+
* the switch and the entries, so the two can never drift apart on disk.
|
|
1750
|
+
*/
|
|
1751
|
+
const allowlistView = switchSettings.value === void 0 ? void 0 : deriveAutoReviewAllowlist(switchSettings.value);
|
|
1752
|
+
const allowlistTools = allowlistView?.tools ?? DEFAULT_READ_ONLY_TOOLS;
|
|
1753
|
+
const allowlistEnabled = allowlistView?.enabled === true;
|
|
1754
|
+
const allowlistWritable = switchSettings.status === "ready" && switchSettings.writable;
|
|
1755
|
+
const persistAllowlist = (next) => {
|
|
1756
|
+
if (busy !== void 0 || !allowlistWritable) return;
|
|
1757
|
+
const tools = next.tools.filter((entry) => validateAllowEntry(entry) === "ok");
|
|
1758
|
+
run("allowlist", () => props.saveReviewAllowlist({
|
|
1759
|
+
enabled: next.enabled,
|
|
1760
|
+
tools
|
|
1761
|
+
}));
|
|
1762
|
+
};
|
|
1763
|
+
const setAllowTool = (name, allowed) => {
|
|
1764
|
+
if (allowed) {
|
|
1765
|
+
if (isAllowedByList(name, allowlistTools)) return;
|
|
1766
|
+
persistAllowlist({
|
|
1767
|
+
enabled: true,
|
|
1768
|
+
tools: [...allowlistTools, name]
|
|
1769
|
+
});
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
persistAllowlist({
|
|
1773
|
+
enabled: allowlistEnabled,
|
|
1774
|
+
tools: allowlistTools.filter((entry) => entry !== name)
|
|
1775
|
+
});
|
|
1776
|
+
};
|
|
1777
|
+
const scanAllowlistTools = () => {
|
|
1778
|
+
const scan = props.scanTools;
|
|
1779
|
+
if (scan === void 0 || busy !== void 0) return;
|
|
1780
|
+
setCatalogState("loading");
|
|
1781
|
+
run("allowlist", async () => {
|
|
1782
|
+
const scanned = await scan();
|
|
1783
|
+
setCatalogRows(scanned.rows);
|
|
1784
|
+
setReviewStatus(scanned.status);
|
|
1785
|
+
}).then((accepted) => {
|
|
1786
|
+
setCatalogState(accepted ? "ready" : "failed");
|
|
1787
|
+
});
|
|
1788
|
+
};
|
|
1789
|
+
const allowlistQueryText = allowlistQuery.trim().toLowerCase();
|
|
1790
|
+
const allowlistIncluded = allowlistTools.map((entry) => ({
|
|
1791
|
+
entry,
|
|
1792
|
+
source: resolveToolSource(entry)
|
|
1793
|
+
})).filter((row) => allowlistQueryText === "" || row.entry.toLowerCase().includes(allowlistQueryText));
|
|
1794
|
+
const allowlistAvailable = (catalogRows ?? []).filter((row) => !isAllowedByList(row.name, allowlistTools)).filter((row) => allowlistQueryText === "" || row.name.toLowerCase().includes(allowlistQueryText) || row.sourceLabel.toLowerCase().includes(allowlistQueryText));
|
|
1795
|
+
const allowlistSummaryText = allowlistView === void 0 ? props.t("loading") : allowlistEnabled ? props.t("reviewAllowlistOnSummary").replace("{count}", String(allowlistTools.length)) : props.t("reviewAllowlistOffSummary");
|
|
1339
1796
|
const saveSubagent = () => {
|
|
1340
1797
|
if (subagentDraft === void 0 || subagentDraft.mode !== "fixed") return;
|
|
1341
1798
|
const matched = subagentCatalogModel;
|
|
@@ -2074,6 +2531,174 @@ window.__ModuleLoader__.load({
|
|
|
2074
2531
|
})
|
|
2075
2532
|
]
|
|
2076
2533
|
}),
|
|
2534
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2535
|
+
className: ModelSwitchSettings_module_css_default.group,
|
|
2536
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
2537
|
+
className: ModelSwitchSettings_module_css_default.groupLabel,
|
|
2538
|
+
children: props.t("reviewAllowlistGroup")
|
|
2539
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(RouteCard, {
|
|
2540
|
+
title: props.t("reviewAllowlist"),
|
|
2541
|
+
summary: allowlistSummaryText,
|
|
2542
|
+
hint: props.t("reviewAllowlistHelp"),
|
|
2543
|
+
icon: "autoReview",
|
|
2544
|
+
open: open === "allowlist",
|
|
2545
|
+
onToggle: () => {
|
|
2546
|
+
toggle("allowlist");
|
|
2547
|
+
},
|
|
2548
|
+
...allowlistEnabled ? { badge: props.t("reviewAllowlistBadge") } : {},
|
|
2549
|
+
trailing: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
|
|
2550
|
+
checked: allowlistEnabled,
|
|
2551
|
+
disabled: busy !== void 0 || !allowlistWritable,
|
|
2552
|
+
label: props.t("reviewAllowlist"),
|
|
2553
|
+
onChange: (value) => {
|
|
2554
|
+
persistAllowlist({
|
|
2555
|
+
enabled: value,
|
|
2556
|
+
tools: allowlistTools
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
}),
|
|
2560
|
+
children: [
|
|
2561
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2562
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
2563
|
+
children: props.t("reviewAllowlistNote")
|
|
2564
|
+
}),
|
|
2565
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2566
|
+
className: ModelSwitchSettings_module_css_default.allowToolbar,
|
|
2567
|
+
children: [
|
|
2568
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2569
|
+
className: ModelSwitchSettings_module_css_default.input,
|
|
2570
|
+
type: "search",
|
|
2571
|
+
value: allowlistQuery,
|
|
2572
|
+
placeholder: props.t("reviewAllowlistSearch"),
|
|
2573
|
+
onChange: (event) => {
|
|
2574
|
+
setAllowlistQuery(event.target.value);
|
|
2575
|
+
}
|
|
2576
|
+
}),
|
|
2577
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2578
|
+
type: "button",
|
|
2579
|
+
className: cx(ModelSwitchSettings_module_css_default.button, ModelSwitchSettings_module_css_default.secondaryButton),
|
|
2580
|
+
disabled: busy !== void 0 || props.scanTools === void 0 || catalogState === "loading",
|
|
2581
|
+
onClick: scanAllowlistTools,
|
|
2582
|
+
children: catalogState === "loading" ? props.t("reviewAllowlistScanning") : props.t("reviewAllowlistScan")
|
|
2583
|
+
}),
|
|
2584
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2585
|
+
type: "button",
|
|
2586
|
+
className: cx(ModelSwitchSettings_module_css_default.button, ModelSwitchSettings_module_css_default.secondaryButton),
|
|
2587
|
+
disabled: busy !== void 0 || !allowlistWritable,
|
|
2588
|
+
onClick: () => {
|
|
2589
|
+
persistAllowlist({
|
|
2590
|
+
enabled: allowlistEnabled,
|
|
2591
|
+
tools: [...DEFAULT_READ_ONLY_TOOLS]
|
|
2592
|
+
});
|
|
2593
|
+
},
|
|
2594
|
+
children: props.t("reviewAllowlistReset")
|
|
2595
|
+
})
|
|
2596
|
+
]
|
|
2597
|
+
}),
|
|
2598
|
+
message?.route === "allowlist" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2599
|
+
className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
|
|
2600
|
+
children: message.text
|
|
2601
|
+
}) : null,
|
|
2602
|
+
reviewStatus === void 0 || reviewStatus.skippedReadOnly === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2603
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
2604
|
+
children: props.t("reviewAllowlistSkipped").replace("{count}", String(reviewStatus.skippedReadOnly))
|
|
2605
|
+
}),
|
|
2606
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2607
|
+
className: ModelSwitchSettings_module_css_default.allowList,
|
|
2608
|
+
children: allowlistIncluded.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2609
|
+
className: ModelSwitchSettings_module_css_default.allowRow,
|
|
2610
|
+
children: [
|
|
2611
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2612
|
+
className: ModelSwitchSettings_module_css_default.allowLabel,
|
|
2613
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2614
|
+
type: "checkbox",
|
|
2615
|
+
checked: true,
|
|
2616
|
+
readOnly: true
|
|
2617
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
2618
|
+
className: ModelSwitchSettings_module_css_default.allowName,
|
|
2619
|
+
children: row.entry
|
|
2620
|
+
})]
|
|
2621
|
+
}),
|
|
2622
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2623
|
+
className: ModelSwitchSettings_module_css_default.allowSource,
|
|
2624
|
+
children: [row.source.label, row.source.repo === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2625
|
+
className: ModelSwitchSettings_module_css_default.allowDot,
|
|
2626
|
+
children: "·"
|
|
2627
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2628
|
+
className: ModelSwitchSettings_module_css_default.allowRepo,
|
|
2629
|
+
href: row.source.repo,
|
|
2630
|
+
target: "_blank",
|
|
2631
|
+
rel: "noreferrer",
|
|
2632
|
+
children: props.t("reviewAllowlistRepo")
|
|
2633
|
+
})] })]
|
|
2634
|
+
}),
|
|
2635
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2636
|
+
type: "button",
|
|
2637
|
+
className: cx(ModelSwitchSettings_module_css_default.button, ModelSwitchSettings_module_css_default.secondaryButton),
|
|
2638
|
+
disabled: busy !== void 0 || !allowlistWritable,
|
|
2639
|
+
onClick: () => {
|
|
2640
|
+
setAllowTool(row.entry, false);
|
|
2641
|
+
},
|
|
2642
|
+
children: props.t("reviewAllowlistRemove")
|
|
2643
|
+
})
|
|
2644
|
+
]
|
|
2645
|
+
}, row.entry))
|
|
2646
|
+
}),
|
|
2647
|
+
allowlistIncluded.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2648
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
2649
|
+
children: props.t("reviewAllowlistNone")
|
|
2650
|
+
}) : null,
|
|
2651
|
+
allowlistAvailable.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2652
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
2653
|
+
children: props.t("reviewAllowlistAvailable")
|
|
2654
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2655
|
+
className: ModelSwitchSettings_module_css_default.allowList,
|
|
2656
|
+
children: allowlistAvailable.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2657
|
+
className: ModelSwitchSettings_module_css_default.allowRow,
|
|
2658
|
+
children: [
|
|
2659
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2660
|
+
className: ModelSwitchSettings_module_css_default.allowLabel,
|
|
2661
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2662
|
+
type: "checkbox",
|
|
2663
|
+
checked: false,
|
|
2664
|
+
readOnly: true
|
|
2665
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
2666
|
+
className: ModelSwitchSettings_module_css_default.allowName,
|
|
2667
|
+
children: row.name
|
|
2668
|
+
})]
|
|
2669
|
+
}),
|
|
2670
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2671
|
+
className: ModelSwitchSettings_module_css_default.allowSource,
|
|
2672
|
+
children: [row.sourceLabel, row.repo === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2673
|
+
className: ModelSwitchSettings_module_css_default.allowDot,
|
|
2674
|
+
children: "·"
|
|
2675
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2676
|
+
className: ModelSwitchSettings_module_css_default.allowRepo,
|
|
2677
|
+
href: row.repo,
|
|
2678
|
+
target: "_blank",
|
|
2679
|
+
rel: "noreferrer",
|
|
2680
|
+
children: props.t("reviewAllowlistRepo")
|
|
2681
|
+
})] })]
|
|
2682
|
+
}),
|
|
2683
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2684
|
+
type: "button",
|
|
2685
|
+
className: cx(ModelSwitchSettings_module_css_default.button, ModelSwitchSettings_module_css_default.secondaryButton),
|
|
2686
|
+
disabled: busy !== void 0 || !allowlistWritable,
|
|
2687
|
+
onClick: () => {
|
|
2688
|
+
setAllowTool(row.name, true);
|
|
2689
|
+
},
|
|
2690
|
+
children: props.t("reviewAllowlistAdd")
|
|
2691
|
+
})
|
|
2692
|
+
]
|
|
2693
|
+
}, row.name))
|
|
2694
|
+
})] }),
|
|
2695
|
+
catalogState === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2696
|
+
className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
|
|
2697
|
+
children: props.t("reviewAllowlistScanFailed")
|
|
2698
|
+
}) : null
|
|
2699
|
+
]
|
|
2700
|
+
})]
|
|
2701
|
+
}),
|
|
2077
2702
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2078
2703
|
className: ModelSwitchSettings_module_css_default.group,
|
|
2079
2704
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
@@ -2406,6 +3031,24 @@ window.__ModuleLoader__.load({
|
|
|
2406
3031
|
groupOverridesMore: "组覆盖:{listed} 等 {count} 个组;无组时用上方默认值",
|
|
2407
3032
|
saveTimeout: "保存超时,请重试",
|
|
2408
3033
|
saveRejected: "保存被拒绝,请重试",
|
|
3034
|
+
reviewAllowlistGroup: "审查豁免",
|
|
3035
|
+
reviewAllowlist: "纯读工具免审查",
|
|
3036
|
+
reviewAllowlistBadge: "已开启",
|
|
3037
|
+
reviewAllowlistOnSummary: "名单内 {count} 条免审查",
|
|
3038
|
+
reviewAllowlistOffSummary: "未开启:每次调用都经审查",
|
|
3039
|
+
reviewAllowlistHelp: "开启后,名单内的纯读工具直接执行,不再调用审查模型;名单外的写操作、命令执行与删除仍照常审查。",
|
|
3040
|
+
reviewAllowlistNote: "来源按工具名解析(MCP 前缀 / 插件前缀 / 扩展后缀);同名工具可以冒充来源,徽章只是提示,不是安全边界。",
|
|
3041
|
+
reviewAllowlistSearch: "搜索工具名或来源",
|
|
3042
|
+
reviewAllowlistScan: "扫描本机工具",
|
|
3043
|
+
reviewAllowlistScanning: "扫描中…",
|
|
3044
|
+
reviewAllowlistScanFailed: "扫描失败:Host 没有提供工具目录接口。",
|
|
3045
|
+
reviewAllowlistReset: "恢复默认名单",
|
|
3046
|
+
reviewAllowlistRemove: "移除",
|
|
3047
|
+
reviewAllowlistAdd: "加入",
|
|
3048
|
+
reviewAllowlistNone: "名单为空:没有任何工具免审查。",
|
|
3049
|
+
reviewAllowlistAvailable: "本机存在但未放行的工具:",
|
|
3050
|
+
reviewAllowlistRepo: "仓库",
|
|
3051
|
+
reviewAllowlistSkipped: "本 Host 进程已为纯读调用跳过 {count} 次审查。",
|
|
2409
3052
|
"reason.central-subagent-routing": "Alpha.4 没有全局子代理启动路由接口。",
|
|
2410
3053
|
"reason.packaged-preset-roots": "Alpha.4 不支持插件提供额外 preset root。",
|
|
2411
3054
|
"reason.tool-owner-suppression": "Alpha.4 没有工具所有者或来源抑制接口。",
|
|
@@ -2468,6 +3111,24 @@ window.__ModuleLoader__.load({
|
|
|
2468
3111
|
groupOverridesMore: "Group overrides: {listed} and {count} groups; without a group the default above applies",
|
|
2469
3112
|
saveTimeout: "The save timed out; try again",
|
|
2470
3113
|
saveRejected: "The save was rejected; try again",
|
|
3114
|
+
reviewAllowlistGroup: "Review bypass",
|
|
3115
|
+
reviewAllowlist: "Skip review for pure reads",
|
|
3116
|
+
reviewAllowlistBadge: "On",
|
|
3117
|
+
reviewAllowlistOnSummary: "{count} entries skip review",
|
|
3118
|
+
reviewAllowlistOffSummary: "Off: every call is reviewed",
|
|
3119
|
+
reviewAllowlistHelp: "When on, allowlisted pure-read tools run immediately instead of calling the reviewer. Writes, command execution and deletions stay reviewed.",
|
|
3120
|
+
reviewAllowlistNote: "Sources are resolved from the tool name (MCP prefix, plugin prefix, extension suffix); a lookalike tool can claim any origin, so the badge is a hint, not a security boundary.",
|
|
3121
|
+
reviewAllowlistSearch: "Search tool name or source",
|
|
3122
|
+
reviewAllowlistScan: "Scan local tools",
|
|
3123
|
+
reviewAllowlistScanning: "Scanning…",
|
|
3124
|
+
reviewAllowlistScanFailed: "Scan failed: the Host exposes no tool catalog endpoint.",
|
|
3125
|
+
reviewAllowlistReset: "Restore defaults",
|
|
3126
|
+
reviewAllowlistRemove: "Remove",
|
|
3127
|
+
reviewAllowlistAdd: "Add",
|
|
3128
|
+
reviewAllowlistNone: "Empty list: no tool skips review.",
|
|
3129
|
+
reviewAllowlistAvailable: "Present on this machine but not allowed:",
|
|
3130
|
+
reviewAllowlistRepo: "repo",
|
|
3131
|
+
reviewAllowlistSkipped: "This Host process has skipped the reviewer {count} times.",
|
|
2471
3132
|
"reason.central-subagent-routing": "Alpha.4 exposes no global Subagent start-routing seam.",
|
|
2472
3133
|
"reason.packaged-preset-roots": "Alpha.4 exposes no plugin-owned preset root.",
|
|
2473
3134
|
"reason.tool-owner-suppression": "Alpha.4 exposes no tool owner or provenance suppression seam.",
|
|
@@ -3982,6 +4643,29 @@ window.__ModuleLoader__.load({
|
|
|
3982
4643
|
throw new PlanDismissalUnsupportedError("this DSH Host exposes neither PendingQuestion.dismiss() (0.2.x) nor PendingQuestion.cancel() (0.1.x)");
|
|
3983
4644
|
}
|
|
3984
4645
|
//#endregion
|
|
4646
|
+
//#region src/picker/discuss-plan.ts
|
|
4647
|
+
/**
|
|
4648
|
+
* Withdraw the review by interrupting the turn, falling back to dismissal.
|
|
4649
|
+
*
|
|
4650
|
+
* Order matters: cancellation is what keeps the work details expanded, so it is
|
|
4651
|
+
* tried first and only genuinely unavailable/refused cancellation falls through
|
|
4652
|
+
* to the released withdrawal (which still gives the human the composer back, just
|
|
4653
|
+
* without the expansion guarantee).
|
|
4654
|
+
*
|
|
4655
|
+
* @param cancelTurn - the session cancel seam; absent on a Host without one.
|
|
4656
|
+
* @param sessionId - the session whose turn is being withdrawn.
|
|
4657
|
+
* @param dismiss - the released `PendingQuestion` withdrawal.
|
|
4658
|
+
* @returns Which route withdrew the review.
|
|
4659
|
+
*/
|
|
4660
|
+
async function discussPlan(cancelTurn, sessionId, dismiss) {
|
|
4661
|
+
if (cancelTurn !== void 0 && sessionId !== "") try {
|
|
4662
|
+
await cancelTurn(sessionId);
|
|
4663
|
+
return "interrupted";
|
|
4664
|
+
} catch {}
|
|
4665
|
+
await dismiss();
|
|
4666
|
+
return "dismissed";
|
|
4667
|
+
}
|
|
4668
|
+
//#endregion
|
|
3985
4669
|
//#region src/picker/plan-goal.ts
|
|
3986
4670
|
/**
|
|
3987
4671
|
* Strip the gateway's transport prefix from a Host message.
|
|
@@ -4277,10 +4961,11 @@ window.__ModuleLoader__.load({
|
|
|
4277
4961
|
goalRemote: props.goalRemote,
|
|
4278
4962
|
planPreview: props.planPreview,
|
|
4279
4963
|
...props.resolveInteractionOperations === void 0 ? {} : { resolveInteractionOperations: props.resolveInteractionOperations },
|
|
4280
|
-
...props.roleOf === void 0 ? {} : { roleOf: props.roleOf }
|
|
4964
|
+
...props.roleOf === void 0 ? {} : { roleOf: props.roleOf },
|
|
4965
|
+
...props.cancelTurn === void 0 ? {} : { cancelTurn: props.cancelTurn }
|
|
4281
4966
|
}, props.matched.key);
|
|
4282
4967
|
}
|
|
4283
|
-
function PlanReviewState({ matched, review, available, providerLock, agentLocked, lockFailed, directory, t, goalRemote, planPreview, resolveInteractionOperations, roleOf }) {
|
|
4968
|
+
function PlanReviewState({ matched, review, available, providerLock, agentLocked, lockFailed, directory, t, goalRemote, planPreview, resolveInteractionOperations, roleOf, cancelTurn }) {
|
|
4284
4969
|
const { snapshot, getDirectorySnapshot, load, select } = directory;
|
|
4285
4970
|
const [execution, setExecution] = (0, react.useState)(snapshot.current ?? void 0);
|
|
4286
4971
|
const [feedback, setFeedback] = (0, react.useState)("");
|
|
@@ -4501,7 +5186,9 @@ window.__ModuleLoader__.load({
|
|
|
4501
5186
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, { size: 14 }),
|
|
4502
5187
|
disabled: busy,
|
|
4503
5188
|
onClick: () => {
|
|
4504
|
-
settle(() =>
|
|
5189
|
+
settle(async () => {
|
|
5190
|
+
await discussPlan(cancelTurn, matched.sessionId, () => respondDismiss(matched, t("plan.cancelRejected"), t("plan.dismissUnsupported")));
|
|
5191
|
+
}, true);
|
|
4505
5192
|
},
|
|
4506
5193
|
children: t("plan.discuss")
|
|
4507
5194
|
}),
|
|
@@ -4820,6 +5507,20 @@ window.__ModuleLoader__.load({
|
|
|
4820
5507
|
const rpcOf = () => {
|
|
4821
5508
|
return scope.get("connection", false)?.rpc;
|
|
4822
5509
|
};
|
|
5510
|
+
/**
|
|
5511
|
+
* Cancel the session's running turn through the official Stop seam.
|
|
5512
|
+
*
|
|
5513
|
+
* The discuss action interrupts instead of merely withdrawing the pending
|
|
5514
|
+
* question: an aborted turn is kept expanded by the transcript, so the work
|
|
5515
|
+
* details the human was reading never fold. The conversation service is
|
|
5516
|
+
* addressed through the session scope exactly like the official composer's
|
|
5517
|
+
* Stop button does.
|
|
5518
|
+
*/
|
|
5519
|
+
const cancelTurn = async (sessionId) => {
|
|
5520
|
+
const conversation = (sessions?.scope?.(sessionId))?.get?.("conversation");
|
|
5521
|
+
if (typeof conversation?.cancel !== "function") throw new Error("model-switch: the conversation service exposes no cancel() through the session scope");
|
|
5522
|
+
await conversation.cancel();
|
|
5523
|
+
};
|
|
4823
5524
|
const directoryFace = (sessionId) => {
|
|
4824
5525
|
const directory = models.directoryFor(sessionId);
|
|
4825
5526
|
const available = sessions?.subagentAddress?.(sessionId) === void 0;
|
|
@@ -4842,6 +5543,7 @@ window.__ModuleLoader__.load({
|
|
|
4842
5543
|
},
|
|
4843
5544
|
goalRemote: () => goalRemoteFrom(scope),
|
|
4844
5545
|
planPreview,
|
|
5546
|
+
cancelTurn,
|
|
4845
5547
|
hooks: {
|
|
4846
5548
|
directory: directory.store,
|
|
4847
5549
|
providerOrder: orderStore
|
|
@@ -5050,6 +5752,28 @@ window.__ModuleLoader__.load({
|
|
|
5050
5752
|
saveCapability: (route, ops) => {
|
|
5051
5753
|
return (route === "search" ? search : route === "image" ? image : route === "compaction" ? compaction : route === "title" ? sessionTitle : autoReview).mutate(ops);
|
|
5052
5754
|
},
|
|
5755
|
+
saveReviewAllowlist: async ({ enabled, tools }) => {
|
|
5756
|
+
if (!await owned.mutate([{
|
|
5757
|
+
op: "set",
|
|
5758
|
+
path: ["autoReviewSkipReadOnly"],
|
|
5759
|
+
value: enabled
|
|
5760
|
+
}, {
|
|
5761
|
+
op: "set",
|
|
5762
|
+
path: ["autoReviewAllowlist"],
|
|
5763
|
+
value: [...tools]
|
|
5764
|
+
}])) throw new Error("settings-rejected");
|
|
5765
|
+
},
|
|
5766
|
+
...connectionRpc === void 0 ? {} : { scanTools: async () => {
|
|
5767
|
+
const response = await callPluginRpc(connectionRpc, "plugin-rpc/model-switch", "tools", {});
|
|
5768
|
+
if (!response.ok || response.value === void 0) throw new Error(t("reviewAllowlistScanFailed"));
|
|
5769
|
+
const rows = decodeToolCatalog(response.value);
|
|
5770
|
+
if (rows === void 0) throw new Error(t("reviewAllowlistScanFailed"));
|
|
5771
|
+
const status = decodeReviewStatus(response.value);
|
|
5772
|
+
return status === void 0 ? { rows } : {
|
|
5773
|
+
rows,
|
|
5774
|
+
status
|
|
5775
|
+
};
|
|
5776
|
+
} },
|
|
5053
5777
|
saveProviderGroup: async (provider, group) => {
|
|
5054
5778
|
await saveProviderGroupFenced(owned, provider, group);
|
|
5055
5779
|
},
|