dsh-capability-panel 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +315 -35
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +6 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +446 -246
- package/lib/index.js.map +1 -1
- package/package.json +7 -6
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
1
2
|
import z from "@deepseek-ai/schemastery";
|
|
2
3
|
import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
4
|
import { homedir } from "node:os";
|
|
@@ -161,14 +162,14 @@ function createCapabilityController(ctx, appendStats, blockedCounts) {
|
|
|
161
162
|
ctx.effect(() => guardDispose, "capability-panel: tool guard");
|
|
162
163
|
};
|
|
163
164
|
ensureGuard();
|
|
164
|
-
ctx.on("tools/result", (exec, result) => {
|
|
165
|
-
const agent = exec.agent;
|
|
165
|
+
ctx.on("tools/result", (exec$1, result) => {
|
|
166
|
+
const agent = exec$1.agent;
|
|
166
167
|
if (agent === void 0 || typeof agent.id !== "string") return;
|
|
167
168
|
const state = states.get(agent.id);
|
|
168
169
|
if (state === void 0) return;
|
|
169
170
|
const hit = classifyBlockedCall({
|
|
170
|
-
name: exec.name,
|
|
171
|
-
arguments: exec.arguments,
|
|
171
|
+
name: exec$1.name,
|
|
172
|
+
arguments: exec$1.arguments,
|
|
172
173
|
agent,
|
|
173
174
|
...result.isError && result.error !== void 0 ? { error: result.error } : {}
|
|
174
175
|
}, new Set(state.skills.keys()), disabledToolNames(state));
|
|
@@ -225,8 +226,8 @@ function createCapabilityController(ctx, appendStats, blockedCounts) {
|
|
|
225
226
|
name: original.name,
|
|
226
227
|
description: original.description,
|
|
227
228
|
content: original.content,
|
|
228
|
-
source: "custom",
|
|
229
|
-
provider: "capability-panel",
|
|
229
|
+
source: typeof original.source === "string" ? original.source : "custom",
|
|
230
|
+
provider: typeof original.provider === "string" ? original.provider : "capability-panel",
|
|
230
231
|
...original.resourceBase === void 0 ? {} : { resourceBase: original.resourceBase },
|
|
231
232
|
invocation: {
|
|
232
233
|
modelInvocable: false,
|
|
@@ -336,8 +337,8 @@ function createCapabilityController(ctx, appendStats, blockedCounts) {
|
|
|
336
337
|
name: original.name,
|
|
337
338
|
description: original.description,
|
|
338
339
|
content: original.content,
|
|
339
|
-
source: "custom",
|
|
340
|
-
provider: "capability-panel",
|
|
340
|
+
source: typeof original.source === "string" ? original.source : "custom",
|
|
341
|
+
provider: typeof original.provider === "string" ? original.provider : "capability-panel",
|
|
341
342
|
...original.resourceBase === void 0 ? {} : { resourceBase: original.resourceBase },
|
|
342
343
|
invocation: {
|
|
343
344
|
modelInvocable: false,
|
|
@@ -451,36 +452,6 @@ function registerPresetEnforcement(ctx, capabilities, presetTools, sessionOverri
|
|
|
451
452
|
});
|
|
452
453
|
}
|
|
453
454
|
|
|
454
|
-
//#endregion
|
|
455
|
-
//#region src/host/settings-scope.ts
|
|
456
|
-
const TOOLKIT_SETTINGS_NAMESPACE = "capability-panel";
|
|
457
|
-
const SessionOverrideSchema = z.object({
|
|
458
|
-
skills: z.dict(z.boolean()).default({}),
|
|
459
|
-
mcpServers: z.dict(z.boolean()).default({}),
|
|
460
|
-
mcpTools: z.dict(z.boolean()).default({}),
|
|
461
|
-
systemTools: z.dict(z.boolean()).default({})
|
|
462
|
-
});
|
|
463
|
-
const ToolkitSettingsSchema = z.object({
|
|
464
|
-
presets: z.dict(z.array(z.string())).default({}),
|
|
465
|
-
presetSkills: z.dict(z.array(z.string())).default({}),
|
|
466
|
-
sessions: z.dict(SessionOverrideSchema).default({})
|
|
467
|
-
});
|
|
468
|
-
function createToolkitSettingsAccess(ctx) {
|
|
469
|
-
let scope;
|
|
470
|
-
let writeQueue = Promise.resolve();
|
|
471
|
-
return {
|
|
472
|
-
scope() {
|
|
473
|
-
if (scope === void 0) scope = ctx.get("settings")?.register(TOOLKIT_SETTINGS_NAMESPACE, ToolkitSettingsSchema, { applies: "live" });
|
|
474
|
-
return scope;
|
|
475
|
-
},
|
|
476
|
-
serialize(work) {
|
|
477
|
-
const next = writeQueue.then(work, work);
|
|
478
|
-
writeQueue = next.then(() => void 0, () => void 0);
|
|
479
|
-
return next;
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
|
|
484
455
|
//#endregion
|
|
485
456
|
//#region src/load-state.ts
|
|
486
457
|
/** Pull the skill name out of a `skill` tool call's stringified arguments. */
|
|
@@ -631,7 +602,7 @@ function decideStates(available, loads, shadowedSeqs, disabledSkills = /* @__PUR
|
|
|
631
602
|
if (bucket === void 0) byName.set(record.skillName, [record]);
|
|
632
603
|
else bucket.push(record);
|
|
633
604
|
}
|
|
634
|
-
return available.map(({ name, description, masked }) => {
|
|
605
|
+
return available.map(({ name, description, masked, source, provider, path, group }) => {
|
|
635
606
|
const records = byName.get(name) ?? [];
|
|
636
607
|
let state = "unloaded";
|
|
637
608
|
if (records.length > 0) {
|
|
@@ -644,7 +615,11 @@ function decideStates(available, loads, shadowedSeqs, disabledSkills = /* @__PUR
|
|
|
644
615
|
...description === void 0 ? {} : { description },
|
|
645
616
|
state,
|
|
646
617
|
enabled: !disabledSkills.has(name) && masked !== true,
|
|
647
|
-
loadCount: records.length
|
|
618
|
+
loadCount: records.length,
|
|
619
|
+
source,
|
|
620
|
+
provider,
|
|
621
|
+
...path === void 0 ? {} : { path },
|
|
622
|
+
...group === void 0 ? {} : { group }
|
|
648
623
|
};
|
|
649
624
|
});
|
|
650
625
|
}
|
|
@@ -669,6 +644,299 @@ function groupMcpTools(toolNames) {
|
|
|
669
644
|
})).sort((a, b) => a.server.localeCompare(b.server));
|
|
670
645
|
}
|
|
671
646
|
|
|
647
|
+
//#endregion
|
|
648
|
+
//#region src/host/catalog.ts
|
|
649
|
+
/** Coerce a skill summary field to a string, falling back when the runtime shape is wrong. */
|
|
650
|
+
function coerceString(value, fallback) {
|
|
651
|
+
return typeof value === "string" ? value : fallback;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* The source ROOT a skill was discovered under. The skills service reports
|
|
655
|
+
* each skill's own directory as resourceBase (`<root>/<name>`), so the group
|
|
656
|
+
* folder is its parent.
|
|
657
|
+
*/
|
|
658
|
+
function parentDir(path) {
|
|
659
|
+
const trimmed = path.replace(/[\\/]+$/, "");
|
|
660
|
+
if (trimmed === "") return "/";
|
|
661
|
+
const slash = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
662
|
+
return slash > 0 ? trimmed.slice(0, slash) : trimmed;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Abbreviate a host path for display: the session cwd becomes a relative
|
|
666
|
+
* path, the user's home becomes `~`. Everything else stays absolute.
|
|
667
|
+
*/
|
|
668
|
+
function displayPath(path, cwd) {
|
|
669
|
+
if (cwd !== void 0 && cwd !== "" && path.startsWith(`${cwd}/`)) return path.slice(cwd.length + 1);
|
|
670
|
+
const home = process.env["HOME"];
|
|
671
|
+
if (home !== void 0 && home !== "" && path.startsWith(home)) return `~${path.slice(home.length)}`;
|
|
672
|
+
return path;
|
|
673
|
+
}
|
|
674
|
+
/** The DSH home, matching the runtime's own resolution order. */
|
|
675
|
+
function dshHome() {
|
|
676
|
+
return process.env["DSH_HOME"] ?? (process.env["HOME"] !== void 0 ? `${process.env["HOME"]}/.dsh` : void 0);
|
|
677
|
+
}
|
|
678
|
+
const EMPTY_STATE = {
|
|
679
|
+
skills: /* @__PURE__ */ new Map(),
|
|
680
|
+
mcpServers: /* @__PURE__ */ new Map(),
|
|
681
|
+
mcpTools: /* @__PURE__ */ new Map(),
|
|
682
|
+
systemTools: /* @__PURE__ */ new Map(),
|
|
683
|
+
userToggled: /* @__PURE__ */ new Set()
|
|
684
|
+
};
|
|
685
|
+
async function readAvailable(services, sessionId, degraded, presetDirs = []) {
|
|
686
|
+
const skills = services.get("skills");
|
|
687
|
+
if (skills === void 0) {
|
|
688
|
+
degraded.push("skills service unavailable");
|
|
689
|
+
return [];
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
const agents = services.get("agents");
|
|
693
|
+
if (agents === void 0) {
|
|
694
|
+
degraded.push("agents service unavailable: session skill view cannot be determined");
|
|
695
|
+
return [];
|
|
696
|
+
}
|
|
697
|
+
const agent = agents.get(sessionId);
|
|
698
|
+
if (agent === void 0) {
|
|
699
|
+
degraded.push(`session agent "${sessionId}" unavailable: session skill view cannot be determined`);
|
|
700
|
+
return [];
|
|
701
|
+
}
|
|
702
|
+
const cwd = agent.session?.header?.cwd;
|
|
703
|
+
const list = await skills.list({
|
|
704
|
+
...cwd === void 0 ? {} : { cwd },
|
|
705
|
+
scope: agent
|
|
706
|
+
});
|
|
707
|
+
const out = [];
|
|
708
|
+
const seen = /* @__PURE__ */ new Map();
|
|
709
|
+
const groupFor = (source, rawRoot) => {
|
|
710
|
+
if (source !== "custom" || rawRoot === "") return void 0;
|
|
711
|
+
const owner = presetDirs.find((p) => rawRoot === p.path || rawRoot.startsWith(`${p.path}/`));
|
|
712
|
+
return owner === void 0 ? void 0 : `preset:${owner.key}`;
|
|
713
|
+
};
|
|
714
|
+
for (const item of list) {
|
|
715
|
+
if (typeof item.name !== "string" || item.name === "") continue;
|
|
716
|
+
const masked = item.invocation?.modelInvocable === false;
|
|
717
|
+
const source = coerceString(item.source, "unknown");
|
|
718
|
+
const provider = coerceString(item.provider, "unknown");
|
|
719
|
+
const base = item.resourceBase;
|
|
720
|
+
const rawPath = base !== null && typeof base === "object" && base.kind === "directory" ? coerceString(base.path, "") : "";
|
|
721
|
+
const rawRoot = rawPath === "" ? "" : parentDir(rawPath);
|
|
722
|
+
const path = rawRoot === "" ? "" : displayPath(rawRoot, cwd);
|
|
723
|
+
const group = groupFor(source, rawRoot);
|
|
724
|
+
const existing = seen.get(item.name);
|
|
725
|
+
if (existing !== void 0) {
|
|
726
|
+
if (masked) existing.masked = true;
|
|
727
|
+
if (existing.provider === "capability-panel" && provider !== "capability-panel") {
|
|
728
|
+
existing.source = source;
|
|
729
|
+
existing.provider = provider;
|
|
730
|
+
if (path !== "") existing.path = path;
|
|
731
|
+
if (group !== void 0) existing.group = group;
|
|
732
|
+
}
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
const description = typeof item.description === "string" ? item.description : void 0;
|
|
736
|
+
const row = {
|
|
737
|
+
name: item.name,
|
|
738
|
+
...description === void 0 ? {} : { description },
|
|
739
|
+
...masked ? { masked: true } : {},
|
|
740
|
+
source,
|
|
741
|
+
provider,
|
|
742
|
+
...path === "" ? {} : { path },
|
|
743
|
+
...group === void 0 ? {} : { group }
|
|
744
|
+
};
|
|
745
|
+
seen.set(item.name, row);
|
|
746
|
+
out.push(row);
|
|
747
|
+
}
|
|
748
|
+
return out;
|
|
749
|
+
} catch (error) {
|
|
750
|
+
degraded.push(`skills read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
751
|
+
return [];
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Read load facts off the LIVE session's in-memory log — the same object the
|
|
756
|
+
* agent loop itself reads to assemble the next request.
|
|
757
|
+
*
|
|
758
|
+
* This used to go through `sessionQuery.readSession` + `listEvents`, which are
|
|
759
|
+
* built for cross-session/cold reads: each call structuredClones the ENTIRE
|
|
760
|
+
* event log, `readSession` additionally replay-validates it via Session.create,
|
|
761
|
+
* and a write landing between the two parallel reads forced a whole second
|
|
762
|
+
* round. On a long session (60k events / tens of MB) that meant four full-log
|
|
763
|
+
* clones plus two full replays of synchronous CPU work on every panel open —
|
|
764
|
+
* blocking the Node event loop and freezing the GUI served by the same process.
|
|
765
|
+
*
|
|
766
|
+
* The live session needs none of that: `snapshotEvents()` hands back borrowed
|
|
767
|
+
* references (zero-copy), and `surface.nodes` is maintained incrementally as
|
|
768
|
+
* events land, so "what the model sees right now" is an O(1) membership test.
|
|
769
|
+
* Scanning references for the few `skill` tool calls costs microseconds even
|
|
770
|
+
* on the longest log. Both reads happen in one synchronous tick, so the view
|
|
771
|
+
* is always self-consistent — no cross-read race, no retry loop.
|
|
772
|
+
*
|
|
773
|
+
* A session without a live in-memory view (e.g. restored but not yet attached)
|
|
774
|
+
* degrades honestly instead of paying for a cold-log read the panel never
|
|
775
|
+
* asked for.
|
|
776
|
+
*/
|
|
777
|
+
function readLogFacts(services, sessionId, degraded) {
|
|
778
|
+
const empty = {
|
|
779
|
+
loads: [],
|
|
780
|
+
shadowed: /* @__PURE__ */ new Set(),
|
|
781
|
+
pruned: /* @__PURE__ */ new Set()
|
|
782
|
+
};
|
|
783
|
+
try {
|
|
784
|
+
const session = services.get("agents")?.get(sessionId)?.session;
|
|
785
|
+
const nodes = session?.surface?.nodes;
|
|
786
|
+
if (session === void 0 || typeof session.snapshotEvents !== "function" || !Array.isArray(nodes)) {
|
|
787
|
+
degraded.push("live session view unavailable: load states cannot be determined");
|
|
788
|
+
return empty;
|
|
789
|
+
}
|
|
790
|
+
const events = session.snapshotEvents();
|
|
791
|
+
if (!Array.isArray(events)) {
|
|
792
|
+
degraded.push("snapshotEvents() returned an unexpected shape; cannot read skill loads");
|
|
793
|
+
return empty;
|
|
794
|
+
}
|
|
795
|
+
const surfaceSeqs = /* @__PURE__ */ new Set();
|
|
796
|
+
for (const seq of nodes) if (typeof seq === "number") surfaceSeqs.add(seq);
|
|
797
|
+
const loads = collectLoadRecords(events);
|
|
798
|
+
const resultSeqs = indexToolResultSeqs(events);
|
|
799
|
+
return {
|
|
800
|
+
loads,
|
|
801
|
+
shadowed: shadowedLoadSeqs(loads, resultSeqs, surfaceSeqs),
|
|
802
|
+
pruned: prunedLoadSeqs(loads, resultSeqs, surfaceSeqs, events)
|
|
803
|
+
};
|
|
804
|
+
} catch (error) {
|
|
805
|
+
degraded.push(`event read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
806
|
+
return empty;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function readMcp(services, degraded, disabledServers, disabledTools, agent, presetName, presetPath) {
|
|
810
|
+
const tools = services.get("tools");
|
|
811
|
+
if (tools === void 0) {
|
|
812
|
+
degraded.push("tools service unavailable");
|
|
813
|
+
return [];
|
|
814
|
+
}
|
|
815
|
+
try {
|
|
816
|
+
const names = [];
|
|
817
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
818
|
+
const collect = (scope) => {
|
|
819
|
+
for (const schema of tools.schemas(scope)) {
|
|
820
|
+
if (typeof schema.name !== "string" || !schema.name.startsWith("mcp__")) continue;
|
|
821
|
+
if (!names.includes(schema.name)) {
|
|
822
|
+
names.push(schema.name);
|
|
823
|
+
if (typeof schema.description === "string" && schema.description !== "") descriptions.set(schema.name, schema.description);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
collect(void 0);
|
|
828
|
+
if (agent !== void 0) collect(agent);
|
|
829
|
+
const globalNames = /* @__PURE__ */ new Set();
|
|
830
|
+
for (const schema of tools.schemas()) if (typeof schema.name === "string" && schema.name.startsWith("mcp__")) globalNames.add(schema.name);
|
|
831
|
+
return groupMcpTools(names).map((group) => {
|
|
832
|
+
const enabled = !disabledServers.has(group.server);
|
|
833
|
+
const entries = group.tools.map((tool) => {
|
|
834
|
+
const name = `mcp__${group.server}__${tool}`;
|
|
835
|
+
const description = descriptions.get(name);
|
|
836
|
+
return {
|
|
837
|
+
name,
|
|
838
|
+
label: tool,
|
|
839
|
+
...description === void 0 ? {} : { description },
|
|
840
|
+
enabled: enabled && !disabledTools.has(name)
|
|
841
|
+
};
|
|
842
|
+
});
|
|
843
|
+
const allGlobal = group.tools.every((tool) => globalNames.has(`mcp__${group.server}__${tool}`));
|
|
844
|
+
const source = allGlobal ? "host" : presetName ?? "preset";
|
|
845
|
+
const rawPath = allGlobal ? dshHome() : presetPath;
|
|
846
|
+
return {
|
|
847
|
+
server: group.server,
|
|
848
|
+
tools: entries,
|
|
849
|
+
enabled,
|
|
850
|
+
source,
|
|
851
|
+
...rawPath === void 0 ? {} : { path: displayPath(rawPath) }
|
|
852
|
+
};
|
|
853
|
+
});
|
|
854
|
+
} catch (error) {
|
|
855
|
+
degraded.push(`tool read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
856
|
+
return [];
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function readSystemTools(services, degraded, disabledTools, agent) {
|
|
860
|
+
const tools = services.get("tools");
|
|
861
|
+
if (tools === void 0) {
|
|
862
|
+
if (!degraded.includes("tools service unavailable")) degraded.push("tools service unavailable");
|
|
863
|
+
return [];
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
let reachable;
|
|
867
|
+
if (agent !== void 0) {
|
|
868
|
+
reachable = /* @__PURE__ */ new Set();
|
|
869
|
+
for (const schema of tools.schemas(agent)) if (typeof schema.name === "string") reachable.add(schema.name);
|
|
870
|
+
}
|
|
871
|
+
const byName = /* @__PURE__ */ new Map();
|
|
872
|
+
const collect = (scope) => {
|
|
873
|
+
for (const schema of tools.schemas(scope)) {
|
|
874
|
+
if (typeof schema.name !== "string" || schema.name.startsWith("mcp__") || byName.has(schema.name)) continue;
|
|
875
|
+
const description = typeof schema.description === "string" && schema.description !== "" ? schema.description : void 0;
|
|
876
|
+
byName.set(schema.name, {
|
|
877
|
+
name: schema.name,
|
|
878
|
+
label: schema.name,
|
|
879
|
+
...description === void 0 ? {} : { description },
|
|
880
|
+
enabled: !disabledTools.has(schema.name) && (reachable === void 0 || reachable.has(schema.name)),
|
|
881
|
+
...schema.name === RESERVED_TOOL ? { reserved: true } : {}
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
collect(void 0);
|
|
886
|
+
if (agent !== void 0) collect(agent);
|
|
887
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
888
|
+
} catch (error) {
|
|
889
|
+
degraded.push(`tool read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
890
|
+
return [];
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
async function buildPayload(services, sessionId, capabilityState = EMPTY_STATE, blocked = {}) {
|
|
894
|
+
const degraded = [];
|
|
895
|
+
const disabledSkills = new Set(capabilityState.skills.keys());
|
|
896
|
+
const disabledServers = new Set(capabilityState.mcpServers.keys());
|
|
897
|
+
const disabledTools = new Set(capabilityState.mcpTools.keys());
|
|
898
|
+
const disabledSystem = new Set(capabilityState.systemTools.keys());
|
|
899
|
+
if (sessionId === null) return {
|
|
900
|
+
sessionId: null,
|
|
901
|
+
skills: [],
|
|
902
|
+
mcp: readMcp(services, degraded, disabledServers, disabledTools),
|
|
903
|
+
systemTools: readSystemTools(services, degraded, disabledSystem),
|
|
904
|
+
blocked,
|
|
905
|
+
...degraded.length > 0 ? { degraded } : {}
|
|
906
|
+
};
|
|
907
|
+
const agent = services.get("agents")?.get(sessionId);
|
|
908
|
+
let presetName;
|
|
909
|
+
let presetPath;
|
|
910
|
+
let presetDirs = [];
|
|
911
|
+
if (agent !== void 0) {
|
|
912
|
+
const presetId = services.get("agentPresets")?.composedPreset(agent.ctx);
|
|
913
|
+
try {
|
|
914
|
+
const presets = await services.get("agentPresets")?.list();
|
|
915
|
+
presetDirs = (presets ?? []).filter((p) => typeof p.path === "string" && p.path !== "").map((p) => ({
|
|
916
|
+
key: p.name ?? p.id,
|
|
917
|
+
path: p.path
|
|
918
|
+
}));
|
|
919
|
+
if (presetId !== void 0) {
|
|
920
|
+
const preset = presets?.find((p) => p.id === presetId);
|
|
921
|
+
presetName = preset?.name ?? presetId;
|
|
922
|
+
presetPath = preset?.path;
|
|
923
|
+
}
|
|
924
|
+
} catch {
|
|
925
|
+
presetName = presetId;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
const available = await readAvailable(services, sessionId, degraded, presetDirs);
|
|
929
|
+
const logFacts = readLogFacts(services, sessionId, degraded);
|
|
930
|
+
return {
|
|
931
|
+
sessionId,
|
|
932
|
+
skills: decideStates(available, logFacts.loads, logFacts.shadowed, disabledSkills, logFacts.pruned),
|
|
933
|
+
mcp: readMcp(services, degraded, disabledServers, disabledTools, agent, presetName, presetPath),
|
|
934
|
+
systemTools: readSystemTools(services, degraded, disabledSystem, agent),
|
|
935
|
+
blocked,
|
|
936
|
+
...degraded.length > 0 ? { degraded } : {}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
672
940
|
//#endregion
|
|
673
941
|
//#region src/host/preset-tools.ts
|
|
674
942
|
function requireService(service, message) {
|
|
@@ -694,18 +962,32 @@ function toolSummaries(tools, scope) {
|
|
|
694
962
|
* workspace would contribute. A project skill is marked rather than dropped:
|
|
695
963
|
* hiding it would silently shorten the list, while marking it says plainly
|
|
696
964
|
* that a session opened elsewhere will not see the row.
|
|
965
|
+
*
|
|
966
|
+
* Each row also carries the same provenance the session payload reports:
|
|
967
|
+
* the raw runtime source, the discovery root (parent of the skill's own
|
|
968
|
+
* directory, abbreviated for display), and a `preset:<name>` group when the
|
|
969
|
+
* root sits inside a preset's directory.
|
|
697
970
|
*/
|
|
698
|
-
async function presetSkillRows(skills, scope, disabled, cwd) {
|
|
971
|
+
async function presetSkillRows(skills, scope, disabled, cwd, presetDirs = []) {
|
|
699
972
|
const seen = /* @__PURE__ */ new Map();
|
|
700
973
|
const add = (summaries, project) => {
|
|
701
974
|
for (const summary of summaries) {
|
|
702
975
|
if (typeof summary.name !== "string" || summary.name === "" || seen.has(summary.name)) continue;
|
|
703
976
|
const description = typeof summary.description === "string" ? summary.description : void 0;
|
|
977
|
+
const source = typeof summary.source === "string" ? summary.source : void 0;
|
|
978
|
+
const base = summary.resourceBase;
|
|
979
|
+
const rawPath = base !== null && typeof base === "object" && base.kind === "directory" ? base.path : void 0;
|
|
980
|
+
const rawRoot = typeof rawPath === "string" && rawPath !== "" ? parentDir(rawPath) : "";
|
|
981
|
+
const path = rawRoot === "" ? void 0 : displayPath(rawRoot, cwd);
|
|
982
|
+
const owner = source === "custom" && rawRoot !== "" ? presetDirs.find((p) => rawRoot === p.path || rawRoot.startsWith(`${p.path}/`)) : void 0;
|
|
704
983
|
seen.set(summary.name, {
|
|
705
984
|
name: summary.name,
|
|
706
985
|
...description === void 0 ? {} : { description },
|
|
707
986
|
enabled: !disabled.has(summary.name),
|
|
708
|
-
...project ? { project: true } : {}
|
|
987
|
+
...project ? { project: true } : {},
|
|
988
|
+
...source === void 0 ? {} : { source },
|
|
989
|
+
...path === void 0 ? {} : { path },
|
|
990
|
+
...owner === void 0 ? {} : { group: `preset:${owner.key}` }
|
|
709
991
|
});
|
|
710
992
|
}
|
|
711
993
|
};
|
|
@@ -743,6 +1025,12 @@ function createPresetToolController(ctx, access) {
|
|
|
743
1025
|
const skills = ctx.get("skills");
|
|
744
1026
|
const cwd = process.cwd();
|
|
745
1027
|
const presets = await agentPresets.list();
|
|
1028
|
+
const presetDirs = presets.filter((p) => typeof p.path === "string" && p.path !== "").map((p) => ({
|
|
1029
|
+
key: p.name ?? p.id,
|
|
1030
|
+
path: p.path
|
|
1031
|
+
}));
|
|
1032
|
+
const globalNames = /* @__PURE__ */ new Set();
|
|
1033
|
+
for (const schema of tools.schemas()) if (typeof schema.name === "string" && schema.name.startsWith("mcp__")) globalNames.add(schema.name);
|
|
746
1034
|
return {
|
|
747
1035
|
presets: await Promise.all(presets.map(async (preset) => {
|
|
748
1036
|
let entries = [];
|
|
@@ -758,7 +1046,7 @@ function createPresetToolController(ctx, access) {
|
|
|
758
1046
|
if (skills !== void 0) {
|
|
759
1047
|
const disabledSkills = new Set(stored.presetSkills[preset.id] ?? []);
|
|
760
1048
|
try {
|
|
761
|
-
skillRows = await presetSkillRows(skills, scope, disabledSkills, cwd);
|
|
1049
|
+
skillRows = await presetSkillRows(skills, scope, disabledSkills, cwd, presetDirs);
|
|
762
1050
|
} catch (error) {
|
|
763
1051
|
throw new HttpError(503, `preset "${preset.id}" skills are unavailable: ${errorMessage(error)}`);
|
|
764
1052
|
}
|
|
@@ -776,12 +1064,17 @@ function createPresetToolController(ctx, access) {
|
|
|
776
1064
|
...name === RESERVED_TOOL ? { reserved: true } : {}
|
|
777
1065
|
};
|
|
778
1066
|
};
|
|
1067
|
+
const presetName = preset.name ?? preset.id;
|
|
779
1068
|
const mcp = groupMcpTools(entries.map((entry) => entry.name)).map((group) => {
|
|
780
1069
|
const tools$1 = group.tools.map((tool) => row(`mcp__${group.server}__${tool}`, tool));
|
|
1070
|
+
const allGlobal = group.tools.every((tool) => globalNames.has(`mcp__${group.server}__${tool}`));
|
|
1071
|
+
const rawPath = allGlobal ? dshHome() : preset.path;
|
|
781
1072
|
return {
|
|
782
1073
|
server: group.server,
|
|
783
1074
|
tools: tools$1,
|
|
784
|
-
enabled: tools$1.some((tool) => tool.enabled)
|
|
1075
|
+
enabled: tools$1.some((tool) => tool.enabled),
|
|
1076
|
+
source: allGlobal ? "host" : presetName,
|
|
1077
|
+
...rawPath === void 0 ? {} : { path: displayPath(rawPath) }
|
|
785
1078
|
};
|
|
786
1079
|
});
|
|
787
1080
|
const systemTools = entries.filter((entry) => !entry.name.startsWith("mcp__")).map((entry) => row(entry.name, entry.name));
|
|
@@ -904,212 +1197,21 @@ function isLoopback(req) {
|
|
|
904
1197
|
}
|
|
905
1198
|
|
|
906
1199
|
//#endregion
|
|
907
|
-
//#region src/host/
|
|
908
|
-
const EMPTY_STATE = {
|
|
909
|
-
skills: /* @__PURE__ */ new Map(),
|
|
910
|
-
mcpServers: /* @__PURE__ */ new Map(),
|
|
911
|
-
mcpTools: /* @__PURE__ */ new Map(),
|
|
912
|
-
systemTools: /* @__PURE__ */ new Map(),
|
|
913
|
-
userToggled: /* @__PURE__ */ new Set()
|
|
914
|
-
};
|
|
915
|
-
async function readAvailable(services, sessionId, degraded) {
|
|
916
|
-
const skills = services.get("skills");
|
|
917
|
-
if (skills === void 0) {
|
|
918
|
-
degraded.push("skills service unavailable");
|
|
919
|
-
return [];
|
|
920
|
-
}
|
|
921
|
-
try {
|
|
922
|
-
const agents = services.get("agents");
|
|
923
|
-
if (agents === void 0) {
|
|
924
|
-
degraded.push("agents service unavailable: session skill view cannot be determined");
|
|
925
|
-
return [];
|
|
926
|
-
}
|
|
927
|
-
const agent = agents.get(sessionId);
|
|
928
|
-
if (agent === void 0) {
|
|
929
|
-
degraded.push(`session agent "${sessionId}" unavailable: session skill view cannot be determined`);
|
|
930
|
-
return [];
|
|
931
|
-
}
|
|
932
|
-
const cwd = agent.session?.header?.cwd;
|
|
933
|
-
const list = await skills.list({
|
|
934
|
-
...cwd === void 0 ? {} : { cwd },
|
|
935
|
-
scope: agent
|
|
936
|
-
});
|
|
937
|
-
const out = [];
|
|
938
|
-
const seen = /* @__PURE__ */ new Map();
|
|
939
|
-
for (const item of list) {
|
|
940
|
-
if (typeof item.name !== "string" || item.name === "") continue;
|
|
941
|
-
const masked = item.invocation?.modelInvocable === false;
|
|
942
|
-
const existing = seen.get(item.name);
|
|
943
|
-
if (existing !== void 0) {
|
|
944
|
-
if (masked) existing.masked = true;
|
|
945
|
-
continue;
|
|
946
|
-
}
|
|
947
|
-
const description = typeof item.description === "string" ? item.description : void 0;
|
|
948
|
-
const row = {
|
|
949
|
-
name: item.name,
|
|
950
|
-
...description === void 0 ? {} : { description },
|
|
951
|
-
...masked ? { masked: true } : {}
|
|
952
|
-
};
|
|
953
|
-
seen.set(item.name, row);
|
|
954
|
-
out.push(row);
|
|
955
|
-
}
|
|
956
|
-
return out;
|
|
957
|
-
} catch (error) {
|
|
958
|
-
degraded.push(`skills read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
959
|
-
return [];
|
|
960
|
-
}
|
|
961
|
-
}
|
|
1200
|
+
//#region src/host/open-folder.ts
|
|
962
1201
|
/**
|
|
963
|
-
*
|
|
964
|
-
* agent loop itself reads to assemble the next request.
|
|
1202
|
+
* Open a folder in the system file manager.
|
|
965
1203
|
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
* and a write landing between the two parallel reads forced a whole second
|
|
970
|
-
* round. On a long session (60k events / tens of MB) that meant four full-log
|
|
971
|
-
* clones plus two full replays of synchronous CPU work on every panel open —
|
|
972
|
-
* blocking the Node event loop and freezing the GUI served by the same process.
|
|
973
|
-
*
|
|
974
|
-
* The live session needs none of that: `snapshotEvents()` hands back borrowed
|
|
975
|
-
* references (zero-copy), and `surface.nodes` is maintained incrementally as
|
|
976
|
-
* events land, so "what the model sees right now" is an O(1) membership test.
|
|
977
|
-
* Scanning references for the few `skill` tool calls costs microseconds even
|
|
978
|
-
* on the longest log. Both reads happen in one synchronous tick, so the view
|
|
979
|
-
* is always self-consistent — no cross-read race, no retry loop.
|
|
980
|
-
*
|
|
981
|
-
* A session without a live in-memory view (e.g. restored but not yet attached)
|
|
982
|
-
* degrades honestly instead of paying for a cold-log read the panel never
|
|
983
|
-
* asked for.
|
|
1204
|
+
* Covers macOS (`open`), Windows (`start`), and every freedesktop Linux
|
|
1205
|
+
* desktop (GNOME, KDE, XFCE, …) via `xdg-open`. Server-only Linux distros
|
|
1206
|
+
* that lack a desktop cannot open folders, but the error is graceful.
|
|
984
1207
|
*/
|
|
985
|
-
function
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
};
|
|
991
|
-
try {
|
|
992
|
-
const session = services.get("agents")?.get(sessionId)?.session;
|
|
993
|
-
const nodes = session?.surface?.nodes;
|
|
994
|
-
if (session === void 0 || typeof session.snapshotEvents !== "function" || !Array.isArray(nodes)) {
|
|
995
|
-
degraded.push("live session view unavailable: load states cannot be determined");
|
|
996
|
-
return empty;
|
|
997
|
-
}
|
|
998
|
-
const events = session.snapshotEvents();
|
|
999
|
-
if (!Array.isArray(events)) {
|
|
1000
|
-
degraded.push("snapshotEvents() returned an unexpected shape; cannot read skill loads");
|
|
1001
|
-
return empty;
|
|
1002
|
-
}
|
|
1003
|
-
const surfaceSeqs = /* @__PURE__ */ new Set();
|
|
1004
|
-
for (const seq of nodes) if (typeof seq === "number") surfaceSeqs.add(seq);
|
|
1005
|
-
const loads = collectLoadRecords(events);
|
|
1006
|
-
const resultSeqs = indexToolResultSeqs(events);
|
|
1007
|
-
return {
|
|
1008
|
-
loads,
|
|
1009
|
-
shadowed: shadowedLoadSeqs(loads, resultSeqs, surfaceSeqs),
|
|
1010
|
-
pruned: prunedLoadSeqs(loads, resultSeqs, surfaceSeqs, events)
|
|
1011
|
-
};
|
|
1012
|
-
} catch (error) {
|
|
1013
|
-
degraded.push(`event read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1014
|
-
return empty;
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
function readMcp(services, degraded, disabledServers, disabledTools) {
|
|
1018
|
-
const tools = services.get("tools");
|
|
1019
|
-
if (tools === void 0) {
|
|
1020
|
-
degraded.push("tools service unavailable");
|
|
1021
|
-
return [];
|
|
1022
|
-
}
|
|
1023
|
-
try {
|
|
1024
|
-
const names = [];
|
|
1025
|
-
const descriptions = /* @__PURE__ */ new Map();
|
|
1026
|
-
for (const schema of tools.schemas()) {
|
|
1027
|
-
if (typeof schema.name !== "string") continue;
|
|
1028
|
-
names.push(schema.name);
|
|
1029
|
-
if (typeof schema.description === "string" && schema.description !== "") descriptions.set(schema.name, schema.description);
|
|
1030
|
-
}
|
|
1031
|
-
return groupMcpTools(names).map((group) => {
|
|
1032
|
-
const enabled = !disabledServers.has(group.server);
|
|
1033
|
-
const entries = group.tools.map((tool) => {
|
|
1034
|
-
const name = `mcp__${group.server}__${tool}`;
|
|
1035
|
-
const description = descriptions.get(name);
|
|
1036
|
-
return {
|
|
1037
|
-
name,
|
|
1038
|
-
label: tool,
|
|
1039
|
-
...description === void 0 ? {} : { description },
|
|
1040
|
-
enabled: enabled && !disabledTools.has(name)
|
|
1041
|
-
};
|
|
1042
|
-
});
|
|
1043
|
-
return {
|
|
1044
|
-
server: group.server,
|
|
1045
|
-
tools: entries,
|
|
1046
|
-
enabled
|
|
1047
|
-
};
|
|
1208
|
+
function openFolder(path) {
|
|
1209
|
+
return new Promise((resolve, reject) => {
|
|
1210
|
+
exec(`${process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"} "${path}"`, (error) => {
|
|
1211
|
+
if (error) reject(/* @__PURE__ */ new Error(`failed to open folder: ${error.message}`));
|
|
1212
|
+
else resolve();
|
|
1048
1213
|
});
|
|
1049
|
-
}
|
|
1050
|
-
degraded.push(`tool read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1051
|
-
return [];
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
function readSystemTools(services, degraded, disabledTools, agent) {
|
|
1055
|
-
const tools = services.get("tools");
|
|
1056
|
-
if (tools === void 0) {
|
|
1057
|
-
if (!degraded.includes("tools service unavailable")) degraded.push("tools service unavailable");
|
|
1058
|
-
return [];
|
|
1059
|
-
}
|
|
1060
|
-
try {
|
|
1061
|
-
let reachable;
|
|
1062
|
-
if (agent !== void 0) {
|
|
1063
|
-
reachable = /* @__PURE__ */ new Set();
|
|
1064
|
-
for (const schema of tools.schemas(agent)) if (typeof schema.name === "string") reachable.add(schema.name);
|
|
1065
|
-
}
|
|
1066
|
-
const byName = /* @__PURE__ */ new Map();
|
|
1067
|
-
const collect = (scope) => {
|
|
1068
|
-
for (const schema of tools.schemas(scope)) {
|
|
1069
|
-
if (typeof schema.name !== "string" || schema.name.startsWith("mcp__") || byName.has(schema.name)) continue;
|
|
1070
|
-
const description = typeof schema.description === "string" && schema.description !== "" ? schema.description : void 0;
|
|
1071
|
-
byName.set(schema.name, {
|
|
1072
|
-
name: schema.name,
|
|
1073
|
-
label: schema.name,
|
|
1074
|
-
...description === void 0 ? {} : { description },
|
|
1075
|
-
enabled: !disabledTools.has(schema.name) && (reachable === void 0 || reachable.has(schema.name)),
|
|
1076
|
-
...schema.name === RESERVED_TOOL ? { reserved: true } : {}
|
|
1077
|
-
});
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
|
-
collect(void 0);
|
|
1081
|
-
if (agent !== void 0) collect(agent);
|
|
1082
|
-
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
1083
|
-
} catch (error) {
|
|
1084
|
-
degraded.push(`tool read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1085
|
-
return [];
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
async function buildPayload(services, sessionId, capabilityState = EMPTY_STATE, blocked = {}) {
|
|
1089
|
-
const degraded = [];
|
|
1090
|
-
const disabledSkills = new Set(capabilityState.skills.keys());
|
|
1091
|
-
const disabledServers = new Set(capabilityState.mcpServers.keys());
|
|
1092
|
-
const disabledTools = new Set(capabilityState.mcpTools.keys());
|
|
1093
|
-
const disabledSystem = new Set(capabilityState.systemTools.keys());
|
|
1094
|
-
if (sessionId === null) return {
|
|
1095
|
-
sessionId: null,
|
|
1096
|
-
skills: [],
|
|
1097
|
-
mcp: readMcp(services, degraded, disabledServers, disabledTools),
|
|
1098
|
-
systemTools: readSystemTools(services, degraded, disabledSystem),
|
|
1099
|
-
blocked,
|
|
1100
|
-
...degraded.length > 0 ? { degraded } : {}
|
|
1101
|
-
};
|
|
1102
|
-
const agent = services.get("agents")?.get(sessionId);
|
|
1103
|
-
const available = await readAvailable(services, sessionId, degraded);
|
|
1104
|
-
const logFacts = readLogFacts(services, sessionId, degraded);
|
|
1105
|
-
return {
|
|
1106
|
-
sessionId,
|
|
1107
|
-
skills: decideStates(available, logFacts.loads, logFacts.shadowed, disabledSkills, logFacts.pruned),
|
|
1108
|
-
mcp: readMcp(services, degraded, disabledServers, disabledTools),
|
|
1109
|
-
systemTools: readSystemTools(services, degraded, disabledSystem, agent),
|
|
1110
|
-
blocked,
|
|
1111
|
-
...degraded.length > 0 ? { degraded } : {}
|
|
1112
|
-
};
|
|
1214
|
+
});
|
|
1113
1215
|
}
|
|
1114
1216
|
|
|
1115
1217
|
//#endregion
|
|
@@ -1253,6 +1355,74 @@ function createRouteHandler(services, capabilities, stats, blockedCounts, preset
|
|
|
1253
1355
|
}, true);
|
|
1254
1356
|
return;
|
|
1255
1357
|
}
|
|
1358
|
+
if (url.pathname === `${ROUTE}/open-folder`) {
|
|
1359
|
+
if (req.method !== "POST") {
|
|
1360
|
+
res.writeHead(405, {
|
|
1361
|
+
allow: "POST",
|
|
1362
|
+
"content-type": "text/plain; charset=utf-8"
|
|
1363
|
+
});
|
|
1364
|
+
res.end("method not allowed");
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
if (!validatePresetContentType(req, res)) return;
|
|
1368
|
+
const body = await readRequestBody(req);
|
|
1369
|
+
if (body === null || typeof body !== "object") {
|
|
1370
|
+
json(res, 400, { error: "invalid request body" });
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
const record = body;
|
|
1374
|
+
if (typeof record.source !== "string" || record.source === "") {
|
|
1375
|
+
json(res, 400, { error: "source is required" });
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const source = record.source;
|
|
1379
|
+
const sessionId$1 = typeof record.sessionId === "string" ? record.sessionId : null;
|
|
1380
|
+
let folderPath;
|
|
1381
|
+
if (sessionId$1 !== null) try {
|
|
1382
|
+
const skills = services.get("skills");
|
|
1383
|
+
const agent = services.get("agents")?.get(sessionId$1);
|
|
1384
|
+
if (skills !== void 0 && agent !== void 0) {
|
|
1385
|
+
const cwd = agent.session?.header?.cwd;
|
|
1386
|
+
const list = await skills.list({
|
|
1387
|
+
...cwd === void 0 ? {} : { cwd },
|
|
1388
|
+
scope: agent
|
|
1389
|
+
});
|
|
1390
|
+
for (const item of list) {
|
|
1391
|
+
if (item.source !== source) continue;
|
|
1392
|
+
const base = item.resourceBase;
|
|
1393
|
+
if (base !== null && typeof base === "object" && base.kind === "directory") {
|
|
1394
|
+
const path = base.path;
|
|
1395
|
+
if (typeof path === "string" && path !== "") {
|
|
1396
|
+
folderPath = parentDir(path);
|
|
1397
|
+
break;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
} catch {}
|
|
1403
|
+
if (folderPath === void 0 && (source === "user-dsh" || source === "user-agents")) {
|
|
1404
|
+
const dshHome$1 = process.env["DSH_HOME"] ?? (process.env["HOME"] !== void 0 ? `${process.env["HOME"]}/.dsh` : void 0);
|
|
1405
|
+
if (dshHome$1 !== void 0) folderPath = source === "user-dsh" ? `${dshHome$1}/skills` : `${process.env["DSH_AGENTS_HOME"] ?? `${process.env["HOME"]}/.agents`}/skills`;
|
|
1406
|
+
} else if (folderPath === void 0 && (source === "project-dsh" || source === "project-agents")) {
|
|
1407
|
+
const cwd = (sessionId$1 === null ? void 0 : services.get("agents")?.get(sessionId$1))?.session?.header?.cwd ?? (sessionId$1 === null ? process.cwd() : void 0);
|
|
1408
|
+
if (cwd !== void 0) folderPath = source === "project-dsh" ? `${cwd}/.dsh/skills` : `${cwd}/.agents/skills`;
|
|
1409
|
+
} else if (folderPath === void 0 && source === "host") folderPath = process.env["DSH_HOME"] ?? (process.env["HOME"] !== void 0 ? `${process.env["HOME"]}/.dsh` : void 0);
|
|
1410
|
+
else if (folderPath === void 0 && source !== "bundled" && source !== "runtime") try {
|
|
1411
|
+
const preset = (await services.get("agentPresets")?.list())?.find((p) => p.id === source || p.name === source);
|
|
1412
|
+
if (preset !== void 0) folderPath = preset.path;
|
|
1413
|
+
} catch {}
|
|
1414
|
+
if (folderPath === void 0) {
|
|
1415
|
+
json(res, 404, { error: `cannot open folder for source "${source}"` });
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
try {
|
|
1419
|
+
await openFolder(folderPath);
|
|
1420
|
+
json(res, 200, { ok: true });
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
json(res, 500, { error: `failed to open folder: ${errorMessage(error)}` });
|
|
1423
|
+
}
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1256
1426
|
if (url.pathname !== ROUTE && url.pathname !== "/") {
|
|
1257
1427
|
res.writeHead(404, {
|
|
1258
1428
|
"content-type": "text/plain; charset=utf-8",
|
|
@@ -1367,6 +1537,36 @@ function createSessionOverrideStore(access) {
|
|
|
1367
1537
|
};
|
|
1368
1538
|
}
|
|
1369
1539
|
|
|
1540
|
+
//#endregion
|
|
1541
|
+
//#region src/host/settings-scope.ts
|
|
1542
|
+
const TOOLKIT_SETTINGS_NAMESPACE = "capability-panel";
|
|
1543
|
+
const SessionOverrideSchema = z.object({
|
|
1544
|
+
skills: z.dict(z.boolean()).default({}),
|
|
1545
|
+
mcpServers: z.dict(z.boolean()).default({}),
|
|
1546
|
+
mcpTools: z.dict(z.boolean()).default({}),
|
|
1547
|
+
systemTools: z.dict(z.boolean()).default({})
|
|
1548
|
+
});
|
|
1549
|
+
const ToolkitSettingsSchema = z.object({
|
|
1550
|
+
presets: z.dict(z.array(z.string())).default({}),
|
|
1551
|
+
presetSkills: z.dict(z.array(z.string())).default({}),
|
|
1552
|
+
sessions: z.dict(SessionOverrideSchema).default({})
|
|
1553
|
+
});
|
|
1554
|
+
function createToolkitSettingsAccess(ctx) {
|
|
1555
|
+
let scope;
|
|
1556
|
+
let writeQueue = Promise.resolve();
|
|
1557
|
+
return {
|
|
1558
|
+
scope() {
|
|
1559
|
+
if (scope === void 0) scope = ctx.get("settings")?.register(TOOLKIT_SETTINGS_NAMESPACE, ToolkitSettingsSchema, { applies: "live" });
|
|
1560
|
+
return scope;
|
|
1561
|
+
},
|
|
1562
|
+
serialize(work) {
|
|
1563
|
+
const next = writeQueue.then(work, work);
|
|
1564
|
+
writeQueue = next.then(() => void 0, () => void 0);
|
|
1565
|
+
return next;
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1370
1570
|
//#endregion
|
|
1371
1571
|
//#region src/host/stats-store.ts
|
|
1372
1572
|
function statsFilePath(environment = process.env, home = homedir()) {
|