dsh-capability-panel 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +4 -4
- package/README.ja.md +24 -2
- package/README.ko.md +24 -2
- package/README.md +22 -2
- package/README.zh.md +22 -2
- package/lib/client.js +321 -36
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +519 -274
- package/lib/index.js.map +1 -1
- package/package.json +7 -6
package/lib/client.js
CHANGED
|
@@ -45,13 +45,20 @@ function parseSkillEntry(value) {
|
|
|
45
45
|
const state = value["state"];
|
|
46
46
|
if (state !== "loaded" && state !== "pruned" && state !== "evicted" && state !== "unloaded") return null;
|
|
47
47
|
if (typeof value["enabled"] !== "boolean" || typeof value["loadCount"] !== "number") return null;
|
|
48
|
+
if (typeof value["source"] !== "string" || typeof value["provider"] !== "string") return null;
|
|
48
49
|
const description = optString(value["description"]);
|
|
50
|
+
const path = optString(value["path"]);
|
|
51
|
+
const group = optString(value["group"]);
|
|
49
52
|
return {
|
|
50
53
|
name: value["name"],
|
|
51
54
|
state,
|
|
52
55
|
enabled: value["enabled"],
|
|
53
56
|
loadCount: value["loadCount"],
|
|
54
|
-
|
|
57
|
+
source: value["source"],
|
|
58
|
+
provider: value["provider"],
|
|
59
|
+
...description === void 0 ? {} : { description },
|
|
60
|
+
...path === void 0 ? {} : { path },
|
|
61
|
+
...group === void 0 ? {} : { group }
|
|
55
62
|
};
|
|
56
63
|
}
|
|
57
64
|
function parseToolEntry(value) {
|
|
@@ -78,10 +85,14 @@ function parseMcpServerEntry(value) {
|
|
|
78
85
|
if (parsed === null) return null;
|
|
79
86
|
tools.push(parsed);
|
|
80
87
|
}
|
|
88
|
+
const source = optString(value["source"]);
|
|
89
|
+
const path = optString(value["path"]);
|
|
81
90
|
return {
|
|
82
91
|
server: value["server"],
|
|
83
92
|
enabled: value["enabled"],
|
|
84
|
-
tools
|
|
93
|
+
tools,
|
|
94
|
+
...source === void 0 ? {} : { source },
|
|
95
|
+
...path === void 0 ? {} : { path }
|
|
85
96
|
};
|
|
86
97
|
}
|
|
87
98
|
function parseBlocked(value) {
|
|
@@ -161,7 +172,7 @@ function close() {
|
|
|
161
172
|
open: false
|
|
162
173
|
});
|
|
163
174
|
}
|
|
164
|
-
const ROUTE = "/api/capability-panel";
|
|
175
|
+
const ROUTE$1 = "/api/capability-panel";
|
|
165
176
|
/**
|
|
166
177
|
* Reads and writes have different ordering domains. A newer refresh supersedes
|
|
167
178
|
* an older refresh, but it cannot invalidate a user's mutation response.
|
|
@@ -196,7 +207,7 @@ function finishRequest(requestEpoch, patch) {
|
|
|
196
207
|
}
|
|
197
208
|
async function requestPayload$1(sessionId, init) {
|
|
198
209
|
const query = sessionId === null ? "" : `?session=${encodeURIComponent(sessionId)}`;
|
|
199
|
-
const response = await fetch(`${ROUTE}${query}`, {
|
|
210
|
+
const response = await fetch(`${ROUTE$1}${query}`, {
|
|
200
211
|
credentials: "same-origin",
|
|
201
212
|
...init
|
|
202
213
|
});
|
|
@@ -282,10 +293,11 @@ function filterCapabilities(source, rawQuery) {
|
|
|
282
293
|
const skills = source.skills.filter((skill) => hit(query, [
|
|
283
294
|
skill.name,
|
|
284
295
|
skill.description,
|
|
285
|
-
skill.stateLabel
|
|
296
|
+
skill.stateLabel,
|
|
297
|
+
skill.sourceLabel
|
|
286
298
|
]));
|
|
287
299
|
const mcp = source.mcp.flatMap((server) => {
|
|
288
|
-
if (hit(query, [server.server])) return [server];
|
|
300
|
+
if (hit(query, [server.server, server.source])) return [server];
|
|
289
301
|
const tools = server.tools.filter((tool) => hit(query, [
|
|
290
302
|
tool.label,
|
|
291
303
|
tool.name,
|
|
@@ -311,11 +323,12 @@ function filterCapabilities(source, rawQuery) {
|
|
|
311
323
|
|
|
312
324
|
//#endregion
|
|
313
325
|
//#region src/client/filter.ts
|
|
314
|
-
function filterPayload(payload, rawQuery, stateLabel = (skill) => skill.state) {
|
|
326
|
+
function filterPayload(payload, rawQuery, stateLabel = (skill) => skill.state, sourceLabel = (skill) => skill.source) {
|
|
315
327
|
if (rawQuery.trim() === "") return filterCapabilities(payload, rawQuery);
|
|
316
328
|
const skills = payload.skills.map((skill) => ({
|
|
317
329
|
...skill,
|
|
318
|
-
stateLabel: stateLabel(skill)
|
|
330
|
+
stateLabel: stateLabel(skill),
|
|
331
|
+
sourceLabel: sourceLabel(skill)
|
|
319
332
|
}));
|
|
320
333
|
return filterCapabilities({
|
|
321
334
|
...payload,
|
|
@@ -409,7 +422,17 @@ const zh = {
|
|
|
409
422
|
"preset.noTools": "这个 preset 没有可用能力。",
|
|
410
423
|
"preset.reserved": "{name} 是保留的传输通道,不能关闭。",
|
|
411
424
|
"preset.broken": "这个 preset 无法组装会话:{reason}。修好它之后才能列出工具。",
|
|
412
|
-
"degraded.item": "⚠ 部分读取失败:{note}"
|
|
425
|
+
"degraded.item": "⚠ 部分读取失败:{note}",
|
|
426
|
+
"source.project-dsh": "项目 .dsh",
|
|
427
|
+
"source.project-agents": "项目 agent",
|
|
428
|
+
"source.runtime": "运行时",
|
|
429
|
+
"source.user-dsh": "用户 .dsh",
|
|
430
|
+
"source.user-agents": "用户 agent",
|
|
431
|
+
"source.custom": "自定义",
|
|
432
|
+
"source.bundled": "内置",
|
|
433
|
+
"source.host": "全局",
|
|
434
|
+
"source.preset": "预设",
|
|
435
|
+
"source.openFolder": "在文件管理器中打开 {source}"
|
|
413
436
|
};
|
|
414
437
|
const en = {
|
|
415
438
|
"state.loaded": "loaded",
|
|
@@ -459,7 +482,17 @@ const en = {
|
|
|
459
482
|
"preset.noTools": "This preset exposes no capabilities.",
|
|
460
483
|
"preset.reserved": "{name} is a reserved transport and cannot be disabled.",
|
|
461
484
|
"preset.broken": "This preset cannot compose a session: {reason}. Fix it before its tools can be listed.",
|
|
462
|
-
"degraded.item": "⚠ Partial read failed: {note}"
|
|
485
|
+
"degraded.item": "⚠ Partial read failed: {note}",
|
|
486
|
+
"source.project-dsh": "project .dsh",
|
|
487
|
+
"source.project-agents": "project agent",
|
|
488
|
+
"source.runtime": "runtime",
|
|
489
|
+
"source.user-dsh": "user .dsh",
|
|
490
|
+
"source.user-agents": "user agent",
|
|
491
|
+
"source.custom": "custom",
|
|
492
|
+
"source.bundled": "bundled",
|
|
493
|
+
"source.host": "global",
|
|
494
|
+
"source.preset": "preset",
|
|
495
|
+
"source.openFolder": "Open {source} in file manager"
|
|
463
496
|
};
|
|
464
497
|
/**
|
|
465
498
|
* Register both dictionaries as one effect: the single-locale form is the
|
|
@@ -482,6 +515,7 @@ const TOK = {
|
|
|
482
515
|
textSecondary: "var(--dsw-alias-label-secondary, #61666b)",
|
|
483
516
|
textTertiary: "var(--dsw-alias-label-tertiary, #81858c)",
|
|
484
517
|
border: "var(--dsw-alias-border-l1, rgba(0,0,0,.04))",
|
|
518
|
+
borderStrong: "var(--dsw-alias-border-l2, rgba(0,0,0,.1))",
|
|
485
519
|
switchOn: "var(--dsw-alias-state-business-primary, #4176e6)",
|
|
486
520
|
switchOff: "var(--dsw-alias-border-l2, rgba(0,0,0,.1))",
|
|
487
521
|
switchThumb: "var(--dsw-alias-bg-layer-1, #ffffff)",
|
|
@@ -533,31 +567,30 @@ const PANEL_CSS = [
|
|
|
533
567
|
".ci-preset-filter{flex:1 1 220px;min-width:180px;height:34px;box-sizing:border-box;padding:0 10px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1));border-radius:8px;background-color:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#0f1115);font:inherit;font-weight:400}",
|
|
534
568
|
".ci-preset-part{margin:18px 0 0}",
|
|
535
569
|
".ci-preset-part:first-of-type{margin-top:10px}",
|
|
536
|
-
".ci-preset-part-title{margin:0;font-size:
|
|
570
|
+
".ci-preset-part-title{margin:0;font-size:14px;font-weight:600;line-height:22px;color:var(--dsw-alias-label-primary,#0f1115)}",
|
|
537
571
|
".ci-preset-badge{display:inline-block;margin-left:6px;padding:1px 6px;line-height:1.5;border-radius:999px;font-size:11px;font-weight:500;vertical-align:middle;background-color:var(--dsw-alias-bg-fill-2,rgba(0,0,0,.05));color:var(--dsw-alias-label-tertiary,#81858c)}",
|
|
538
572
|
".ci-preset-group{padding:0}",
|
|
539
573
|
".ci-preset-server-trigger{display:flex;align-items:center;gap:8px;flex:1;min-width:0;background:none;border:none;padding:0;text-align:left;font:inherit;color:inherit;cursor:pointer}",
|
|
540
574
|
".ci-preset-server-trigger:disabled{cursor:default}",
|
|
541
|
-
".ci-preset-group .ci-preset-tool-list{margin:0;padding-left:26px
|
|
542
|
-
".ci-preset-group .ci-preset-tool-row:last-child{border-bottom:none}",
|
|
575
|
+
".ci-preset-group .ci-preset-tool-list{margin:0;padding-left:26px}",
|
|
543
576
|
".ci-preset-picker-label{display:grid;gap:6px;color:var(--dsw-alias-label-primary,#0f1115);font-weight:600}",
|
|
544
577
|
".ci-preset-picker{height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1));border-radius:8px;background-color:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#0f1115);font:inherit;font-weight:400}",
|
|
545
578
|
".ci-preset-picker:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:1px}",
|
|
546
579
|
".ci-settings-subtitle{margin:0 0 6px;color:var(--dsw-alias-label-primary,#0f1115);font-size:14px;line-height:22px}",
|
|
547
|
-
".ci-preset-tool-list{list-style:none;margin:0;padding:0
|
|
548
|
-
".ci-preset-tool-row{display:flex;align-items:center;gap:16px
|
|
580
|
+
".ci-preset-tool-list{list-style:none;margin:0;padding:0}",
|
|
581
|
+
".ci-preset-tool-row{display:flex;align-items:center;gap:16px}",
|
|
549
582
|
".ci-preset-tool-copy{display:grid;min-width:0;flex:1}",
|
|
550
583
|
".ci-preset-tool-name{color:var(--dsw-alias-label-primary,#0f1115);font-weight:600;overflow-wrap:anywhere}",
|
|
551
584
|
".ci-preset-tool-description{color:var(--dsw-alias-label-tertiary,#81858c);line-height:18px;overflow-wrap:anywhere}",
|
|
552
|
-
".ci-preset-item{list-style:none
|
|
553
|
-
".ci-preset-item:last-child{border-bottom:none}",
|
|
554
|
-
".ci-preset-item .ci-preset-tool-row{border-bottom:none}",
|
|
585
|
+
".ci-preset-item{list-style:none}",
|
|
555
586
|
".ci-preset-disclosure{display:block}",
|
|
556
587
|
".ci-preset-spacer{width:18px;flex:none}",
|
|
557
588
|
".ci-preset-detail{padding:0 0 10px 24px;color:var(--dsw-alias-label-tertiary,#81858c);line-height:18px;overflow-wrap:anywhere}",
|
|
558
589
|
".ci-preset-kinds{margin:0 0 4px}",
|
|
559
590
|
".ci-preset-kinds .ci-tabs{display:inline-flex}",
|
|
560
591
|
".ci-preset-kinds .ci-tab{flex:0 0 auto;padding:0 12px}",
|
|
592
|
+
".ci-source-header:hover .ci-folder-icon{opacity:1 !important}",
|
|
593
|
+
".ci-source-divider{list-style:none}",
|
|
561
594
|
"@media (prefers-reduced-motion: reduce){.ci-thumb,.ci-panel,.ci-collapse,.ci-chevron svg{transition:none !important}}"
|
|
562
595
|
].join("\n");
|
|
563
596
|
|
|
@@ -4560,15 +4593,21 @@ function parseSkills(raw) {
|
|
|
4560
4593
|
const skills = [];
|
|
4561
4594
|
for (const rawSkill of raw) {
|
|
4562
4595
|
if (!isRecord(rawSkill)) return null;
|
|
4563
|
-
const { name, description, enabled, project } = rawSkill;
|
|
4596
|
+
const { name, description, enabled, project, source, path, group } = rawSkill;
|
|
4564
4597
|
if (typeof name !== "string" || typeof enabled !== "boolean") return null;
|
|
4565
4598
|
if (description !== void 0 && typeof description !== "string") return null;
|
|
4566
4599
|
if (project !== void 0 && typeof project !== "boolean") return null;
|
|
4600
|
+
if (source !== void 0 && typeof source !== "string") return null;
|
|
4601
|
+
if (path !== void 0 && typeof path !== "string") return null;
|
|
4602
|
+
if (group !== void 0 && typeof group !== "string") return null;
|
|
4567
4603
|
skills.push({
|
|
4568
4604
|
name,
|
|
4569
4605
|
...description === void 0 ? {} : { description },
|
|
4570
4606
|
enabled,
|
|
4571
|
-
...project === void 0 ? {} : { project }
|
|
4607
|
+
...project === void 0 ? {} : { project },
|
|
4608
|
+
...source === void 0 ? {} : { source },
|
|
4609
|
+
...path === void 0 ? {} : { path },
|
|
4610
|
+
...group === void 0 ? {} : { group }
|
|
4572
4611
|
});
|
|
4573
4612
|
}
|
|
4574
4613
|
return skills;
|
|
@@ -4593,14 +4632,18 @@ function parsePresetToolPayload(value) {
|
|
|
4593
4632
|
const mcp = [];
|
|
4594
4633
|
for (const rawServer of rawMcp) {
|
|
4595
4634
|
if (!isRecord(rawServer)) return null;
|
|
4596
|
-
const { server, tools: rawTools, enabled } = rawServer;
|
|
4635
|
+
const { server, tools: rawTools, enabled, source, path } = rawServer;
|
|
4597
4636
|
if (typeof server !== "string" || typeof enabled !== "boolean" || !Array.isArray(rawTools)) return null;
|
|
4637
|
+
if (source !== void 0 && typeof source !== "string") return null;
|
|
4638
|
+
if (path !== void 0 && typeof path !== "string") return null;
|
|
4598
4639
|
const tools = parseTools(rawTools);
|
|
4599
4640
|
if (tools === null) return null;
|
|
4600
4641
|
mcp.push({
|
|
4601
4642
|
server,
|
|
4602
4643
|
tools,
|
|
4603
|
-
enabled
|
|
4644
|
+
enabled,
|
|
4645
|
+
...source === void 0 ? {} : { source },
|
|
4646
|
+
...path === void 0 ? {} : { path }
|
|
4604
4647
|
});
|
|
4605
4648
|
}
|
|
4606
4649
|
presets.push({
|
|
@@ -4748,6 +4791,28 @@ function resetPresetTools() {
|
|
|
4748
4791
|
|
|
4749
4792
|
//#endregion
|
|
4750
4793
|
//#region src/client/preset-section.ts
|
|
4794
|
+
/** Middle ellipsis for long path labels; the full path stays in the tooltip. */
|
|
4795
|
+
const ellipsizeMiddle = (text, max$1 = 56) => {
|
|
4796
|
+
if (text.length <= max$1) return text;
|
|
4797
|
+
const head = Math.ceil((max$1 - 1) / 2);
|
|
4798
|
+
const tail = Math.floor((max$1 - 1) / 2);
|
|
4799
|
+
return `${text.slice(0, head)}…${text.slice(text.length - tail)}`;
|
|
4800
|
+
};
|
|
4801
|
+
/** Group items by a key, preserving first-seen order. */
|
|
4802
|
+
function groupBy(items, keyOf) {
|
|
4803
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4804
|
+
const order = [];
|
|
4805
|
+
for (const item of items) {
|
|
4806
|
+
const key = keyOf(item);
|
|
4807
|
+
const bucket = groups.get(key);
|
|
4808
|
+
if (bucket !== void 0) bucket.push(item);
|
|
4809
|
+
else {
|
|
4810
|
+
groups.set(key, [item]);
|
|
4811
|
+
order.push(key);
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4814
|
+
return order.map((key) => [key, groups.get(key)]);
|
|
4815
|
+
}
|
|
4751
4816
|
/**
|
|
4752
4817
|
* Settings-side twin of the composer panel. It reuses that panel's vocabulary
|
|
4753
4818
|
* and interaction — an always-visible filter, MCP tools collapsed under their
|
|
@@ -4778,6 +4843,101 @@ function PresetToolSection(props) {
|
|
|
4778
4843
|
label,
|
|
4779
4844
|
onCheckedChange: onChange
|
|
4780
4845
|
});
|
|
4846
|
+
/**
|
|
4847
|
+
* Open a source folder from the settings page. The route resolves
|
|
4848
|
+
* user/project/host/preset sources without a session (project sources
|
|
4849
|
+
* against the dsh process's own cwd, matching how this page lists them).
|
|
4850
|
+
*/
|
|
4851
|
+
const openSourceFolder = (source) => {
|
|
4852
|
+
fetch("/api/capability-panel/open-folder", {
|
|
4853
|
+
method: "POST",
|
|
4854
|
+
credentials: "same-origin",
|
|
4855
|
+
headers: { "content-type": "application/json" },
|
|
4856
|
+
body: JSON.stringify({ source })
|
|
4857
|
+
}).then((response) => {
|
|
4858
|
+
if (!response.ok) console.warn(`[capability-panel] cannot open source folder (${response.status})`);
|
|
4859
|
+
}).catch((error) => {
|
|
4860
|
+
console.warn("[capability-panel] open source folder request failed", error);
|
|
4861
|
+
});
|
|
4862
|
+
};
|
|
4863
|
+
/** Sources the open-folder route can resolve without a session. */
|
|
4864
|
+
const SESSIONLESS_OPENABLE = new Set([
|
|
4865
|
+
"user-dsh",
|
|
4866
|
+
"user-agents",
|
|
4867
|
+
"project-dsh",
|
|
4868
|
+
"project-agents",
|
|
4869
|
+
"host"
|
|
4870
|
+
]);
|
|
4871
|
+
/**
|
|
4872
|
+
* A ruled source divider inside a settings list — the same visual language
|
|
4873
|
+
* as the session panel's group headers (`── label (count) ────`). Preset
|
|
4874
|
+
* groups name their preset; other groups show the directory (ellipsized);
|
|
4875
|
+
* openable groups offer the hover folder icon and a click-through.
|
|
4876
|
+
*/
|
|
4877
|
+
const sourceDivider = (groupKey, items, first) => {
|
|
4878
|
+
const presetName = groupKey.startsWith("preset:") ? groupKey.slice(7) : void 0;
|
|
4879
|
+
const path = items.find((item) => item.path !== void 0)?.path;
|
|
4880
|
+
const translated = t(`source.${groupKey}`);
|
|
4881
|
+
const fallbackLabel = translated === `source.${groupKey}` ? groupKey : translated;
|
|
4882
|
+
const label = presetName ?? (path !== void 0 ? ellipsizeMiddle(path) : groupKey === "host" ? t("source.host") : fallbackLabel);
|
|
4883
|
+
const openSource = presetName ?? groupKey;
|
|
4884
|
+
const openable = presetName !== void 0 || SESSIONLESS_OPENABLE.has(groupKey);
|
|
4885
|
+
const rule = (grow) => react.createElement("span", { style: grow ? {
|
|
4886
|
+
flex: "1",
|
|
4887
|
+
height: "1px",
|
|
4888
|
+
background: TOK.borderStrong
|
|
4889
|
+
} : {
|
|
4890
|
+
flex: "none",
|
|
4891
|
+
width: "16px",
|
|
4892
|
+
height: "1px",
|
|
4893
|
+
background: TOK.borderStrong
|
|
4894
|
+
} });
|
|
4895
|
+
return react.createElement("li", {
|
|
4896
|
+
key: `source:${groupKey}`,
|
|
4897
|
+
className: "ci-source-divider"
|
|
4898
|
+
}, react.createElement("div", {
|
|
4899
|
+
className: "ci-source-header",
|
|
4900
|
+
style: {
|
|
4901
|
+
display: "flex",
|
|
4902
|
+
alignItems: "center",
|
|
4903
|
+
gap: "8px",
|
|
4904
|
+
margin: first ? "6px 0 2px" : "18px 0 2px"
|
|
4905
|
+
}
|
|
4906
|
+
}, rule(false), react.createElement("span", {
|
|
4907
|
+
style: {
|
|
4908
|
+
flex: "none",
|
|
4909
|
+
display: "inline-flex",
|
|
4910
|
+
alignItems: "center",
|
|
4911
|
+
gap: "4px",
|
|
4912
|
+
fontSize: "12px",
|
|
4913
|
+
fontWeight: 500,
|
|
4914
|
+
color: TOK.textTertiary,
|
|
4915
|
+
fontVariantNumeric: "tabular-nums",
|
|
4916
|
+
cursor: openable ? "pointer" : "default"
|
|
4917
|
+
},
|
|
4918
|
+
title: openable ? t("source.openFolder", { source: path ?? label }) : void 0,
|
|
4919
|
+
onClick: openable ? () => {
|
|
4920
|
+
openSourceFolder(openSource);
|
|
4921
|
+
} : void 0
|
|
4922
|
+
}, openable ? react.createElement("span", {
|
|
4923
|
+
className: "ci-folder-icon",
|
|
4924
|
+
style: {
|
|
4925
|
+
display: "inline-grid",
|
|
4926
|
+
placeItems: "center",
|
|
4927
|
+
opacity: 0,
|
|
4928
|
+
transition: "opacity 0.15s"
|
|
4929
|
+
}
|
|
4930
|
+
}, react.createElement("svg", {
|
|
4931
|
+
width: "12",
|
|
4932
|
+
height: "12",
|
|
4933
|
+
viewBox: "0 0 24 24",
|
|
4934
|
+
fill: "none",
|
|
4935
|
+
stroke: "currentColor",
|
|
4936
|
+
"stroke-width": "2",
|
|
4937
|
+
"stroke-linecap": "round",
|
|
4938
|
+
"stroke-linejoin": "round"
|
|
4939
|
+
}, react.createElement("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" }))) : null, `${label} (${items.length})`), rule(true)));
|
|
4940
|
+
};
|
|
4781
4941
|
const toggle$1 = (tool, presetId) => switchFor(tool.enabled, tool.reserved === true, tool.reserved === true ? t("preset.reserved", { name: tool.label }) : t(tool.enabled ? "action.disable" : "action.enable", { name: tool.label }), (checked) => {
|
|
4782
4942
|
setPresetTool(presetId, tool.name, checked);
|
|
4783
4943
|
});
|
|
@@ -4869,8 +5029,8 @@ function PresetToolSection(props) {
|
|
|
4869
5029
|
group("skills", t("group.skills", {
|
|
4870
5030
|
shown: view.skills.length,
|
|
4871
5031
|
total: totals.skills
|
|
4872
|
-
}), view.skills.map((skill) => skillRow(skill, selected.id)));
|
|
4873
|
-
const
|
|
5032
|
+
}), groupBy(view.skills, (skill) => skill.group ?? skill.source ?? "unknown").flatMap(([groupKey, items], i) => [sourceDivider(groupKey, items, i === 0), ...items.map((skill) => skillRow(skill, selected.id))]));
|
|
5033
|
+
const serverRow = (server) => {
|
|
4874
5034
|
const key = `mcp:${server.server}`;
|
|
4875
5035
|
const disclosure = resolveDisclosure(expanded[key] === true, filtering);
|
|
4876
5036
|
return react.createElement("li", {
|
|
@@ -4893,11 +5053,11 @@ function PresetToolSection(props) {
|
|
|
4893
5053
|
}, chevronIcon), react.createElement("span", { className: "ci-preset-tool-copy" }, react.createElement("span", { className: "ci-preset-tool-name" }, server.server), react.createElement("span", { className: "ci-preset-tool-description" }, server.tools.length === 1 ? t("server.tool.one") : t("server.tools", { count: server.tools.length })))), switchFor(server.enabled, false, t(server.enabled ? "action.disable" : "action.enable", { name: server.server }), (checked) => {
|
|
4894
5054
|
setPresetServer(selected.id, server.server, checked);
|
|
4895
5055
|
})), react.createElement(CollapsiblePanel, { className: "ci-collapse" }, react.createElement("ul", { className: "ci-preset-tool-list" }, ...server.tools.map((tool) => toolRow(tool, selected.id, true))))));
|
|
4896
|
-
}
|
|
5056
|
+
};
|
|
4897
5057
|
group("mcp", t("group.mcp", {
|
|
4898
5058
|
shown: view.mcp.length,
|
|
4899
5059
|
total: totals.mcp
|
|
4900
|
-
}),
|
|
5060
|
+
}), groupBy(view.mcp, (server) => server.source === void 0 || server.source === "host" ? "host" : `preset:${server.source}`).flatMap(([groupKey, items], i) => [sourceDivider(groupKey, items, i === 0), ...items.map((server) => serverRow(server))]));
|
|
4901
5061
|
group("system", t("group.system", {
|
|
4902
5062
|
shown: view.systemTools.length,
|
|
4903
5063
|
total: totals.systemTools
|
|
@@ -10631,6 +10791,7 @@ const PopoverPopup = /* @__PURE__ */ react.forwardRef(function PopoverPopup$1(co
|
|
|
10631
10791
|
|
|
10632
10792
|
//#endregion
|
|
10633
10793
|
//#region src/client/index.ts
|
|
10794
|
+
const ROUTE = "/api/capability-panel";
|
|
10634
10795
|
/**
|
|
10635
10796
|
* Order by how much the reader needs to act on it: what fell out of context
|
|
10636
10797
|
* first, then what is in it, then the rest.
|
|
@@ -10704,7 +10865,121 @@ function apply(ctx) {
|
|
|
10704
10865
|
const normalizedQuery = query.trim();
|
|
10705
10866
|
const filtering = normalizedQuery !== "";
|
|
10706
10867
|
const payload = snap.payload;
|
|
10707
|
-
const
|
|
10868
|
+
const skillSourceLabel = (skill) => {
|
|
10869
|
+
const key = `source.${skill.source}`;
|
|
10870
|
+
const translated = t(key);
|
|
10871
|
+
return translated === key ? skill.source : translated;
|
|
10872
|
+
};
|
|
10873
|
+
const openSourceFolder = (source) => {
|
|
10874
|
+
if (sessionId === null) return;
|
|
10875
|
+
fetch(`${ROUTE}/open-folder`, {
|
|
10876
|
+
method: "POST",
|
|
10877
|
+
credentials: "same-origin",
|
|
10878
|
+
headers: { "content-type": "application/json" },
|
|
10879
|
+
body: JSON.stringify({
|
|
10880
|
+
sessionId,
|
|
10881
|
+
source
|
|
10882
|
+
})
|
|
10883
|
+
}).then(async (response) => {
|
|
10884
|
+
if (!response.ok) {
|
|
10885
|
+
const detail = await response.text();
|
|
10886
|
+
console.warn(`[capability-panel] cannot open source folder (${response.status}): ${detail}`);
|
|
10887
|
+
}
|
|
10888
|
+
}).catch((error) => {
|
|
10889
|
+
console.warn("[capability-panel] open source folder request failed", error);
|
|
10890
|
+
});
|
|
10891
|
+
};
|
|
10892
|
+
/**
|
|
10893
|
+
* Middle ellipsis for long path labels: both ends carry the meaning
|
|
10894
|
+
* (`~/…` context, the tail directory), the middle is the expendable
|
|
10895
|
+
* part. The full path stays in the hover tooltip.
|
|
10896
|
+
*/
|
|
10897
|
+
const ellipsizeMiddle$1 = (text, max$1 = 42) => {
|
|
10898
|
+
if (text.length <= max$1) return text;
|
|
10899
|
+
const head = Math.ceil((max$1 - 1) / 2);
|
|
10900
|
+
const tail = Math.floor((max$1 - 1) / 2);
|
|
10901
|
+
return `${text.slice(0, head)}…${text.slice(text.length - tail)}`;
|
|
10902
|
+
};
|
|
10903
|
+
/**
|
|
10904
|
+
* Section header for a source group, rendered as a labeled rule:
|
|
10905
|
+
* `── 📁 ~/.agents/skills (4) ────────────`. Label rule: preset-bundled
|
|
10906
|
+
* entries name their preset; every other group shows its real directory
|
|
10907
|
+
* (host-abbreviated `~`/cwd-relative, middle-ellipsized when long), and
|
|
10908
|
+
* only groups without a filesystem location fall back to the translated
|
|
10909
|
+
* source name. `groupKey` is the grouping identity; `rawSource` is what
|
|
10910
|
+
* the open-folder route resolves by.
|
|
10911
|
+
*/
|
|
10912
|
+
const sourceSectionHeader = (groupKey, rawSource, count, first, path, openable = false) => {
|
|
10913
|
+
const label = groupKey.startsWith("preset:") ? groupKey.slice(7) : path !== void 0 ? ellipsizeMiddle$1(path) : groupKey === "host" ? t("source.host") : skillSourceLabel({ source: groupKey });
|
|
10914
|
+
const rule = (grow) => h("span", { style: grow ? {
|
|
10915
|
+
flex: "1",
|
|
10916
|
+
height: "1px",
|
|
10917
|
+
background: TOK.borderStrong
|
|
10918
|
+
} : {
|
|
10919
|
+
flex: "none",
|
|
10920
|
+
width: "16px",
|
|
10921
|
+
height: "1px",
|
|
10922
|
+
background: TOK.borderStrong
|
|
10923
|
+
} });
|
|
10924
|
+
return h("div", {
|
|
10925
|
+
key: `source:${groupKey}`,
|
|
10926
|
+
className: "ci-source-header",
|
|
10927
|
+
style: {
|
|
10928
|
+
display: "flex",
|
|
10929
|
+
alignItems: "center",
|
|
10930
|
+
gap: "8px",
|
|
10931
|
+
margin: first ? "0 0 2px" : "10px 0 2px"
|
|
10932
|
+
}
|
|
10933
|
+
}, rule(false), h("span", {
|
|
10934
|
+
style: {
|
|
10935
|
+
flex: "none",
|
|
10936
|
+
display: "inline-flex",
|
|
10937
|
+
alignItems: "center",
|
|
10938
|
+
gap: "4px",
|
|
10939
|
+
fontWeight: 500,
|
|
10940
|
+
color: TOK.textTertiary,
|
|
10941
|
+
fontVariantNumeric: "tabular-nums",
|
|
10942
|
+
cursor: openable ? "pointer" : "default"
|
|
10943
|
+
},
|
|
10944
|
+
title: openable ? t("source.openFolder", { source: path ?? label }) : void 0,
|
|
10945
|
+
onClick: openable ? () => {
|
|
10946
|
+
openSourceFolder(rawSource);
|
|
10947
|
+
} : void 0
|
|
10948
|
+
}, openable ? h("span", {
|
|
10949
|
+
className: "ci-folder-icon",
|
|
10950
|
+
style: {
|
|
10951
|
+
display: "inline-grid",
|
|
10952
|
+
placeItems: "center",
|
|
10953
|
+
opacity: 0,
|
|
10954
|
+
transition: "opacity 0.15s"
|
|
10955
|
+
}
|
|
10956
|
+
}, h("svg", {
|
|
10957
|
+
width: "12",
|
|
10958
|
+
height: "12",
|
|
10959
|
+
viewBox: "0 0 24 24",
|
|
10960
|
+
fill: "none",
|
|
10961
|
+
stroke: "currentColor",
|
|
10962
|
+
"stroke-width": "2",
|
|
10963
|
+
"stroke-linecap": "round",
|
|
10964
|
+
"stroke-linejoin": "round"
|
|
10965
|
+
}, h("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" }))) : null, `${label} (${count})`), rule(true));
|
|
10966
|
+
};
|
|
10967
|
+
/** Group items by source, preserving order within each group. */
|
|
10968
|
+
const groupBySource = (items, getSource) => {
|
|
10969
|
+
const groups = /* @__PURE__ */ new Map();
|
|
10970
|
+
const order = [];
|
|
10971
|
+
for (const item of items) {
|
|
10972
|
+
const source = getSource(item);
|
|
10973
|
+
const bucket = groups.get(source);
|
|
10974
|
+
if (bucket) bucket.push(item);
|
|
10975
|
+
else {
|
|
10976
|
+
groups.set(source, [item]);
|
|
10977
|
+
order.push(source);
|
|
10978
|
+
}
|
|
10979
|
+
}
|
|
10980
|
+
return order.map((source) => [source, groups.get(source)]);
|
|
10981
|
+
};
|
|
10982
|
+
const view = payload === null ? null : filterPayload(payload, normalizedQuery, (skill) => t(`state.${skill.state}`), skillSourceLabel);
|
|
10708
10983
|
const skills = view === null ? [] : sortSkills(view.skills);
|
|
10709
10984
|
const mcp = view?.mcp ?? [];
|
|
10710
10985
|
const systemTools = view?.systemTools ?? [];
|
|
@@ -10769,7 +11044,7 @@ function apply(ctx) {
|
|
|
10769
11044
|
margin: first ? "0 0 2px" : "12px 0 2px"
|
|
10770
11045
|
}
|
|
10771
11046
|
}, text);
|
|
10772
|
-
const nameText = (text) => h("span", {
|
|
11047
|
+
const nameText = (text, sourceLabel) => h("span", {
|
|
10773
11048
|
className: "ci-name",
|
|
10774
11049
|
style: {
|
|
10775
11050
|
flex: "1 1 auto",
|
|
@@ -10777,7 +11052,12 @@ function apply(ctx) {
|
|
|
10777
11052
|
color: TOK.textPrimary,
|
|
10778
11053
|
fontWeight: 500
|
|
10779
11054
|
}
|
|
10780
|
-
}, text
|
|
11055
|
+
}, text, sourceLabel !== void 0 ? h("span", { style: {
|
|
11056
|
+
color: TOK.textTertiary,
|
|
11057
|
+
fontWeight: 400,
|
|
11058
|
+
fontSize: "11px",
|
|
11059
|
+
marginLeft: "2px"
|
|
11060
|
+
} }, `· ${sourceLabel}`) : null);
|
|
10781
11061
|
/** The trigger's accessible name, localized with its subject. */
|
|
10782
11062
|
const disclosureAria = (subject, detailKey, disclosure) => {
|
|
10783
11063
|
const detail = t(detailKey);
|
|
@@ -10798,7 +11078,7 @@ function apply(ctx) {
|
|
|
10798
11078
|
* trigger owns only chevron + label; trailing actions remain independent
|
|
10799
11079
|
* buttons and never toggle the description.
|
|
10800
11080
|
*/
|
|
10801
|
-
const disclosureRow$1 = (key, enabled, label, description, actions, className = ROW_ROOT_CLASS) => {
|
|
11081
|
+
const disclosureRow$1 = (key, enabled, label, description, actions, className = ROW_ROOT_CLASS, sourceLabel) => {
|
|
10802
11082
|
const hasDescription = description !== void 0 && description !== "";
|
|
10803
11083
|
const disclosure = resolveDisclosure(expanded[key] === true, filtering);
|
|
10804
11084
|
const ariaLabel = disclosureAria(label, "detail.description", disclosure);
|
|
@@ -10809,7 +11089,7 @@ function apply(ctx) {
|
|
|
10809
11089
|
}, h("div", { className: ROW_HEADER_CLASS }, h("span", { style: {
|
|
10810
11090
|
width: "18px",
|
|
10811
11091
|
flex: "none"
|
|
10812
|
-
} }), nameText(label), ...actions));
|
|
11092
|
+
} }), nameText(label, sourceLabel), ...actions));
|
|
10813
11093
|
return h(CollapsibleRoot, {
|
|
10814
11094
|
key,
|
|
10815
11095
|
open: disclosure.open,
|
|
@@ -10825,18 +11105,23 @@ function apply(ctx) {
|
|
|
10825
11105
|
}, h("span", {
|
|
10826
11106
|
className: "ci-chevron",
|
|
10827
11107
|
"aria-hidden": true
|
|
10828
|
-
}, chevronIcon$1), nameText(label)), ...actions), h(CollapsiblePanel, { className: "ci-collapse" }, h("div", { className: "ci-description" }, description)));
|
|
11108
|
+
}, chevronIcon$1), nameText(label, sourceLabel)), ...actions), h(CollapsiblePanel, { className: "ci-collapse" }, h("div", { className: "ci-description" }, description)));
|
|
10829
11109
|
};
|
|
10830
11110
|
/**
|
|
10831
11111
|
* Put a skill's slash command into the composer — never auto-submit:
|
|
10832
11112
|
* whether to send is the user's call (Enter). A non-empty draft is
|
|
10833
11113
|
* appended to, never replaced. Works for disabled skills too: the
|
|
10834
11114
|
* disable shadow keeps userInvocable: true by design.
|
|
11115
|
+
*
|
|
11116
|
+
* The draft comes from the live input-state selector: the old
|
|
11117
|
+
* `props.input` snapshot is gone in newer hosts, and reading a stale
|
|
11118
|
+
* snapshot would REPLACE the user's draft instead of appending.
|
|
10835
11119
|
*/
|
|
11120
|
+
const composerDraft = props.useInput === void 0 ? props.input?.draft ?? "" : props.useInput((state) => state.draft);
|
|
10836
11121
|
const insertCommand = (name) => {
|
|
10837
11122
|
const actions = props.inputActions;
|
|
10838
11123
|
if (actions === void 0) return;
|
|
10839
|
-
const draft =
|
|
11124
|
+
const draft = composerDraft;
|
|
10840
11125
|
actions.setDraft(draft.trim() === "" ? `/${name} ` : `${draft} /${name} `);
|
|
10841
11126
|
close();
|
|
10842
11127
|
};
|
|
@@ -10868,7 +11153,7 @@ function apply(ctx) {
|
|
|
10868
11153
|
insertButton(skill.name),
|
|
10869
11154
|
blockedChip(blocked[skill.name] ?? 0),
|
|
10870
11155
|
switchControl("skill", skill.name, skill.enabled)
|
|
10871
|
-
]);
|
|
11156
|
+
], ROW_ROOT_CLASS);
|
|
10872
11157
|
const mcpToolRow = (tool, serverEnabled) => disclosureRow$1(`mcp-tool:${tool.name}`, tool.enabled, tool.label, tool.description, [blockedChip(blocked[tool.name] ?? 0), serverEnabled ? switchControl("mcp-tool", tool.name, tool.enabled) : null], MCP_TOOL_ROOT_CLASS);
|
|
10873
11158
|
const serverRow = (server) => {
|
|
10874
11159
|
const serverBlocked = server.tools.reduce((sum, tool) => sum + (blocked[tool.name] ?? 0), 0);
|
|
@@ -10981,7 +11266,7 @@ function apply(ctx) {
|
|
|
10981
11266
|
value: "system",
|
|
10982
11267
|
className: "ci-tab",
|
|
10983
11268
|
"aria-label": t("tab.system.aria", { count: totals.systemTools })
|
|
10984
|
-
}, `${t("tab.system")} ${totals.systemTools}`)), h(TabsPanel, { value: "skills" }, skills.length === 0 && payload !== null && !snap.loading ? emptyNote(t("empty.skills")) : h("div", {}, ...skills.map(skillRow))), h(TabsPanel, { value: "mcp" }, mcp.length === 0 && payload !== null && !snap.loading ? emptyNote(t("empty.mcp")) : h("div", {}, ...mcp.map(serverRow))), h(TabsPanel, { value: "system" }, systemTools.length === 0 && payload !== null && !snap.loading ? emptyNote(t("empty.system")) : h("div", {}, ...systemTools.map(systemRow))))];
|
|
11269
|
+
}, `${t("tab.system")} ${totals.systemTools}`)), h(TabsPanel, { value: "skills" }, skills.length === 0 && payload !== null && !snap.loading ? emptyNote(t("empty.skills")) : h("div", {}, ...groupBySource(skills, (s) => s.group ?? s.source).flatMap(([groupKey, items], i) => [sourceSectionHeader(groupKey, items[0].source, items.length, i === 0, items.find((item) => item.path !== void 0)?.path, items.some((item) => item.path !== void 0)), ...items.map(skillRow)]))), h(TabsPanel, { value: "mcp" }, mcp.length === 0 && payload !== null && !snap.loading ? emptyNote(t("empty.mcp")) : h("div", {}, ...groupBySource(mcp, (s) => s.source === void 0 || s.source === "host" ? "host" : `preset:${s.source}`).flatMap(([groupKey, items], i) => [sourceSectionHeader(groupKey, items[0].source ?? "host", items.length, i === 0, items.find((item) => item.path !== void 0)?.path, true), ...items.map(serverRow)]))), h(TabsPanel, { value: "system" }, systemTools.length === 0 && payload !== null && !snap.loading ? emptyNote(t("empty.system")) : h("div", {}, ...systemTools.map(systemRow))))];
|
|
10985
11270
|
return h(PopoverRoot, {
|
|
10986
11271
|
open: snap.open,
|
|
10987
11272
|
onOpenChange: syncOpen
|
|
@@ -11018,7 +11303,7 @@ function apply(ctx) {
|
|
|
11018
11303
|
"aria-label": t("panel.aria"),
|
|
11019
11304
|
style: {
|
|
11020
11305
|
boxSizing: "border-box",
|
|
11021
|
-
width: "
|
|
11306
|
+
width: "480px",
|
|
11022
11307
|
maxHeight: "min(60vh, var(--available-height, 60vh))",
|
|
11023
11308
|
overflowY: "auto",
|
|
11024
11309
|
padding: "10px 12px",
|