vskill 0.5.126 → 0.5.127
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/agents.json +1 -1
- package/dist/eval-server/api-routes.js +97 -12
- package/dist/eval-server/api-routes.js.map +1 -1
- package/dist/eval-server/install-engine-routes.js +1 -1
- package/dist/eval-server/install-engine-routes.js.map +1 -1
- package/dist/eval-server/utils/scan-install-locations.d.ts +18 -0
- package/dist/eval-server/utils/scan-install-locations.js +136 -0
- package/dist/eval-server/utils/scan-install-locations.js.map +1 -0
- package/dist/eval-ui/assets/{CommandPalette-CBrGufxz.js → CommandPalette-LtejhB76.js} +1 -1
- package/dist/eval-ui/assets/{CreateSkillPage-DXrpM1On.js → CreateSkillPage-3RuqLQnm.js} +3 -3
- package/dist/eval-ui/assets/{FindSkillsPalette-D99SoJx3.js → FindSkillsPalette-Dbl2_IC4.js} +2 -2
- package/dist/eval-ui/assets/SearchPaletteCore-CLZmxDat.js +14 -0
- package/dist/eval-ui/assets/{SkillDetailPanel-DzmlOsAc.js → SkillDetailPanel-RQc5u4nm.js} +1 -1
- package/dist/eval-ui/assets/UpdateDropdown-O8YGQyMH.js +1 -0
- package/dist/eval-ui/assets/{index-BAN03ugM.css → index-C8DXCPPg.css} +1 -1
- package/dist/eval-ui/assets/{index-BGQolxHD.js → index-DlZduKAT.js} +32 -32
- package/dist/eval-ui/assets/{skill-url-lxDpBkBU.js → skill-url-BpZjDR8A.js} +1 -1
- package/dist/eval-ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/eval-ui/assets/SearchPaletteCore-CnRYmTuv.js +0 -6
- package/dist/eval-ui/assets/UpdateDropdown-BsAm4uMW.js +0 -1
package/agents.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// api-routes.ts -- REST API route handlers for the eval UI
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
5
|
-
import { execSync } from "node:child_process";
|
|
5
|
+
import { execSync, execFileSync } from "node:child_process";
|
|
6
6
|
import { join, resolve, dirname, basename } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { sendJson, readBody } from "./router.js";
|
|
@@ -1510,20 +1510,54 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1510
1510
|
// directly instead of shelling out to whatever `vskill` is on PATH (which
|
|
1511
1511
|
// may be a different version than the studio is bundling). This guarantees
|
|
1512
1512
|
// the disk-version reconcile from outdated.ts is applied uniformly.
|
|
1513
|
+
//
|
|
1514
|
+
// 0747 T-002: enrich each row with `installLocations[]`, `localPlugin`,
|
|
1515
|
+
// `localSkill` so the Studio's bell dropdown can render tooltips and route
|
|
1516
|
+
// smart clicks via `revealSkill` instead of guessing local fs identifiers
|
|
1517
|
+
// from the canonical platform name.
|
|
1513
1518
|
router.get("/api/skills/updates", async (req, res) => {
|
|
1514
1519
|
try {
|
|
1515
1520
|
const { getOutdatedJson } = await import("../commands/outdated.js");
|
|
1521
|
+
const { scanSkillInstallLocations } = await import("./utils/scan-install-locations.js");
|
|
1516
1522
|
const programmatic = await getOutdatedJson();
|
|
1517
1523
|
if (!programmatic) {
|
|
1518
1524
|
sendJson(res, [], 200, req);
|
|
1519
1525
|
return;
|
|
1520
1526
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
+
// 0747 AC-US4-06 + code-review F-004: per-request memoization keyed by
|
|
1528
|
+
// canonical skill name. Each unique name is scanned exactly once even
|
|
1529
|
+
// if it appears in multiple rows, so the syscall cost is O(unique
|
|
1530
|
+
// names × agents × scopes), not O(rows × agents × scopes). For a
|
|
1531
|
+
// typical /updates response every row has a distinct name, so this
|
|
1532
|
+
// matches the previous one-call-per-row behavior on the happy path
|
|
1533
|
+
// while giving a correct lower bound when names ever duplicate.
|
|
1534
|
+
const scanCache = new Map();
|
|
1535
|
+
const scanOnce = (name) => {
|
|
1536
|
+
const hit = scanCache.get(name);
|
|
1537
|
+
if (hit !== undefined)
|
|
1538
|
+
return hit;
|
|
1539
|
+
const fresh = scanSkillInstallLocations(name, root);
|
|
1540
|
+
scanCache.set(name, fresh);
|
|
1541
|
+
return fresh;
|
|
1542
|
+
};
|
|
1543
|
+
const enriched = programmatic.results.map((r) => {
|
|
1544
|
+
const locations = scanOnce(r.name);
|
|
1545
|
+
const localSkill = r.name.split("/").pop();
|
|
1546
|
+
// Highest-precedence install wins for the local fs pair the click
|
|
1547
|
+
// handler reveals: project > personal > plugin.
|
|
1548
|
+
const precedence = { project: 0, personal: 1, plugin: 2 };
|
|
1549
|
+
const winner = [...locations].sort((a, b) => precedence[a.scope] -
|
|
1550
|
+
precedence[b.scope])[0];
|
|
1551
|
+
return {
|
|
1552
|
+
...r,
|
|
1553
|
+
...(programmatic.pinMap.has(r.name)
|
|
1554
|
+
? { pinned: true, pinnedVersion: programmatic.pinMap.get(r.name) }
|
|
1555
|
+
: {}),
|
|
1556
|
+
installLocations: locations,
|
|
1557
|
+
localSkill,
|
|
1558
|
+
...(winner?.pluginSlug ? { localPlugin: winner.pluginSlug } : {}),
|
|
1559
|
+
};
|
|
1560
|
+
});
|
|
1527
1561
|
sendJson(res, enriched, 200, req);
|
|
1528
1562
|
}
|
|
1529
1563
|
catch {
|
|
@@ -1637,18 +1671,66 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1637
1671
|
}
|
|
1638
1672
|
});
|
|
1639
1673
|
// T-011: Single-skill update SSE endpoint
|
|
1674
|
+
//
|
|
1675
|
+
// 0747 T-003: optional `?agent=<id>` query param scopes the update to a
|
|
1676
|
+
// single agent's install. The id MUST be in AGENTS_REGISTRY (allowlist) —
|
|
1677
|
+
// it is interpolated into the execSync command, so any non-allowlisted
|
|
1678
|
+
// value is rejected before reaching the shell. Without `?agent`, behavior
|
|
1679
|
+
// is unchanged and `vskill update` does its built-in cross-agent fan-out.
|
|
1640
1680
|
router.post("/api/skills/:plugin/:skill/update", async (req, res, params) => {
|
|
1641
1681
|
initSSE(res, req);
|
|
1642
1682
|
const skillName = params.skill;
|
|
1643
|
-
|
|
1683
|
+
// Parse optional ?agent=<id>
|
|
1684
|
+
let agentId = null;
|
|
1644
1685
|
try {
|
|
1645
|
-
|
|
1686
|
+
const reqUrl = req.url ?? "";
|
|
1687
|
+
const url = new URL(reqUrl, "http://localhost");
|
|
1688
|
+
const raw = url.searchParams.get("agent");
|
|
1689
|
+
if (raw !== null) {
|
|
1690
|
+
const allowed = AGENTS_REGISTRY.some((a) => a.id === raw);
|
|
1691
|
+
if (!allowed) {
|
|
1692
|
+
sendSSE(res, "error", {
|
|
1693
|
+
error: `Unknown agent id: ${raw}`,
|
|
1694
|
+
skill: skillName,
|
|
1695
|
+
});
|
|
1696
|
+
sendSSEDone(res, { status: "error", skill: skillName });
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
agentId = raw;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
catch {
|
|
1703
|
+
// URL parse error → behave as if ?agent was omitted
|
|
1704
|
+
}
|
|
1705
|
+
sendSSE(res, "progress", {
|
|
1706
|
+
status: "updating",
|
|
1707
|
+
skill: skillName,
|
|
1708
|
+
...(agentId ? { agent: agentId } : {}),
|
|
1709
|
+
});
|
|
1710
|
+
try {
|
|
1711
|
+
// 0747 code-review F-001: use execFileSync with argv array — never
|
|
1712
|
+
// shell-interpolate user-controlled values. skillName comes from a
|
|
1713
|
+
// route param and could otherwise carry shell metacharacters via
|
|
1714
|
+
// decodeURIComponent. agentId is allowlisted above; we still pass it
|
|
1715
|
+
// as a separate arg for defense-in-depth.
|
|
1716
|
+
const args = ["update", skillName];
|
|
1717
|
+
if (agentId)
|
|
1718
|
+
args.push("--agent", agentId);
|
|
1719
|
+
execFileSync("vskill", args, {
|
|
1646
1720
|
timeout: 60_000,
|
|
1647
1721
|
encoding: "utf-8",
|
|
1648
1722
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1649
1723
|
});
|
|
1650
|
-
sendSSE(res, "progress", {
|
|
1651
|
-
|
|
1724
|
+
sendSSE(res, "progress", {
|
|
1725
|
+
status: "done",
|
|
1726
|
+
skill: skillName,
|
|
1727
|
+
...(agentId ? { agent: agentId } : {}),
|
|
1728
|
+
});
|
|
1729
|
+
sendSSEDone(res, {
|
|
1730
|
+
status: "done",
|
|
1731
|
+
skill: skillName,
|
|
1732
|
+
...(agentId ? { agent: agentId } : {}),
|
|
1733
|
+
});
|
|
1652
1734
|
}
|
|
1653
1735
|
catch (err) {
|
|
1654
1736
|
sendSSE(res, "error", { error: err.message, skill: skillName });
|
|
@@ -1672,7 +1754,10 @@ export function registerRoutes(router, root, projectName) {
|
|
|
1672
1754
|
for (const skill of skills) {
|
|
1673
1755
|
sendSSE(res, "skill:start", { skill });
|
|
1674
1756
|
try {
|
|
1675
|
-
|
|
1757
|
+
// 0747 code-review F-001 (defense-in-depth): batch endpoint also
|
|
1758
|
+
// shell-interpolated body.skills[]. Switch to execFileSync argv
|
|
1759
|
+
// form so request-body values can never reach the shell.
|
|
1760
|
+
execFileSync("vskill", ["update", skill], {
|
|
1676
1761
|
timeout: 60_000,
|
|
1677
1762
|
encoding: "utf-8",
|
|
1678
1763
|
stdio: ["ignore", "pipe", "pipe"],
|