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/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,
|
|
@@ -359,33 +360,60 @@ function createCapabilityController(ctx, appendStats, blockedCounts) {
|
|
|
359
360
|
}
|
|
360
361
|
if (maskedAny) ensurePromptNote(agent, ensureState());
|
|
361
362
|
};
|
|
363
|
+
const mutationQueues = /* @__PURE__ */ new Map();
|
|
364
|
+
const enqueue = (sessionId, op) => {
|
|
365
|
+
const next = (mutationQueues.get(sessionId) ?? Promise.resolve()).then(op, op);
|
|
366
|
+
mutationQueues.set(sessionId, next.then(() => void 0, () => void 0));
|
|
367
|
+
return next;
|
|
368
|
+
};
|
|
369
|
+
const restoreImpl = async (sessionId, overrides) => {
|
|
370
|
+
const groups = [
|
|
371
|
+
["skill", overrides.skills],
|
|
372
|
+
["mcp-server", overrides.mcpServers],
|
|
373
|
+
["mcp-tool", overrides.mcpTools],
|
|
374
|
+
["system-tool", overrides.systemTools]
|
|
375
|
+
];
|
|
376
|
+
for (const [kind, positions] of groups) for (const [name, enabled] of Object.entries(positions)) {
|
|
377
|
+
if (states.get(sessionId)?.userToggled.has(`${kind}:${name}`) === true) continue;
|
|
378
|
+
try {
|
|
379
|
+
if (kind === "skill") await setSkill(sessionId, name, enabled);
|
|
380
|
+
else if (kind === "mcp-server") setServer(sessionId, name, enabled);
|
|
381
|
+
else setTool(sessionId, name, enabled, kind === "system-tool");
|
|
382
|
+
} catch {}
|
|
383
|
+
}
|
|
384
|
+
const st = states.get(sessionId);
|
|
385
|
+
if (st !== void 0 && st.userToggled.size === 0 && st.skills.size === 0 && st.mcpServers.size === 0 && st.mcpTools.size === 0 && st.systemTools.size === 0) {
|
|
386
|
+
st.noteDispose?.();
|
|
387
|
+
delete st.noteDispose;
|
|
388
|
+
states.delete(sessionId);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
362
391
|
return {
|
|
363
392
|
states,
|
|
364
393
|
state: (sessionId) => states.get(sessionId),
|
|
365
|
-
seed,
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
["skill", overrides.skills],
|
|
369
|
-
["mcp-server", overrides.mcpServers],
|
|
370
|
-
["mcp-tool", overrides.mcpTools],
|
|
371
|
-
["system-tool", overrides.systemTools]
|
|
372
|
-
];
|
|
373
|
-
for (const [kind, positions] of groups) for (const [name, enabled] of Object.entries(positions)) {
|
|
374
|
-
if (states.get(sessionId)?.userToggled.has(`${kind}:${name}`) === true) continue;
|
|
375
|
-
try {
|
|
376
|
-
if (kind === "skill") await setSkill(sessionId, name, enabled);
|
|
377
|
-
else if (kind === "mcp-server") setServer(sessionId, name, enabled);
|
|
378
|
-
else setTool(sessionId, name, enabled, kind === "system-tool");
|
|
379
|
-
} catch {}
|
|
380
|
-
}
|
|
394
|
+
seed: (sessionId, defaults) => enqueue(sessionId, () => seed(sessionId, defaults)),
|
|
395
|
+
restore: (sessionId, overrides) => enqueue(sessionId, () => restoreImpl(sessionId, overrides)),
|
|
396
|
+
reseed: (sessionId, defaults, overrides) => enqueue(sessionId, async () => {
|
|
381
397
|
const st = states.get(sessionId);
|
|
382
|
-
if (st !== void 0
|
|
398
|
+
if (st !== void 0) {
|
|
399
|
+
for (const map of [
|
|
400
|
+
st.systemTools,
|
|
401
|
+
st.mcpServers,
|
|
402
|
+
st.mcpTools,
|
|
403
|
+
st.skills
|
|
404
|
+
]) {
|
|
405
|
+
for (const dispose of map.values()) dispose();
|
|
406
|
+
map.clear();
|
|
407
|
+
}
|
|
383
408
|
st.noteDispose?.();
|
|
384
409
|
delete st.noteDispose;
|
|
410
|
+
st.userToggled.clear();
|
|
385
411
|
states.delete(sessionId);
|
|
386
412
|
}
|
|
387
|
-
|
|
388
|
-
|
|
413
|
+
if (defaults.tools.length > 0 || defaults.skills.length > 0) await seed(sessionId, defaults);
|
|
414
|
+
if (overrides !== void 0) await restoreImpl(sessionId, overrides);
|
|
415
|
+
}),
|
|
416
|
+
set: (sessionId, kind, name, enabled) => enqueue(sessionId, async () => {
|
|
389
417
|
stateFor(sessionId).userToggled.add(`${kind}:${name}`);
|
|
390
418
|
if (kind === "skill") await setSkill(sessionId, name, enabled);
|
|
391
419
|
else if (kind === "mcp-server") setServer(sessionId, name, enabled);
|
|
@@ -396,7 +424,7 @@ function createCapabilityController(ctx, appendStats, blockedCounts) {
|
|
|
396
424
|
kind: enabled ? "enable" : "disable",
|
|
397
425
|
name: `${kind}:${name}`
|
|
398
426
|
});
|
|
399
|
-
}
|
|
427
|
+
})
|
|
400
428
|
};
|
|
401
429
|
}
|
|
402
430
|
|
|
@@ -449,36 +477,27 @@ function registerPresetEnforcement(ctx, capabilities, presetTools, sessionOverri
|
|
|
449
477
|
return;
|
|
450
478
|
}
|
|
451
479
|
});
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
+
* A preset switch lands AFTER creation (the session header records the
|
|
482
|
+
* composing preset at creation, and the picker's select() recomposes the
|
|
483
|
+
* blank session later): the first agent/created seeded the ORIGINAL
|
|
484
|
+
* preset's defaults, so the new preset's defaults must re-seed on top of a
|
|
485
|
+
* clean slate. Session overrides replay last — the user's own switches in
|
|
486
|
+
* this session outrank either preset's defaults.
|
|
487
|
+
*/
|
|
488
|
+
ctx.on("agent-preset/selected", (sessionId, presetId) => {
|
|
489
|
+
try {
|
|
490
|
+
if (typeof sessionId !== "string" || typeof presetId !== "string") return void 0;
|
|
491
|
+
const defaults = presetTools.defaultsFor(presetId) ?? {
|
|
492
|
+
tools: [],
|
|
493
|
+
skills: []
|
|
494
|
+
};
|
|
495
|
+
const overrides = sessionOverrides.overridesFor(sessionId);
|
|
496
|
+
return capabilities.reseed(sessionId, defaults, overrides);
|
|
497
|
+
} catch {
|
|
498
|
+
return;
|
|
480
499
|
}
|
|
481
|
-
};
|
|
500
|
+
});
|
|
482
501
|
}
|
|
483
502
|
|
|
484
503
|
//#endregion
|
|
@@ -631,7 +650,7 @@ function decideStates(available, loads, shadowedSeqs, disabledSkills = /* @__PUR
|
|
|
631
650
|
if (bucket === void 0) byName.set(record.skillName, [record]);
|
|
632
651
|
else bucket.push(record);
|
|
633
652
|
}
|
|
634
|
-
return available.map(({ name, description, masked }) => {
|
|
653
|
+
return available.map(({ name, description, masked, source, provider, path, group }) => {
|
|
635
654
|
const records = byName.get(name) ?? [];
|
|
636
655
|
let state = "unloaded";
|
|
637
656
|
if (records.length > 0) {
|
|
@@ -644,7 +663,11 @@ function decideStates(available, loads, shadowedSeqs, disabledSkills = /* @__PUR
|
|
|
644
663
|
...description === void 0 ? {} : { description },
|
|
645
664
|
state,
|
|
646
665
|
enabled: !disabledSkills.has(name) && masked !== true,
|
|
647
|
-
loadCount: records.length
|
|
666
|
+
loadCount: records.length,
|
|
667
|
+
source,
|
|
668
|
+
provider,
|
|
669
|
+
...path === void 0 ? {} : { path },
|
|
670
|
+
...group === void 0 ? {} : { group }
|
|
648
671
|
};
|
|
649
672
|
});
|
|
650
673
|
}
|
|
@@ -669,6 +692,299 @@ function groupMcpTools(toolNames) {
|
|
|
669
692
|
})).sort((a, b) => a.server.localeCompare(b.server));
|
|
670
693
|
}
|
|
671
694
|
|
|
695
|
+
//#endregion
|
|
696
|
+
//#region src/host/catalog.ts
|
|
697
|
+
/** Coerce a skill summary field to a string, falling back when the runtime shape is wrong. */
|
|
698
|
+
function coerceString(value, fallback) {
|
|
699
|
+
return typeof value === "string" ? value : fallback;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* The source ROOT a skill was discovered under. The skills service reports
|
|
703
|
+
* each skill's own directory as resourceBase (`<root>/<name>`), so the group
|
|
704
|
+
* folder is its parent.
|
|
705
|
+
*/
|
|
706
|
+
function parentDir(path) {
|
|
707
|
+
const trimmed = path.replace(/[\\/]+$/, "");
|
|
708
|
+
if (trimmed === "") return "/";
|
|
709
|
+
const slash = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
710
|
+
return slash > 0 ? trimmed.slice(0, slash) : trimmed;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Abbreviate a host path for display: the session cwd becomes a relative
|
|
714
|
+
* path, the user's home becomes `~`. Everything else stays absolute.
|
|
715
|
+
*/
|
|
716
|
+
function displayPath(path, cwd) {
|
|
717
|
+
if (cwd !== void 0 && cwd !== "" && path.startsWith(`${cwd}/`)) return path.slice(cwd.length + 1);
|
|
718
|
+
const home = process.env["HOME"];
|
|
719
|
+
if (home !== void 0 && home !== "" && path.startsWith(home)) return `~${path.slice(home.length)}`;
|
|
720
|
+
return path;
|
|
721
|
+
}
|
|
722
|
+
/** The DSH home, matching the runtime's own resolution order. */
|
|
723
|
+
function dshHome() {
|
|
724
|
+
return process.env["DSH_HOME"] ?? (process.env["HOME"] !== void 0 ? `${process.env["HOME"]}/.dsh` : void 0);
|
|
725
|
+
}
|
|
726
|
+
const EMPTY_STATE = {
|
|
727
|
+
skills: /* @__PURE__ */ new Map(),
|
|
728
|
+
mcpServers: /* @__PURE__ */ new Map(),
|
|
729
|
+
mcpTools: /* @__PURE__ */ new Map(),
|
|
730
|
+
systemTools: /* @__PURE__ */ new Map(),
|
|
731
|
+
userToggled: /* @__PURE__ */ new Set()
|
|
732
|
+
};
|
|
733
|
+
async function readAvailable(services, sessionId, degraded, presetDirs = []) {
|
|
734
|
+
const skills = services.get("skills");
|
|
735
|
+
if (skills === void 0) {
|
|
736
|
+
degraded.push("skills service unavailable");
|
|
737
|
+
return [];
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
const agents = services.get("agents");
|
|
741
|
+
if (agents === void 0) {
|
|
742
|
+
degraded.push("agents service unavailable: session skill view cannot be determined");
|
|
743
|
+
return [];
|
|
744
|
+
}
|
|
745
|
+
const agent = agents.get(sessionId);
|
|
746
|
+
if (agent === void 0) {
|
|
747
|
+
degraded.push(`session agent "${sessionId}" unavailable: session skill view cannot be determined`);
|
|
748
|
+
return [];
|
|
749
|
+
}
|
|
750
|
+
const cwd = agent.session?.header?.cwd;
|
|
751
|
+
const list = await skills.list({
|
|
752
|
+
...cwd === void 0 ? {} : { cwd },
|
|
753
|
+
scope: agent
|
|
754
|
+
});
|
|
755
|
+
const out = [];
|
|
756
|
+
const seen = /* @__PURE__ */ new Map();
|
|
757
|
+
const groupFor = (source, rawRoot) => {
|
|
758
|
+
if (source !== "custom" || rawRoot === "") return void 0;
|
|
759
|
+
const owner = presetDirs.find((p) => rawRoot === p.path || rawRoot.startsWith(`${p.path}/`));
|
|
760
|
+
return owner === void 0 ? void 0 : `preset:${owner.key}`;
|
|
761
|
+
};
|
|
762
|
+
for (const item of list) {
|
|
763
|
+
if (typeof item.name !== "string" || item.name === "") continue;
|
|
764
|
+
const masked = item.invocation?.modelInvocable === false;
|
|
765
|
+
const source = coerceString(item.source, "unknown");
|
|
766
|
+
const provider = coerceString(item.provider, "unknown");
|
|
767
|
+
const base = item.resourceBase;
|
|
768
|
+
const rawPath = base !== null && typeof base === "object" && base.kind === "directory" ? coerceString(base.path, "") : "";
|
|
769
|
+
const rawRoot = rawPath === "" ? "" : parentDir(rawPath);
|
|
770
|
+
const path = rawRoot === "" ? "" : displayPath(rawRoot, cwd);
|
|
771
|
+
const group = groupFor(source, rawRoot);
|
|
772
|
+
const existing = seen.get(item.name);
|
|
773
|
+
if (existing !== void 0) {
|
|
774
|
+
if (masked) existing.masked = true;
|
|
775
|
+
if (existing.provider === "capability-panel" && provider !== "capability-panel") {
|
|
776
|
+
existing.source = source;
|
|
777
|
+
existing.provider = provider;
|
|
778
|
+
if (path !== "") existing.path = path;
|
|
779
|
+
if (group !== void 0) existing.group = group;
|
|
780
|
+
}
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
const description = typeof item.description === "string" ? item.description : void 0;
|
|
784
|
+
const row = {
|
|
785
|
+
name: item.name,
|
|
786
|
+
...description === void 0 ? {} : { description },
|
|
787
|
+
...masked ? { masked: true } : {},
|
|
788
|
+
source,
|
|
789
|
+
provider,
|
|
790
|
+
...path === "" ? {} : { path },
|
|
791
|
+
...group === void 0 ? {} : { group }
|
|
792
|
+
};
|
|
793
|
+
seen.set(item.name, row);
|
|
794
|
+
out.push(row);
|
|
795
|
+
}
|
|
796
|
+
return out;
|
|
797
|
+
} catch (error) {
|
|
798
|
+
degraded.push(`skills read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
799
|
+
return [];
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Read load facts off the LIVE session's in-memory log — the same object the
|
|
804
|
+
* agent loop itself reads to assemble the next request.
|
|
805
|
+
*
|
|
806
|
+
* This used to go through `sessionQuery.readSession` + `listEvents`, which are
|
|
807
|
+
* built for cross-session/cold reads: each call structuredClones the ENTIRE
|
|
808
|
+
* event log, `readSession` additionally replay-validates it via Session.create,
|
|
809
|
+
* and a write landing between the two parallel reads forced a whole second
|
|
810
|
+
* round. On a long session (60k events / tens of MB) that meant four full-log
|
|
811
|
+
* clones plus two full replays of synchronous CPU work on every panel open —
|
|
812
|
+
* blocking the Node event loop and freezing the GUI served by the same process.
|
|
813
|
+
*
|
|
814
|
+
* The live session needs none of that: `snapshotEvents()` hands back borrowed
|
|
815
|
+
* references (zero-copy), and `surface.nodes` is maintained incrementally as
|
|
816
|
+
* events land, so "what the model sees right now" is an O(1) membership test.
|
|
817
|
+
* Scanning references for the few `skill` tool calls costs microseconds even
|
|
818
|
+
* on the longest log. Both reads happen in one synchronous tick, so the view
|
|
819
|
+
* is always self-consistent — no cross-read race, no retry loop.
|
|
820
|
+
*
|
|
821
|
+
* A session without a live in-memory view (e.g. restored but not yet attached)
|
|
822
|
+
* degrades honestly instead of paying for a cold-log read the panel never
|
|
823
|
+
* asked for.
|
|
824
|
+
*/
|
|
825
|
+
function readLogFacts(services, sessionId, degraded) {
|
|
826
|
+
const empty = {
|
|
827
|
+
loads: [],
|
|
828
|
+
shadowed: /* @__PURE__ */ new Set(),
|
|
829
|
+
pruned: /* @__PURE__ */ new Set()
|
|
830
|
+
};
|
|
831
|
+
try {
|
|
832
|
+
const session = services.get("agents")?.get(sessionId)?.session;
|
|
833
|
+
const nodes = session?.surface?.nodes;
|
|
834
|
+
if (session === void 0 || typeof session.snapshotEvents !== "function" || !Array.isArray(nodes)) {
|
|
835
|
+
degraded.push("live session view unavailable: load states cannot be determined");
|
|
836
|
+
return empty;
|
|
837
|
+
}
|
|
838
|
+
const events = session.snapshotEvents();
|
|
839
|
+
if (!Array.isArray(events)) {
|
|
840
|
+
degraded.push("snapshotEvents() returned an unexpected shape; cannot read skill loads");
|
|
841
|
+
return empty;
|
|
842
|
+
}
|
|
843
|
+
const surfaceSeqs = /* @__PURE__ */ new Set();
|
|
844
|
+
for (const seq of nodes) if (typeof seq === "number") surfaceSeqs.add(seq);
|
|
845
|
+
const loads = collectLoadRecords(events);
|
|
846
|
+
const resultSeqs = indexToolResultSeqs(events);
|
|
847
|
+
return {
|
|
848
|
+
loads,
|
|
849
|
+
shadowed: shadowedLoadSeqs(loads, resultSeqs, surfaceSeqs),
|
|
850
|
+
pruned: prunedLoadSeqs(loads, resultSeqs, surfaceSeqs, events)
|
|
851
|
+
};
|
|
852
|
+
} catch (error) {
|
|
853
|
+
degraded.push(`event read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
854
|
+
return empty;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function readMcp(services, degraded, disabledServers, disabledTools, agent, presetName, presetPath) {
|
|
858
|
+
const tools = services.get("tools");
|
|
859
|
+
if (tools === void 0) {
|
|
860
|
+
degraded.push("tools service unavailable");
|
|
861
|
+
return [];
|
|
862
|
+
}
|
|
863
|
+
try {
|
|
864
|
+
const names = [];
|
|
865
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
866
|
+
const collect = (scope) => {
|
|
867
|
+
for (const schema of tools.schemas(scope)) {
|
|
868
|
+
if (typeof schema.name !== "string" || !schema.name.startsWith("mcp__")) continue;
|
|
869
|
+
if (!names.includes(schema.name)) {
|
|
870
|
+
names.push(schema.name);
|
|
871
|
+
if (typeof schema.description === "string" && schema.description !== "") descriptions.set(schema.name, schema.description);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
collect(void 0);
|
|
876
|
+
if (agent !== void 0) collect(agent);
|
|
877
|
+
const globalNames = /* @__PURE__ */ new Set();
|
|
878
|
+
for (const schema of tools.schemas()) if (typeof schema.name === "string" && schema.name.startsWith("mcp__")) globalNames.add(schema.name);
|
|
879
|
+
return groupMcpTools(names).map((group) => {
|
|
880
|
+
const enabled = !disabledServers.has(group.server);
|
|
881
|
+
const entries = group.tools.map((tool) => {
|
|
882
|
+
const name = `mcp__${group.server}__${tool}`;
|
|
883
|
+
const description = descriptions.get(name);
|
|
884
|
+
return {
|
|
885
|
+
name,
|
|
886
|
+
label: tool,
|
|
887
|
+
...description === void 0 ? {} : { description },
|
|
888
|
+
enabled: enabled && !disabledTools.has(name)
|
|
889
|
+
};
|
|
890
|
+
});
|
|
891
|
+
const allGlobal = group.tools.every((tool) => globalNames.has(`mcp__${group.server}__${tool}`));
|
|
892
|
+
const source = allGlobal ? "host" : presetName ?? "preset";
|
|
893
|
+
const rawPath = allGlobal ? dshHome() : presetPath;
|
|
894
|
+
return {
|
|
895
|
+
server: group.server,
|
|
896
|
+
tools: entries,
|
|
897
|
+
enabled,
|
|
898
|
+
source,
|
|
899
|
+
...rawPath === void 0 ? {} : { path: displayPath(rawPath) }
|
|
900
|
+
};
|
|
901
|
+
});
|
|
902
|
+
} catch (error) {
|
|
903
|
+
degraded.push(`tool read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
904
|
+
return [];
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function readSystemTools(services, degraded, disabledTools, agent) {
|
|
908
|
+
const tools = services.get("tools");
|
|
909
|
+
if (tools === void 0) {
|
|
910
|
+
if (!degraded.includes("tools service unavailable")) degraded.push("tools service unavailable");
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
let reachable;
|
|
915
|
+
if (agent !== void 0) {
|
|
916
|
+
reachable = /* @__PURE__ */ new Set();
|
|
917
|
+
for (const schema of tools.schemas(agent)) if (typeof schema.name === "string") reachable.add(schema.name);
|
|
918
|
+
}
|
|
919
|
+
const byName = /* @__PURE__ */ new Map();
|
|
920
|
+
const collect = (scope) => {
|
|
921
|
+
for (const schema of tools.schemas(scope)) {
|
|
922
|
+
if (typeof schema.name !== "string" || schema.name.startsWith("mcp__") || byName.has(schema.name)) continue;
|
|
923
|
+
const description = typeof schema.description === "string" && schema.description !== "" ? schema.description : void 0;
|
|
924
|
+
byName.set(schema.name, {
|
|
925
|
+
name: schema.name,
|
|
926
|
+
label: schema.name,
|
|
927
|
+
...description === void 0 ? {} : { description },
|
|
928
|
+
enabled: !disabledTools.has(schema.name) && (reachable === void 0 || reachable.has(schema.name)),
|
|
929
|
+
...schema.name === RESERVED_TOOL ? { reserved: true } : {}
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
collect(void 0);
|
|
934
|
+
if (agent !== void 0) collect(agent);
|
|
935
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
936
|
+
} catch (error) {
|
|
937
|
+
degraded.push(`tool read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
938
|
+
return [];
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
async function buildPayload(services, sessionId, capabilityState = EMPTY_STATE, blocked = {}) {
|
|
942
|
+
const degraded = [];
|
|
943
|
+
const disabledSkills = new Set(capabilityState.skills.keys());
|
|
944
|
+
const disabledServers = new Set(capabilityState.mcpServers.keys());
|
|
945
|
+
const disabledTools = new Set(capabilityState.mcpTools.keys());
|
|
946
|
+
const disabledSystem = new Set(capabilityState.systemTools.keys());
|
|
947
|
+
if (sessionId === null) return {
|
|
948
|
+
sessionId: null,
|
|
949
|
+
skills: [],
|
|
950
|
+
mcp: readMcp(services, degraded, disabledServers, disabledTools),
|
|
951
|
+
systemTools: readSystemTools(services, degraded, disabledSystem),
|
|
952
|
+
blocked,
|
|
953
|
+
...degraded.length > 0 ? { degraded } : {}
|
|
954
|
+
};
|
|
955
|
+
const agent = services.get("agents")?.get(sessionId);
|
|
956
|
+
let presetName;
|
|
957
|
+
let presetPath;
|
|
958
|
+
let presetDirs = [];
|
|
959
|
+
if (agent !== void 0) {
|
|
960
|
+
const presetId = services.get("agentPresets")?.composedPreset(agent.ctx);
|
|
961
|
+
try {
|
|
962
|
+
const presets = await services.get("agentPresets")?.list();
|
|
963
|
+
presetDirs = (presets ?? []).filter((p) => typeof p.path === "string" && p.path !== "").map((p) => ({
|
|
964
|
+
key: p.name ?? p.id,
|
|
965
|
+
path: p.path
|
|
966
|
+
}));
|
|
967
|
+
if (presetId !== void 0) {
|
|
968
|
+
const preset = presets?.find((p) => p.id === presetId);
|
|
969
|
+
presetName = preset?.name ?? presetId;
|
|
970
|
+
presetPath = preset?.path;
|
|
971
|
+
}
|
|
972
|
+
} catch {
|
|
973
|
+
presetName = presetId;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
const available = await readAvailable(services, sessionId, degraded, presetDirs);
|
|
977
|
+
const logFacts = readLogFacts(services, sessionId, degraded);
|
|
978
|
+
return {
|
|
979
|
+
sessionId,
|
|
980
|
+
skills: decideStates(available, logFacts.loads, logFacts.shadowed, disabledSkills, logFacts.pruned),
|
|
981
|
+
mcp: readMcp(services, degraded, disabledServers, disabledTools, agent, presetName, presetPath),
|
|
982
|
+
systemTools: readSystemTools(services, degraded, disabledSystem, agent),
|
|
983
|
+
blocked,
|
|
984
|
+
...degraded.length > 0 ? { degraded } : {}
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
672
988
|
//#endregion
|
|
673
989
|
//#region src/host/preset-tools.ts
|
|
674
990
|
function requireService(service, message) {
|
|
@@ -694,18 +1010,32 @@ function toolSummaries(tools, scope) {
|
|
|
694
1010
|
* workspace would contribute. A project skill is marked rather than dropped:
|
|
695
1011
|
* hiding it would silently shorten the list, while marking it says plainly
|
|
696
1012
|
* that a session opened elsewhere will not see the row.
|
|
1013
|
+
*
|
|
1014
|
+
* Each row also carries the same provenance the session payload reports:
|
|
1015
|
+
* the raw runtime source, the discovery root (parent of the skill's own
|
|
1016
|
+
* directory, abbreviated for display), and a `preset:<name>` group when the
|
|
1017
|
+
* root sits inside a preset's directory.
|
|
697
1018
|
*/
|
|
698
|
-
async function presetSkillRows(skills, scope, disabled, cwd) {
|
|
1019
|
+
async function presetSkillRows(skills, scope, disabled, cwd, presetDirs = []) {
|
|
699
1020
|
const seen = /* @__PURE__ */ new Map();
|
|
700
1021
|
const add = (summaries, project) => {
|
|
701
1022
|
for (const summary of summaries) {
|
|
702
1023
|
if (typeof summary.name !== "string" || summary.name === "" || seen.has(summary.name)) continue;
|
|
703
1024
|
const description = typeof summary.description === "string" ? summary.description : void 0;
|
|
1025
|
+
const source = typeof summary.source === "string" ? summary.source : void 0;
|
|
1026
|
+
const base = summary.resourceBase;
|
|
1027
|
+
const rawPath = base !== null && typeof base === "object" && base.kind === "directory" ? base.path : void 0;
|
|
1028
|
+
const rawRoot = typeof rawPath === "string" && rawPath !== "" ? parentDir(rawPath) : "";
|
|
1029
|
+
const path = rawRoot === "" ? void 0 : displayPath(rawRoot, cwd);
|
|
1030
|
+
const owner = source === "custom" && rawRoot !== "" ? presetDirs.find((p) => rawRoot === p.path || rawRoot.startsWith(`${p.path}/`)) : void 0;
|
|
704
1031
|
seen.set(summary.name, {
|
|
705
1032
|
name: summary.name,
|
|
706
1033
|
...description === void 0 ? {} : { description },
|
|
707
1034
|
enabled: !disabled.has(summary.name),
|
|
708
|
-
...project ? { project: true } : {}
|
|
1035
|
+
...project ? { project: true } : {},
|
|
1036
|
+
...source === void 0 ? {} : { source },
|
|
1037
|
+
...path === void 0 ? {} : { path },
|
|
1038
|
+
...owner === void 0 ? {} : { group: `preset:${owner.key}` }
|
|
709
1039
|
});
|
|
710
1040
|
}
|
|
711
1041
|
};
|
|
@@ -743,25 +1073,28 @@ function createPresetToolController(ctx, access) {
|
|
|
743
1073
|
const skills = ctx.get("skills");
|
|
744
1074
|
const cwd = process.cwd();
|
|
745
1075
|
const presets = await agentPresets.list();
|
|
1076
|
+
const presetDirs = presets.filter((p) => typeof p.path === "string" && p.path !== "").map((p) => ({
|
|
1077
|
+
key: p.name ?? p.id,
|
|
1078
|
+
path: p.path
|
|
1079
|
+
}));
|
|
1080
|
+
const globalNames = /* @__PURE__ */ new Set();
|
|
1081
|
+
for (const schema of tools.schemas()) if (typeof schema.name === "string" && schema.name.startsWith("mcp__")) globalNames.add(schema.name);
|
|
746
1082
|
return {
|
|
747
1083
|
presets: await Promise.all(presets.map(async (preset) => {
|
|
748
1084
|
let entries = [];
|
|
749
1085
|
let skillRows = [];
|
|
1086
|
+
let mountError;
|
|
750
1087
|
if (preset.broken === void 0) {
|
|
751
1088
|
let scope;
|
|
752
1089
|
try {
|
|
753
1090
|
scope = await agentPresets.standingKeyFor(preset.id);
|
|
754
1091
|
entries = toolSummaries(tools, scope);
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (skills !== void 0) {
|
|
759
|
-
const disabledSkills = new Set(stored.presetSkills[preset.id] ?? []);
|
|
760
|
-
try {
|
|
761
|
-
skillRows = await presetSkillRows(skills, scope, disabledSkills, cwd);
|
|
762
|
-
} catch (error) {
|
|
763
|
-
throw new HttpError(503, `preset "${preset.id}" skills are unavailable: ${errorMessage(error)}`);
|
|
1092
|
+
if (skills !== void 0) {
|
|
1093
|
+
const disabledSkills = new Set(stored.presetSkills[preset.id] ?? []);
|
|
1094
|
+
skillRows = await presetSkillRows(skills, scope, disabledSkills, cwd, presetDirs);
|
|
764
1095
|
}
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
mountError = errorMessage(error);
|
|
765
1098
|
}
|
|
766
1099
|
}
|
|
767
1100
|
const disabled = new Set(configured[preset.id] ?? []);
|
|
@@ -776,12 +1109,17 @@ function createPresetToolController(ctx, access) {
|
|
|
776
1109
|
...name === RESERVED_TOOL ? { reserved: true } : {}
|
|
777
1110
|
};
|
|
778
1111
|
};
|
|
1112
|
+
const presetName = preset.name ?? preset.id;
|
|
779
1113
|
const mcp = groupMcpTools(entries.map((entry) => entry.name)).map((group) => {
|
|
780
1114
|
const tools$1 = group.tools.map((tool) => row(`mcp__${group.server}__${tool}`, tool));
|
|
1115
|
+
const allGlobal = group.tools.every((tool) => globalNames.has(`mcp__${group.server}__${tool}`));
|
|
1116
|
+
const rawPath = allGlobal ? dshHome() : preset.path;
|
|
781
1117
|
return {
|
|
782
1118
|
server: group.server,
|
|
783
1119
|
tools: tools$1,
|
|
784
|
-
enabled: tools$1.some((tool) => tool.enabled)
|
|
1120
|
+
enabled: tools$1.some((tool) => tool.enabled),
|
|
1121
|
+
source: allGlobal ? "host" : presetName,
|
|
1122
|
+
...rawPath === void 0 ? {} : { path: displayPath(rawPath) }
|
|
785
1123
|
};
|
|
786
1124
|
});
|
|
787
1125
|
const systemTools = entries.filter((entry) => !entry.name.startsWith("mcp__")).map((entry) => row(entry.name, entry.name));
|
|
@@ -790,7 +1128,7 @@ function createPresetToolController(ctx, access) {
|
|
|
790
1128
|
name: preset.name ?? preset.id,
|
|
791
1129
|
trust: preset.trust,
|
|
792
1130
|
...preset.description === void 0 ? {} : { description: preset.description },
|
|
793
|
-
...preset.broken === void 0 ? {} : { broken:
|
|
1131
|
+
...preset.broken !== void 0 ? { broken: preset.broken } : mountError === void 0 ? {} : { broken: `failed to mount: ${mountError}` },
|
|
794
1132
|
skills: skillRows,
|
|
795
1133
|
mcp,
|
|
796
1134
|
systemTools
|
|
@@ -904,212 +1242,21 @@ function isLoopback(req) {
|
|
|
904
1242
|
}
|
|
905
1243
|
|
|
906
1244
|
//#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
|
-
}
|
|
1245
|
+
//#region src/host/open-folder.ts
|
|
962
1246
|
/**
|
|
963
|
-
*
|
|
964
|
-
* agent loop itself reads to assemble the next request.
|
|
965
|
-
*
|
|
966
|
-
* This used to go through `sessionQuery.readSession` + `listEvents`, which are
|
|
967
|
-
* built for cross-session/cold reads: each call structuredClones the ENTIRE
|
|
968
|
-
* event log, `readSession` additionally replay-validates it via Session.create,
|
|
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.
|
|
1247
|
+
* Open a folder in the system file manager.
|
|
980
1248
|
*
|
|
981
|
-
*
|
|
982
|
-
*
|
|
983
|
-
*
|
|
1249
|
+
* Covers macOS (`open`), Windows (`start`), and every freedesktop Linux
|
|
1250
|
+
* desktop (GNOME, KDE, XFCE, …) via `xdg-open`. Server-only Linux distros
|
|
1251
|
+
* that lack a desktop cannot open folders, but the error is graceful.
|
|
984
1252
|
*/
|
|
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
|
-
};
|
|
1253
|
+
function openFolder(path) {
|
|
1254
|
+
return new Promise((resolve, reject) => {
|
|
1255
|
+
exec(`${process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"} "${path}"`, (error) => {
|
|
1256
|
+
if (error) reject(/* @__PURE__ */ new Error(`failed to open folder: ${error.message}`));
|
|
1257
|
+
else resolve();
|
|
1048
1258
|
});
|
|
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
|
-
};
|
|
1259
|
+
});
|
|
1113
1260
|
}
|
|
1114
1261
|
|
|
1115
1262
|
//#endregion
|
|
@@ -1253,6 +1400,74 @@ function createRouteHandler(services, capabilities, stats, blockedCounts, preset
|
|
|
1253
1400
|
}, true);
|
|
1254
1401
|
return;
|
|
1255
1402
|
}
|
|
1403
|
+
if (url.pathname === `${ROUTE}/open-folder`) {
|
|
1404
|
+
if (req.method !== "POST") {
|
|
1405
|
+
res.writeHead(405, {
|
|
1406
|
+
allow: "POST",
|
|
1407
|
+
"content-type": "text/plain; charset=utf-8"
|
|
1408
|
+
});
|
|
1409
|
+
res.end("method not allowed");
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (!validatePresetContentType(req, res)) return;
|
|
1413
|
+
const body = await readRequestBody(req);
|
|
1414
|
+
if (body === null || typeof body !== "object") {
|
|
1415
|
+
json(res, 400, { error: "invalid request body" });
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
const record = body;
|
|
1419
|
+
if (typeof record.source !== "string" || record.source === "") {
|
|
1420
|
+
json(res, 400, { error: "source is required" });
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
const source = record.source;
|
|
1424
|
+
const sessionId$1 = typeof record.sessionId === "string" ? record.sessionId : null;
|
|
1425
|
+
let folderPath;
|
|
1426
|
+
if (sessionId$1 !== null) try {
|
|
1427
|
+
const skills = services.get("skills");
|
|
1428
|
+
const agent = services.get("agents")?.get(sessionId$1);
|
|
1429
|
+
if (skills !== void 0 && agent !== void 0) {
|
|
1430
|
+
const cwd = agent.session?.header?.cwd;
|
|
1431
|
+
const list = await skills.list({
|
|
1432
|
+
...cwd === void 0 ? {} : { cwd },
|
|
1433
|
+
scope: agent
|
|
1434
|
+
});
|
|
1435
|
+
for (const item of list) {
|
|
1436
|
+
if (item.source !== source) continue;
|
|
1437
|
+
const base = item.resourceBase;
|
|
1438
|
+
if (base !== null && typeof base === "object" && base.kind === "directory") {
|
|
1439
|
+
const path = base.path;
|
|
1440
|
+
if (typeof path === "string" && path !== "") {
|
|
1441
|
+
folderPath = parentDir(path);
|
|
1442
|
+
break;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
} catch {}
|
|
1448
|
+
if (folderPath === void 0 && (source === "user-dsh" || source === "user-agents")) {
|
|
1449
|
+
const dshHome$1 = process.env["DSH_HOME"] ?? (process.env["HOME"] !== void 0 ? `${process.env["HOME"]}/.dsh` : void 0);
|
|
1450
|
+
if (dshHome$1 !== void 0) folderPath = source === "user-dsh" ? `${dshHome$1}/skills` : `${process.env["DSH_AGENTS_HOME"] ?? `${process.env["HOME"]}/.agents`}/skills`;
|
|
1451
|
+
} else if (folderPath === void 0 && (source === "project-dsh" || source === "project-agents")) {
|
|
1452
|
+
const cwd = (sessionId$1 === null ? void 0 : services.get("agents")?.get(sessionId$1))?.session?.header?.cwd ?? (sessionId$1 === null ? process.cwd() : void 0);
|
|
1453
|
+
if (cwd !== void 0) folderPath = source === "project-dsh" ? `${cwd}/.dsh/skills` : `${cwd}/.agents/skills`;
|
|
1454
|
+
} else if (folderPath === void 0 && source === "host") folderPath = process.env["DSH_HOME"] ?? (process.env["HOME"] !== void 0 ? `${process.env["HOME"]}/.dsh` : void 0);
|
|
1455
|
+
else if (folderPath === void 0 && source !== "bundled" && source !== "runtime") try {
|
|
1456
|
+
const preset = (await services.get("agentPresets")?.list())?.find((p) => p.id === source || p.name === source);
|
|
1457
|
+
if (preset !== void 0) folderPath = preset.path;
|
|
1458
|
+
} catch {}
|
|
1459
|
+
if (folderPath === void 0) {
|
|
1460
|
+
json(res, 404, { error: `cannot open folder for source "${source}"` });
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
try {
|
|
1464
|
+
await openFolder(folderPath);
|
|
1465
|
+
json(res, 200, { ok: true });
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
json(res, 500, { error: `failed to open folder: ${errorMessage(error)}` });
|
|
1468
|
+
}
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1256
1471
|
if (url.pathname !== ROUTE && url.pathname !== "/") {
|
|
1257
1472
|
res.writeHead(404, {
|
|
1258
1473
|
"content-type": "text/plain; charset=utf-8",
|
|
@@ -1367,6 +1582,36 @@ function createSessionOverrideStore(access) {
|
|
|
1367
1582
|
};
|
|
1368
1583
|
}
|
|
1369
1584
|
|
|
1585
|
+
//#endregion
|
|
1586
|
+
//#region src/host/settings-scope.ts
|
|
1587
|
+
const TOOLKIT_SETTINGS_NAMESPACE = "capability-panel";
|
|
1588
|
+
const SessionOverrideSchema = z.object({
|
|
1589
|
+
skills: z.dict(z.boolean()).default({}),
|
|
1590
|
+
mcpServers: z.dict(z.boolean()).default({}),
|
|
1591
|
+
mcpTools: z.dict(z.boolean()).default({}),
|
|
1592
|
+
systemTools: z.dict(z.boolean()).default({})
|
|
1593
|
+
});
|
|
1594
|
+
const ToolkitSettingsSchema = z.object({
|
|
1595
|
+
presets: z.dict(z.array(z.string())).default({}),
|
|
1596
|
+
presetSkills: z.dict(z.array(z.string())).default({}),
|
|
1597
|
+
sessions: z.dict(SessionOverrideSchema).default({})
|
|
1598
|
+
});
|
|
1599
|
+
function createToolkitSettingsAccess(ctx) {
|
|
1600
|
+
let scope;
|
|
1601
|
+
let writeQueue = Promise.resolve();
|
|
1602
|
+
return {
|
|
1603
|
+
scope() {
|
|
1604
|
+
if (scope === void 0) scope = ctx.get("settings")?.register(TOOLKIT_SETTINGS_NAMESPACE, ToolkitSettingsSchema, { applies: "live" });
|
|
1605
|
+
return scope;
|
|
1606
|
+
},
|
|
1607
|
+
serialize(work) {
|
|
1608
|
+
const next = writeQueue.then(work, work);
|
|
1609
|
+
writeQueue = next.then(() => void 0, () => void 0);
|
|
1610
|
+
return next;
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1370
1615
|
//#endregion
|
|
1371
1616
|
//#region src/host/stats-store.ts
|
|
1372
1617
|
function statsFilePath(environment = process.env, home = homedir()) {
|