@hasna/skills 0.10.70 → 0.10.72
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.md +110 -6
- package/bin/index.js +6023 -5233
- package/bin/mcp.js +1972 -1794
- package/bin/migrate.js +2 -2
- package/bin/server.js +2 -2
- package/bin/worker.js +2 -2
- package/dist/cli/commands/context.d.ts +1 -0
- package/dist/index.js +4485 -3970
- package/dist/lib/agent-discovery-directories.d.ts +8 -3
- package/dist/lib/agent-discovery.d.ts +20 -4
- package/dist/lib/agent-integration.d.ts +39 -1
- package/dist/lib/claude-plugin-cache-marker.d.ts +10 -0
- package/dist/lib/codex-plugin-skill-controls.d.ts +9 -0
- package/dist/lib/codex-vendor-refresh.d.ts +54 -0
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/hook-diagnostics.d.ts +3 -1
- package/dist/lib/native-discovery-witness-change.d.ts +25 -0
- package/dist/lib/selection-cache.d.ts +22 -0
- package/dist/lib/selection-resolver.d.ts +3 -1
- package/dist/lib/session-reconciliation.d.ts +57 -1
- package/dist/runtime-prerequisites.js +188 -102
- package/dist/sdk/index.js +2348 -1837
- package/dist/storage.js +8 -4
- package/docs/plugin-admission.md +18 -3
- package/package.json +1 -1
|
@@ -47,9 +47,9 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
47
47
|
var __require = import.meta.require;
|
|
48
48
|
|
|
49
49
|
// src/runtime-prerequisites.ts
|
|
50
|
-
import { isAbsolute as
|
|
50
|
+
import { isAbsolute as isAbsolute7 } from "path";
|
|
51
51
|
// package.json
|
|
52
|
-
var version = "0.10.
|
|
52
|
+
var version = "0.10.72";
|
|
53
53
|
var package_default = {
|
|
54
54
|
name: "@hasna/skills",
|
|
55
55
|
version,
|
|
@@ -191,8 +191,8 @@ var package_default = {
|
|
|
191
191
|
};
|
|
192
192
|
|
|
193
193
|
// src/lib/agent-install-selection.ts
|
|
194
|
-
import { lstatSync as
|
|
195
|
-
import { dirname as
|
|
194
|
+
import { lstatSync as lstatSync6 } from "fs";
|
|
195
|
+
import { dirname as dirname6, join as join7, resolve as resolve7 } from "path";
|
|
196
196
|
import { homedir as homedir2 } from "os";
|
|
197
197
|
|
|
198
198
|
// src/lib/agent-adapters.ts
|
|
@@ -284,11 +284,11 @@ var NATIVE_SKILL_ROOTS = [
|
|
|
284
284
|
];
|
|
285
285
|
|
|
286
286
|
// src/lib/managed-policy.ts
|
|
287
|
-
import { closeSync as
|
|
287
|
+
import { closeSync as closeSync5, constants as constants5, fstatSync as fstatSync5, lstatSync as lstatSync5, openSync as openSync5, readSync as readSync4 } from "fs";
|
|
288
288
|
import { join as join6 } from "path";
|
|
289
289
|
|
|
290
290
|
// src/lib/config.ts
|
|
291
|
-
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
291
|
+
import { closeSync, constants, existsSync as existsSync2, fstatSync, lstatSync, openSync, readFileSync, statSync, writeFileSync, mkdirSync } from "fs";
|
|
292
292
|
import { join as join2, dirname } from "path";
|
|
293
293
|
|
|
294
294
|
// src/lib/retired-settings.ts
|
|
@@ -424,7 +424,8 @@ function skillsDataRootForHome(home) {
|
|
|
424
424
|
var ENUM_KEYS = {
|
|
425
425
|
defaultAgent: ["claude", "codex", "gemini", "pi", "opencode", "all"],
|
|
426
426
|
defaultScope: ["global", "project"],
|
|
427
|
-
format: ["compact", "json", "csv"]
|
|
427
|
+
format: ["compact", "json", "csv"],
|
|
428
|
+
sessionPinMigration: ["auto", "off"]
|
|
428
429
|
};
|
|
429
430
|
var STRING_KEYS = ["extensionsDir"];
|
|
430
431
|
function validKeys() {
|
|
@@ -495,6 +496,9 @@ function saveConfig(key, value, scope = "project") {
|
|
|
495
496
|
if (!validKeys().includes(key)) {
|
|
496
497
|
throw new Error(`Unknown config key: ${key}. Valid keys: ${validKeys().join(", ")}`);
|
|
497
498
|
}
|
|
499
|
+
if (key === "sessionPinMigration" && scope !== "global") {
|
|
500
|
+
throw new Error(`${key} is read only from the global configuration. Use: skills config set ${key} <auto|off> --global`);
|
|
501
|
+
}
|
|
498
502
|
const normalized = normalizeConfigValue(key, value);
|
|
499
503
|
if (normalized === undefined) {
|
|
500
504
|
const allowed = allowedValues(key);
|
|
@@ -549,14 +553,14 @@ function unsetConfig(key, scope = "project") {
|
|
|
549
553
|
// src/lib/selection-cache.ts
|
|
550
554
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
551
555
|
import { spawnSync } from "child_process";
|
|
552
|
-
import { closeSync, constants, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync3, readlinkSync, readSync, renameSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
556
|
+
import { closeSync as closeSync2, constants as constants2, fstatSync as fstatSync2, fsyncSync, linkSync, lstatSync as lstatSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync3, readlinkSync, readSync, renameSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
553
557
|
import { hostname } from "os";
|
|
554
|
-
import { dirname as dirname2, join as join4, parse, resolve as resolve2 } from "path";
|
|
558
|
+
import { dirname as dirname2, isAbsolute, join as join4, parse, resolve as resolve2 } from "path";
|
|
555
559
|
import { isDeepStrictEqual } from "util";
|
|
556
560
|
|
|
557
561
|
// src/lib/skill-bundle.ts
|
|
558
562
|
import { createHash } from "crypto";
|
|
559
|
-
import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
563
|
+
import { readFileSync as readFileSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
560
564
|
import { join as join3, relative } from "path";
|
|
561
565
|
import { createGunzip } from "zlib";
|
|
562
566
|
|
|
@@ -774,7 +778,7 @@ function walk(root, current, out) {
|
|
|
774
778
|
continue;
|
|
775
779
|
if (isCredentialFile(entry.name))
|
|
776
780
|
continue;
|
|
777
|
-
const stats =
|
|
781
|
+
const stats = statSync2(absolute);
|
|
778
782
|
out.push({
|
|
779
783
|
path: rel,
|
|
780
784
|
bytes: ownBytes(readFileSync2(absolute)),
|
|
@@ -1289,7 +1293,7 @@ function assertRegularPath(path, createParents = false) {
|
|
|
1289
1293
|
for (let index = 0;index < segments.length; index++) {
|
|
1290
1294
|
cursor = join4(cursor, segments[index]);
|
|
1291
1295
|
try {
|
|
1292
|
-
const stat =
|
|
1296
|
+
const stat = lstatSync2(cursor);
|
|
1293
1297
|
if (stat.isSymbolicLink() || index < segments.length - 1 && !stat.isDirectory()) {
|
|
1294
1298
|
throw new SkillSelectionError("UNSAFE_CACHE_PATH", "Skills cache paths must not traverse symlinks or non-directories.");
|
|
1295
1299
|
}
|
|
@@ -1305,14 +1309,14 @@ function readRegularFile(path, limit) {
|
|
|
1305
1309
|
assertRegularPath(path);
|
|
1306
1310
|
let fd;
|
|
1307
1311
|
try {
|
|
1308
|
-
fd =
|
|
1312
|
+
fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
|
|
1309
1313
|
} catch (error) {
|
|
1310
1314
|
if (error.code === "ENOENT")
|
|
1311
1315
|
return null;
|
|
1312
1316
|
throw error;
|
|
1313
1317
|
}
|
|
1314
1318
|
try {
|
|
1315
|
-
const stat =
|
|
1319
|
+
const stat = fstatSync2(fd);
|
|
1316
1320
|
if (!stat.isFile() || stat.size > limit)
|
|
1317
1321
|
throw new SkillSelectionError("INVALID_CACHE_FILE", "The Skills cache contains an invalid or oversized file.");
|
|
1318
1322
|
const bytes = Buffer.alloc(stat.size + 1);
|
|
@@ -1327,7 +1331,7 @@ function readRegularFile(path, limit) {
|
|
|
1327
1331
|
throw new SkillSelectionError("INVALID_CACHE_FILE", "The Skills cache file grew during its bounded read.");
|
|
1328
1332
|
return bytes.subarray(0, size);
|
|
1329
1333
|
} finally {
|
|
1330
|
-
|
|
1334
|
+
closeSync2(fd);
|
|
1331
1335
|
}
|
|
1332
1336
|
}
|
|
1333
1337
|
function writeSelectionJson(path, value) {
|
|
@@ -1341,13 +1345,13 @@ function atomicWrite(path, bytes, mode, durable = false) {
|
|
|
1341
1345
|
assertRegularPath(path, true);
|
|
1342
1346
|
const temporary = join4(dirname2(path), `.selection-${randomUUID()}.tmp`);
|
|
1343
1347
|
try {
|
|
1344
|
-
const fd =
|
|
1348
|
+
const fd = openSync2(temporary, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, mode);
|
|
1345
1349
|
try {
|
|
1346
1350
|
writeFileSync2(fd, bytes);
|
|
1347
1351
|
if (durable)
|
|
1348
1352
|
fsyncSync(fd);
|
|
1349
1353
|
} finally {
|
|
1350
|
-
|
|
1354
|
+
closeSync2(fd);
|
|
1351
1355
|
}
|
|
1352
1356
|
renameSync(temporary, path);
|
|
1353
1357
|
if (durable)
|
|
@@ -1485,11 +1489,19 @@ function assertFreshCachedProfile(receipt, options = {}) {
|
|
|
1485
1489
|
function projectSelectionLockPath(projectDir) {
|
|
1486
1490
|
return join4(resolve2(projectDir), ".skills", SELECTION_LOCK_FILE);
|
|
1487
1491
|
}
|
|
1488
|
-
function
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1492
|
+
function readProjectSelectionSnapshot(projectDir) {
|
|
1493
|
+
const path = projectSelectionLockPath(projectDir);
|
|
1494
|
+
const bytes = readRegularFile(path, MAX_PROFILE_DOCUMENT_BYTES);
|
|
1495
|
+
if (!bytes)
|
|
1496
|
+
return null;
|
|
1497
|
+
let receipt;
|
|
1498
|
+
try {
|
|
1499
|
+
receipt = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
1500
|
+
} catch {
|
|
1501
|
+
throw new SkillSelectionError("INVALID_RECEIPT", "The Skills selection receipt is unreadable; sync the profile again.");
|
|
1502
|
+
}
|
|
1503
|
+
validateSelectionReceipt(receipt);
|
|
1504
|
+
return { receipt, lock: { path, sha256: sha256Hex(bytes) } };
|
|
1493
1505
|
}
|
|
1494
1506
|
function sessionReceiptPath(sessionId, options = {}) {
|
|
1495
1507
|
if (!sessionId.trim() || sessionId.length > MAX_SKILL_SESSION_ID_CHARS)
|
|
@@ -1515,6 +1527,10 @@ function validateSkillSessionReceipt(receipt, sessionId) {
|
|
|
1515
1527
|
if (receipt.parent.sessionId === sessionId)
|
|
1516
1528
|
throw new SkillSelectionError("INVALID_RECEIPT", "A Skills child session cannot name itself as its parent.");
|
|
1517
1529
|
}
|
|
1530
|
+
const lock = receipt.projectLock;
|
|
1531
|
+
if (lock !== undefined && (!lock || typeof lock !== "object" || Array.isArray(lock) || Object.keys(lock).sort().join(",") !== "path,sha256" || typeof lock.path !== "string" || !isAbsolute(lock.path) || lock.path.length > 4096 || !/^[a-f0-9]{64}$/.test(lock.sha256))) {
|
|
1532
|
+
throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt contains invalid project lock provenance.");
|
|
1533
|
+
}
|
|
1518
1534
|
}
|
|
1519
1535
|
function readSkillSessionSnapshotIfExists(sessionId, options = {}) {
|
|
1520
1536
|
const path = sessionReceiptPath(sessionId, options), bytes = readRegularFile(path, MAX_PROFILE_DOCUMENT_BYTES);
|
|
@@ -1599,14 +1615,14 @@ function localLockIdentity() {
|
|
|
1599
1615
|
}
|
|
1600
1616
|
function readSessionLock(sessionId, options) {
|
|
1601
1617
|
const path = `${sessionReceiptPath(sessionId, options)}.write-lock`;
|
|
1602
|
-
const before =
|
|
1618
|
+
const before = lstatSync2(path, { throwIfNoEntry: false });
|
|
1603
1619
|
if (!before)
|
|
1604
1620
|
return null;
|
|
1605
1621
|
if (!before.isFile() || ![1, 2].includes(before.nlink) || (before.mode & 63) !== 0 || process.getuid && before.uid !== process.getuid()) {
|
|
1606
1622
|
throw new SkillSelectionError("SESSION_WRITE_LOCKED", "The session lock is not a private regular file; recovery requires review.");
|
|
1607
1623
|
}
|
|
1608
1624
|
const bytes = readRegularFile(path, SESSION_LOCK_MAX_BYTES);
|
|
1609
|
-
const after =
|
|
1625
|
+
const after = lstatSync2(path, { throwIfNoEntry: false });
|
|
1610
1626
|
if (!bytes || !after?.isFile() || before.dev !== after.dev || before.ino !== after.ino || before.mtimeMs !== after.mtimeMs || before.mode !== after.mode || before.uid !== after.uid || before.gid !== after.gid || before.nlink !== after.nlink) {
|
|
1611
1627
|
throw new SkillSelectionError("SESSION_WRITE_LOCKED", "The session lock changed during inspection; recovery requires review.");
|
|
1612
1628
|
}
|
|
@@ -1623,7 +1639,7 @@ function readSessionLock(sessionId, options) {
|
|
|
1623
1639
|
if (after.nlink === 2) {
|
|
1624
1640
|
stagingPath = `${path}.${marker.operationId}.prepared`;
|
|
1625
1641
|
assertRegularPath(stagingPath);
|
|
1626
|
-
const staged =
|
|
1642
|
+
const staged = lstatSync2(stagingPath, { throwIfNoEntry: false });
|
|
1627
1643
|
if (marker.publication !== "hard-link-v1" || !staged?.isFile() || staged.dev !== after.dev || staged.ino !== after.ino || staged.nlink !== 2 || staged.mode !== after.mode || staged.uid !== after.uid || staged.gid !== after.gid) {
|
|
1628
1644
|
throw new SkillSelectionError("SESSION_WRITE_LOCKED", "The session lock has an unbound publication link; recovery requires review.");
|
|
1629
1645
|
}
|
|
@@ -1634,8 +1650,8 @@ function publishSessionLock(path, marker) {
|
|
|
1634
1650
|
const stagingPath = `${path}.${marker.operationId}.prepared`;
|
|
1635
1651
|
const bytes = Buffer.from(`${JSON.stringify({ ...marker, publication: "hard-link-v1" })}
|
|
1636
1652
|
`);
|
|
1637
|
-
const fd =
|
|
1638
|
-
const stat =
|
|
1653
|
+
const fd = openSync2(stagingPath, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
|
|
1654
|
+
const stat = fstatSync2(fd);
|
|
1639
1655
|
let published = false, handedOff = false;
|
|
1640
1656
|
try {
|
|
1641
1657
|
writeFileSync2(fd, bytes);
|
|
@@ -1643,7 +1659,7 @@ function publishSessionLock(path, marker) {
|
|
|
1643
1659
|
linkSync(stagingPath, path);
|
|
1644
1660
|
published = true;
|
|
1645
1661
|
syncDirectory(dirname2(path));
|
|
1646
|
-
const staged =
|
|
1662
|
+
const staged = lstatSync2(stagingPath);
|
|
1647
1663
|
if (!staged.isFile() || staged.dev !== stat.dev || staged.ino !== stat.ino) {
|
|
1648
1664
|
throw new SkillSelectionError("SESSION_WRITE_LOCK_CHANGED", "The initialized lock staging inode changed before publication completed.");
|
|
1649
1665
|
}
|
|
@@ -1655,12 +1671,12 @@ function publishSessionLock(path, marker) {
|
|
|
1655
1671
|
if (!handedOff) {
|
|
1656
1672
|
try {
|
|
1657
1673
|
for (const target of [...published ? [path] : [], stagingPath]) {
|
|
1658
|
-
const current =
|
|
1674
|
+
const current = lstatSync2(target, { throwIfNoEntry: false });
|
|
1659
1675
|
if (current?.isFile() && current.dev === stat.dev && current.ino === stat.ino)
|
|
1660
1676
|
unlinkSync(target);
|
|
1661
1677
|
}
|
|
1662
1678
|
} finally {
|
|
1663
|
-
|
|
1679
|
+
closeSync2(fd);
|
|
1664
1680
|
}
|
|
1665
1681
|
}
|
|
1666
1682
|
}
|
|
@@ -1682,13 +1698,13 @@ function archiveSessionLock(lock, receipt, explicitLocalHost) {
|
|
|
1682
1698
|
const guardPath = `${lock.path}.recovery-guard`;
|
|
1683
1699
|
let guardFd;
|
|
1684
1700
|
try {
|
|
1685
|
-
guardFd =
|
|
1701
|
+
guardFd = openSync2(guardPath, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
|
|
1686
1702
|
} catch (error) {
|
|
1687
1703
|
if (error.code === "EEXIST")
|
|
1688
1704
|
return null;
|
|
1689
1705
|
throw error;
|
|
1690
1706
|
}
|
|
1691
|
-
const guardStat =
|
|
1707
|
+
const guardStat = fstatSync2(guardFd);
|
|
1692
1708
|
try {
|
|
1693
1709
|
writeFileSync2(guardFd, `${JSON.stringify({ schemaVersion: 1, pid: process.pid, operationId: randomUUID() })}
|
|
1694
1710
|
`);
|
|
@@ -1734,9 +1750,9 @@ function archiveSessionLock(lock, receipt, explicitLocalHost) {
|
|
|
1734
1750
|
syncDirectory(directory);
|
|
1735
1751
|
syncDirectory(dirname2(lock.path));
|
|
1736
1752
|
for (const path of archivedPaths) {
|
|
1737
|
-
const before =
|
|
1753
|
+
const before = lstatSync2(path, { throwIfNoEntry: false });
|
|
1738
1754
|
const bytes = readRegularFile(path, SESSION_LOCK_MAX_BYTES);
|
|
1739
|
-
const after =
|
|
1755
|
+
const after = lstatSync2(path, { throwIfNoEntry: false });
|
|
1740
1756
|
if (!before?.isFile() || before.dev !== lock.dev || before.ino !== lock.ino || before.nlink !== archivedPaths.length || before.mtimeMs !== lock.mtimeMs || (before.mode & 63) !== 0 || process.getuid && before.uid !== process.getuid() || !bytes || sha256Hex(bytes) !== lock.sha256 || !after?.isFile() || before.dev !== after.dev || before.ino !== after.ino || before.mtimeMs !== after.mtimeMs || before.mode !== after.mode || before.uid !== after.uid || before.gid !== after.gid || before.nlink !== after.nlink) {
|
|
1741
1757
|
throw new Error("Archived lock ownership or bytes changed");
|
|
1742
1758
|
}
|
|
@@ -1748,8 +1764,8 @@ function archiveSessionLock(lock, receipt, explicitLocalHost) {
|
|
|
1748
1764
|
}
|
|
1749
1765
|
return archivePath;
|
|
1750
1766
|
} finally {
|
|
1751
|
-
|
|
1752
|
-
const current =
|
|
1767
|
+
closeSync2(guardFd);
|
|
1768
|
+
const current = lstatSync2(guardPath, { throwIfNoEntry: false });
|
|
1753
1769
|
if (current?.isFile() && current.dev === guardStat.dev && current.ino === guardStat.ino)
|
|
1754
1770
|
unlinkSync(guardPath);
|
|
1755
1771
|
}
|
|
@@ -1761,7 +1777,7 @@ function withSessionWriteLocks(sessionIds, options, action) {
|
|
|
1761
1777
|
const owned = [];
|
|
1762
1778
|
const assertOwned = () => {
|
|
1763
1779
|
for (const lock of owned) {
|
|
1764
|
-
const current =
|
|
1780
|
+
const current = lstatSync2(lock.path, { throwIfNoEntry: false });
|
|
1765
1781
|
if (!current?.isFile() || current.dev !== lock.dev || current.ino !== lock.ino) {
|
|
1766
1782
|
throw new SkillSelectionError("SESSION_WRITE_LOCK_CHANGED", "A session write lock changed during the operation; no replacement is permitted.");
|
|
1767
1783
|
}
|
|
@@ -1805,12 +1821,12 @@ function withSessionWriteLocks(sessionIds, options, action) {
|
|
|
1805
1821
|
for (const lock of owned.reverse()) {
|
|
1806
1822
|
try {
|
|
1807
1823
|
for (const path of [lock.path, lock.stagingPath]) {
|
|
1808
|
-
const current =
|
|
1824
|
+
const current = lstatSync2(path, { throwIfNoEntry: false });
|
|
1809
1825
|
if (current?.isFile() && current.dev === lock.dev && current.ino === lock.ino)
|
|
1810
1826
|
unlinkSync(path);
|
|
1811
1827
|
}
|
|
1812
1828
|
} finally {
|
|
1813
|
-
|
|
1829
|
+
closeSync2(lock.fd);
|
|
1814
1830
|
}
|
|
1815
1831
|
}
|
|
1816
1832
|
}
|
|
@@ -1861,21 +1877,21 @@ function writeSkillSession(receipt, expected, options = {}) {
|
|
|
1861
1877
|
}
|
|
1862
1878
|
function syncDirectory(path) {
|
|
1863
1879
|
assertRegularPath(path);
|
|
1864
|
-
const fd =
|
|
1880
|
+
const fd = openSync2(path, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
|
|
1865
1881
|
try {
|
|
1866
1882
|
fsyncSync(fd);
|
|
1867
1883
|
} finally {
|
|
1868
|
-
|
|
1884
|
+
closeSync2(fd);
|
|
1869
1885
|
}
|
|
1870
1886
|
}
|
|
1871
1887
|
function archiveBytes(path, bytes) {
|
|
1872
1888
|
assertRegularPath(path, true);
|
|
1873
|
-
const fd =
|
|
1889
|
+
const fd = openSync2(path, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
|
|
1874
1890
|
try {
|
|
1875
1891
|
writeFileSync2(fd, bytes);
|
|
1876
1892
|
fsyncSync(fd);
|
|
1877
1893
|
} finally {
|
|
1878
|
-
|
|
1894
|
+
closeSync2(fd);
|
|
1879
1895
|
}
|
|
1880
1896
|
}
|
|
1881
1897
|
function replaceSkillSession(expected, replacement, plan, options = {}, beforeCommit = () => {}) {
|
|
@@ -1921,7 +1937,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
|
|
|
1921
1937
|
}
|
|
1922
1938
|
|
|
1923
1939
|
// src/lib/agent-policy-limits.ts
|
|
1924
|
-
import { basename as
|
|
1940
|
+
import { basename as basename4, isAbsolute as isAbsolute5, join as join5, resolve as resolve6, sep as sep2 } from "path";
|
|
1925
1941
|
|
|
1926
1942
|
// src/lib/codex-native-compatibility.ts
|
|
1927
1943
|
var MEASURED_CODEX_CAPABILITIES = {
|
|
@@ -1948,25 +1964,25 @@ var SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAP
|
|
|
1948
1964
|
|
|
1949
1965
|
// src/lib/claude-marketplace-entry-witness.ts
|
|
1950
1966
|
import { createHash as createHash4 } from "crypto";
|
|
1951
|
-
import { basename as basename2, dirname as dirname4, isAbsolute as
|
|
1967
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
|
|
1952
1968
|
|
|
1953
1969
|
// src/lib/claude-settings-witness.ts
|
|
1954
1970
|
import { createHash as createHash3 } from "crypto";
|
|
1955
|
-
import { closeSync as
|
|
1956
|
-
import { basename, dirname as dirname3, isAbsolute, resolve as resolve3 } from "path";
|
|
1971
|
+
import { closeSync as closeSync3, constants as constants3, fstatSync as fstatSync3, lstatSync as lstatSync3, openSync as openSync3, readSync as readSync2 } from "fs";
|
|
1972
|
+
import { basename, dirname as dirname3, isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
|
|
1957
1973
|
var CLAUDE_SETTINGS_WITNESS_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
1958
1974
|
function need(value, reason) {
|
|
1959
1975
|
if (!value)
|
|
1960
1976
|
throw new Error(`Claude settings witness ${reason}`);
|
|
1961
1977
|
}
|
|
1962
1978
|
function absolutePath(value) {
|
|
1963
|
-
need(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_SETTINGS_WITNESS_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) &&
|
|
1979
|
+
need(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_SETTINGS_WITNESS_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute2(value) && resolve3(value) === value, "requires a normalized absolute path");
|
|
1964
1980
|
}
|
|
1965
1981
|
var sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs;
|
|
1966
1982
|
function parents(path) {
|
|
1967
1983
|
const result = [];
|
|
1968
1984
|
for (let at = dirname3(path);; at = dirname3(at)) {
|
|
1969
|
-
const stat =
|
|
1985
|
+
const stat = lstatSync3(at, { bigint: true });
|
|
1970
1986
|
need(stat.isDirectory() && !stat.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
1971
1987
|
result.push([at, stat]);
|
|
1972
1988
|
if (at === dirname3(at))
|
|
@@ -1977,12 +1993,12 @@ function readNativeSettingsWitnessFile(path, budget, filename = "settings.json")
|
|
|
1977
1993
|
absolutePath(path);
|
|
1978
1994
|
need(basename(path) === filename, `requires ${filename}`);
|
|
1979
1995
|
need(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
1980
|
-
const ancestors = parents(path), initial =
|
|
1996
|
+
const ancestors = parents(path), initial = lstatSync3(path, { bigint: true });
|
|
1981
1997
|
need(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_SETTINGS_WITNESS_LIMITS.bytes), "is not a bounded regular file");
|
|
1982
1998
|
need(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
1983
|
-
const fd =
|
|
1999
|
+
const fd = openSync3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
|
|
1984
2000
|
try {
|
|
1985
|
-
const opened =
|
|
2001
|
+
const opened = fstatSync3(fd, { bigint: true });
|
|
1986
2002
|
need(opened.isFile() && sameFile(initial, opened), "changed during open");
|
|
1987
2003
|
const bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
1988
2004
|
let length = 0;
|
|
@@ -1992,10 +2008,10 @@ function readNativeSettingsWitnessFile(path, budget, filename = "settings.json")
|
|
|
1992
2008
|
break;
|
|
1993
2009
|
length += count;
|
|
1994
2010
|
}
|
|
1995
|
-
const after =
|
|
2011
|
+
const after = fstatSync3(fd, { bigint: true }), current = lstatSync3(path, { bigint: true });
|
|
1996
2012
|
need(current.isFile() && sameFile(opened, after) && sameFile(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
1997
2013
|
for (const [ancestor, before] of ancestors) {
|
|
1998
|
-
const now =
|
|
2014
|
+
const now = lstatSync3(ancestor, { bigint: true });
|
|
1999
2015
|
need(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
2000
2016
|
}
|
|
2001
2017
|
budget.remaining -= length;
|
|
@@ -2005,7 +2021,7 @@ function readNativeSettingsWitnessFile(path, budget, filename = "settings.json")
|
|
|
2005
2021
|
throw new Error("Claude settings witness is not strict UTF-8");
|
|
2006
2022
|
}
|
|
2007
2023
|
} finally {
|
|
2008
|
-
|
|
2024
|
+
closeSync3(fd);
|
|
2009
2025
|
}
|
|
2010
2026
|
}
|
|
2011
2027
|
function parse2(text) {
|
|
@@ -2375,7 +2391,7 @@ var stringArray = (value) => value?.kind === "array" && value.items.every(isStri
|
|
|
2375
2391
|
var stringObject = (value) => value?.kind === "object" && value.entries.every(([, item]) => isString(item));
|
|
2376
2392
|
function claudeMarketplaceEntrySourceValid(source) {
|
|
2377
2393
|
const path = source?.path;
|
|
2378
|
-
return typeof path === "string" && path.length > 0 && path.length <= PATH_CHARACTERS && !/[\x00-\x1f\x7f]/.test(path) &&
|
|
2394
|
+
return typeof path === "string" && path.length > 0 && path.length <= PATH_CHARACTERS && !/[\x00-\x1f\x7f]/.test(path) && isAbsolute3(path) && resolve4(path) === path && basename2(path) === "marketplace.json" && basename2(dirname4(path)) === ".claude-plugin" && typeof source.marketplace === "string" && NAME.test(source.marketplace) && !source.marketplace.includes("..") && typeof source.plugin === "string" && NAME.test(source.plugin);
|
|
2379
2395
|
}
|
|
2380
2396
|
function canonical2(value) {
|
|
2381
2397
|
if (value.kind === "string")
|
|
@@ -2501,6 +2517,74 @@ function captureClaudeMarketplaceEntry(path, marketplace, plugin, budget = { rem
|
|
|
2501
2517
|
return { path, hashMode: CLAUDE_MARKETPLACE_ENTRY_HASH_MODE, marketplace, plugin, sha256: hashClaudeMarketplaceEntry(text, { marketplace, plugin }) };
|
|
2502
2518
|
}
|
|
2503
2519
|
|
|
2520
|
+
// src/lib/claude-plugin-cache-marker.ts
|
|
2521
|
+
import { createHash as createHash5 } from "crypto";
|
|
2522
|
+
import { closeSync as closeSync4, constants as constants4, fstatSync as fstatSync4, lstatSync as lstatSync4, openSync as openSync4, readSync as readSync3 } from "fs";
|
|
2523
|
+
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute4, resolve as resolve5, sep } from "path";
|
|
2524
|
+
var CLAUDE_ORPHAN_MARKER_MODE = "claude-plugin-orphaned-at-v1";
|
|
2525
|
+
var CACHE = `${sep}.claude${sep}plugins${sep}cache${sep}`;
|
|
2526
|
+
function isClaudePluginCacheOrphanMarker(path) {
|
|
2527
|
+
if (typeof path !== "string" || basename3(path) !== ".orphaned_at" || !isAbsolute4(path) || resolve5(path) !== path || path.includes("\x00"))
|
|
2528
|
+
return false;
|
|
2529
|
+
const at = path.indexOf(CACHE);
|
|
2530
|
+
if (at < 0 || path.indexOf(CACHE, at + 1) >= 0)
|
|
2531
|
+
return false;
|
|
2532
|
+
const parts = path.slice(at + CACHE.length).split(sep);
|
|
2533
|
+
return parts.length === 4 && parts.every((part) => part && part !== "." && part !== "..");
|
|
2534
|
+
}
|
|
2535
|
+
function same(left, right) {
|
|
2536
|
+
return Boolean(right && right.isFile() && left.dev === right.dev && left.ino === right.ino && left.uid === right.uid && left.mode === right.mode && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs);
|
|
2537
|
+
}
|
|
2538
|
+
function safe(path) {
|
|
2539
|
+
for (let cursor = path;; cursor = dirname5(cursor)) {
|
|
2540
|
+
if (lstatSync4(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
2541
|
+
throw new Error("Refusing symlink Claude orphan marker input");
|
|
2542
|
+
if (dirname5(cursor) === cursor)
|
|
2543
|
+
break;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
function hashClaudePluginCacheOrphanMarker(path, budget, changes) {
|
|
2547
|
+
if (!isClaudePluginCacheOrphanMarker(path))
|
|
2548
|
+
throw new Error("Expected a Claude plugin-cache version-root orphan marker");
|
|
2549
|
+
if (changes?.has(path))
|
|
2550
|
+
throw new Error("Claude orphan markers cannot intersect a planned write");
|
|
2551
|
+
safe(path);
|
|
2552
|
+
const before = lstatSync4(path, { throwIfNoEntry: false });
|
|
2553
|
+
if (!before)
|
|
2554
|
+
return null;
|
|
2555
|
+
const uid = process.getuid?.();
|
|
2556
|
+
if (!before.isFile() || uid === undefined || before.uid !== uid || before.size > 32)
|
|
2557
|
+
throw new Error("Invalid Claude orphan marker file or owner");
|
|
2558
|
+
const fd = openSync4(path, constants4.O_RDONLY | constants4.O_NOFOLLOW | constants4.O_NONBLOCK);
|
|
2559
|
+
try {
|
|
2560
|
+
const opened = fstatSync4(fd);
|
|
2561
|
+
if (!same(before, opened))
|
|
2562
|
+
throw new Error("Claude orphan marker changed during open");
|
|
2563
|
+
const bytes = Buffer.alloc(33);
|
|
2564
|
+
let length = 0;
|
|
2565
|
+
while (length < bytes.length) {
|
|
2566
|
+
const count = readSync3(fd, bytes, length, bytes.length - length, null);
|
|
2567
|
+
if (!count)
|
|
2568
|
+
break;
|
|
2569
|
+
length += count;
|
|
2570
|
+
}
|
|
2571
|
+
if (!/^[1-9][0-9]{12}$/.test(bytes.subarray(0, length).toString("latin1")))
|
|
2572
|
+
throw new Error("Malformed Claude orphan marker timestamp");
|
|
2573
|
+
const after = fstatSync4(fd), current = lstatSync4(path, { throwIfNoEntry: false });
|
|
2574
|
+
safe(path);
|
|
2575
|
+
if (!same(opened, after) || !same(after, current) || length !== after.size)
|
|
2576
|
+
throw new Error("Claude orphan marker changed during read");
|
|
2577
|
+
if (budget) {
|
|
2578
|
+
if (length > budget.remaining)
|
|
2579
|
+
throw new Error("Raw discovery aggregate byte limit exceeded");
|
|
2580
|
+
budget.remaining -= length;
|
|
2581
|
+
}
|
|
2582
|
+
return createHash5("sha256").update(`${CLAUDE_ORPHAN_MARKER_MODE}\x00${path}\x00file`).digest("hex");
|
|
2583
|
+
} finally {
|
|
2584
|
+
closeSync4(fd);
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2504
2588
|
// src/lib/agent-policy-limits.ts
|
|
2505
2589
|
var AGENT_POLICY_LIMITS = Object.freeze({ bytes: 1024 * 1024, agents: 16, discoverySources: 2048, discoveryRawSourceBytes: 64 * 1024 * 1024, discoveryRawTotalBytes: 256 * 1024 * 1024, discoveryPathLinks: 40, discoveryPathSteps: 256, discoveryPathSourceMetadataBytes: 64 * 1024, discoveryPathTotalMetadataBytes: 8 * 1024 * 1024, discoveryRoots: 512, discoveryDirectories: 64, discoveryDirectoryEntries: 20000, discoveryDirectoryBytes: 8 * 1024 * 1024, builtinNames: 2048, rootAliases: 2, fields: 64, pathCharacters: 4096 });
|
|
2506
2590
|
function object(value) {
|
|
@@ -2555,7 +2639,7 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2555
2639
|
text(control.pluginId, 1024);
|
|
2556
2640
|
text(control.namespace, 64);
|
|
2557
2641
|
text(control.pluginParent);
|
|
2558
|
-
requireBound(
|
|
2642
|
+
requireBound(isAbsolute5(control.pluginParent) && resolve6(control.pluginParent) === control.pluginParent && /^[a-f0-9]{64}$/.test(control.manifestSha256));
|
|
2559
2643
|
if (control.remotePluginId !== undefined)
|
|
2560
2644
|
requireBound(typeof control.remotePluginId === "string" && /^[A-Za-z0-9_~-]{1,1024}$/.test(control.remotePluginId));
|
|
2561
2645
|
if (control.skillsOnly !== undefined)
|
|
@@ -2573,7 +2657,7 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2573
2657
|
text(control.pluginId, 1024);
|
|
2574
2658
|
text(control.namespace, 64);
|
|
2575
2659
|
text(control.pluginParent);
|
|
2576
|
-
requireBound(!ids.has(control.pluginId) &&
|
|
2660
|
+
requireBound(!ids.has(control.pluginId) && isAbsolute5(control.pluginParent) && resolve6(control.pluginParent) === control.pluginParent && ["local", "git", "npm"].includes(control.sourceType) && typeof control.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(control.sourceSha256));
|
|
2577
2661
|
ids.add(control.pluginId);
|
|
2578
2662
|
}
|
|
2579
2663
|
}
|
|
@@ -2582,7 +2666,7 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2582
2666
|
requireBound(agent === "gemini" && object(executable) && Object.keys(executable).length === 3 && executable.command === "gemini");
|
|
2583
2667
|
for (const path of [executable.path, executable.target]) {
|
|
2584
2668
|
text(path);
|
|
2585
|
-
requireBound(!/[\x00-\x1f\x7f-\x9f\u200e\u200f\u202a-\u202e\u2066-\u2069]/.test(path) &&
|
|
2669
|
+
requireBound(!/[\x00-\x1f\x7f-\x9f\u200e\u200f\u202a-\u202e\u2066-\u2069]/.test(path) && isAbsolute5(path) && resolve6(path) === path);
|
|
2586
2670
|
}
|
|
2587
2671
|
}
|
|
2588
2672
|
if (bridge.disabledBuiltins !== undefined)
|
|
@@ -2610,11 +2694,11 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2610
2694
|
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && Object.keys(proof).every((key) => ["roots", "parents", "directories"].includes(key)) && roots.length + parents2.length > 0 && new Set(roots).size === roots.length && new Set(parents2).size === parents2.length);
|
|
2611
2695
|
for (const root of roots) {
|
|
2612
2696
|
text(root);
|
|
2613
|
-
requireBound(
|
|
2697
|
+
requireBound(isAbsolute5(root) && resolve6(root) === root);
|
|
2614
2698
|
}
|
|
2615
2699
|
for (const parent of parents2) {
|
|
2616
2700
|
text(parent);
|
|
2617
|
-
requireBound(
|
|
2701
|
+
requireBound(isAbsolute5(parent) && resolve6(parent) === parent);
|
|
2618
2702
|
}
|
|
2619
2703
|
for (const directory of array(proof.directories, AGENT_POLICY_LIMITS.discoveryDirectories)) {
|
|
2620
2704
|
requireBound(object(directory));
|
|
@@ -2634,7 +2718,7 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2634
2718
|
text(input.namespace, 64);
|
|
2635
2719
|
text(input.pluginParent);
|
|
2636
2720
|
text(input.sourceRoot);
|
|
2637
|
-
requireBound(!ids.has(input.pluginId) && [input.pluginParent, input.sourceRoot].every((path) =>
|
|
2721
|
+
requireBound(!ids.has(input.pluginId) && [input.pluginParent, input.sourceRoot].every((path) => isAbsolute5(path) && resolve6(path) === path) && typeof input.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(input.sourceSha256));
|
|
2638
2722
|
ids.add(input.pluginId);
|
|
2639
2723
|
}
|
|
2640
2724
|
if (proof.directories !== undefined)
|
|
@@ -2648,14 +2732,14 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2648
2732
|
const proof = record(value.codexSemanticCache, 9), witness = record(proof.witness, 7);
|
|
2649
2733
|
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && [8, 9].includes(Object.keys(proof).length) && Object.keys(proof).every((key) => ["schema", "configPath", "hookRootAlias", "witness", "appOnlyParents", "previousPolicySha256", "supersededSources", "supersededDirectories", "reviewedHookParents"].includes(key)) && proof.schema === "hasna.codex-semantic-cache-discovery.v1" && typeof proof.previousPolicySha256 === "string" && /^[a-f0-9]{64}$/.test(proof.previousPolicySha256) && [6, 7].includes(Object.keys(witness).length) && Object.keys(witness).every((key) => ["schema", "cacheRoot", "controls", "appOnlyParents", "skills", "sha256", "hookParents"].includes(key)) && witness.schema === "hasna.codex-semantic-cache-witness.v1" && typeof witness.sha256 === "string" && /^[a-f0-9]{64}$/.test(witness.sha256));
|
|
2650
2734
|
text(proof.configPath);
|
|
2651
|
-
requireBound(
|
|
2735
|
+
requireBound(isAbsolute5(proof.configPath) && resolve6(proof.configPath) === proof.configPath && basename4(proof.configPath) === "config.toml");
|
|
2652
2736
|
text(witness.cacheRoot);
|
|
2653
|
-
requireBound(
|
|
2737
|
+
requireBound(isAbsolute5(witness.cacheRoot) && resolve6(witness.cacheRoot) === witness.cacheRoot);
|
|
2654
2738
|
if (proof.hookRootAlias !== null) {
|
|
2655
2739
|
requireBound(object(proof.hookRootAlias) && Object.keys(proof.hookRootAlias).length === 2);
|
|
2656
2740
|
for (const key of ["alias", "target"]) {
|
|
2657
2741
|
text(proof.hookRootAlias[key]);
|
|
2658
|
-
requireBound(
|
|
2742
|
+
requireBound(isAbsolute5(proof.hookRootAlias[key]) && resolve6(proof.hookRootAlias[key]) === proof.hookRootAlias[key]);
|
|
2659
2743
|
}
|
|
2660
2744
|
}
|
|
2661
2745
|
const parents2 = array(proof.appOnlyParents, 4096);
|
|
@@ -2663,7 +2747,7 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2663
2747
|
requireBound(object(parent) && Object.keys(parent).length === 5 && parent.role === "app-only");
|
|
2664
2748
|
for (const key of ["pluginId", "namespace", "pluginParent", "remotePluginId"])
|
|
2665
2749
|
text(parent[key]);
|
|
2666
|
-
requireBound(
|
|
2750
|
+
requireBound(isAbsolute5(parent.pluginParent) && resolve6(parent.pluginParent) === parent.pluginParent);
|
|
2667
2751
|
}
|
|
2668
2752
|
if (proof.reviewedHookParents !== undefined)
|
|
2669
2753
|
for (const parent of array(proof.reviewedHookParents, 4096)) {
|
|
@@ -2675,9 +2759,9 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2675
2759
|
text(parent.remotePluginId);
|
|
2676
2760
|
else {
|
|
2677
2761
|
text(parent.marketplaceSourcePath);
|
|
2678
|
-
requireBound(typeof parent.marketplaceSha256 === "string" && /^[a-f0-9]{64}$/.test(parent.marketplaceSha256) &&
|
|
2762
|
+
requireBound(typeof parent.marketplaceSha256 === "string" && /^[a-f0-9]{64}$/.test(parent.marketplaceSha256) && isAbsolute5(parent.marketplaceSourcePath) && resolve6(parent.marketplaceSourcePath) === parent.marketplaceSourcePath && basename4(parent.marketplaceSourcePath) === "marketplace.json");
|
|
2679
2763
|
}
|
|
2680
|
-
requireBound(
|
|
2764
|
+
requireBound(isAbsolute5(parent.pluginParent) && resolve6(parent.pluginParent) === parent.pluginParent);
|
|
2681
2765
|
}
|
|
2682
2766
|
for (const row of [...array(witness.controls, 4096), ...array(witness.appOnlyParents, 4096)]) {
|
|
2683
2767
|
requireBound(object(row));
|
|
@@ -2697,9 +2781,9 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2697
2781
|
text(row.remotePluginId);
|
|
2698
2782
|
else {
|
|
2699
2783
|
text(row.marketplaceSourcePath);
|
|
2700
|
-
requireBound(typeof row.marketplaceSha256 === "string" && /^[a-f0-9]{64}$/.test(row.marketplaceSha256) &&
|
|
2784
|
+
requireBound(typeof row.marketplaceSha256 === "string" && /^[a-f0-9]{64}$/.test(row.marketplaceSha256) && isAbsolute5(row.marketplaceSourcePath) && resolve6(row.marketplaceSourcePath) === row.marketplaceSourcePath && basename4(row.marketplaceSourcePath) === "marketplace.json");
|
|
2701
2785
|
}
|
|
2702
|
-
requireBound(
|
|
2786
|
+
requireBound(isAbsolute5(row.pluginParent) && resolve6(row.pluginParent) === row.pluginParent);
|
|
2703
2787
|
for (const key of ["manifestSha256", "hooksSha256"])
|
|
2704
2788
|
requireBound(typeof row[key] === "string" && /^[a-f0-9]{64}$/.test(row[key]));
|
|
2705
2789
|
if (row.installation === "remote-receipt")
|
|
@@ -2732,15 +2816,17 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2732
2816
|
text(source.path);
|
|
2733
2817
|
requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
|
|
2734
2818
|
if (source.hashMode !== undefined)
|
|
2735
|
-
requireBound(["bytes", "path-bytes", "claude-plugin-manifest-v1", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-marketplace-registry-v2", "codex-settings-v1", "codex-settings-v2", "codex-settings-v3", "codex-settings-v4", "codex-settings-v5", "codex-settings-v6", "executable-v1", "sumi-settings-v1", "claude-marketplace-entry-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode
|
|
2819
|
+
requireBound(["bytes", "path-bytes", CLAUDE_ORPHAN_MARKER_MODE, "claude-plugin-manifest-v1", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-marketplace-registry-v2", "codex-settings-v1", "codex-settings-v2", "codex-settings-v3", "codex-settings-v4", "codex-settings-v5", "codex-settings-v6", "executable-v1", "sumi-settings-v1", "claude-marketplace-entry-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (["bytes", CLAUDE_ORPHAN_MARKER_MODE].includes(source.hashMode) || source.sha256 !== null));
|
|
2820
|
+
if (source.hashMode === CLAUDE_ORPHAN_MARKER_MODE)
|
|
2821
|
+
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && isClaudePluginCacheOrphanMarker(source.path) && source.managedPlugins === undefined && source.reviewArtifact === undefined);
|
|
2736
2822
|
if (source.hashMode === "executable-v1")
|
|
2737
|
-
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && value.sources.some((row) => row.hashMode === "codex-settings-v6") &&
|
|
2823
|
+
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && value.sources.some((row) => row.hashMode === "codex-settings-v6") && isAbsolute5(source.path) && resolve6(source.path) === source.path);
|
|
2738
2824
|
if (source.hashMode === "codex-settings-v6") {
|
|
2739
2825
|
const seen = new Set;
|
|
2740
2826
|
for (const hook of array(source.hookSources, AGENT_POLICY_LIMITS.discoverySources)) {
|
|
2741
2827
|
requireBound(object(hook) && Object.keys(hook).length === 2 && Object.keys(hook).every((key) => ["path", "sha256"].includes(key)));
|
|
2742
2828
|
text(hook.path);
|
|
2743
|
-
requireBound(
|
|
2829
|
+
requireBound(isAbsolute5(hook.path) && resolve6(hook.path) === hook.path && !seen.has(hook.path));
|
|
2744
2830
|
seen.add(hook.path);
|
|
2745
2831
|
requireBound(hook.sha256 === null || typeof hook.sha256 === "string" && /^[a-f0-9]{64}$/.test(hook.sha256));
|
|
2746
2832
|
requireBound(value.sources.some((row) => row.path === hook.path && row.sha256 === hook.sha256 && [undefined, "bytes"].includes(row.hashMode) && row.format === undefined && row.fields === undefined));
|
|
@@ -2748,15 +2834,15 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2748
2834
|
} else
|
|
2749
2835
|
requireBound(source.hookSources === undefined);
|
|
2750
2836
|
if (source.hashMode === "claude-plugin-manifest-v1")
|
|
2751
|
-
requireBound(agent === "claude" && value.agent === "claude" && ["automatic", "reviewed"].includes(value.method) && !/[\x00-\x1f\x7f]/.test(source.path) &&
|
|
2837
|
+
requireBound(agent === "claude" && value.agent === "claude" && ["automatic", "reviewed"].includes(value.method) && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute5(source.path) && resolve6(source.path) === source.path && source.path.endsWith(join5(sep2, ".claude-plugin", "plugin.json")) && source.managedPlugins === undefined);
|
|
2752
2838
|
if (source.hashMode === "sumi-settings-v1")
|
|
2753
|
-
requireBound(agent === "sumi" && value.agent === "sumi" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) &&
|
|
2839
|
+
requireBound(agent === "sumi" && value.agent === "sumi" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute5(source.path) && resolve6(source.path) === source.path && basename4(source.path) === "sumi.json");
|
|
2754
2840
|
if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4" || source.hashMode === "codex-settings-v5" || source.hashMode === "codex-settings-v6"))
|
|
2755
|
-
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) &&
|
|
2841
|
+
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute5(source.path) && resolve6(source.path) === source.path && basename4(source.path) === "config.toml");
|
|
2756
2842
|
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4"))
|
|
2757
|
-
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) &&
|
|
2843
|
+
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute5(source.path) && resolve6(source.path) === source.path && basename4(source.path) === "settings.json");
|
|
2758
2844
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
|
|
2759
|
-
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) &&
|
|
2845
|
+
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute5(source.path) && resolve6(source.path) === source.path && basename4(source.path) === "known_marketplaces.json");
|
|
2760
2846
|
if (source.hashMode === "claude-marketplace-entry-v1")
|
|
2761
2847
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && claudeMarketplaceEntrySourceValid(source));
|
|
2762
2848
|
if (source.hashMode === "claude-plugin-registry") {
|
|
@@ -2775,7 +2861,7 @@ function assertAgentPolicyCollections(policy) {
|
|
|
2775
2861
|
requireBound(agent === "codex" && value.method === "reviewed" && source.hashMode === "bytes" && object(artifact) && Object.keys(artifact).length === 6 && Object.keys(artifact).every((key) => ["version", "kind", "storeRoot", "originalPath", "sha256", "catalogSha256"].includes(key)) && artifact.version === 1 && artifact.kind === "codex-native-catalog" && artifact.sha256 === source.sha256 && /^[a-f0-9]{64}$/.test(artifact.catalogSha256));
|
|
2776
2862
|
for (const path of [artifact.storeRoot, artifact.originalPath]) {
|
|
2777
2863
|
text(path);
|
|
2778
|
-
requireBound(
|
|
2864
|
+
requireBound(isAbsolute5(path) && resolve6(path) === path);
|
|
2779
2865
|
}
|
|
2780
2866
|
requireBound(source.path === join5(artifact.storeRoot, "agent-discovery", "artifacts", artifact.sha256, "catalog.json"));
|
|
2781
2867
|
}
|
|
@@ -2831,14 +2917,14 @@ function readManagedSkillPolicySnapshot(dataDir2 = getDataDirReadOnly()) {
|
|
|
2831
2917
|
const path = join6(dataDir2, "agent-policy.json");
|
|
2832
2918
|
let descriptor, observed = false;
|
|
2833
2919
|
try {
|
|
2834
|
-
const stat =
|
|
2920
|
+
const stat = lstatSync5(path);
|
|
2835
2921
|
observed = true;
|
|
2836
2922
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
2837
2923
|
throw refusal();
|
|
2838
2924
|
if (stat.size > AGENT_POLICY_LIMITS.bytes)
|
|
2839
2925
|
throw refusal("exceeds its size limit");
|
|
2840
|
-
descriptor =
|
|
2841
|
-
const opened =
|
|
2926
|
+
descriptor = openSync5(path, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0) | constants5.O_NONBLOCK);
|
|
2927
|
+
const opened = fstatSync5(descriptor);
|
|
2842
2928
|
if (!opened.isFile())
|
|
2843
2929
|
throw refusal();
|
|
2844
2930
|
if (opened.size > AGENT_POLICY_LIMITS.bytes)
|
|
@@ -2846,7 +2932,7 @@ function readManagedSkillPolicySnapshot(dataDir2 = getDataDirReadOnly()) {
|
|
|
2846
2932
|
const bytes = Buffer.allocUnsafe(AGENT_POLICY_LIMITS.bytes + 1);
|
|
2847
2933
|
let length = 0;
|
|
2848
2934
|
while (length < bytes.length) {
|
|
2849
|
-
const count =
|
|
2935
|
+
const count = readSync4(descriptor, bytes, length, bytes.length - length, null);
|
|
2850
2936
|
if (count === 0)
|
|
2851
2937
|
break;
|
|
2852
2938
|
length += count;
|
|
@@ -2857,7 +2943,7 @@ function readManagedSkillPolicySnapshot(dataDir2 = getDataDirReadOnly()) {
|
|
|
2857
2943
|
return { text: text2, value: parseManagedSkillPolicy(text2) };
|
|
2858
2944
|
} catch (error) {
|
|
2859
2945
|
if (error.code === "ENOENT" && !observed) {
|
|
2860
|
-
if (
|
|
2946
|
+
if (lstatSync5(path, { throwIfNoEntry: false }) === undefined)
|
|
2861
2947
|
return null;
|
|
2862
2948
|
}
|
|
2863
2949
|
if (error instanceof SkillSelectionError)
|
|
@@ -2865,7 +2951,7 @@ function readManagedSkillPolicySnapshot(dataDir2 = getDataDirReadOnly()) {
|
|
|
2865
2951
|
throw refusal();
|
|
2866
2952
|
} finally {
|
|
2867
2953
|
if (descriptor !== undefined)
|
|
2868
|
-
|
|
2954
|
+
closeSync5(descriptor);
|
|
2869
2955
|
}
|
|
2870
2956
|
}
|
|
2871
2957
|
function readManagedSkillPolicy(dataDir2 = getDataDirReadOnly()) {
|
|
@@ -2887,7 +2973,7 @@ function detectedIntegrationAgents(options = {}) {
|
|
|
2887
2973
|
throw new Error("Invalid existing bridge agent inventory");
|
|
2888
2974
|
const present = (path) => {
|
|
2889
2975
|
try {
|
|
2890
|
-
return
|
|
2976
|
+
return lstatSync6(path, { throwIfNoEntry: false }) !== undefined;
|
|
2891
2977
|
} catch {
|
|
2892
2978
|
throw new Error("Agent presence cannot be inspected; review native configuration before hook installation");
|
|
2893
2979
|
}
|
|
@@ -2897,13 +2983,13 @@ function detectedIntegrationAgents(options = {}) {
|
|
|
2897
2983
|
return true;
|
|
2898
2984
|
if (agent === "sumi") {
|
|
2899
2985
|
const xdg = env.XDG_CONFIG_HOME;
|
|
2900
|
-
const xdgSumi = xdg?.trim() ?
|
|
2901
|
-
return ["SUMI_HOME", "SUMI_CONFIG_DIR", "SUMI_CONFIG", "SUMI_CONFIG_CONTENT"].some((key) => env[key] !== undefined) || Bun.which("sumi-paths", { PATH: env.PATH ?? "" }) !== null || NATIVE_SKILL_ROOTS.some(([provider, root]) => provider === "sumi" && present(join7(home,
|
|
2986
|
+
const xdgSumi = xdg?.trim() ? resolve7(options.cwd ?? process.cwd(), xdg === "~" ? home : xdg.startsWith("~/") || xdg.startsWith("~\\") ? resolve7(home, xdg.slice(2)) : xdg, "sumi") : null;
|
|
2987
|
+
return ["SUMI_HOME", "SUMI_CONFIG_DIR", "SUMI_CONFIG", "SUMI_CONFIG_CONTENT"].some((key) => env[key] !== undefined) || Bun.which("sumi-paths", { PATH: env.PATH ?? "" }) !== null || NATIVE_SKILL_ROOTS.some(([provider, root]) => provider === "sumi" && present(join7(home, dirname6(root)))) || present(join7(home, ".config", "sumi")) || xdgSumi !== null && present(xdgSumi);
|
|
2902
2988
|
}
|
|
2903
2989
|
if (agent === "hermes" && env.HERMES_HOME !== undefined)
|
|
2904
2990
|
return true;
|
|
2905
2991
|
const adapter = AGENT_ADAPTERS[agent];
|
|
2906
|
-
return present(join7(home,
|
|
2992
|
+
return present(join7(home, dirname6(adapter.config))) || NATIVE_SKILL_ROOTS.some(([provider, root]) => provider === agent && dirname6(root) === `.${agent}` && present(join7(home, dirname6(root))));
|
|
2907
2993
|
});
|
|
2908
2994
|
if (!selected.length && !options.allowEmpty)
|
|
2909
2995
|
throw new Error(`No configured or detected agents; select --agent <agent> explicitly (${INTEGRATION_AGENTS.join(", ")}).`);
|
|
@@ -2912,8 +2998,8 @@ function detectedIntegrationAgents(options = {}) {
|
|
|
2912
2998
|
|
|
2913
2999
|
// src/lib/agent-sumi.ts
|
|
2914
3000
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2915
|
-
import { accessSync, constants as
|
|
2916
|
-
import { delimiter, isAbsolute as
|
|
3001
|
+
import { accessSync, constants as constants6, existsSync as existsSync4, lstatSync as lstatSync7, realpathSync, statSync as statSync3 } from "fs";
|
|
3002
|
+
import { delimiter, isAbsolute as isAbsolute6, join as join8, resolve as resolve8 } from "path";
|
|
2917
3003
|
|
|
2918
3004
|
// src/lib/hook-diagnostics.ts
|
|
2919
3005
|
var CONTEXT_ERROR_CODES = new Set([
|
|
@@ -3002,10 +3088,10 @@ function recordWithKeys(value, keys) {
|
|
|
3002
3088
|
return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
3003
3089
|
}
|
|
3004
3090
|
function absolutePath2(value) {
|
|
3005
|
-
return typeof value === "string" && value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) &&
|
|
3091
|
+
return typeof value === "string" && value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute6(value) && resolve8(value) === value;
|
|
3006
3092
|
}
|
|
3007
3093
|
function absoluteInput(value) {
|
|
3008
|
-
return value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) &&
|
|
3094
|
+
return value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute6(value);
|
|
3009
3095
|
}
|
|
3010
3096
|
function resolveSumiPathsCommand(pathValue) {
|
|
3011
3097
|
if (!pathValue)
|
|
@@ -3013,10 +3099,10 @@ function resolveSumiPathsCommand(pathValue) {
|
|
|
3013
3099
|
for (const directory of pathValue.split(delimiter)) {
|
|
3014
3100
|
if (!directory || /[\x00-\x1f\x7f]/.test(directory))
|
|
3015
3101
|
continue;
|
|
3016
|
-
const candidate =
|
|
3102
|
+
const candidate = resolve8(directory, "sumi-paths");
|
|
3017
3103
|
try {
|
|
3018
|
-
accessSync(candidate,
|
|
3019
|
-
if (
|
|
3104
|
+
accessSync(candidate, constants6.X_OK);
|
|
3105
|
+
if (statSync3(candidate).isFile())
|
|
3020
3106
|
return candidate;
|
|
3021
3107
|
} catch {}
|
|
3022
3108
|
}
|
|
@@ -3025,7 +3111,7 @@ function resolveSumiPathsCommand(pathValue) {
|
|
|
3025
3111
|
function validPathPlan(value, home, cwd) {
|
|
3026
3112
|
if (!recordWithKeys(value, ["schemaVersion", "kind", "home", "cwd", "roots", "legacyRoots", "configFiles", "skillRoots"]))
|
|
3027
3113
|
return false;
|
|
3028
|
-
if (value.schemaVersion !== 1 || value.kind !== "sumi-paths" || value.home !==
|
|
3114
|
+
if (value.schemaVersion !== 1 || value.kind !== "sumi-paths" || value.home !== resolve8(home) || value.cwd !== resolve8(cwd))
|
|
3029
3115
|
return false;
|
|
3030
3116
|
if (!recordWithKeys(value.roots, SUMI_PATH_KEYS) || !recordWithKeys(value.legacyRoots, SUMI_PATH_KEYS))
|
|
3031
3117
|
return false;
|
|
@@ -3061,7 +3147,7 @@ function sumiPathPlan(home, cwd = process.cwd(), env = process.env) {
|
|
|
3061
3147
|
throw new SumiPathResolverError("SUMI_PATH_INPUT_REFUSED");
|
|
3062
3148
|
const runtimePath = process.env.PATH ?? "";
|
|
3063
3149
|
const executable = resolveSumiPathsCommand(runtimePath);
|
|
3064
|
-
if (!executable || !
|
|
3150
|
+
if (!executable || !isAbsolute6(executable))
|
|
3065
3151
|
throw new SumiPathResolverError("SUMI_PATH_RESOLVER_UNAVAILABLE");
|
|
3066
3152
|
const childEnv = { PATH: runtimePath };
|
|
3067
3153
|
for (const key of SUMI_PATH_SELECTORS)
|
|
@@ -3069,8 +3155,8 @@ function sumiPathPlan(home, cwd = process.cwd(), env = process.env) {
|
|
|
3069
3155
|
childEnv[key] = env[key];
|
|
3070
3156
|
let child2;
|
|
3071
3157
|
try {
|
|
3072
|
-
child2 = spawnSync2(executable, ["--json", "--home",
|
|
3073
|
-
cwd:
|
|
3158
|
+
child2 = spawnSync2(executable, ["--json", "--home", resolve8(home), "--cwd", resolve8(cwd)], {
|
|
3159
|
+
cwd: resolve8(cwd),
|
|
3074
3160
|
env: childEnv,
|
|
3075
3161
|
encoding: "buffer",
|
|
3076
3162
|
timeout: 3000,
|
|
@@ -3102,7 +3188,7 @@ function selectedConfigDirectory(plan) {
|
|
|
3102
3188
|
const canonical3 = plan.roots.config, legacy = plan.legacyRoots.config;
|
|
3103
3189
|
if (legacy === null)
|
|
3104
3190
|
return existsSync4(canonical3) ? realpathSync(canonical3) : canonical3;
|
|
3105
|
-
const canonicalStat =
|
|
3191
|
+
const canonicalStat = lstatSync7(canonical3, { throwIfNoEntry: false }), legacyStat = lstatSync7(legacy, { throwIfNoEntry: false });
|
|
3106
3192
|
if (canonicalStat && legacyStat && realpathSync(canonical3) !== realpathSync(legacy))
|
|
3107
3193
|
throw new Error("Sumi config roots conflict; complete native home adoption before Skills integration");
|
|
3108
3194
|
const selected = canonicalStat ? canonical3 : legacyStat ? legacy : canonical3;
|
|
@@ -3247,7 +3333,7 @@ function runtimePrerequisites(home, cwd) {
|
|
|
3247
3333
|
checked: [],
|
|
3248
3334
|
code: null
|
|
3249
3335
|
};
|
|
3250
|
-
if (!
|
|
3336
|
+
if (!isAbsolute7(home) || !isAbsolute7(cwd))
|
|
3251
3337
|
return { ...result, code: "RUNTIME_PREREQUISITE_INPUT_REFUSED" };
|
|
3252
3338
|
let agents;
|
|
3253
3339
|
try {
|