@hasna/skills 0.10.33 → 0.10.35
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 +26 -0
- package/bin/index.js +610 -345
- package/bin/mcp.js +124 -123
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/runtime-copyfile.d.ts +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +398 -214
- package/dist/lib/agent-integration.d.ts +27 -0
- package/dist/sdk/index.js +189 -6
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -36868,7 +36868,7 @@ var package_default;
|
|
|
36868
36868
|
var init_package = __esm(() => {
|
|
36869
36869
|
package_default = {
|
|
36870
36870
|
name: "@hasna/skills",
|
|
36871
|
-
version: "0.10.
|
|
36871
|
+
version: "0.10.35",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -52593,18 +52593,80 @@ var init_agent_sumi = __esm(() => {
|
|
|
52593
52593
|
];
|
|
52594
52594
|
});
|
|
52595
52595
|
|
|
52596
|
+
// src/lib/codex-hook-trust-files.ts
|
|
52597
|
+
import { closeSync as closeSync8, constants as constants8, fstatSync as fstatSync8, fsyncSync as fsyncSync2, lstatSync as lstatSync16, openSync as openSync8, readSync as readSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
52598
|
+
import { createHash as createHash12 } from "crypto";
|
|
52599
|
+
import { dirname as dirname11 } from "path";
|
|
52600
|
+
function need5(value, code) {
|
|
52601
|
+
if (!value)
|
|
52602
|
+
throw new Error(`CODEX_HOOK_TRUST_${code}`);
|
|
52603
|
+
}
|
|
52604
|
+
function safeParents(file) {
|
|
52605
|
+
for (let p = dirname11(file);; p = dirname11(p)) {
|
|
52606
|
+
const s = lstatSync16(p);
|
|
52607
|
+
need5(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
|
|
52608
|
+
need5((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
52609
|
+
if (dirname11(p) === p)
|
|
52610
|
+
break;
|
|
52611
|
+
}
|
|
52612
|
+
}
|
|
52613
|
+
function snapshot(file, privateFile = false, options = {}) {
|
|
52614
|
+
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
52615
|
+
safeParents(file);
|
|
52616
|
+
const initial2 = lstatSync16(file, { bigint: true });
|
|
52617
|
+
need5(initial2.isFile() && initial2.uid === BigInt(process.getuid()) && (initial2.nlink === 1n || readOnlyPackage && initial2.nlink > 1n) && (initial2.mode & 0o022n) === 0n && (!privateFile || (initial2.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
|
|
52618
|
+
need5(initial2.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
52619
|
+
const fd = openSync8(file, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
|
|
52620
|
+
try {
|
|
52621
|
+
const before2 = fstatSync8(fd, { bigint: true });
|
|
52622
|
+
need5(sameStat(initial2, before2), "INPUT_CHANGED");
|
|
52623
|
+
const bytes = Buffer.alloc(Number(before2.size) + 1);
|
|
52624
|
+
let length = 0;
|
|
52625
|
+
while (length < bytes.length) {
|
|
52626
|
+
const n2 = readSync6(fd, bytes, length, bytes.length - length, null);
|
|
52627
|
+
if (!n2)
|
|
52628
|
+
break;
|
|
52629
|
+
length += n2;
|
|
52630
|
+
}
|
|
52631
|
+
need5(length === Number(before2.size) && sameStat(before2, fstatSync8(fd, { bigint: true })) && sameStat(before2, lstatSync16(file, { bigint: true })), "INPUT_CHANGED");
|
|
52632
|
+
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
52633
|
+
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before2, sha256: createHash12("sha256").update(content).digest("hex") };
|
|
52634
|
+
} finally {
|
|
52635
|
+
closeSync8(fd);
|
|
52636
|
+
}
|
|
52637
|
+
}
|
|
52638
|
+
function unchanged2(before2) {
|
|
52639
|
+
const after2 = snapshot(before2.file, before2.privateFile, before2.readOnlyPackage ? { readOnlyPackage: true } : {});
|
|
52640
|
+
need5(sameStat(before2.stat, after2.stat) && after2.sha256 === before2.sha256, "INPUT_CHANGED");
|
|
52641
|
+
}
|
|
52642
|
+
function save(file, content) {
|
|
52643
|
+
safeParents(file);
|
|
52644
|
+
const fd = openSync8(file, constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW, 384);
|
|
52645
|
+
try {
|
|
52646
|
+
writeFileSync4(fd, content);
|
|
52647
|
+
fsyncSync2(fd);
|
|
52648
|
+
} finally {
|
|
52649
|
+
closeSync8(fd);
|
|
52650
|
+
}
|
|
52651
|
+
}
|
|
52652
|
+
var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
|
|
52653
|
+
var init_codex_hook_trust_files = () => {};
|
|
52654
|
+
|
|
52596
52655
|
// src/lib/agent-integration.ts
|
|
52597
|
-
import { createHash as
|
|
52656
|
+
import { createHash as createHash13, randomUUID as randomUUID5 } from "crypto";
|
|
52598
52657
|
import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
|
|
52599
|
-
import { existsSync as existsSync13, lstatSync as
|
|
52600
|
-
import { dirname as
|
|
52658
|
+
import { existsSync as existsSync13, lstatSync as lstatSync17, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync13, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync7, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, chmodSync, openSync as openSync9, closeSync as closeSync9, fsyncSync as fsyncSync3, fstatSync as fstatSync9, readSync as readSync7, constants as constants9, linkSync as linkSync2 } from "fs";
|
|
52659
|
+
import { dirname as dirname12, join as join23, resolve as resolve12, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
|
|
52601
52660
|
import { homedir as homedir3 } from "os";
|
|
52602
|
-
function
|
|
52661
|
+
function hasTrustedDarwinAcl(fd, allowDenyOnly = false) {
|
|
52603
52662
|
try {
|
|
52604
|
-
const { dlopen, read: read2 } = __require("bun:ffi");
|
|
52663
|
+
const { dlopen, read: read2, ptr } = __require("bun:ffi");
|
|
52605
52664
|
const library = dlopen("/usr/lib/libSystem.B.dylib", {
|
|
52606
52665
|
acl_get_fd: { args: ["i32"], returns: "ptr" },
|
|
52607
52666
|
acl_free: { args: ["ptr"], returns: "i32" },
|
|
52667
|
+
acl_valid: { args: ["ptr"], returns: "i32" },
|
|
52668
|
+
acl_get_entry: { args: ["ptr", "i32", "ptr"], returns: "i32" },
|
|
52669
|
+
acl_get_tag_type: { args: ["ptr", "ptr"], returns: "i32" },
|
|
52608
52670
|
__error: { args: [], returns: "ptr" }
|
|
52609
52671
|
});
|
|
52610
52672
|
try {
|
|
@@ -52613,8 +52675,23 @@ function hasNoDarwinAcl(fd) {
|
|
|
52613
52675
|
return false;
|
|
52614
52676
|
const acl = library.symbols.acl_get_fd(fd);
|
|
52615
52677
|
if (acl) {
|
|
52616
|
-
|
|
52617
|
-
|
|
52678
|
+
try {
|
|
52679
|
+
if (!allowDenyOnly || library.symbols.acl_valid(acl) !== 0)
|
|
52680
|
+
return false;
|
|
52681
|
+
const entry = new BigUint64Array(1), tag = new Int32Array(1);
|
|
52682
|
+
for (let count = 0;count <= 128; count++) {
|
|
52683
|
+
const status = library.symbols.acl_get_entry(acl, count === 0 ? 0 : -1, ptr(entry));
|
|
52684
|
+
if (status !== 0) {
|
|
52685
|
+
return status === -1 && read2.i32(errno) === 22 && library.symbols.acl_valid(acl) === 0;
|
|
52686
|
+
}
|
|
52687
|
+
const value = read2.ptr(ptr(entry));
|
|
52688
|
+
if (count === 128 || !value || library.symbols.acl_get_tag_type(value, ptr(tag)) !== 0 || tag[0] !== 2)
|
|
52689
|
+
return false;
|
|
52690
|
+
}
|
|
52691
|
+
return false;
|
|
52692
|
+
} finally {
|
|
52693
|
+
library.symbols.acl_free(acl);
|
|
52694
|
+
}
|
|
52618
52695
|
}
|
|
52619
52696
|
return (acl === null || acl === 0) && read2.i32(errno) === 2;
|
|
52620
52697
|
} finally {
|
|
@@ -52630,11 +52707,11 @@ function isSystemRootAlias(path) {
|
|
|
52630
52707
|
const target = `/private${path}`, link2 = `private${path}`;
|
|
52631
52708
|
const identity5 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
|
|
52632
52709
|
try {
|
|
52633
|
-
const before2 =
|
|
52710
|
+
const before2 = lstatSync17(path, { bigint: true });
|
|
52634
52711
|
if (!before2.isSymbolicLink() || before2.uid !== 0n || readlinkSync4(path) !== link2)
|
|
52635
52712
|
return false;
|
|
52636
52713
|
const directories = ["/", "/private", target].map((directory) => {
|
|
52637
|
-
const stat =
|
|
52714
|
+
const stat = lstatSync17(directory, { bigint: true });
|
|
52638
52715
|
if (!stat.isDirectory() || stat.uid !== 0n)
|
|
52639
52716
|
throw new Error("Untrusted system alias directory");
|
|
52640
52717
|
if (directory !== "/private/tmp") {
|
|
@@ -52642,23 +52719,23 @@ function isSystemRootAlias(path) {
|
|
|
52642
52719
|
throw new Error("Writable system alias directory");
|
|
52643
52720
|
} else if ((stat.mode & 0o1000n) === 0n)
|
|
52644
52721
|
throw new Error("Unprotected system temporary directory");
|
|
52645
|
-
const fd =
|
|
52722
|
+
const fd = openSync9(directory, constants9.O_RDONLY | constants9.O_DIRECTORY | constants9.O_NOFOLLOW);
|
|
52646
52723
|
try {
|
|
52647
|
-
if (identity5(
|
|
52724
|
+
if (identity5(fstatSync9(fd, { bigint: true })) !== identity5(stat) || !hasTrustedDarwinAcl(fd) || identity5(fstatSync9(fd, { bigint: true })) !== identity5(stat))
|
|
52648
52725
|
throw new Error("Unverified system alias directory ACL");
|
|
52649
52726
|
} finally {
|
|
52650
|
-
|
|
52727
|
+
closeSync9(fd);
|
|
52651
52728
|
}
|
|
52652
52729
|
return { directory, identity: identity5(stat) };
|
|
52653
52730
|
});
|
|
52654
|
-
return realpathSync7(path) === target && readlinkSync4(path) === link2 && identity5(
|
|
52731
|
+
return realpathSync7(path) === target && readlinkSync4(path) === link2 && identity5(lstatSync17(path, { bigint: true })) === identity5(before2) && directories.every((item) => identity5(lstatSync17(item.directory, { bigint: true })) === item.identity);
|
|
52655
52732
|
} catch {
|
|
52656
52733
|
return false;
|
|
52657
52734
|
}
|
|
52658
52735
|
}
|
|
52659
52736
|
function canonicalSystemPath(path) {
|
|
52660
52737
|
const absolute = resolve12(path), root = `/${absolute.split(sep5)[1]}`;
|
|
52661
|
-
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !
|
|
52738
|
+
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync17(root, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
52662
52739
|
return absolute;
|
|
52663
52740
|
if (!isSystemRootAlias(root))
|
|
52664
52741
|
throw new Error(`Refusing symlink path: ${root}`);
|
|
@@ -52667,9 +52744,9 @@ function canonicalSystemPath(path) {
|
|
|
52667
52744
|
function assertSafePath(path) {
|
|
52668
52745
|
let cursor = resolve12(path);
|
|
52669
52746
|
while (true) {
|
|
52670
|
-
if (
|
|
52747
|
+
if (lstatSync17(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
|
|
52671
52748
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
52672
|
-
const parent =
|
|
52749
|
+
const parent = dirname12(cursor);
|
|
52673
52750
|
if (parent === cursor)
|
|
52674
52751
|
return;
|
|
52675
52752
|
cursor = parent;
|
|
@@ -52678,12 +52755,12 @@ function assertSafePath(path) {
|
|
|
52678
52755
|
function readRootAlias(home, agent) {
|
|
52679
52756
|
assertSafePath(home);
|
|
52680
52757
|
const realHome = realpathSync7(home), alias = join23(realHome, `.${agent}`);
|
|
52681
|
-
const stat =
|
|
52758
|
+
const stat = lstatSync17(alias, { throwIfNoEntry: false, bigint: true });
|
|
52682
52759
|
if (!stat?.isSymbolicLink())
|
|
52683
52760
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
52684
|
-
const link2 = readlinkSync4(alias), targetPath = resolve12(
|
|
52761
|
+
const link2 = readlinkSync4(alias), targetPath = resolve12(dirname12(alias), link2);
|
|
52685
52762
|
assertSafePath(targetPath);
|
|
52686
|
-
const targetStat =
|
|
52763
|
+
const targetStat = lstatSync17(targetPath, { throwIfNoEntry: false });
|
|
52687
52764
|
if (!targetStat?.isDirectory())
|
|
52688
52765
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
52689
52766
|
const target = realpathSync7(targetPath), inside = relative5(realHome, target);
|
|
@@ -52696,7 +52773,7 @@ function rootAliases(home, allowed = false) {
|
|
|
52696
52773
|
return [];
|
|
52697
52774
|
const result2 = [];
|
|
52698
52775
|
for (const agent of ["claude", "codex"])
|
|
52699
|
-
if (
|
|
52776
|
+
if (lstatSync17(join23(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
52700
52777
|
result2.push(readRootAlias(home, agent));
|
|
52701
52778
|
if (result2.some((item, index) => result2.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep5) || other.target.startsWith(item.target + sep5))))
|
|
52702
52779
|
throw new Error("Agent root alias targets must not overlap");
|
|
@@ -52731,7 +52808,7 @@ function disabledCodexSkillPaths(config, aliases) {
|
|
|
52731
52808
|
continue;
|
|
52732
52809
|
settings.set(document3, [...settings.get(document3) ?? [], entry.enabled === false]);
|
|
52733
52810
|
}
|
|
52734
|
-
return [...settings].filter(([, values2]) => values2.length === 1 && values2[0] === true).map(([document3]) =>
|
|
52811
|
+
return [...settings].filter(([, values2]) => values2.length === 1 && values2[0] === true).map(([document3]) => dirname12(document3));
|
|
52735
52812
|
}
|
|
52736
52813
|
function recheckRootAliases(aliases) {
|
|
52737
52814
|
for (const binding of aliases) {
|
|
@@ -52744,35 +52821,35 @@ function recheckRootAliases(aliases) {
|
|
|
52744
52821
|
}
|
|
52745
52822
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
52746
52823
|
assertSafePath(path);
|
|
52747
|
-
const before2 =
|
|
52824
|
+
const before2 = lstatSync17(path);
|
|
52748
52825
|
if (!before2.isFile() || before2.size > maximum)
|
|
52749
52826
|
throw new Error("Unsupported or oversized native skill file");
|
|
52750
|
-
const descriptor =
|
|
52827
|
+
const descriptor = openSync9(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
|
|
52751
52828
|
try {
|
|
52752
|
-
const opened =
|
|
52829
|
+
const opened = fstatSync9(descriptor);
|
|
52753
52830
|
if (!opened.isFile() || opened.dev !== before2.dev || opened.ino !== before2.ino || opened.size !== before2.size)
|
|
52754
52831
|
throw new Error("Native skill file changed while reading");
|
|
52755
52832
|
const bytes = Buffer.allocUnsafe(before2.size + 1);
|
|
52756
52833
|
let length = 0;
|
|
52757
52834
|
while (length < bytes.length) {
|
|
52758
|
-
const count =
|
|
52835
|
+
const count = readSync7(descriptor, bytes, length, bytes.length - length, null);
|
|
52759
52836
|
if (!count)
|
|
52760
52837
|
break;
|
|
52761
52838
|
length += count;
|
|
52762
52839
|
}
|
|
52763
|
-
const after2 =
|
|
52840
|
+
const after2 = fstatSync9(descriptor), current = lstatSync17(path);
|
|
52764
52841
|
if (current.dev !== opened.dev || current.ino !== opened.ino || current.size !== after2.size || current.mtimeMs !== after2.mtimeMs || current.ctimeMs !== after2.ctimeMs || length !== before2.size || after2.size !== before2.size || after2.mtimeMs !== opened.mtimeMs || after2.ctimeMs !== opened.ctimeMs)
|
|
52765
52842
|
throw new Error("Native skill file changed while reading");
|
|
52766
52843
|
return bytes.subarray(0, length);
|
|
52767
52844
|
} finally {
|
|
52768
|
-
|
|
52845
|
+
closeSync9(descriptor);
|
|
52769
52846
|
}
|
|
52770
52847
|
}
|
|
52771
52848
|
function treeHash(root) {
|
|
52772
|
-
const hash2 =
|
|
52849
|
+
const hash2 = createHash13("sha256");
|
|
52773
52850
|
let bytes = 0, files = 0;
|
|
52774
52851
|
function visit2(path) {
|
|
52775
|
-
const stat =
|
|
52852
|
+
const stat = lstatSync17(path);
|
|
52776
52853
|
if (stat.isSymbolicLink())
|
|
52777
52854
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
52778
52855
|
const rel = relative5(root, path);
|
|
@@ -52823,7 +52900,7 @@ function parseNativeMigrationTargetManifest(input) {
|
|
|
52823
52900
|
if (!isAbsolute11(item.projectRoot) || resolve12(item.projectRoot) !== item.projectRoot)
|
|
52824
52901
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${item.projectRoot}`);
|
|
52825
52902
|
const projectRoot = canonicalSystemPath(item.projectRoot);
|
|
52826
|
-
if (!existsSync13(projectRoot) || !
|
|
52903
|
+
if (!existsSync13(projectRoot) || !lstatSync17(projectRoot).isDirectory())
|
|
52827
52904
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${item.projectRoot}`);
|
|
52828
52905
|
assertSafePath(projectRoot);
|
|
52829
52906
|
if (isAbsolute11(item.path) || item.path.includes("\\") || item.path.length === 0 || item.path.includes("\x00"))
|
|
@@ -52853,7 +52930,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
52853
52930
|
if (!isAbsolute11(target.projectRoot) || resolve12(target.projectRoot) !== target.projectRoot)
|
|
52854
52931
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
52855
52932
|
const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot, resolve12(projectRoot, target.path));
|
|
52856
|
-
if (!existsSync13(projectRoot) || !
|
|
52933
|
+
if (!existsSync13(projectRoot) || !lstatSync17(projectRoot).isDirectory())
|
|
52857
52934
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
52858
52935
|
assertSafePath(projectRoot);
|
|
52859
52936
|
if (isAbsolute11(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep5}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
|
|
@@ -52882,7 +52959,7 @@ function projectAncestorDirectories(projects) {
|
|
|
52882
52959
|
if (depth >= 100)
|
|
52883
52960
|
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
52884
52961
|
directories.add(path);
|
|
52885
|
-
const parent =
|
|
52962
|
+
const parent = dirname12(path);
|
|
52886
52963
|
if (parent === path)
|
|
52887
52964
|
break;
|
|
52888
52965
|
path = parent;
|
|
@@ -52928,12 +53005,12 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
52928
53005
|
let reviewedCacheAliasFound = false;
|
|
52929
53006
|
function verifiedSiblingCacheAlias(agent, path, parent) {
|
|
52930
53007
|
const link2 = readlinkSync4(path), lexicalTarget = resolve12(parent, link2);
|
|
52931
|
-
if (isAbsolute11(link2) ? link2 !== lexicalTarget :
|
|
53008
|
+
if (isAbsolute11(link2) ? link2 !== lexicalTarget : dirname12(link2) !== ".")
|
|
52932
53009
|
return false;
|
|
52933
53010
|
const target = canonicalSystemPath(lexicalTarget);
|
|
52934
|
-
if (
|
|
53011
|
+
if (dirname12(target) !== parent)
|
|
52935
53012
|
return false;
|
|
52936
|
-
if (!
|
|
53013
|
+
if (!lstatSync17(target, { throwIfNoEntry: false })?.isDirectory())
|
|
52937
53014
|
return false;
|
|
52938
53015
|
const scan = cacheScans.get(target);
|
|
52939
53016
|
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
@@ -52958,7 +53035,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
52958
53035
|
return scan;
|
|
52959
53036
|
if (!existsSync13(path))
|
|
52960
53037
|
return scan;
|
|
52961
|
-
const stat =
|
|
53038
|
+
const stat = lstatSync17(path);
|
|
52962
53039
|
if (stat.isFile()) {
|
|
52963
53040
|
if (agent === "sumi" && depth === 1 && path.endsWith(".md"))
|
|
52964
53041
|
throw new Error("NATIVE_SKILL_DRIFT: flat Sumi skill documents require a dedicated migration adapter");
|
|
@@ -53007,7 +53084,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
53007
53084
|
if (isVendor && !options.includeVendor)
|
|
53008
53085
|
continue;
|
|
53009
53086
|
const child = join23(path, name);
|
|
53010
|
-
if (isVendor &&
|
|
53087
|
+
if (isVendor && lstatSync17(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
53011
53088
|
const target = statSync8(child, { throwIfNoEntry: false });
|
|
53012
53089
|
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child, path)) {
|
|
53013
53090
|
scan.entries++;
|
|
@@ -53341,6 +53418,171 @@ function planAgentIntegration(options) {
|
|
|
53341
53418
|
recheckRootAliases(aliases);
|
|
53342
53419
|
return { dataDir: dataDir3, profileId, changes, nativeSkills, ...observedNativeSources.length ? { observedNativeSources } : {}, ...nextPolicy.bridge.codexPluginSkillReview ? { codexPluginSkillReview: nextPolicy.bridge.codexPluginSkillReview } : {}, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
|
|
53343
53420
|
}
|
|
53421
|
+
function hookProjectionRefusal(reason) {
|
|
53422
|
+
throw new Error(`CLAUDE_MANAGED_HOOK_${reason}`);
|
|
53423
|
+
}
|
|
53424
|
+
function projectedHookAcl(path, observed) {
|
|
53425
|
+
if (process.platform !== "darwin")
|
|
53426
|
+
return;
|
|
53427
|
+
const identity5 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}:${stat.nlink}:${stat.size}:${stat.mtimeNs}`;
|
|
53428
|
+
const fd = openSync9(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK | (observed.isDirectory() ? constants9.O_DIRECTORY : 0));
|
|
53429
|
+
try {
|
|
53430
|
+
if (identity5(fstatSync9(fd, { bigint: true })) !== identity5(observed) || !hasTrustedDarwinAcl(fd, true) || identity5(fstatSync9(fd, { bigint: true })) !== identity5(observed) || identity5(lstatSync17(path, { bigint: true })) !== identity5(observed))
|
|
53431
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53432
|
+
} finally {
|
|
53433
|
+
closeSync9(fd);
|
|
53434
|
+
}
|
|
53435
|
+
}
|
|
53436
|
+
function projectedHookExecutable(command, expectedResolved) {
|
|
53437
|
+
try {
|
|
53438
|
+
if (!isAbsolute11(command) || /[\0\r\n]/.test(command) || resolve12(command) !== command)
|
|
53439
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53440
|
+
command = canonicalSystemPath(command);
|
|
53441
|
+
const parents2 = (file = command) => {
|
|
53442
|
+
const result2 = [];
|
|
53443
|
+
for (let path = dirname12(file);; path = dirname12(path)) {
|
|
53444
|
+
const stat = lstatSync17(path, { bigint: true });
|
|
53445
|
+
if (!stat.isDirectory() || (stat.mode & 0o022n) !== 0n && !(stat.uid === 0n && (stat.mode & 0o1000n) !== 0n))
|
|
53446
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53447
|
+
projectedHookAcl(path, stat);
|
|
53448
|
+
result2.push(`${path}:${stat.dev}:${stat.ino}:${stat.uid}:${stat.mode}`);
|
|
53449
|
+
if (dirname12(path) === path)
|
|
53450
|
+
break;
|
|
53451
|
+
}
|
|
53452
|
+
return result2;
|
|
53453
|
+
};
|
|
53454
|
+
const parentIdentity = parents2(), link2 = lstatSync17(command, { bigint: true });
|
|
53455
|
+
if (link2.uid !== BigInt(process.getuid()) || !link2.isFile() && !link2.isSymbolicLink())
|
|
53456
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53457
|
+
const linkIdentity = (stat) => `${stat.dev}:${stat.ino}:${stat.mode}:${stat.uid}:${stat.ctimeNs}`;
|
|
53458
|
+
const resolved = realpathSync7(command);
|
|
53459
|
+
if (expectedResolved !== undefined && resolved !== expectedResolved)
|
|
53460
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53461
|
+
const resolvedParentIdentity = parents2(resolved);
|
|
53462
|
+
const manifest = snapshot(join23(dirname12(dirname12(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
53463
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
53464
|
+
const pkg = JSON.parse(manifest.text);
|
|
53465
|
+
if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !== join23(dirname12(manifest.file), "bin/index.js"))
|
|
53466
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53467
|
+
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
53468
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
53469
|
+
if ((cli.stat.mode & 0o100n) === 0n)
|
|
53470
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53471
|
+
const recheck = () => {
|
|
53472
|
+
try {
|
|
53473
|
+
if (realpathSync7(command) !== resolved || linkIdentity(lstatSync17(command, { bigint: true })) !== linkIdentity(link2) || !isDeepStrictEqual5(parents2(), parentIdentity) || !isDeepStrictEqual5(parents2(resolved), resolvedParentIdentity))
|
|
53474
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53475
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
53476
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
53477
|
+
unchanged2(cli);
|
|
53478
|
+
unchanged2(manifest);
|
|
53479
|
+
} catch {
|
|
53480
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53481
|
+
}
|
|
53482
|
+
};
|
|
53483
|
+
recheck();
|
|
53484
|
+
return { resolved, sha256: cli.sha256, manifestSha256: manifest.sha256, recheck };
|
|
53485
|
+
} catch {
|
|
53486
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53487
|
+
}
|
|
53488
|
+
}
|
|
53489
|
+
function planClaudeManagedHookProjection(options) {
|
|
53490
|
+
if (typeof options.targetSettings !== "string" || Buffer.byteLength(options.targetSettings) > 1024 * 1024)
|
|
53491
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
53492
|
+
const targetSha256 = sha3(options.targetSettings);
|
|
53493
|
+
if (!/^[a-f0-9]{64}$/.test(options.expectedTargetSha256) || options.expectedTargetSha256 !== targetSha256)
|
|
53494
|
+
hookProjectionRefusal("TARGET_CHANGED");
|
|
53495
|
+
let target;
|
|
53496
|
+
try {
|
|
53497
|
+
target = jsonObject(options.targetSettings, "target");
|
|
53498
|
+
} catch {
|
|
53499
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
53500
|
+
}
|
|
53501
|
+
const home = resolve12(options.home ?? homedir3()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
|
|
53502
|
+
const policyPath = join23(dataDir3, "agent-policy.json");
|
|
53503
|
+
let policyBytes, settingsBytes, settingsPath, command, profile;
|
|
53504
|
+
try {
|
|
53505
|
+
policyBytes = readNativeBytes(policyPath, 1024 * 1024);
|
|
53506
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
|
|
53507
|
+
if (!snapshot2 || snapshot2.text !== policyBytes.toString("utf8"))
|
|
53508
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
53509
|
+
const binding = snapshot2.value.bridge;
|
|
53510
|
+
settingsPath = canonicalAgentPath(join23(home, AGENT_ADAPTERS.claude.config), binding?.rootAliases ?? []);
|
|
53511
|
+
settingsBytes = readNativeBytes(settingsPath, 1024 * 1024);
|
|
53512
|
+
command = binding?.commands?.claude;
|
|
53513
|
+
profile = binding?.profiles?.claude;
|
|
53514
|
+
if (typeof command !== "string" || typeof profile !== "string")
|
|
53515
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
53516
|
+
assertManagedAgentBridge("claude", { home, dataDir: dataDir3, projectDir: options.projectDir, profileId: profile });
|
|
53517
|
+
} catch {
|
|
53518
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
53519
|
+
}
|
|
53520
|
+
const executable = projectedHookExecutable(command);
|
|
53521
|
+
const sourceSha256 = sha3(JSON.stringify({
|
|
53522
|
+
home,
|
|
53523
|
+
policyPath,
|
|
53524
|
+
policy: sha3(policyBytes),
|
|
53525
|
+
settingsPath,
|
|
53526
|
+
settings: sha3(settingsBytes),
|
|
53527
|
+
executable: { path: executable.resolved, sha256: executable.sha256, manifestSha256: executable.manifestSha256 }
|
|
53528
|
+
}));
|
|
53529
|
+
if (options.expectedSourceSha256 !== undefined && options.expectedSourceSha256 !== sourceSha256)
|
|
53530
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
53531
|
+
const replacements2 = [];
|
|
53532
|
+
const observed = [executable];
|
|
53533
|
+
if (target.hooks !== undefined) {
|
|
53534
|
+
if (!target.hooks || typeof target.hooks !== "object" || Array.isArray(target.hooks))
|
|
53535
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
53536
|
+
for (const event of [...AGENT_ADAPTERS.claude.events, "PreToolUse"]) {
|
|
53537
|
+
const groups = target.hooks[event];
|
|
53538
|
+
if (groups === undefined)
|
|
53539
|
+
continue;
|
|
53540
|
+
if (!Array.isArray(groups))
|
|
53541
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
53542
|
+
let recognized = 0;
|
|
53543
|
+
for (const [groupIndex, group] of groups.entries()) {
|
|
53544
|
+
if (!group || !Array.isArray(group.hooks))
|
|
53545
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
53546
|
+
for (const [hookIndex, hook] of group.hooks.entries()) {
|
|
53547
|
+
if (hook?.type !== "command" || typeof hook.command !== "string")
|
|
53548
|
+
continue;
|
|
53549
|
+
if (!hook.command.includes("hook user-prompt --agent claude"))
|
|
53550
|
+
continue;
|
|
53551
|
+
const match = /^('(?:[^']|'\\'')*') hook user-prompt --agent claude --selection-profile ([A-Za-z0-9][A-Za-z0-9._-]{0,127}) --event ([A-Za-z]+)$/.exec(hook.command);
|
|
53552
|
+
if (!match || match[3] !== event)
|
|
53553
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
53554
|
+
const oldCommand = match[1].slice(1, -1).replaceAll("'\\''", "'");
|
|
53555
|
+
let rendered;
|
|
53556
|
+
try {
|
|
53557
|
+
rendered = renderAgentHookCommand(oldCommand, "claude", match[2], event);
|
|
53558
|
+
} catch {
|
|
53559
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
53560
|
+
}
|
|
53561
|
+
if (rendered !== hook.command)
|
|
53562
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
53563
|
+
const legacy = projectedHookExecutable(oldCommand, executable.resolved);
|
|
53564
|
+
if (legacy.resolved !== executable.resolved || legacy.sha256 !== executable.sha256 || legacy.manifestSha256 !== executable.manifestSha256)
|
|
53565
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
53566
|
+
observed.push(legacy);
|
|
53567
|
+
if (++recognized > 1)
|
|
53568
|
+
hookProjectionRefusal("AMBIGUOUS");
|
|
53569
|
+
const replacement = renderAgentHookCommand(command, "claude", profile, event);
|
|
53570
|
+
if (replacement !== hook.command)
|
|
53571
|
+
replacements2.push({ event, groupIndex, hookIndex, beforeSha256: sha3(hook.command), command: replacement });
|
|
53572
|
+
}
|
|
53573
|
+
}
|
|
53574
|
+
}
|
|
53575
|
+
}
|
|
53576
|
+
try {
|
|
53577
|
+
if (!readNativeBytes(policyPath, 1024 * 1024).equals(policyBytes) || !readNativeBytes(settingsPath, 1024 * 1024).equals(settingsBytes))
|
|
53578
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
53579
|
+
} catch {
|
|
53580
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
53581
|
+
}
|
|
53582
|
+
for (const item of observed)
|
|
53583
|
+
item.recheck();
|
|
53584
|
+
return { schema: "hasna.claude-managed-hook-projection.v1", targetSha256, sourceSha256, replacements: replacements2 };
|
|
53585
|
+
}
|
|
53344
53586
|
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
53345
53587
|
try {
|
|
53346
53588
|
if (binding.method !== "automatic" && binding.method !== "reviewed")
|
|
@@ -53357,10 +53599,10 @@ function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
|
53357
53599
|
}
|
|
53358
53600
|
function atomicWrite2(path, content) {
|
|
53359
53601
|
assertSafePath(path);
|
|
53360
|
-
mkdirSync5(
|
|
53602
|
+
mkdirSync5(dirname12(path), { recursive: true, mode: 448 });
|
|
53361
53603
|
const temporary = `${path}.skills-${randomUUID5()}`;
|
|
53362
53604
|
try {
|
|
53363
|
-
|
|
53605
|
+
writeFileSync5(temporary, content, { mode: 384, flag: "wx" });
|
|
53364
53606
|
renameSync2(temporary, path);
|
|
53365
53607
|
} finally {
|
|
53366
53608
|
if (existsSync13(temporary))
|
|
@@ -53371,10 +53613,10 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
53371
53613
|
if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3].includes(options.targetCodexVersion)))
|
|
53372
53614
|
throw new Error("Invalid Codex witness target version");
|
|
53373
53615
|
const home = resolve12(options.home ?? homedir3()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
|
|
53374
|
-
const
|
|
53375
|
-
if (!
|
|
53616
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
|
|
53617
|
+
if (!snapshot2 || sha3(snapshot2.text) !== options.expectedPolicySha256)
|
|
53376
53618
|
throw new Error("Managed policy preimage changed");
|
|
53377
|
-
const policy =
|
|
53619
|
+
const policy = snapshot2.value, aliases = policy.bridge?.rootAliases ?? [];
|
|
53378
53620
|
recheckRootAliases(aliases);
|
|
53379
53621
|
const binding = policy.bridge?.discovery?.[options.agent];
|
|
53380
53622
|
if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
|
|
@@ -53418,7 +53660,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
53418
53660
|
const replacement = { ...binding, sources: rebound };
|
|
53419
53661
|
verifyAgentDiscovery(replacement);
|
|
53420
53662
|
const after2 = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
|
|
53421
|
-
return { dataDir: dataDir3, profileId: policy.profileId, changes: [{ path: join23(dataDir3, "agent-policy.json"), before:
|
|
53663
|
+
return { dataDir: dataDir3, profileId: policy.profileId, changes: [{ path: join23(dataDir3, "agent-policy.json"), before: snapshot2.text, after: after2 }], nativeSkills: [], observedPolicy: { path: join23(dataDir3, "agent-policy.json"), before: snapshot2.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], rootAliases: aliases, managedAgentChecks: { home, agents: [options.agent] }, settingsWitnessUpgrade: { agent: options.agent, path: configPath, fromHashMode: previous.hashMode ?? "bytes", fromSha256: previous.sha256, toHashMode: next.hashMode, toSha256: next.sha256, reviewedPreimage: options.reviewedPreimage, currentSettingsSha256: options.expectedSettingsSha256, replacedWitnesses: replaced.map((source) => ({ hashMode: source.hashMode ?? "bytes", sha256: source.sha256 })) } };
|
|
53422
53664
|
}
|
|
53423
53665
|
function applyAgentIntegration(plan) {
|
|
53424
53666
|
for (const source of plan.observedNativeSources ?? [])
|
|
@@ -53471,7 +53713,7 @@ function applyAgentIntegration(plan) {
|
|
|
53471
53713
|
for (const [index, change] of plan.changes.entries())
|
|
53472
53714
|
if (change.before !== null) {
|
|
53473
53715
|
const backup = join23(backupRoot, `${index}-${sha3(change.path).slice(0, 12)}.backup`);
|
|
53474
|
-
|
|
53716
|
+
writeFileSync5(backup, change.before, { mode: 384, flag: "wx" });
|
|
53475
53717
|
if (readFileSync13(backup, "utf8") !== change.before)
|
|
53476
53718
|
throw new Error("Native configuration preservation readback failed");
|
|
53477
53719
|
backups.push(backup);
|
|
@@ -53481,7 +53723,7 @@ function applyAgentIntegration(plan) {
|
|
|
53481
53723
|
recheckRootAliases(aliases);
|
|
53482
53724
|
if (currentText(change.path) !== change.before)
|
|
53483
53725
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
53484
|
-
for (let directory =
|
|
53726
|
+
for (let directory = dirname12(change.path);!existsSync13(directory); directory = dirname12(directory))
|
|
53485
53727
|
createdDirectories.add(directory);
|
|
53486
53728
|
const after2 = change.after;
|
|
53487
53729
|
atomicWrite2(change.path, after2);
|
|
@@ -53532,11 +53774,11 @@ function applyAgentIntegration(plan) {
|
|
|
53532
53774
|
}
|
|
53533
53775
|
function syncArchiveDirectory(path) {
|
|
53534
53776
|
assertSafePath(path);
|
|
53535
|
-
const descriptor =
|
|
53777
|
+
const descriptor = openSync9(path, constants9.O_RDONLY | constants9.O_DIRECTORY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
|
|
53536
53778
|
try {
|
|
53537
|
-
|
|
53779
|
+
fsyncSync3(descriptor);
|
|
53538
53780
|
} finally {
|
|
53539
|
-
|
|
53781
|
+
closeSync9(descriptor);
|
|
53540
53782
|
}
|
|
53541
53783
|
}
|
|
53542
53784
|
function writeArchiveJournal(path, value) {
|
|
@@ -53544,18 +53786,18 @@ function writeArchiveJournal(path, value) {
|
|
|
53544
53786
|
const temporary = `${path}.skills-${randomUUID5()}`;
|
|
53545
53787
|
let descriptor;
|
|
53546
53788
|
try {
|
|
53547
|
-
descriptor =
|
|
53548
|
-
|
|
53789
|
+
descriptor = openSync9(temporary, constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW, 384);
|
|
53790
|
+
writeFileSync5(descriptor, `${JSON.stringify(value)}
|
|
53549
53791
|
`);
|
|
53550
|
-
|
|
53551
|
-
|
|
53792
|
+
fsyncSync3(descriptor);
|
|
53793
|
+
closeSync9(descriptor);
|
|
53552
53794
|
descriptor = undefined;
|
|
53553
53795
|
renameSync2(temporary, path);
|
|
53554
|
-
syncArchiveDirectory(
|
|
53796
|
+
syncArchiveDirectory(dirname12(path));
|
|
53555
53797
|
} finally {
|
|
53556
53798
|
if (descriptor !== undefined)
|
|
53557
|
-
|
|
53558
|
-
if (
|
|
53799
|
+
closeSync9(descriptor);
|
|
53800
|
+
if (lstatSync17(temporary, { throwIfNoEntry: false }))
|
|
53559
53801
|
unlinkSync3(temporary);
|
|
53560
53802
|
}
|
|
53561
53803
|
}
|
|
@@ -53589,7 +53831,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
53589
53831
|
const moved = [];
|
|
53590
53832
|
const verifyArchive = (entry) => {
|
|
53591
53833
|
assertSafePath(entry.archive);
|
|
53592
|
-
const stat =
|
|
53834
|
+
const stat = lstatSync17(entry.archive);
|
|
53593
53835
|
if (entry.discoveryOnly ? !stat.isFile() : !stat.isDirectory())
|
|
53594
53836
|
throw new Error("Archive type changed");
|
|
53595
53837
|
if ((entry.discoveryOnly ? sha3(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
|
|
@@ -53597,14 +53839,14 @@ function archiveNativeSkills(inventory, options) {
|
|
|
53597
53839
|
};
|
|
53598
53840
|
assertSafePath(archiveRoot);
|
|
53599
53841
|
const created = [];
|
|
53600
|
-
for (let path = archiveRoot;!existsSync13(path); path =
|
|
53842
|
+
for (let path = archiveRoot;!existsSync13(path); path = dirname12(path))
|
|
53601
53843
|
created.push(path);
|
|
53602
53844
|
mkdirSync5(archiveRoot, { recursive: true, mode: 448 });
|
|
53603
53845
|
try {
|
|
53604
53846
|
for (const directory of created)
|
|
53605
53847
|
syncArchiveDirectory(directory);
|
|
53606
53848
|
if (created.length)
|
|
53607
|
-
syncArchiveDirectory(
|
|
53849
|
+
syncArchiveDirectory(dirname12(created.at(-1)));
|
|
53608
53850
|
writeArchiveJournal(receiptPath, journal);
|
|
53609
53851
|
for (const [index, entry] of selected.entries()) {
|
|
53610
53852
|
recheckRootAliases(aliases);
|
|
@@ -53616,7 +53858,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
53616
53858
|
writeArchiveJournal(receiptPath, journal);
|
|
53617
53859
|
renameSync2(move.source, move.archive);
|
|
53618
53860
|
moved.push(move);
|
|
53619
|
-
syncArchiveDirectory(
|
|
53861
|
+
syncArchiveDirectory(dirname12(move.source));
|
|
53620
53862
|
syncArchiveDirectory(archiveRoot);
|
|
53621
53863
|
verifyArchive(move);
|
|
53622
53864
|
move.status = "archived";
|
|
@@ -53637,7 +53879,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
53637
53879
|
recheckRootAliases(aliases);
|
|
53638
53880
|
assertSafePath(entry.source);
|
|
53639
53881
|
assertSafePath(entry.archive);
|
|
53640
|
-
if (
|
|
53882
|
+
if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
|
|
53641
53883
|
entry.status = "recovery-required";
|
|
53642
53884
|
entry.conflict = "source-occupied";
|
|
53643
53885
|
continue;
|
|
@@ -53651,10 +53893,10 @@ function archiveNativeSkills(inventory, options) {
|
|
|
53651
53893
|
}
|
|
53652
53894
|
if (entry.discoveryOnly) {
|
|
53653
53895
|
linkSync2(entry.archive, entry.source);
|
|
53654
|
-
syncArchiveDirectory(
|
|
53896
|
+
syncArchiveDirectory(dirname12(entry.source));
|
|
53655
53897
|
unlinkSync3(entry.archive);
|
|
53656
53898
|
} else {
|
|
53657
|
-
if (
|
|
53899
|
+
if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
|
|
53658
53900
|
entry.status = "recovery-required";
|
|
53659
53901
|
entry.conflict = "source-occupied";
|
|
53660
53902
|
continue;
|
|
@@ -53662,7 +53904,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
53662
53904
|
renameSync2(entry.archive, entry.source);
|
|
53663
53905
|
}
|
|
53664
53906
|
entry.status = "restored";
|
|
53665
|
-
syncArchiveDirectory(
|
|
53907
|
+
syncArchiveDirectory(dirname12(entry.source));
|
|
53666
53908
|
syncArchiveDirectory(archiveRoot);
|
|
53667
53909
|
} catch {
|
|
53668
53910
|
entry.status = "recovery-required";
|
|
@@ -53687,10 +53929,10 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
53687
53929
|
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
53688
53930
|
const home = resolve12(options.home ?? homedir3()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
|
|
53689
53931
|
assertSafePath(join23(dataDir3, "agent-policy.json"));
|
|
53690
|
-
const
|
|
53691
|
-
if (!
|
|
53932
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
|
|
53933
|
+
if (!snapshot2)
|
|
53692
53934
|
throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
|
|
53693
|
-
const binding =
|
|
53935
|
+
const binding = snapshot2.value.bridge;
|
|
53694
53936
|
if (!binding || binding.version !== CLI_BRIDGE_VERSION || binding.digest !== CLI_BRIDGE_DIGEST || binding.home !== home || !Array.isArray(binding.agents) || !binding.agents.includes(agent))
|
|
53695
53937
|
throw new Error("NATIVE_SKILL_DRIFT: the managed Skills bridge binding is missing or incompatible; run skills hook install");
|
|
53696
53938
|
const aliases = binding.rootAliases ?? [];
|
|
@@ -53846,7 +54088,7 @@ function assertNativeExportAllowed(dataDir3 = getDataDirReadOnly()) {
|
|
|
53846
54088
|
if (requiresCliSkillLoading(dataDir3))
|
|
53847
54089
|
throw new Error("NATIVE_SKILL_EXPORT_DISABLED: this station loads skills through the Skills CLI; use skills sync --selection-profile <id>");
|
|
53848
54090
|
}
|
|
53849
|
-
var sha3 = (data) =>
|
|
54091
|
+
var sha3 = (data) => createHash13("sha256").update(data).digest("hex"), HOOK_EVENTS, ROOTS, SKILLS_LOADING_POLICY = "Skills loading policy: discover skills with `skills list` or `skills search`, and read selected instructions with `skills load <slug>`. Use `skills sync` to refresh the shared profile. The only native skill is the owned skills-cli bridge. Create and edit payload skills in the Skills authoring workspace; do not copy payload instructions into native discovery folders. Context loading does not authorize execution; use `skills run` only when the task calls for running a skill.";
|
|
53850
54092
|
var init_agent_integration = __esm(() => {
|
|
53851
54093
|
init_codex_plugin_skill_controls();
|
|
53852
54094
|
init_codex_native_skill_catalog();
|
|
@@ -53862,12 +54104,13 @@ var init_agent_integration = __esm(() => {
|
|
|
53862
54104
|
init_agent_sumi();
|
|
53863
54105
|
init_agent_hermes();
|
|
53864
54106
|
init_claude_settings_witness();
|
|
54107
|
+
init_codex_hook_trust_files();
|
|
53865
54108
|
HOOK_EVENTS = ["UserPromptSubmit", "SessionStart", "SubagentStart"];
|
|
53866
54109
|
ROOTS = NATIVE_SKILL_ROOTS;
|
|
53867
54110
|
});
|
|
53868
54111
|
|
|
53869
54112
|
// src/lib/home-migration.ts
|
|
53870
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync9, renameSync as renameSync3, rmSync, statSync as statSync9, writeFileSync as
|
|
54113
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync9, renameSync as renameSync3, rmSync, statSync as statSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
53871
54114
|
import { join as join25 } from "path";
|
|
53872
54115
|
function layoutMigrationRecordPath(appDir) {
|
|
53873
54116
|
return join25(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD);
|
|
@@ -53990,7 +54233,7 @@ function migrateOwnerLayout(options = {}) {
|
|
|
53990
54233
|
moved,
|
|
53991
54234
|
note: "installed/ and legacy flat skill dirs moved into skills/; custom/ retained"
|
|
53992
54235
|
};
|
|
53993
|
-
|
|
54236
|
+
writeFileSync6(layoutMigrationRecordPath(appDir), `${JSON.stringify(record3, null, 2)}
|
|
53994
54237
|
`);
|
|
53995
54238
|
return { status: "migrated", moved, created, record: record3 };
|
|
53996
54239
|
}
|
|
@@ -54005,7 +54248,7 @@ var init_home_migration = __esm(() => {
|
|
|
54005
54248
|
import {
|
|
54006
54249
|
cpSync as cpSync2,
|
|
54007
54250
|
existsSync as existsSync15,
|
|
54008
|
-
lstatSync as
|
|
54251
|
+
lstatSync as lstatSync18,
|
|
54009
54252
|
mkdirSync as mkdirSync7,
|
|
54010
54253
|
mkdtempSync,
|
|
54011
54254
|
readFileSync as readFileSync14,
|
|
@@ -54013,17 +54256,17 @@ import {
|
|
|
54013
54256
|
renameSync as renameSync4,
|
|
54014
54257
|
rmSync as rmSync2,
|
|
54015
54258
|
statSync as statSync10,
|
|
54016
|
-
writeFileSync as
|
|
54259
|
+
writeFileSync as writeFileSync7
|
|
54017
54260
|
} from "fs";
|
|
54018
54261
|
import { homedir as homedir4 } from "os";
|
|
54019
|
-
import { basename as basename6, dirname as
|
|
54262
|
+
import { basename as basename6, dirname as dirname13, join as join26 } from "path";
|
|
54020
54263
|
function isSkillsOwnershipMarker(marker) {
|
|
54021
54264
|
return typeof marker === "object" && marker !== null && Object.hasOwn(marker, "managedBy") && marker.managedBy === SYNC_MARKER_MANAGED_BY;
|
|
54022
54265
|
}
|
|
54023
54266
|
function hasSkillsOwnershipMarker(dir) {
|
|
54024
54267
|
const path = join26(dir, SYNC_MARKER_FILE);
|
|
54025
54268
|
try {
|
|
54026
|
-
if (!
|
|
54269
|
+
if (!lstatSync18(path).isFile())
|
|
54027
54270
|
return false;
|
|
54028
54271
|
const marker = JSON.parse(readFileSync14(path, "utf8"));
|
|
54029
54272
|
return isSkillsOwnershipMarker(marker);
|
|
@@ -54248,7 +54491,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
54248
54491
|
const action = dirExists ? "update" : "create";
|
|
54249
54492
|
if (options.dryRun)
|
|
54250
54493
|
return { action, path: skillMdPath };
|
|
54251
|
-
const parentDir =
|
|
54494
|
+
const parentDir = dirname13(dir);
|
|
54252
54495
|
mkdirSync7(parentDir, { recursive: true });
|
|
54253
54496
|
const transactionDir = mkdtempSync(join26(parentDir, `.hasna-skills-write-${basename6(dir)}-`));
|
|
54254
54497
|
const candidateDir = join26(transactionDir, "candidate");
|
|
@@ -54270,10 +54513,10 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
54270
54513
|
} else {
|
|
54271
54514
|
mkdirSync7(candidateDir, { recursive: true });
|
|
54272
54515
|
}
|
|
54273
|
-
|
|
54516
|
+
writeFileSync7(candidateSkillMdPath, skillMd.endsWith(`
|
|
54274
54517
|
`) ? skillMd : `${skillMd}
|
|
54275
54518
|
`);
|
|
54276
|
-
|
|
54519
|
+
writeFileSync7(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
|
|
54277
54520
|
`);
|
|
54278
54521
|
if (dirExists) {
|
|
54279
54522
|
originalMoved = true;
|
|
@@ -54356,7 +54599,7 @@ var init_skill_aliases = __esm(() => {
|
|
|
54356
54599
|
});
|
|
54357
54600
|
|
|
54358
54601
|
// src/lib/project-state.ts
|
|
54359
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as
|
|
54602
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
54360
54603
|
import { join as join27 } from "path";
|
|
54361
54604
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
54362
54605
|
return join27(targetDir, SKILLS_PROJECT_DIR);
|
|
@@ -54392,7 +54635,7 @@ function saveProjectConfig(config, targetDir = process.cwd()) {
|
|
|
54392
54635
|
const dir = getProjectStateDir(targetDir);
|
|
54393
54636
|
mkdirSync8(dir, { recursive: true });
|
|
54394
54637
|
const normalized = normalizeProjectConfig({ ...config, updatedAt: new Date().toISOString() });
|
|
54395
|
-
|
|
54638
|
+
writeFileSync8(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
|
|
54396
54639
|
`);
|
|
54397
54640
|
}
|
|
54398
54641
|
function pinProjectSkill(name, details = {}, targetDir = process.cwd()) {
|
|
@@ -55431,7 +55674,7 @@ var init_selection_resolver = __esm(() => {
|
|
|
55431
55674
|
});
|
|
55432
55675
|
|
|
55433
55676
|
// src/lib/plugin-projection.ts
|
|
55434
|
-
import { createHash as
|
|
55677
|
+
import { createHash as createHash14 } from "crypto";
|
|
55435
55678
|
import { posix } from "path";
|
|
55436
55679
|
function pluginRefusal(message) {
|
|
55437
55680
|
throw new SkillSelectionError("PLUGIN_ADMISSION_REFUSED", message);
|
|
@@ -55624,7 +55867,7 @@ function buildPluginProjection(entries) {
|
|
|
55624
55867
|
files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
55625
55868
|
return { manifest, payloads: manifest.payloads, files, witnesses: pluginFileWitnesses(files), originalTreeDigest, projectionTreeDigest: pluginTreeDigest(files), removed: removed.length ? pluginFileWitnesses(removed) : [] };
|
|
55626
55869
|
}
|
|
55627
|
-
var PLUGIN_PROJECTION_MANIFEST = "plugin-projection.json", PLUGIN_PROJECTION_LIMITS, pluginHash = (bytes) =>
|
|
55870
|
+
var PLUGIN_PROJECTION_MANIFEST = "plugin-projection.json", PLUGIN_PROJECTION_LIMITS, pluginHash = (bytes) => createHash14("sha256").update(bytes).digest("hex"), MANIFEST_FIELDS;
|
|
55628
55871
|
var init_plugin_projection = __esm(() => {
|
|
55629
55872
|
init_selection_cache();
|
|
55630
55873
|
PLUGIN_PROJECTION_LIMITS = Object.freeze({ files: 1024, bytes: 64 * 1024 * 1024, fileBytes: 16 * 1024 * 1024, metadataBytes: 512 * 1024, registrations: 64, history: 128 });
|
|
@@ -55632,12 +55875,12 @@ var init_plugin_projection = __esm(() => {
|
|
|
55632
55875
|
});
|
|
55633
55876
|
|
|
55634
55877
|
// src/lib/plugin-projection-store.ts
|
|
55635
|
-
import { createHash as
|
|
55636
|
-
import { constants as
|
|
55637
|
-
import { dirname as
|
|
55878
|
+
import { createHash as createHash15, randomUUID as randomUUID6 } from "crypto";
|
|
55879
|
+
import { constants as constants10, closeSync as closeSync10, fchmodSync, fstatSync as fstatSync10, fsyncSync as fsyncSync4, linkSync as linkSync3, lstatSync as lstatSync19, mkdirSync as mkdirSync9, openSync as openSync10, opendirSync as opendirSync4, readSync as readSync8, renameSync as renameSync5, rmdirSync as rmdirSync3, rmSync as rmSync4, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
|
|
55880
|
+
import { dirname as dirname14, isAbsolute as isAbsolute12, join as join30, parse as parse5, resolve as resolve13 } from "path";
|
|
55638
55881
|
function stat(path) {
|
|
55639
55882
|
try {
|
|
55640
|
-
return
|
|
55883
|
+
return lstatSync19(path, { bigint: true });
|
|
55641
55884
|
} catch (error) {
|
|
55642
55885
|
if (missing(error))
|
|
55643
55886
|
return null;
|
|
@@ -55656,7 +55899,7 @@ function same3(left, right, identityOnly = false) {
|
|
|
55656
55899
|
}
|
|
55657
55900
|
function ancestors(path, create3 = false) {
|
|
55658
55901
|
canonical5(path);
|
|
55659
|
-
const result2 = [], parent =
|
|
55902
|
+
const result2 = [], parent = dirname14(path), filesystemRoot = parse5(parent).root;
|
|
55660
55903
|
let current = filesystemRoot;
|
|
55661
55904
|
for (const segment of ["", ...parent.slice(filesystemRoot.length).split("/").filter(Boolean)]) {
|
|
55662
55905
|
if (segment)
|
|
@@ -55684,7 +55927,7 @@ function verifyAncestors(chain, identityOnly = true) {
|
|
|
55684
55927
|
}
|
|
55685
55928
|
function privateParent(path) {
|
|
55686
55929
|
const chain = ancestors(path, true), parent = chain.at(-1);
|
|
55687
|
-
pluginNeed(parent && parent.path ===
|
|
55930
|
+
pluginNeed(parent && parent.path === dirname14(path), "Plugin storage parent is unavailable");
|
|
55688
55931
|
owner(parent.stat);
|
|
55689
55932
|
pluginNeed((parent.stat.mode & 0o7777n) === 0o700n, "Plugin storage parent must have mode 0700");
|
|
55690
55933
|
return chain;
|
|
@@ -55713,19 +55956,19 @@ function readBytes(path, maximum, kind) {
|
|
|
55713
55956
|
fileShape(before2, maximum, kind);
|
|
55714
55957
|
let fd;
|
|
55715
55958
|
try {
|
|
55716
|
-
fd =
|
|
55717
|
-
const opened =
|
|
55959
|
+
fd = openSync10(path, READ_FLAGS);
|
|
55960
|
+
const opened = fstatSync10(fd, { bigint: true });
|
|
55718
55961
|
same3(before2, opened);
|
|
55719
55962
|
fileShape(opened, maximum, kind);
|
|
55720
55963
|
const bytes = new Uint8Array(Number(opened.size));
|
|
55721
55964
|
let offset = 0;
|
|
55722
55965
|
while (offset < bytes.byteLength) {
|
|
55723
|
-
const count =
|
|
55966
|
+
const count = readSync8(fd, bytes, offset, bytes.byteLength - offset, null);
|
|
55724
55967
|
pluginNeed(count > 0, "Plugin file changed while being read");
|
|
55725
55968
|
offset += count;
|
|
55726
55969
|
}
|
|
55727
|
-
pluginNeed(
|
|
55728
|
-
same3(opened,
|
|
55970
|
+
pluginNeed(readSync8(fd, new Uint8Array(1), 0, 1, null) === 0, "Plugin file grew while being read");
|
|
55971
|
+
same3(opened, fstatSync10(fd, { bigint: true }));
|
|
55729
55972
|
same3(opened, stat(path));
|
|
55730
55973
|
verifyAncestors(chain);
|
|
55731
55974
|
return bytes;
|
|
@@ -55735,7 +55978,7 @@ function readBytes(path, maximum, kind) {
|
|
|
55735
55978
|
return pluginRefusal("Cannot safely read plugin file");
|
|
55736
55979
|
} finally {
|
|
55737
55980
|
if (fd !== undefined)
|
|
55738
|
-
|
|
55981
|
+
closeSync10(fd);
|
|
55739
55982
|
}
|
|
55740
55983
|
}
|
|
55741
55984
|
function exactPaths(entries) {
|
|
@@ -55845,16 +56088,16 @@ function verifyPluginTree(root, witnesses) {
|
|
|
55845
56088
|
function writeExclusive(path, bytes, mode) {
|
|
55846
56089
|
let fd, created;
|
|
55847
56090
|
try {
|
|
55848
|
-
fd =
|
|
56091
|
+
fd = openSync10(path, WRITE_FLAGS, mode);
|
|
55849
56092
|
fchmodSync(fd, mode);
|
|
55850
|
-
created =
|
|
56093
|
+
created = fstatSync10(fd, { bigint: true });
|
|
55851
56094
|
let offset = 0;
|
|
55852
56095
|
while (offset < bytes.byteLength) {
|
|
55853
56096
|
const count = writeSync2(fd, bytes, offset, bytes.byteLength - offset);
|
|
55854
56097
|
pluginNeed(count > 0, "Plugin file write did not progress");
|
|
55855
56098
|
offset += count;
|
|
55856
56099
|
}
|
|
55857
|
-
|
|
56100
|
+
fsyncSync4(fd);
|
|
55858
56101
|
} catch (error) {
|
|
55859
56102
|
if (created) {
|
|
55860
56103
|
same3(created, stat(path), true);
|
|
@@ -55863,7 +56106,7 @@ function writeExclusive(path, bytes, mode) {
|
|
|
55863
56106
|
throw error;
|
|
55864
56107
|
} finally {
|
|
55865
56108
|
if (fd !== undefined)
|
|
55866
|
-
|
|
56109
|
+
closeSync10(fd);
|
|
55867
56110
|
}
|
|
55868
56111
|
}
|
|
55869
56112
|
function materializePluginTree(root, entries) {
|
|
@@ -55966,20 +56209,20 @@ function pluginExecutableDigest(path) {
|
|
|
55966
56209
|
fileShape(before2, maximum, "executable");
|
|
55967
56210
|
let fd;
|
|
55968
56211
|
try {
|
|
55969
|
-
fd =
|
|
55970
|
-
const opened =
|
|
56212
|
+
fd = openSync10(path, READ_FLAGS);
|
|
56213
|
+
const opened = fstatSync10(fd, { bigint: true });
|
|
55971
56214
|
same3(before2, opened);
|
|
55972
56215
|
fileShape(opened, maximum, "executable");
|
|
55973
|
-
const hash2 =
|
|
56216
|
+
const hash2 = createHash15("sha256"), buffer = new Uint8Array(64 * 1024);
|
|
55974
56217
|
let remaining = Number(opened.size);
|
|
55975
56218
|
while (remaining > 0) {
|
|
55976
|
-
const count =
|
|
56219
|
+
const count = readSync8(fd, buffer, 0, Math.min(buffer.byteLength, remaining), null);
|
|
55977
56220
|
pluginNeed(count > 0, "Plugin executable changed during hashing");
|
|
55978
56221
|
hash2.update(buffer.subarray(0, count));
|
|
55979
56222
|
remaining -= count;
|
|
55980
56223
|
}
|
|
55981
|
-
pluginNeed(
|
|
55982
|
-
same3(opened,
|
|
56224
|
+
pluginNeed(readSync8(fd, buffer, 0, 1, null) === 0, "Plugin executable grew during hashing");
|
|
56225
|
+
same3(opened, fstatSync10(fd, { bigint: true }));
|
|
55983
56226
|
same3(opened, stat(path));
|
|
55984
56227
|
verifyAncestors(chain);
|
|
55985
56228
|
return `sha256:${hash2.digest("hex")}`;
|
|
@@ -55989,14 +56232,14 @@ function pluginExecutableDigest(path) {
|
|
|
55989
56232
|
return pluginRefusal("Cannot safely hash plugin executable");
|
|
55990
56233
|
} finally {
|
|
55991
56234
|
if (fd !== undefined)
|
|
55992
|
-
|
|
56235
|
+
closeSync10(fd);
|
|
55993
56236
|
}
|
|
55994
56237
|
}
|
|
55995
56238
|
var READ_FLAGS, WRITE_FLAGS, uid = () => typeof process.getuid === "function" ? BigInt(process.getuid()) : undefined, missing = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
55996
56239
|
var init_plugin_projection_store = __esm(() => {
|
|
55997
56240
|
init_plugin_projection();
|
|
55998
|
-
READ_FLAGS =
|
|
55999
|
-
WRITE_FLAGS =
|
|
56241
|
+
READ_FLAGS = constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK;
|
|
56242
|
+
WRITE_FLAGS = constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW | constants10.O_NONBLOCK;
|
|
56000
56243
|
});
|
|
56001
56244
|
|
|
56002
56245
|
// src/lib/selected-manifest.ts
|
|
@@ -56405,8 +56648,8 @@ var init_plugin_admission = __esm(() => {
|
|
|
56405
56648
|
});
|
|
56406
56649
|
|
|
56407
56650
|
// src/lib/plugin-discovery.ts
|
|
56408
|
-
import { closeSync as
|
|
56409
|
-
import { basename as basename7, dirname as
|
|
56651
|
+
import { closeSync as closeSync11, constants as constants11, fstatSync as fstatSync11, lstatSync as lstatSync20, openSync as openSync11, opendirSync as opendirSync5, readSync as readSync9 } from "fs";
|
|
56652
|
+
import { basename as basename7, dirname as dirname15, isAbsolute as isAbsolute14, join as join32, resolve as resolve15 } from "path";
|
|
56410
56653
|
function validateManagedPluginWitnesses(value) {
|
|
56411
56654
|
pluginNeed(Array.isArray(value) && value.length > 0 && value.length <= PLUGIN_PROJECTION_LIMITS.registrations, "Invalid managed plugin witness collection");
|
|
56412
56655
|
const seen = new Set;
|
|
@@ -56421,29 +56664,29 @@ function validateManagedPluginWitnesses(value) {
|
|
|
56421
56664
|
function safe3(path) {
|
|
56422
56665
|
pluginText(path);
|
|
56423
56666
|
pluginNeed(isAbsolute14(path) && resolve15(path) === path, "Invalid plugin registry path");
|
|
56424
|
-
for (let at2 = path;; at2 =
|
|
56425
|
-
pluginNeed(!
|
|
56426
|
-
if (at2 ===
|
|
56667
|
+
for (let at2 = path;; at2 = dirname15(at2)) {
|
|
56668
|
+
pluginNeed(!lstatSync20(at2, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
56669
|
+
if (at2 === dirname15(at2))
|
|
56427
56670
|
break;
|
|
56428
56671
|
}
|
|
56429
56672
|
}
|
|
56430
56673
|
function readRegistry(path) {
|
|
56431
56674
|
safe3(path);
|
|
56432
|
-
const initial2 =
|
|
56675
|
+
const initial2 = lstatSync20(path);
|
|
56433
56676
|
pluginNeed(initial2.isFile() && initial2.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
|
|
56434
|
-
const fd =
|
|
56677
|
+
const fd = openSync11(path, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
|
|
56435
56678
|
try {
|
|
56436
|
-
const opened =
|
|
56679
|
+
const opened = fstatSync11(fd);
|
|
56437
56680
|
pluginNeed(opened.isFile() && opened.ino === initial2.ino && opened.dev === initial2.dev, "Plugin registry changed during open");
|
|
56438
56681
|
const bytes = Buffer.alloc(1024 * 1024 + 1);
|
|
56439
56682
|
let length = 0;
|
|
56440
56683
|
while (length < bytes.length) {
|
|
56441
|
-
const count =
|
|
56684
|
+
const count = readSync9(fd, bytes, length, bytes.length - length, null);
|
|
56442
56685
|
if (!count)
|
|
56443
56686
|
break;
|
|
56444
56687
|
length += count;
|
|
56445
56688
|
}
|
|
56446
|
-
const after2 =
|
|
56689
|
+
const after2 = fstatSync11(fd), current = lstatSync20(path);
|
|
56447
56690
|
safe3(path);
|
|
56448
56691
|
pluginNeed(length <= 1024 * 1024 && length === opened.size && after2.size === opened.size && after2.mtimeMs === opened.mtimeMs && after2.ctimeMs === opened.ctimeMs && current.dev === opened.dev && current.ino === opened.ino && current.mtimeMs === opened.mtimeMs && current.ctimeMs === opened.ctimeMs, "Plugin registry changed during read");
|
|
56449
56692
|
let value;
|
|
@@ -56455,7 +56698,7 @@ function readRegistry(path) {
|
|
|
56455
56698
|
pluginNeed(pluginObject(value) && value.version === 2 && pluginObject(value.plugins), "Unsupported native plugin registry schema");
|
|
56456
56699
|
return { value, rawDigest: pluginHash(bytes.subarray(0, length)) };
|
|
56457
56700
|
} finally {
|
|
56458
|
-
|
|
56701
|
+
closeSync11(fd);
|
|
56459
56702
|
}
|
|
56460
56703
|
}
|
|
56461
56704
|
function nativeVersionMatches(version, receipt) {
|
|
@@ -56464,7 +56707,7 @@ function nativeVersionMatches(version, receipt) {
|
|
|
56464
56707
|
}
|
|
56465
56708
|
function hashManagedPluginRegistry(path, managed) {
|
|
56466
56709
|
validateManagedPluginWitnesses(managed);
|
|
56467
|
-
const
|
|
56710
|
+
const snapshot2 = readRegistry(path), plugins = snapshot2.value.plugins;
|
|
56468
56711
|
const seen = new Set, verified = new Set;
|
|
56469
56712
|
let verifiedBytes = 0;
|
|
56470
56713
|
for (const witness of managed) {
|
|
@@ -56473,7 +56716,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
56473
56716
|
seen.add(target.pluginId);
|
|
56474
56717
|
const rows = plugins[target.pluginId];
|
|
56475
56718
|
pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
|
|
56476
|
-
const [name, market] = target.pluginId.split("@"), cacheRoot = join32(
|
|
56719
|
+
const [name, market] = target.pluginId.split("@"), cacheRoot = join32(dirname15(path), "cache", market, name);
|
|
56477
56720
|
const scopes = new Set, approvals = new Map, currentFiles = new Map;
|
|
56478
56721
|
plugins[target.pluginId] = rows.map((row) => {
|
|
56479
56722
|
pluginNeed(pluginObject(row), "Invalid managed plugin registration");
|
|
@@ -56516,7 +56759,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
56516
56759
|
return Object.fromEntries(Object.entries(row).filter(([key]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key)));
|
|
56517
56760
|
});
|
|
56518
56761
|
safe3(cacheRoot);
|
|
56519
|
-
const before2 =
|
|
56762
|
+
const before2 = lstatSync20(cacheRoot);
|
|
56520
56763
|
pluginNeed(before2.isDirectory(), "Invalid managed plugin cache root");
|
|
56521
56764
|
const directory = opendirSync5(cacheRoot), names = [];
|
|
56522
56765
|
try {
|
|
@@ -56539,12 +56782,12 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
56539
56782
|
}
|
|
56540
56783
|
for (const row of rows)
|
|
56541
56784
|
pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
|
|
56542
|
-
const after2 =
|
|
56785
|
+
const after2 = lstatSync20(cacheRoot);
|
|
56543
56786
|
safe3(cacheRoot);
|
|
56544
56787
|
pluginNeed(before2.dev === after2.dev && before2.ino === after2.ino && before2.mtimeMs === after2.mtimeMs && before2.ctimeMs === after2.ctimeMs, "Managed cache version membership changed during verification");
|
|
56545
56788
|
}
|
|
56546
|
-
pluginNeed(readRegistry(path).rawDigest ===
|
|
56547
|
-
return pluginHash(JSON.stringify(
|
|
56789
|
+
pluginNeed(readRegistry(path).rawDigest === snapshot2.rawDigest, "Native plugin registry changed during content verification");
|
|
56790
|
+
return pluginHash(JSON.stringify(snapshot2.value));
|
|
56548
56791
|
}
|
|
56549
56792
|
var init_plugin_discovery = __esm(() => {
|
|
56550
56793
|
init_plugin_admission();
|
|
@@ -56553,50 +56796,50 @@ var init_plugin_discovery = __esm(() => {
|
|
|
56553
56796
|
});
|
|
56554
56797
|
|
|
56555
56798
|
// src/lib/claude-marketplace-registry.ts
|
|
56556
|
-
import { createHash as
|
|
56557
|
-
import { closeSync as
|
|
56558
|
-
import { basename as basename8, dirname as
|
|
56559
|
-
function
|
|
56799
|
+
import { createHash as createHash16 } from "crypto";
|
|
56800
|
+
import { closeSync as closeSync12, constants as constants12, fstatSync as fstatSync12, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10 } from "fs";
|
|
56801
|
+
import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute15, resolve as resolve16 } from "path";
|
|
56802
|
+
function need6(value, reason) {
|
|
56560
56803
|
if (!value)
|
|
56561
56804
|
throw new Error(`Claude marketplace registry ${reason}`);
|
|
56562
56805
|
}
|
|
56563
56806
|
function absolutePath3(value) {
|
|
56564
|
-
|
|
56807
|
+
need6(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve16(value) === value, "requires a normalized absolute path");
|
|
56565
56808
|
}
|
|
56566
56809
|
function parents2(path) {
|
|
56567
56810
|
const result2 = [];
|
|
56568
|
-
for (let at2 =
|
|
56569
|
-
const stat2 =
|
|
56570
|
-
|
|
56811
|
+
for (let at2 = dirname16(path);; at2 = dirname16(at2)) {
|
|
56812
|
+
const stat2 = lstatSync21(at2, { bigint: true });
|
|
56813
|
+
need6(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
56571
56814
|
result2.push([at2, stat2]);
|
|
56572
|
-
if (at2 ===
|
|
56815
|
+
if (at2 === dirname16(at2))
|
|
56573
56816
|
return result2;
|
|
56574
56817
|
}
|
|
56575
56818
|
}
|
|
56576
56819
|
function readRegistry2(path, budget) {
|
|
56577
56820
|
absolutePath3(path);
|
|
56578
|
-
|
|
56579
|
-
|
|
56580
|
-
const ancestors2 = parents2(path), initial2 =
|
|
56581
|
-
|
|
56582
|
-
|
|
56583
|
-
const fd =
|
|
56821
|
+
need6(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
56822
|
+
need6(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
56823
|
+
const ancestors2 = parents2(path), initial2 = lstatSync21(path, { bigint: true });
|
|
56824
|
+
need6(initial2.isFile() && !initial2.isSymbolicLink() && initial2.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
56825
|
+
need6(initial2.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
56826
|
+
const fd = openSync12(path, constants12.O_RDONLY | constants12.O_NOFOLLOW | constants12.O_NONBLOCK);
|
|
56584
56827
|
try {
|
|
56585
|
-
const opened =
|
|
56586
|
-
|
|
56828
|
+
const opened = fstatSync12(fd, { bigint: true });
|
|
56829
|
+
need6(opened.isFile() && sameFile2(initial2, opened), "changed during open");
|
|
56587
56830
|
const bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
56588
56831
|
let length = 0;
|
|
56589
56832
|
while (length < bytes.length) {
|
|
56590
|
-
const count =
|
|
56833
|
+
const count = readSync10(fd, bytes, length, bytes.length - length, null);
|
|
56591
56834
|
if (!count)
|
|
56592
56835
|
break;
|
|
56593
56836
|
length += count;
|
|
56594
56837
|
}
|
|
56595
|
-
const after2 =
|
|
56596
|
-
|
|
56838
|
+
const after2 = fstatSync12(fd, { bigint: true }), current = lstatSync21(path, { bigint: true });
|
|
56839
|
+
need6(current.isFile() && sameFile2(opened, after2) && sameFile2(after2, current) && BigInt(length) === opened.size, "changed during read");
|
|
56597
56840
|
for (const [ancestor, before2] of ancestors2) {
|
|
56598
|
-
const now3 =
|
|
56599
|
-
|
|
56841
|
+
const now3 = lstatSync21(ancestor, { bigint: true });
|
|
56842
|
+
need6(now3.isDirectory() && before2.dev === now3.dev && before2.ino === now3.ino && before2.mode === now3.mode, "parent changed during read");
|
|
56600
56843
|
}
|
|
56601
56844
|
budget.remaining -= length;
|
|
56602
56845
|
try {
|
|
@@ -56605,7 +56848,7 @@ function readRegistry2(path, budget) {
|
|
|
56605
56848
|
throw new Error("Claude marketplace registry is not strict UTF-8");
|
|
56606
56849
|
}
|
|
56607
56850
|
} finally {
|
|
56608
|
-
|
|
56851
|
+
closeSync12(fd);
|
|
56609
56852
|
}
|
|
56610
56853
|
}
|
|
56611
56854
|
function parse6(text2) {
|
|
@@ -56616,7 +56859,7 @@ function parse6(text2) {
|
|
|
56616
56859
|
at2++;
|
|
56617
56860
|
};
|
|
56618
56861
|
function string() {
|
|
56619
|
-
|
|
56862
|
+
need6(text2[at2] === '"', "contains invalid JSON");
|
|
56620
56863
|
const start = at2++;
|
|
56621
56864
|
while (at2 < text2.length) {
|
|
56622
56865
|
const character = text2[at2++];
|
|
@@ -56627,7 +56870,7 @@ function parse6(text2) {
|
|
|
56627
56870
|
} catch {
|
|
56628
56871
|
throw new Error("Claude marketplace registry contains invalid JSON string");
|
|
56629
56872
|
}
|
|
56630
|
-
|
|
56873
|
+
need6(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
|
|
56631
56874
|
return value2;
|
|
56632
56875
|
}
|
|
56633
56876
|
if (character === "\\")
|
|
@@ -56636,7 +56879,7 @@ function parse6(text2) {
|
|
|
56636
56879
|
throw new Error("Claude marketplace registry contains an unterminated JSON string");
|
|
56637
56880
|
}
|
|
56638
56881
|
function value(depth) {
|
|
56639
|
-
|
|
56882
|
+
need6(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
|
|
56640
56883
|
space();
|
|
56641
56884
|
const character = text2[at2];
|
|
56642
56885
|
if (character === '"')
|
|
@@ -56650,17 +56893,17 @@ function parse6(text2) {
|
|
|
56650
56893
|
if (object5) {
|
|
56651
56894
|
space();
|
|
56652
56895
|
const key = string();
|
|
56653
|
-
|
|
56896
|
+
need6(!keys2.has(key), "contains a duplicate JSON key");
|
|
56654
56897
|
keys2.add(key);
|
|
56655
56898
|
space();
|
|
56656
|
-
|
|
56899
|
+
need6(text2[at2++] === ":", "contains invalid JSON");
|
|
56657
56900
|
entries.push([key, value(depth + 1)]);
|
|
56658
56901
|
} else
|
|
56659
56902
|
items.push(value(depth + 1));
|
|
56660
56903
|
space();
|
|
56661
56904
|
if (text2[at2] === end)
|
|
56662
56905
|
break;
|
|
56663
|
-
|
|
56906
|
+
need6(text2[at2++] === ",", "contains invalid JSON");
|
|
56664
56907
|
}
|
|
56665
56908
|
at2++;
|
|
56666
56909
|
return object5 ? { kind: "object", entries } : { kind: "array", items };
|
|
@@ -56672,21 +56915,21 @@ function parse6(text2) {
|
|
|
56672
56915
|
}
|
|
56673
56916
|
numberPattern.lastIndex = at2;
|
|
56674
56917
|
const number = numberPattern.exec(text2);
|
|
56675
|
-
|
|
56918
|
+
need6(number, "contains invalid JSON");
|
|
56676
56919
|
at2 += number[0].length;
|
|
56677
56920
|
return { kind: "number", value: number[0] };
|
|
56678
56921
|
}
|
|
56679
56922
|
const root2 = value(0);
|
|
56680
56923
|
space();
|
|
56681
|
-
|
|
56682
|
-
|
|
56924
|
+
need6(at2 === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
|
|
56925
|
+
need6(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
|
|
56683
56926
|
return root2;
|
|
56684
56927
|
}
|
|
56685
56928
|
function exactKeys(value, keys2) {
|
|
56686
56929
|
return value?.kind === "object" && value.entries.length === keys2.length && value.entries.every(([key]) => keys2.includes(key));
|
|
56687
56930
|
}
|
|
56688
56931
|
function textField(value) {
|
|
56689
|
-
|
|
56932
|
+
need6(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
56690
56933
|
return value.value;
|
|
56691
56934
|
}
|
|
56692
56935
|
function normalizeRow(row, version = 1) {
|
|
@@ -56694,7 +56937,7 @@ function normalizeRow(row, version = 1) {
|
|
|
56694
56937
|
if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
|
|
56695
56938
|
return;
|
|
56696
56939
|
const autoUpdate = field(row, "autoUpdate");
|
|
56697
|
-
|
|
56940
|
+
need6(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
|
|
56698
56941
|
}
|
|
56699
56942
|
const source = field(row, "source");
|
|
56700
56943
|
if (source?.kind !== "object")
|
|
@@ -56710,23 +56953,23 @@ function normalizeRow(row, version = 1) {
|
|
|
56710
56953
|
absolutePath3(sourceLocation);
|
|
56711
56954
|
absolutePath3(textField(field(row, "installLocation")));
|
|
56712
56955
|
const updated = textField(field(row, "lastUpdated"));
|
|
56713
|
-
|
|
56956
|
+
need6(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
|
|
56714
56957
|
const millis = Date.parse(updated);
|
|
56715
|
-
|
|
56958
|
+
need6(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
|
|
56716
56959
|
row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
|
|
56717
56960
|
}
|
|
56718
56961
|
function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
56719
56962
|
const value = parse6(readRegistry2(path, budget));
|
|
56720
56963
|
for (const [, row] of value.entries)
|
|
56721
56964
|
normalizeRow(row);
|
|
56722
|
-
const sha2562 =
|
|
56965
|
+
const sha2562 = createHash16("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
56723
56966
|
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
56724
56967
|
}
|
|
56725
56968
|
function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
56726
56969
|
const value = parse6(readRegistry2(path, budget));
|
|
56727
56970
|
for (const [, row] of value.entries)
|
|
56728
56971
|
normalizeRow(row, 2);
|
|
56729
|
-
const sha2562 =
|
|
56972
|
+
const sha2562 = createHash16("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
|
|
56730
56973
|
return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
|
|
56731
56974
|
}
|
|
56732
56975
|
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS, sameFile2 = (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, field = (object5, key) => object5.entries.find(([name]) => name === key)?.[1];
|
|
@@ -56734,65 +56977,6 @@ var init_claude_marketplace_registry = __esm(() => {
|
|
|
56734
56977
|
CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
56735
56978
|
});
|
|
56736
56979
|
|
|
56737
|
-
// src/lib/codex-hook-trust-files.ts
|
|
56738
|
-
import { closeSync as closeSync12, constants as constants12, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
56739
|
-
import { createHash as createHash16 } from "crypto";
|
|
56740
|
-
import { dirname as dirname16 } from "path";
|
|
56741
|
-
function need6(value, code) {
|
|
56742
|
-
if (!value)
|
|
56743
|
-
throw new Error(`CODEX_HOOK_TRUST_${code}`);
|
|
56744
|
-
}
|
|
56745
|
-
function safeParents(file) {
|
|
56746
|
-
for (let p = dirname16(file);; p = dirname16(p)) {
|
|
56747
|
-
const s = lstatSync21(p);
|
|
56748
|
-
need6(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
|
|
56749
|
-
need6((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
56750
|
-
if (dirname16(p) === p)
|
|
56751
|
-
break;
|
|
56752
|
-
}
|
|
56753
|
-
}
|
|
56754
|
-
function snapshot(file, privateFile = false, options = {}) {
|
|
56755
|
-
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
56756
|
-
safeParents(file);
|
|
56757
|
-
const initial2 = lstatSync21(file, { bigint: true });
|
|
56758
|
-
need6(initial2.isFile() && initial2.uid === BigInt(process.getuid()) && (initial2.nlink === 1n || readOnlyPackage && initial2.nlink > 1n) && (initial2.mode & 0o022n) === 0n && (!privateFile || (initial2.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
|
|
56759
|
-
need6(initial2.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
56760
|
-
const fd = openSync12(file, constants12.O_RDONLY | constants12.O_NOFOLLOW | constants12.O_NONBLOCK);
|
|
56761
|
-
try {
|
|
56762
|
-
const before2 = fstatSync12(fd, { bigint: true });
|
|
56763
|
-
need6(sameStat(initial2, before2), "INPUT_CHANGED");
|
|
56764
|
-
const bytes = Buffer.alloc(Number(before2.size) + 1);
|
|
56765
|
-
let length = 0;
|
|
56766
|
-
while (length < bytes.length) {
|
|
56767
|
-
const n2 = readSync10(fd, bytes, length, bytes.length - length, null);
|
|
56768
|
-
if (!n2)
|
|
56769
|
-
break;
|
|
56770
|
-
length += n2;
|
|
56771
|
-
}
|
|
56772
|
-
need6(length === Number(before2.size) && sameStat(before2, fstatSync12(fd, { bigint: true })) && sameStat(before2, lstatSync21(file, { bigint: true })), "INPUT_CHANGED");
|
|
56773
|
-
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
56774
|
-
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before2, sha256: createHash16("sha256").update(content).digest("hex") };
|
|
56775
|
-
} finally {
|
|
56776
|
-
closeSync12(fd);
|
|
56777
|
-
}
|
|
56778
|
-
}
|
|
56779
|
-
function unchanged2(before2) {
|
|
56780
|
-
const after2 = snapshot(before2.file, before2.privateFile, before2.readOnlyPackage ? { readOnlyPackage: true } : {});
|
|
56781
|
-
need6(sameStat(before2.stat, after2.stat) && after2.sha256 === before2.sha256, "INPUT_CHANGED");
|
|
56782
|
-
}
|
|
56783
|
-
function save(file, content) {
|
|
56784
|
-
safeParents(file);
|
|
56785
|
-
const fd = openSync12(file, constants12.O_WRONLY | constants12.O_CREAT | constants12.O_EXCL | constants12.O_NOFOLLOW, 384);
|
|
56786
|
-
try {
|
|
56787
|
-
writeFileSync8(fd, content);
|
|
56788
|
-
fsyncSync4(fd);
|
|
56789
|
-
} finally {
|
|
56790
|
-
closeSync12(fd);
|
|
56791
|
-
}
|
|
56792
|
-
}
|
|
56793
|
-
var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
|
|
56794
|
-
var init_codex_hook_trust_files = () => {};
|
|
56795
|
-
|
|
56796
56980
|
// src/lib/codex-hook-trust-layout.ts
|
|
56797
56981
|
import { isDeepStrictEqual as isDeepStrictEqual6 } from "util";
|
|
56798
56982
|
function codexTrustTextWitness(text2, keys2, canonicalize = true) {
|
|
@@ -56931,53 +57115,53 @@ var init_codex_hook_trust_layout = () => {};
|
|
|
56931
57115
|
import { isAbsolute as isAbsolute16 } from "path";
|
|
56932
57116
|
import { isDeepStrictEqual as isDeepStrictEqual7 } from "util";
|
|
56933
57117
|
function exactKeys2(value, keys2) {
|
|
56934
|
-
|
|
57118
|
+
need5(value && typeof value === "object" && !Array.isArray(value) && isDeepStrictEqual7(Object.keys(value).sort(), [...keys2].sort()), "RECONCILE_ADDITIONS_REVIEW_SCHEMA");
|
|
56935
57119
|
}
|
|
56936
57120
|
function readReviewedAdditionTrustStates(proof, before2, current, journalHooks) {
|
|
56937
57121
|
const captured = proofs.get(proof);
|
|
56938
|
-
|
|
57122
|
+
need5(!!captured && captured.before.file === before2.file && captured.before.sha256 === before2.sha256 && captured.current.file === current.file && captured.current.sha256 === current.sha256 && isDeepStrictEqual7(captured.journalHooks, journalHooks.map(({ key, currentHash }) => ({ key, currentHash }))), "RECONCILE_ADDITIONS_PROOF_CHANGED");
|
|
56939
57123
|
captured.recheck();
|
|
56940
57124
|
unchanged2(before2);
|
|
56941
57125
|
unchanged2(current);
|
|
56942
57126
|
return captured.states;
|
|
56943
57127
|
}
|
|
56944
57128
|
function reviewNativeHookAdditions(options) {
|
|
56945
|
-
|
|
57129
|
+
need5(isAbsolute16(options.path), "RECONCILE_ADDITIONS_REVIEW_PATH");
|
|
56946
57130
|
const reviewFile = snapshot(options.path, true), review = JSON.parse(reviewFile.text);
|
|
56947
57131
|
exactKeys2(review, ["version", "kind", "reason", "producerEvidence", "journalIntentSha256", "policySha256", "configCurrentSha256", "hooksCurrentSha256", "producerBefore", "additions"]);
|
|
56948
|
-
|
|
57132
|
+
need5((review.version === 1 || review.version === 2) && review.kind === "native-hook-additions" && text2(review.reason) && text2(review.producerEvidence), "RECONCILE_ADDITIONS_REVIEW_SCHEMA");
|
|
56949
57133
|
for (const [name, expected] of [["journalIntentSha256", options.intent.sha256], ["policySha256", options.policy.sha256], ["configCurrentSha256", options.configCurrent.sha256], ["hooksCurrentSha256", options.hooksCurrent.sha256]])
|
|
56950
|
-
|
|
57134
|
+
need5(hash2(review[name]) && review[name] === expected, "RECONCILE_ADDITIONS_REVIEW_CHANGED");
|
|
56951
57135
|
exactKeys2(review.producerBefore, ["configPath", "configSha256", "hooksPath", "hooksSha256"]);
|
|
56952
57136
|
const before2 = review.producerBefore;
|
|
56953
|
-
|
|
57137
|
+
need5(isAbsolute16(before2.configPath) && isAbsolute16(before2.hooksPath) && hash2(before2.configSha256) && hash2(before2.hooksSha256), "RECONCILE_ADDITIONS_PRODUCER_PREIMAGE");
|
|
56954
57138
|
const producerConfig = snapshot(before2.configPath, true), producerHooks = snapshot(before2.hooksPath, true);
|
|
56955
|
-
|
|
57139
|
+
need5(producerConfig.sha256 === before2.configSha256 && producerHooks.sha256 === before2.hooksSha256 && producerHooks.sha256 === options.hooksBefore.sha256, "RECONCILE_ADDITIONS_PRODUCER_PREIMAGE");
|
|
56956
57140
|
const original = structure(options.configBefore.text), producer = structure(producerConfig.text), expectedProducer = structuredClone(original), originalKeys = options.journalHooks.map((hook) => hook.key);
|
|
56957
57141
|
expectedProducer.hooks ??= {};
|
|
56958
57142
|
expectedProducer.hooks.state ??= {};
|
|
56959
57143
|
for (const hook of options.journalHooks)
|
|
56960
57144
|
expectedProducer.hooks.state[hook.key] = { ...expectedProducer.hooks.state[hook.key], enabled: true, trusted_hash: hook.currentHash };
|
|
56961
|
-
|
|
56962
|
-
|
|
57145
|
+
need5(isDeepStrictEqual7(expectedProducer, producer) && codexTrustReconcileWitness(options.configBefore.text, originalKeys) === codexTrustReconcileWitness(producerConfig.text, originalKeys) && codexTrustUnmanagedSemantic(options.configBefore.text, originalKeys) === codexTrustUnmanagedSemantic(producerConfig.text, originalKeys), "RECONCILE_ADDITIONS_PRODUCER_PREIMAGE");
|
|
57146
|
+
need5(Array.isArray(review.additions) && review.additions.length > 0 && review.additions.length <= 20, "RECONCILE_ADDITIONS_REVIEW_SCHEMA");
|
|
56963
57147
|
const expectedHooks = JSON.parse(options.hooksBefore.text), currentHooks = JSON.parse(options.hooksCurrent.text), expectedConfig = structuredClone(producer);
|
|
56964
|
-
|
|
57148
|
+
need5(expectedHooks.hooks && typeof expectedHooks.hooks === "object" && !Array.isArray(expectedHooks.hooks), "RECONCILE_ADDITIONS_DECLARATIONS");
|
|
56965
57149
|
const added = [], states = [], seen = new Set, commands = new Set(options.recordedNativeHooks.map((hook) => hook.command));
|
|
56966
57150
|
for (const addition of review.additions) {
|
|
56967
57151
|
exactKeys2(addition, review.version === 1 ? ["event", "nativeHook"] : ["event", "nativeHook", "trustState"]);
|
|
56968
|
-
|
|
57152
|
+
need5(Object.hasOwn(review.version === 1 ? events : matchedEvents, addition.event), "RECONCILE_ADDITIONS_DECLARATIONS");
|
|
56969
57153
|
const { event, nativeHook: hook } = addition;
|
|
56970
57154
|
exactKeys2(hook, nativeKeys);
|
|
56971
57155
|
const groups = expectedHooks.hooks[event] ?? [];
|
|
56972
|
-
|
|
57156
|
+
need5(Array.isArray(groups), "RECONCILE_ADDITIONS_DECLARATIONS");
|
|
56973
57157
|
const key = `${options.nativeHooksPath}:${matchedEvents[event][1]}:${groups.length}:0`;
|
|
56974
|
-
|
|
56975
|
-
|
|
57158
|
+
need5(hook.key === key && !seen.has(key) && !options.recordedNativeHooks.some((originalHook) => originalHook.key === key) && !Object.hasOwn(expectedConfig.hooks.state, key), "RECONCILE_ADDITIONS_IDENTITY");
|
|
57159
|
+
need5(hook.eventName === matchedEvents[event][0] && hook.handlerType === "command" && (review.version === 2 ? v2Command(hook.command) : text2(hook.command, 16384)) && !commands.has(hook.command) && Number.isInteger(hook.timeoutSec) && hook.timeoutSec > 0 && hook.timeoutSec <= 60 && (event === "PreToolUse" ? text2(hook.matcher) : hook.matcher === null) && hook.async === false && hook.statusMessage === null && hook.additionalContextLimit === null && hook.sourcePath === options.nativeHooksPath && hook.source === "user" && hook.pluginId === null && hook.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash) && hook.enabled === true && hook.trustStatus === "trusted", "RECONCILE_ADDITIONS_IDENTITY");
|
|
56976
57160
|
let trustState = { enabled: true, trusted_hash: hook.currentHash };
|
|
56977
57161
|
if (review.version === 2) {
|
|
56978
57162
|
const state = addition.trustState;
|
|
56979
57163
|
exactKeys2(state, state && Object.hasOwn(state, "enabled") ? ["enabled", "trusted_hash"] : ["trusted_hash"]);
|
|
56980
|
-
|
|
57164
|
+
need5(state.trusted_hash === hook.currentHash && (!Object.hasOwn(state, "enabled") || state.enabled === true), "RECONCILE_ADDITIONS_TRUST_STATE");
|
|
56981
57165
|
trustState = { ...state };
|
|
56982
57166
|
}
|
|
56983
57167
|
seen.add(key);
|
|
@@ -56987,9 +57171,9 @@ function reviewNativeHookAdditions(options) {
|
|
|
56987
57171
|
expectedHooks.hooks[event] = [...groups, { ...event === "PreToolUse" ? { matcher: hook.matcher } : {}, hooks: [{ type: "command", command: hook.command, timeout: hook.timeoutSec }] }];
|
|
56988
57172
|
expectedConfig.hooks.state[key] = trustState;
|
|
56989
57173
|
}
|
|
56990
|
-
|
|
57174
|
+
need5(isDeepStrictEqual7(expectedHooks, currentHooks), "RECONCILE_ADDITIONS_DECLARATIONS");
|
|
56991
57175
|
const keys2 = added.map((hook) => hook.key);
|
|
56992
|
-
|
|
57176
|
+
need5(isDeepStrictEqual7(expectedConfig, structure(options.configCurrent.text)) && codexTrustReconcileWitness(producerConfig.text, keys2) === codexTrustReconcileWitness(options.configCurrent.text, keys2) && codexTrustUnmanagedSemantic(producerConfig.text, keys2) === codexTrustUnmanagedSemantic(options.configCurrent.text, keys2), "RECONCILE_ADDITIONS_CONFIG");
|
|
56993
57177
|
const inputs = [reviewFile, producerConfig, producerHooks, options.intent, options.policy, options.configBefore, options.configCurrent, options.hooksBefore, options.hooksCurrent];
|
|
56994
57178
|
const recheck = () => {
|
|
56995
57179
|
for (const input of inputs)
|
|
@@ -57023,16 +57207,16 @@ function createCodexHookDiscoveryRecovery(binding, before2, current, hooks, addi
|
|
|
57023
57207
|
const raw = binding.sources.filter((source) => source.path === current.file && source.hashMode === "bytes");
|
|
57024
57208
|
if (!raw.some((source) => source.sha256 !== current.sha256))
|
|
57025
57209
|
return;
|
|
57026
|
-
|
|
57027
|
-
|
|
57210
|
+
need5(binding.agent === "codex" && binding.method === "reviewed" && raw.length === 1 && raw[0].sha256 === before2.sha256 && before2.sha256 !== current.sha256, "RECONCILE_DISCOVERY_CONFIG_WITNESS");
|
|
57211
|
+
need5(raw[0].format === undefined && raw[0].fields === undefined && raw[0].managedPlugins === undefined, "RECONCILE_DISCOVERY_CONFIG_WITNESS");
|
|
57028
57212
|
const typed = binding.sources.filter((source) => source.path === current.file && source.format === "toml");
|
|
57029
|
-
|
|
57213
|
+
need5(typed.length === 1 && typed[0].hashMode === undefined && isDeepStrictEqual8(typed[0].fields, fields), "RECONCILE_DISCOVERY_TYPED_WITNESS");
|
|
57030
57214
|
const original = structure2(before2.text), actual = structure2(current.text);
|
|
57031
57215
|
const project = (value) => Object.fromEntries(fields.map((field2) => [field2, value[field2] ?? null]));
|
|
57032
57216
|
const projection = sha4(JSON.stringify(project(original)));
|
|
57033
|
-
|
|
57217
|
+
need5(typed[0].sha256 === projection && sha4(JSON.stringify(project(actual))) === projection && isDeepStrictEqual8(project(original), project(actual)), "RECONCILE_DISCOVERY_TYPED_WITNESS");
|
|
57034
57218
|
const keys2 = [...journalHooks.map((hook) => hook.key), ...additionStates.map((hook) => hook.key)];
|
|
57035
|
-
|
|
57219
|
+
need5(journalHooks.length > 0 && keys2.length <= 100 && new Set(keys2).size === keys2.length && journalHooks.every((hook) => typeof hook.key === "string" && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash)), "RECONCILE_DISCOVERY_TRUST_WITNESS");
|
|
57036
57220
|
const expected = structuredClone(original);
|
|
57037
57221
|
expected.hooks ??= {};
|
|
57038
57222
|
expected.hooks.state ??= {};
|
|
@@ -57040,7 +57224,7 @@ function createCodexHookDiscoveryRecovery(binding, before2, current, hooks, addi
|
|
|
57040
57224
|
expected.hooks.state[hook.key] = { ...expected.hooks.state[hook.key], enabled: true, trusted_hash: hook.currentHash };
|
|
57041
57225
|
for (const { key, state } of additionStates)
|
|
57042
57226
|
expected.hooks.state[key] = { ...state };
|
|
57043
|
-
|
|
57227
|
+
need5(isDeepStrictEqual8(expected, actual) && codexTrustReconcileWitness(before2.text, keys2) === codexTrustReconcileWitness(current.text, keys2) && codexTrustUnmanagedSemantic(before2.text, keys2) === codexTrustUnmanagedSemantic(current.text, keys2), "RECONCILE_DISCOVERY_TRUST_WITNESS");
|
|
57044
57228
|
unchanged2(before2);
|
|
57045
57229
|
unchanged2(current);
|
|
57046
57230
|
const witness = Object.freeze({ configPath: current.file, beforeSha256: before2.sha256, currentSha256: current.sha256, discoverySha256: sha4(JSON.stringify(binding)), typedProjectionSha256: projection });
|
|
@@ -57049,7 +57233,7 @@ function createCodexHookDiscoveryRecovery(binding, before2, current, hooks, addi
|
|
|
57049
57233
|
}
|
|
57050
57234
|
function assertCodexHookDiscoveryRecovery(binding, witness) {
|
|
57051
57235
|
const proof = proofs2.get(witness);
|
|
57052
|
-
|
|
57236
|
+
need5(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha4(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
|
|
57053
57237
|
unchanged2(proof.before);
|
|
57054
57238
|
unchanged2(proof.current);
|
|
57055
57239
|
proof.recheckAdditions();
|
|
@@ -57058,7 +57242,7 @@ function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witnes
|
|
|
57058
57242
|
if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
|
|
57059
57243
|
return false;
|
|
57060
57244
|
assertCodexHookDiscoveryRecovery(binding, witness);
|
|
57061
|
-
|
|
57245
|
+
need5(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
|
|
57062
57246
|
return true;
|
|
57063
57247
|
}
|
|
57064
57248
|
var sha4 = (text3) => createHash17("sha256").update(text3).digest("hex"), fields, structure2 = (text3) => JSON.parse(JSON.stringify(Bun.TOML.parse(text3))), proofs2;
|
|
@@ -109753,16 +109937,47 @@ function assertConfigUnchanged(configs) {
|
|
|
109753
109937
|
}
|
|
109754
109938
|
}
|
|
109755
109939
|
}
|
|
109756
|
-
|
|
109940
|
+
function releaseAgePolicy(options) {
|
|
109941
|
+
if (options.minReleaseAgeExclude !== undefined && !Array.isArray(options.minReleaseAgeExclude))
|
|
109942
|
+
throw new Error("MIN_RELEASE_AGE_EXCLUDE_INVALID");
|
|
109943
|
+
if (options.minReleaseAge === undefined) {
|
|
109944
|
+
if (options.minReleaseAgeExclude?.length)
|
|
109945
|
+
throw new Error("MIN_RELEASE_AGE_REQUIRED_FOR_EXCLUSIONS");
|
|
109946
|
+
return;
|
|
109947
|
+
}
|
|
109948
|
+
if (!Number.isSafeInteger(options.minReleaseAge) || options.minReleaseAge < 1)
|
|
109949
|
+
throw new Error("MIN_RELEASE_AGE_INVALID");
|
|
109950
|
+
const exclusions = options.minReleaseAgeExclude ?? [];
|
|
109951
|
+
if (!Array.isArray(exclusions) || exclusions.some((pattern) => typeof pattern !== "string" || pattern.length > 214 || !/^(?:@[a-z0-9*?][a-z0-9._*?-]*\/)?[a-z0-9*?][a-z0-9._*?-]*$/.test(pattern))) {
|
|
109952
|
+
throw new Error("MIN_RELEASE_AGE_EXCLUDE_INVALID");
|
|
109953
|
+
}
|
|
109954
|
+
return { minReleaseAge: options.minReleaseAge, minReleaseAgeExclude: [...new Set(exclusions)] };
|
|
109955
|
+
}
|
|
109956
|
+
async function npmReleaseAgeCapability(cwd2, env3) {
|
|
109957
|
+
try {
|
|
109958
|
+
const versionProbe = Bun.spawn(["npm", "--version"], { cwd: cwd2, env: env3, stdin: "ignore", stdout: "pipe", stderr: "ignore" });
|
|
109959
|
+
const [versionText, versionStatus] = await Promise.all([new Response(versionProbe.stdout).text(), versionProbe.exited]);
|
|
109960
|
+
const version2 = versionText.trim();
|
|
109961
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version2);
|
|
109962
|
+
if (versionStatus !== 0 || !match || Number(match[1]) < 11 || Number(match[1]) === 11 && Number(match[2]) < 19)
|
|
109963
|
+
throw new Error("NPM_RELEASE_AGE_UNSUPPORTED");
|
|
109964
|
+
const configProbe = Bun.spawn(["npm", "config", "list", "--json"], { cwd: cwd2, env: env3, stdin: "ignore", stdout: "pipe", stderr: "ignore" });
|
|
109965
|
+
const [configText, configStatus] = await Promise.all([new Response(configProbe.stdout).text(), configProbe.exited]);
|
|
109966
|
+
const config2 = object9(JSON.parse(configText), "NPM_RELEASE_AGE_UNSUPPORTED");
|
|
109967
|
+
if (configStatus !== 0 || !Object.hasOwn(config2, "min-release-age") || !Object.hasOwn(config2, "min-release-age-exclude") || !Array.isArray(config2["min-release-age-exclude"]))
|
|
109968
|
+
throw new Error("NPM_RELEASE_AGE_UNSUPPORTED");
|
|
109969
|
+
return version2;
|
|
109970
|
+
} catch {
|
|
109971
|
+
throw new Error("NPM_RELEASE_AGE_UNSUPPORTED");
|
|
109972
|
+
}
|
|
109973
|
+
}
|
|
109974
|
+
async function runNpm(args2, cwd2, home, staging, policy) {
|
|
109757
109975
|
const userNpmrc = join56(staging, "npm-user.npmrc"), globalNpmrc = join56(staging, "npm-global.npmrc");
|
|
109758
|
-
|
|
109759
|
-
writeFileSync23(userNpmrc, `registry=${REGISTRY_ORIGIN}/
|
|
109976
|
+
const baseConfig = `registry=${REGISTRY_ORIGIN}/
|
|
109760
109977
|
ignore-scripts=true
|
|
109761
|
-
|
|
109978
|
+
`;
|
|
109762
109979
|
if (!existsSync34(globalNpmrc))
|
|
109763
|
-
writeFileSync23(globalNpmrc,
|
|
109764
|
-
ignore-scripts=true
|
|
109765
|
-
`, { mode: 384, flag: "wx" });
|
|
109980
|
+
writeFileSync23(globalNpmrc, baseConfig, { mode: 384, flag: "wx" });
|
|
109766
109981
|
const env3 = {
|
|
109767
109982
|
PATH: process.env.PATH ?? "",
|
|
109768
109983
|
HOME: home,
|
|
@@ -109776,6 +109991,19 @@ ignore-scripts=true
|
|
|
109776
109991
|
NPM_CONFIG_REGISTRY: `${REGISTRY_ORIGIN}/`,
|
|
109777
109992
|
NPM_CONFIG_IGNORE_SCRIPTS: "true"
|
|
109778
109993
|
};
|
|
109994
|
+
const capabilityNpmrc = join56(staging, "npm-capability.npmrc");
|
|
109995
|
+
if (policy && !existsSync34(capabilityNpmrc))
|
|
109996
|
+
writeFileSync23(capabilityNpmrc, baseConfig, { mode: 384, flag: "wx" });
|
|
109997
|
+
if (policy && readFileSync37(capabilityNpmrc, "utf8") !== baseConfig)
|
|
109998
|
+
throw new Error("NPM_ISOLATED_CONFIG_DRIFT");
|
|
109999
|
+
const npmVersion = policy ? await npmReleaseAgeCapability(cwd2, { ...env3, NPM_CONFIG_USERCONFIG: capabilityNpmrc }) : undefined;
|
|
110000
|
+
const config2 = baseConfig + (policy ? `min-release-age=${policy.minReleaseAge}
|
|
110001
|
+
${policy.minReleaseAgeExclude.map((pattern) => `min-release-age-exclude[]=${pattern}
|
|
110002
|
+
`).join("")}` : "");
|
|
110003
|
+
if (!existsSync34(userNpmrc))
|
|
110004
|
+
writeFileSync23(userNpmrc, config2, { mode: 384, flag: "wx" });
|
|
110005
|
+
if (readFileSync37(userNpmrc, "utf8") !== config2 || readFileSync37(globalNpmrc, "utf8") !== baseConfig)
|
|
110006
|
+
throw new Error("NPM_ISOLATED_CONFIG_DRIFT");
|
|
109779
110007
|
const proc = Bun.spawn(["npm", ...args2, "--registry", `${REGISTRY_ORIGIN}/`, "--ignore-scripts", "--no-audit", "--no-fund"], {
|
|
109780
110008
|
cwd: cwd2,
|
|
109781
110009
|
env: env3,
|
|
@@ -109786,6 +110014,7 @@ ignore-scripts=true
|
|
|
109786
110014
|
const exitCode = await proc.exited;
|
|
109787
110015
|
if (exitCode !== 0)
|
|
109788
110016
|
throw new Error("DEPENDENCY_GRAPH_INSTALL_FAILED");
|
|
110017
|
+
return npmVersion;
|
|
109789
110018
|
}
|
|
109790
110019
|
function comparePackageTrees(expectedRoot, installedRoot) {
|
|
109791
110020
|
const expected = readTree(expectedRoot, false);
|
|
@@ -110164,6 +110393,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
|
|
|
110164
110393
|
throw new Error("EXACT_VERSION_REQUIRED");
|
|
110165
110394
|
if (!STABLE_SEMVER_PATTERN.test(version2))
|
|
110166
110395
|
throw new Error("EXACT_STABLE_VERSION_REQUIRED");
|
|
110396
|
+
const policy = releaseAgePolicy(options);
|
|
110167
110397
|
const home = resolve24(options.homeDir ?? process.env.HOME ?? "");
|
|
110168
110398
|
if (!home || !existsSync34(home))
|
|
110169
110399
|
throw new Error("HOME_NOT_FOUND");
|
|
@@ -110201,8 +110431,10 @@ async function updateCopyfileRuntime(version2, options = {}) {
|
|
|
110201
110431
|
mkdirSync24(stagePackage, { mode: 448 });
|
|
110202
110432
|
copyFileSync(artifact.tarballPath, join56(stagePackage, "verified.tgz"));
|
|
110203
110433
|
writeFileSync23(join56(stagePackage, "package.json"), JSON.stringify({ private: true, dependencies: { [PACKAGE_NAME]: "file:./verified.tgz" } }, null, 2), { mode: 384, flag: "wx" });
|
|
110204
|
-
await runNpm(["install", "--package-lock-only", "--prefix", stagePackage], stagePackage, home, stagePath);
|
|
110205
|
-
await runNpm(["ci", "--prefix", stagePackage], stagePackage, home, stagePath);
|
|
110434
|
+
const npmVersion = await runNpm(["install", "--package-lock-only", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
|
|
110435
|
+
const ciNpmVersion = await runNpm(["ci", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
|
|
110436
|
+
if (npmVersion !== ciNpmVersion)
|
|
110437
|
+
throw new Error("NPM_VERSION_DRIFT_DURING_UPDATE");
|
|
110206
110438
|
const lockBytes = readFileSync37(join56(stagePackage, "package-lock.json"));
|
|
110207
110439
|
const installedPackage = join56(stagePackage, "node_modules", "@hasna", "skills");
|
|
110208
110440
|
const installed = readPackage(join56(installedPackage, "package.json"));
|
|
@@ -110243,6 +110475,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
|
|
|
110243
110475
|
targetPackageRoot,
|
|
110244
110476
|
configs: configPreimages,
|
|
110245
110477
|
preimageSha256,
|
|
110478
|
+
...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {},
|
|
110246
110479
|
launchers,
|
|
110247
110480
|
switchedLaunchers: [],
|
|
110248
110481
|
rollbackCompletedLaunchers: []
|
|
@@ -110281,7 +110514,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
|
|
|
110281
110514
|
}
|
|
110282
110515
|
receipt.state = "switched";
|
|
110283
110516
|
persistReceipt(join56(finalPath, "rollout-receipt.json"), receipt);
|
|
110284
|
-
return { updated: true, version: version2, currentVersion: layout.currentVersion, receiptId: id2, runtimeRoot: finalPath, tarballSha256: artifact.sha256, tarballIntegrity: artifact.integrity, runtimeTreeSha256: stagedTree.digest, launcherCount: launchers.length, configCount: configPreimages.length };
|
|
110517
|
+
return { updated: true, version: version2, currentVersion: layout.currentVersion, receiptId: id2, runtimeRoot: finalPath, tarballSha256: artifact.sha256, tarballIntegrity: artifact.integrity, runtimeTreeSha256: stagedTree.digest, launcherCount: launchers.length, configCount: configPreimages.length, ...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {} };
|
|
110285
110518
|
} catch (error61) {
|
|
110286
110519
|
if (switched.length > 0 || moved) {
|
|
110287
110520
|
let rollbackFailed = false;
|
|
@@ -110505,7 +110738,12 @@ function registerRuntime(parent) {
|
|
|
110505
110738
|
});
|
|
110506
110739
|
const setup = parent.command("setup").description("Point this CLI at a Skills API server, or register agent integrations").option("--api-url <url>", "Skills API origin to send remote work to").option("--global", "Accepted for compatibility; the API origin is always per-user", false).option("--json", "Output setup result as JSON", false).action(async (options) => handleSetup(options));
|
|
110507
110740
|
setup.command("agents").option("--json", "Output registration result as JSON", false).description("Register the Skills MCP server with all supported agents").action(async (options) => handleMcp({ register: "all", json: options.json }));
|
|
110508
|
-
parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
|
|
110741
|
+
parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--min-release-age <days>", "Require a positive integer dependency release age in days for an exact-version update").option("--min-release-age-exclude <pattern>", "Exclude matching package identities from the release-age policy (repeatable)", (pattern, prior) => [...prior, pattern], []).option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
|
|
110742
|
+
if ((options.minReleaseAge !== undefined || options.minReleaseAgeExclude.length > 0) && (!options.version || options.rollback || options.adoptAliases || options.rollbackAliases)) {
|
|
110743
|
+
console.error(JSON.stringify({ error: "RELEASE_AGE_POLICY_REQUIRES_EXACT_VERSION" }));
|
|
110744
|
+
process.exitCode = 1;
|
|
110745
|
+
return;
|
|
110746
|
+
}
|
|
110509
110747
|
if ([options.version, options.rollback, options.adoptAliases, options.rollbackAliases].filter(Boolean).length > 1) {
|
|
110510
110748
|
console.error(JSON.stringify({ error: "SELF_UPDATE_OPERATIONS_ARE_MUTUALLY_EXCLUSIVE" }));
|
|
110511
110749
|
process.exitCode = 1;
|
|
@@ -110541,7 +110779,8 @@ function registerRuntime(parent) {
|
|
|
110541
110779
|
}
|
|
110542
110780
|
if (options.version) {
|
|
110543
110781
|
try {
|
|
110544
|
-
const
|
|
110782
|
+
const minReleaseAge = options.minReleaseAge === undefined ? undefined : /^\d+$/.test(options.minReleaseAge) ? Number(options.minReleaseAge) : NaN;
|
|
110783
|
+
const result2 = await updateCopyfileRuntime(options.version, { minReleaseAge, minReleaseAgeExclude: options.minReleaseAgeExclude });
|
|
110545
110784
|
if (options.json)
|
|
110546
110785
|
console.log(JSON.stringify(result2));
|
|
110547
110786
|
else
|
|
@@ -112755,40 +112994,40 @@ import { realpathSync as realpathSync17 } from "fs";
|
|
|
112755
112994
|
import { dirname as dirname33, isAbsolute as isAbsolute27, join as join60 } from "path";
|
|
112756
112995
|
import { isDeepStrictEqual as isDeepStrictEqual13 } from "util";
|
|
112757
112996
|
function inspectRecordedSkillsCli(command, expected) {
|
|
112758
|
-
|
|
112997
|
+
need5(isAbsolute27(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
|
|
112759
112998
|
const resolved = realpathSync17(command);
|
|
112760
112999
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
112761
113000
|
const manifest = snapshot(join60(dirname33(dirname33(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
112762
113001
|
const pkg = JSON.parse(manifest.text);
|
|
112763
|
-
|
|
112764
|
-
|
|
113002
|
+
need5(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join60(dirname33(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
|
|
113003
|
+
need5((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
112765
113004
|
const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
|
|
112766
|
-
|
|
113005
|
+
need5(isDeepStrictEqual13(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
112767
113006
|
return { receipt, recheck() {
|
|
112768
|
-
|
|
113007
|
+
need5(realpathSync17(command) === resolved, "SKILLS_COMMAND_CHANGED");
|
|
112769
113008
|
unchanged2(cli);
|
|
112770
113009
|
unchanged2(manifest);
|
|
112771
113010
|
} };
|
|
112772
113011
|
}
|
|
112773
113012
|
function bindSkillsCli(command, reviewed) {
|
|
112774
113013
|
const normal = Bun.which("skills", { PATH: process.env.PATH });
|
|
112775
|
-
|
|
113014
|
+
need5(normal, "SKILLS_COMMAND_UNAVAILABLE");
|
|
112776
113015
|
const resolved = realpathSync17(normal);
|
|
112777
113016
|
const bound = isAbsolute27(command) ? command : command === "skills" ? normal : undefined;
|
|
112778
|
-
|
|
113017
|
+
need5(bound && realpathSync17(bound) === resolved, "SKILLS_COMMAND_MISMATCH");
|
|
112779
113018
|
const entrypoint = reviewed?.path ?? process.argv[1];
|
|
112780
|
-
|
|
113019
|
+
need5(entrypoint && realpathSync17(entrypoint) === resolved, "SKILLS_ENTRYPOINT_MISMATCH");
|
|
112781
113020
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
112782
113021
|
const manifest = snapshot(join60(dirname33(dirname33(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
112783
113022
|
const pkg = JSON.parse(manifest.text);
|
|
112784
|
-
|
|
113023
|
+
need5(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join60(dirname33(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
|
|
112785
113024
|
if (reviewed)
|
|
112786
|
-
|
|
112787
|
-
|
|
113025
|
+
need5(/^[a-f0-9]{64}$/.test(reviewed.sha256) && reviewed.sha256 === cli.sha256 && reviewed.version === pkg.version, "SKILLS_RELEASE_MISMATCH");
|
|
113026
|
+
need5((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
112788
113027
|
return {
|
|
112789
113028
|
receipt: { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 },
|
|
112790
113029
|
recheck() {
|
|
112791
|
-
|
|
113030
|
+
need5(Bun.which("skills", { PATH: process.env.PATH }) === normal && realpathSync17(normal) === resolved && realpathSync17(bound) === resolved && realpathSync17(entrypoint) === resolved, "SKILLS_COMMAND_CHANGED");
|
|
112792
113031
|
unchanged2(cli);
|
|
112793
113032
|
unchanged2(manifest);
|
|
112794
113033
|
}
|
|
@@ -112801,17 +113040,17 @@ var init_codex_hook_trust_identity = __esm(() => {
|
|
|
112801
113040
|
// src/lib/codex-hook-policy-recovery.ts
|
|
112802
113041
|
import { isDeepStrictEqual as isDeepStrictEqual14 } from "util";
|
|
112803
113042
|
function reviewedClaudeDiscoveryChange(beforeText, currentText, options) {
|
|
112804
|
-
|
|
112805
|
-
|
|
113043
|
+
need5(typeof options.reason === "string" && options.reason.trim().length > 0 && options.reason.length <= 512 && !/[\x00-\x1f\x7f]/.test(options.reason), "RECONCILE_RECOVERY_REASON_REQUIRED");
|
|
113044
|
+
need5(options.reviewedPlanDigest === undefined || /^[a-f0-9]{64}$/.test(options.reviewedPlanDigest), "RECONCILE_RECOVERY_PLAN_CHANGED");
|
|
112806
113045
|
const before2 = parseManagedSkillPolicy(beforeText), current = parseManagedSkillPolicy(currentText);
|
|
112807
113046
|
const oldDiscovery = before2.bridge?.discovery?.claude, newDiscovery = current.bridge?.discovery?.claude;
|
|
112808
|
-
|
|
113047
|
+
need5(oldDiscovery?.agent === "claude" && newDiscovery?.agent === "claude" && oldDiscovery.method === "reviewed" && newDiscovery.method === "reviewed", "RECONCILE_POLICY_CHANGED");
|
|
112809
113048
|
const changes = ["roots", "sources"].filter((key) => !isDeepStrictEqual14(oldDiscovery[key], newDiscovery[key])).map((key) => ({ path: `bridge.discovery.claude.${key}`, before: oldDiscovery[key], after: newDiscovery[key] }));
|
|
112810
|
-
|
|
113049
|
+
need5(changes.length > 0, "RECONCILE_POLICY_CHANGED");
|
|
112811
113050
|
const compared = structuredClone(current);
|
|
112812
113051
|
compared.bridge.discovery.claude.roots = oldDiscovery.roots;
|
|
112813
113052
|
compared.bridge.discovery.claude.sources = oldDiscovery.sources;
|
|
112814
|
-
|
|
113053
|
+
need5(isDeepStrictEqual14(before2, compared), "RECONCILE_POLICY_CHANGED");
|
|
112815
113054
|
verifyAgentDiscovery(newDiscovery);
|
|
112816
113055
|
return { reason: options.reason, changes };
|
|
112817
113056
|
}
|
|
@@ -112829,15 +113068,15 @@ import { dirname as dirname34, isAbsolute as isAbsolute28, join as join61, resol
|
|
|
112829
113068
|
import { homedir as homedir12 } from "os";
|
|
112830
113069
|
import { isDeepStrictEqual as isDeepStrictEqual15 } from "util";
|
|
112831
113070
|
function verifyReleaseArtifact(proof, skillsCli) {
|
|
112832
|
-
|
|
113071
|
+
need5(isAbsolute28(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
112833
113072
|
const tarPath = realpathSync18(proof.packageTarPath);
|
|
112834
|
-
|
|
113073
|
+
need5(statSync21(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
112835
113074
|
const tarBytes = readFileSync40(tarPath);
|
|
112836
|
-
|
|
113075
|
+
need5(tarBytes.byteLength === proof.packageTarBytes && createHash31("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
|
|
112837
113076
|
const packageRoot = dirname34(dirname34(realpathSync18(skillsCli.receipt.path)));
|
|
112838
113077
|
const manifest = readFileSync40(join61(packageRoot, "package.json"));
|
|
112839
|
-
|
|
112840
|
-
|
|
113078
|
+
need5(createHash31("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
|
|
113079
|
+
need5(createHash31("sha256").update(readFileSync40(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
|
|
112841
113080
|
const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync40(skillsCli.receipt.path)]];
|
|
112842
113081
|
for (const [member, expected] of members2) {
|
|
112843
113082
|
let actual;
|
|
@@ -112846,7 +113085,7 @@ function verifyReleaseArtifact(proof, skillsCli) {
|
|
|
112846
113085
|
} catch {
|
|
112847
113086
|
throw new Error("RECONCILE_RELEASE_ARTIFACT_MEMBER_MISSING");
|
|
112848
113087
|
}
|
|
112849
|
-
|
|
113088
|
+
need5(Buffer.compare(actual, expected) === 0, "RECONCILE_RELEASE_ARTIFACT_MEMBER_CHANGED");
|
|
112850
113089
|
}
|
|
112851
113090
|
}
|
|
112852
113091
|
async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc) {
|
|
@@ -112854,48 +113093,48 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
112854
113093
|
try {
|
|
112855
113094
|
const home = resolve26(options.home ?? homedir12()), dataDir3 = resolve26(options.dataDir ?? getDataDirReadOnly());
|
|
112856
113095
|
const parent = resolve26(join61(dataDir3, "native-hook-trust")), journal = resolve26(options.journal);
|
|
112857
|
-
|
|
113096
|
+
need5(journal.startsWith(parent + "/") && /^[a-f0-9-]{36}$/.test(journal.slice(parent.length + 1)), "RECONCILE_JOURNAL_PATH");
|
|
112858
113097
|
const dir = lstatSync32(journal, { throwIfNoEntry: false });
|
|
112859
|
-
|
|
112860
|
-
|
|
112861
|
-
|
|
113098
|
+
need5(!!dir && dir.isDirectory() && !dir.isSymbolicLink() && dir.uid === process.getuid() && (dir.mode & 511) === 448, "UNSAFE_JOURNAL");
|
|
113099
|
+
need5(!lstatSync32(join61(journal, "receipt.json"), { throwIfNoEntry: false }), "RECONCILE_ALREADY_COMPLETE");
|
|
113100
|
+
need5(lstatSync32(join61(journal, "stopped.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_JOURNAL_INCOMPLETE");
|
|
112862
113101
|
const stoppedFile = snapshot(join61(journal, "stopped.json"), true);
|
|
112863
113102
|
const intentFile = snapshot(join61(journal, "intent.json"), true), intent = JSON.parse(intentFile.text);
|
|
112864
113103
|
const configBefore = snapshot(join61(journal, "config.before.toml"), true), hooksBefore = snapshot(join61(journal, "hooks.before.json"), true), policyBefore = snapshot(join61(journal, "policy.before.json"), true);
|
|
112865
113104
|
const legacyIntent = intent.home === undefined && intent.configSha256 === undefined && intent.configVersion === undefined && intent.declarations === undefined && intent.admitted === undefined && intent.nativeHooks === undefined;
|
|
112866
|
-
|
|
113105
|
+
need5(intent.version === 1 && /^[a-f0-9]{64}$/.test(intent.planDigest) && typeof intent.configPath === "string" && /^[a-f0-9]{64}$/.test(intent.policySha256) && /^[a-f0-9]{64}$/.test(intent.hooksSha256) && Array.isArray(intent.hooks) && intent.hooks.length > 0 && intent.hooks.length <= 100 && (legacyIntent || typeof intent.home === "string" && intent.home === home && /^[a-f0-9]{64}$/.test(intent.configSha256) && typeof intent.configVersion === "string" && Array.isArray(intent.declarations) && Array.isArray(intent.admitted) && Array.isArray(intent.nativeHooks)), "RECONCILE_INTENT_INVALID");
|
|
112867
113106
|
const declarations = intent.declarations ?? intent.hooks.map((hook) => ({ event: hook.event, key: `${join61(home, ".codex/hooks.json")}${hook.key.slice(hook.key.indexOf(".codex/hooks.json") + ".codex/hooks.json".length)}`, command: hook.command }));
|
|
112868
113107
|
const admitted = intent.admitted ?? intent.hooks;
|
|
112869
113108
|
const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
|
|
112870
113109
|
const digestInput = { version: 1, home: intent.home ?? home, configPath: intent.configPath, ...intent.nativeConfigPath !== undefined ? { nativeConfigPath: intent.nativeConfigPath } : {}, configSha256, configVersion, policySha256: intent.policySha256, hooksSha256: intent.hooksSha256, skillsCli: intent.skillsCli, nativeVersion: intent.nativeVersion, declarations, admitted };
|
|
112871
|
-
|
|
112872
|
-
|
|
112873
|
-
|
|
113110
|
+
need5(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
|
|
113111
|
+
need5(isDeepStrictEqual15(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
|
|
113112
|
+
need5(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
|
|
112874
113113
|
const policyCurrent = snapshot(join61(dataDir3, "agent-policy.json"), true);
|
|
112875
113114
|
const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
|
|
112876
113115
|
const recovery = claudeRecovery ?? additionsRecovery;
|
|
112877
|
-
|
|
112878
|
-
|
|
112879
|
-
|
|
113116
|
+
need5(!recovery || !options.supersedeBinding, "RECONCILE_RECOVERY_MODE_CONFLICT");
|
|
113117
|
+
need5(!claudeRecovery || !additionsRecovery || claudeRecovery.reviewedPlanDigest === additionsRecovery.reviewedPlanDigest, "RECONCILE_RECOVERY_MODE_CONFLICT");
|
|
113118
|
+
need5(recovery?.reviewedPlanDigest === undefined || /^[a-f0-9]{64}$/.test(recovery.reviewedPlanDigest), "RECONCILE_RECOVERY_PLAN_CHANGED");
|
|
112880
113119
|
const policyChange = claudeRecovery ? reviewedClaudeDiscoveryChange(policyBefore.text, policyCurrent.text, claudeRecovery) : undefined;
|
|
112881
113120
|
let supersededBinding = false;
|
|
112882
113121
|
if (policyCurrent.sha256 !== policyBefore.sha256 && !claudeRecovery) {
|
|
112883
|
-
|
|
113122
|
+
need5(options.supersedeBinding === true, "RECONCILE_POLICY_CHANGED");
|
|
112884
113123
|
const oldPolicy = JSON.parse(policyBefore.text), currentPolicy = JSON.parse(policyCurrent.text);
|
|
112885
|
-
|
|
113124
|
+
need5(isDeepStrictEqual15(withoutCodexCommand(oldPolicy), withoutCodexCommand(currentPolicy)), "RECONCILE_POLICY_CHANGED");
|
|
112886
113125
|
supersededBinding = true;
|
|
112887
113126
|
}
|
|
112888
113127
|
if (supersededBinding || recovery) {
|
|
112889
|
-
|
|
113128
|
+
need5(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
|
|
112890
113129
|
const proof = options.releaseProof;
|
|
112891
|
-
|
|
113130
|
+
need5(!!proof && isAbsolute28(proof.packageTarPath) && /^[a-f0-9]{64}$/.test(proof.packageTarSha256) && Number.isInteger(proof.packageTarBytes) && proof.packageTarBytes > 0 && /^[a-f0-9]{64}$/.test(proof.manifestSha256) && /^[a-f0-9]{64}$/.test(proof.executableSha256), "RECONCILE_RELEASE_PROVENANCE_REQUIRED");
|
|
112892
113131
|
}
|
|
112893
113132
|
const currentConfig = snapshot(intent.configPath, true);
|
|
112894
113133
|
const policy = parseManagedSkillPolicy(policyCurrent.text), binding = policy.bridge;
|
|
112895
113134
|
const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ?? join61(home, ".codex"), hooksPath = join61(physicalRoot, "hooks.json");
|
|
112896
|
-
|
|
113135
|
+
need5(resolve26(intent.configPath) === resolve26(join61(physicalRoot, "config.toml")), "RECONCILE_CONFIG_PATH_CHANGED");
|
|
112897
113136
|
const nativeConfigPath = intent.nativeConfigPath ?? intent.configPath;
|
|
112898
|
-
|
|
113137
|
+
need5([join61(home, ".codex/config.toml"), join61(physicalRoot, "config.toml")].includes(nativeConfigPath), "RECONCILE_NATIVE_CONFIG_PATH_CHANGED");
|
|
112899
113138
|
const codexHome = nativeConfigPath !== intent.configPath ? undefined : dirname34(nativeConfigPath);
|
|
112900
113139
|
const hooksCurrent = snapshot(hooksPath);
|
|
112901
113140
|
const additions = additionsRecovery ? reviewNativeHookAdditions({ path: additionsRecovery.path, intent: intentFile, policy: policyCurrent, configBefore, configCurrent: currentConfig, hooksBefore, hooksCurrent, nativeHooksPath: join61(dirname34(nativeConfigPath), "hooks.json"), journalHooks: intent.hooks, recordedNativeHooks: intent.nativeHooks }) : undefined;
|
|
@@ -112903,7 +113142,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
112903
113142
|
const managedKeys = managedHooks.map((h) => h.key);
|
|
112904
113143
|
const witnessSame = codexTrustReconcileWitness(configBefore.text, managedKeys) === codexTrustReconcileWitness(currentConfig.text, managedKeys);
|
|
112905
113144
|
const unmanagedSame = codexTrustUnmanagedSemantic(configBefore.text, managedKeys) === codexTrustUnmanagedSemantic(currentConfig.text, managedKeys);
|
|
112906
|
-
|
|
113145
|
+
need5(unmanagedSame && (witnessSame || supersededBinding), "RECONCILE_UNRELATED_CONFIG_CHANGED");
|
|
112907
113146
|
const before2 = structure3(configBefore.text), current = structure3(currentConfig.text), expectedConfig = structuredClone(before2);
|
|
112908
113147
|
expectedConfig.hooks ??= {};
|
|
112909
113148
|
expectedConfig.hooks.state ??= {};
|
|
@@ -112915,10 +113154,10 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
112915
113154
|
if (supersededBinding) {
|
|
112916
113155
|
for (const hook of intent.hooks) {
|
|
112917
113156
|
const state = current.hooks?.state?.[hook.key];
|
|
112918
|
-
|
|
113157
|
+
need5(state?.enabled === true && typeof state.trusted_hash === "string" && /^sha256:[a-f0-9]{64}$/.test(state.trusted_hash), "RECONCILE_CONFIG_DRIFT");
|
|
112919
113158
|
}
|
|
112920
113159
|
} else
|
|
112921
|
-
|
|
113160
|
+
need5(isDeepStrictEqual15(expectedConfig, current), "RECONCILE_CONFIG_DRIFT");
|
|
112922
113161
|
const configDiscoveryRecovery = recovery && binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, configBefore, currentConfig, intent.hooks, additions?.trustProof) : undefined;
|
|
112923
113162
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: configDiscoveryRecovery });
|
|
112924
113163
|
const recoveryCli = recovery ? bindSkillsCli("skills", options.reviewedSkillsCli) : undefined;
|
|
@@ -112928,28 +113167,28 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
112928
113167
|
if (supersededBinding)
|
|
112929
113168
|
verifyReleaseArtifact(options.releaseProof, skillsCli);
|
|
112930
113169
|
const skillsBindingChanged = !isDeepStrictEqual15(skillsCli.receipt, intent.skillsCli);
|
|
112931
|
-
|
|
113170
|
+
need5(!skillsBindingChanged || supersededBinding, "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
112932
113171
|
if (!additions && hooksCurrent.sha256 !== intent.hooksSha256 && hooksCurrent.sha256 !== hooksBefore.sha256) {
|
|
112933
|
-
|
|
113172
|
+
need5(supersededBinding, "RECONCILE_HOOKS_CHANGED");
|
|
112934
113173
|
const currentDeclarations = JSON.parse(hooksCurrent.text).hooks;
|
|
112935
113174
|
for (const [event, [nativeEvent]] of Object.entries(eventNames)) {
|
|
112936
113175
|
const command = renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, event);
|
|
112937
|
-
|
|
113176
|
+
need5(isDeepStrictEqual15(currentDeclarations?.[event], [{ hooks: [{ type: "command", command, timeout: 15 }] }]), "RECONCILE_BINDING_HOOK_CHANGED");
|
|
112938
113177
|
}
|
|
112939
113178
|
}
|
|
112940
113179
|
rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
|
|
112941
|
-
|
|
113180
|
+
need5(rpc.version === intent.nativeVersion, "RECONCILE_NATIVE_VERSION_CHANGED");
|
|
112942
113181
|
const discovered = await rpc.request("hooks/list", { cwds: [home] });
|
|
112943
|
-
|
|
113182
|
+
need5(Array.isArray(discovered?.data) && discovered.data.length === 1 && discovered.data[0].cwd === home && !discovered.data[0].errors?.length && !discovered.data[0].warnings?.length, "RECONCILE_NATIVE_DISCOVERY_REFUSED");
|
|
112944
113183
|
const nativeHooks = discovered.data[0].hooks;
|
|
112945
113184
|
const recordedNativeHooks = intent.nativeHooks;
|
|
112946
113185
|
if (recordedNativeHooks)
|
|
112947
|
-
|
|
113186
|
+
need5(nativeHooks.length === recordedNativeHooks.length + (additions?.hooks.length ?? 0), "RECONCILE_NATIVE_HOOK_LIST_CHANGED");
|
|
112948
113187
|
const plannedKeys = new Set(intent.hooks.map((hook) => hook.key));
|
|
112949
113188
|
let nativeTrustPending = false;
|
|
112950
113189
|
for (const expected of [...recordedNativeHooks ?? [], ...additions?.hooks ?? []]) {
|
|
112951
113190
|
const matches2 = nativeHooks.filter((hook) => hook.key === expected.key);
|
|
112952
|
-
|
|
113191
|
+
need5(matches2.length === 1, "RECONCILE_NATIVE_HOOK_LIST_CHANGED");
|
|
112953
113192
|
const actual = safeNativeHook(matches2[0]);
|
|
112954
113193
|
const stable = (hook) => {
|
|
112955
113194
|
const { enabled, trustStatus, ...identity5 } = hook;
|
|
@@ -112957,35 +113196,35 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
112957
113196
|
delete identity5.command;
|
|
112958
113197
|
return identity5;
|
|
112959
113198
|
};
|
|
112960
|
-
|
|
113199
|
+
need5(isDeepStrictEqual15(stable(actual), stable(expected)), "RECONCILE_NATIVE_IDENTITY_CHANGED");
|
|
112961
113200
|
if (!plannedKeys.has(expected.key))
|
|
112962
|
-
|
|
113201
|
+
need5(isDeepStrictEqual15(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
|
|
112963
113202
|
}
|
|
112964
113203
|
for (const wanted of intent.hooks) {
|
|
112965
113204
|
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join61(home, ".codex/hooks.json"), hooksPath));
|
|
112966
|
-
|
|
113205
|
+
need5(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
|
|
112967
113206
|
const h = matches2[0];
|
|
112968
113207
|
const trusted = h.trustStatus === "trusted";
|
|
112969
|
-
|
|
113208
|
+
need5(h.enabled === true && (trusted || supersededBinding && h.trustStatus === "modified") && (h.currentHash === wanted.currentHash || supersededBinding && /^sha256:[a-f0-9]{64}$/.test(h.currentHash)), "RECONCILE_NATIVE_STATE_INCOMPLETE");
|
|
112970
113209
|
if (!trusted)
|
|
112971
113210
|
nativeTrustPending = true;
|
|
112972
113211
|
if (supersededBinding) {
|
|
112973
113212
|
const nativeToCliEvent = { userPromptSubmit: "UserPromptSubmit", sessionStart: "SessionStart", subagentStart: "SubagentStart" };
|
|
112974
|
-
|
|
113213
|
+
need5(h.command === renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, nativeToCliEvent[wanted.event] ?? ""), "RECONCILE_BINDING_HOOK_CHANGED");
|
|
112975
113214
|
}
|
|
112976
113215
|
}
|
|
112977
113216
|
const config2 = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
112978
113217
|
const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
112979
|
-
|
|
113218
|
+
need5(layers?.length === 1 && !layers[0].disabledReason && codexNativeConfigEqual(layers[0].config, current), "RECONCILE_NATIVE_CONFIG_MISMATCH");
|
|
112980
113219
|
const finalDiscovery = await rpc.request("hooks/list", { cwds: [home] });
|
|
112981
|
-
|
|
113220
|
+
need5(Array.isArray(finalDiscovery?.data) && finalDiscovery.data.length === 1 && finalDiscovery.data[0].cwd === home && !finalDiscovery.data[0].errors?.length && !finalDiscovery.data[0].warnings?.length && (!recordedNativeHooks || isDeepStrictEqual15(finalDiscovery.data[0].hooks.map(safeNativeHook), nativeHooks.map(safeNativeHook))), "RECONCILE_NATIVE_STATE_CHANGED");
|
|
112982
113221
|
const finalConfig = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
112983
113222
|
const finalLayers = finalConfig?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
112984
|
-
|
|
113223
|
+
need5(finalLayers?.length === 1 && !finalLayers[0].disabledReason && codexNativeConfigEqual(finalLayers[0].config, current), "RECONCILE_NATIVE_CONFIG_CHANGED");
|
|
112985
113224
|
if (recovery) {
|
|
112986
113225
|
for (const response of [config2, finalConfig]) {
|
|
112987
113226
|
const layerIndex = response.layers.findIndex((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
112988
|
-
|
|
113227
|
+
need5(response.config && response.config.features?.hooks !== false && response.layers.slice(0, layerIndex).every((layer) => layer.config?.hooks === undefined && layer.config?.features?.hooks === undefined), "RECONCILE_NATIVE_CONFIG_OVERRIDDEN");
|
|
112989
113228
|
}
|
|
112990
113229
|
recoveryCli.recheck();
|
|
112991
113230
|
verifyReleaseArtifact(options.releaseProof, recoveryCli);
|
|
@@ -113006,7 +113245,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
113006
113245
|
const recoveryPlan = recovery ? { version: 1, kind: additions ? claudeRecovery ? "claude-discovery-and-native-hook-additions" : "native-hook-additions" : "claude-discovery-only", journal, journalIntentSha256: intentFile.sha256, stoppedSha256: stoppedFile.sha256, originalPlanDigest: intent.planDigest, policyBeforeSha256: policyBefore.sha256, policyCurrentSha256: policyCurrent.sha256, configBeforeSha256: configBefore.sha256, configCurrentSha256: currentConfig.sha256, ...configDiscoveryRecovery ? { configDiscoveryRecovery } : {}, ...additions ? { nativeHookAdditions: additions.receipt } : {}, hooksSha256: hooksCurrent.sha256, nativeConfigPath, nativeVersion: rpc.version, nativeInventorySha256: makePlanDigest(nativeHooks.map(safeNativeHook)), recordedNativeInventorySha256: makePlanDigest(recordedNativeHooks), boundSkillsCli: skillsCli.receipt, recoveryCli: recoveryCli.receipt, releaseProof: options.releaseProof, ...policyChange } : undefined;
|
|
113007
113246
|
const recoveryPlanDigest = recoveryPlan ? makePlanDigest(recoveryPlan) : undefined;
|
|
113008
113247
|
if (recovery?.reviewedPlanDigest !== undefined)
|
|
113009
|
-
|
|
113248
|
+
need5(recovery.reviewedPlanDigest === recoveryPlanDigest, "RECONCILE_RECOVERY_PLAN_CHANGED");
|
|
113010
113249
|
const preview2 = !!recovery && recovery.reviewedPlanDigest === undefined;
|
|
113011
113250
|
const receipt = { version: 1, status: preview2 ? "recovery-review-required" : supersededBinding ? "superseded" : "reconciled", reconciled: !preview2, supersededBinding, automaticRollback: false, journal, planDigest: intent.planDigest, nativeVersion: rpc.version, nativeStateVerified: !nativeTrustPending, nativeTrustPending, bindingRefreshRequired: !!configDiscoveryRecovery, nativeExecutionVerified: false, configSha256: currentConfig.sha256, hooksSha256: hooksCurrent.sha256, policySha256: policyBefore.sha256, policyBeforeSha256: policyBefore.sha256, policyCurrentSha256: policyCurrent.sha256, previousSkillsCli: intent.skillsCli, skillsCli: skillsCli.receipt, releaseProof: options.releaseProof, nativeInventoryScope: recordedNativeHooks ? "complete" : "declared-managed-hooks-only", unrelatedNativeHooksPreserved: !!recordedNativeHooks, unrelatedSettingsAndCommentsPreserved: true, ...recoveryPlan ? { recoveryPlan, recoveryPlanDigest } : {} };
|
|
113012
113251
|
if (preview2)
|
|
@@ -113029,7 +113268,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
113029
113268
|
const binding = policy.bridge, alias = binding.rootAliases?.find((item) => item.agent === "codex");
|
|
113030
113269
|
const skillsCli = bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
|
|
113031
113270
|
const physicalRoot = alias?.target ?? join61(home, ".codex"), hooksPath = join61(physicalRoot, "hooks.json"), configPath = join61(physicalRoot, "config.toml");
|
|
113032
|
-
|
|
113271
|
+
need5(options.codexHome === undefined || isAbsolute28(options.codexHome) && [join61(home, ".codex"), physicalRoot].includes(options.codexHome), "NATIVE_HOME_NOT_ADMITTED");
|
|
113033
113272
|
const codexHome = options.codexHome === undefined ? undefined : physicalRoot;
|
|
113034
113273
|
const nativeConfigPath = join61(codexHome ?? join61(home, ".codex"), "config.toml");
|
|
113035
113274
|
const hooksFile = snapshot(hooksPath), configFile = snapshot(configPath, true), original = structure3(configFile.text);
|
|
@@ -113037,38 +113276,38 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
113037
113276
|
for (const [event, [nativeEvent, label]] of Object.entries(eventNames)) {
|
|
113038
113277
|
const command = renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, event);
|
|
113039
113278
|
const groups = declarations[event];
|
|
113040
|
-
|
|
113279
|
+
need5(Array.isArray(groups), "DECLARATION_CHANGED");
|
|
113041
113280
|
const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
|
|
113042
|
-
|
|
113281
|
+
need5(matches2.length === 1 && isDeepStrictEqual15(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
|
|
113043
113282
|
expected.push({ event: nativeEvent, key: `${join61(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
|
|
113044
113283
|
}
|
|
113045
113284
|
codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
|
|
113046
113285
|
rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
|
|
113047
|
-
|
|
113286
|
+
need5(SUPPORTED_CODEX_HOOK_VERSIONS.includes(rpc.version), "NATIVE_UNSUPPORTED_VERSION");
|
|
113048
113287
|
const list2 = async () => {
|
|
113049
113288
|
const response = await rpc.request("hooks/list", { cwds: [home] });
|
|
113050
113289
|
const nativeRoots = [...new Set([physicalRoot, join61(home, ".codex")])];
|
|
113051
113290
|
const coexistenceWarnings = nativeRoots.flatMap((hooksRoot) => nativeRoots.map((configRoot) => `loading hooks from both ${join61(hooksRoot, "hooks.json")} and ${join61(configRoot, "config.toml")}; prefer a single representation for this layer`));
|
|
113052
113291
|
const entry = response?.data?.[0];
|
|
113053
|
-
|
|
113292
|
+
need5(Array.isArray(response?.data) && response.data.length === 1 && entry.cwd === home && Array.isArray(entry.hooks) && entry.hooks.length <= 1e4 && Array.isArray(entry.errors) && entry.errors.length === 0 && Array.isArray(entry.warnings) && entry.warnings.length <= 1 && entry.warnings.every((warning) => typeof warning === "string" && coexistenceWarnings.includes(warning)), "NATIVE_DISCOVERY_REFUSED");
|
|
113054
113293
|
return response.data[0].hooks;
|
|
113055
113294
|
};
|
|
113056
113295
|
const admit = (hooks) => expected.map((wanted) => {
|
|
113057
113296
|
const canonicalKey = wanted.key.replace(join61(home, ".codex/hooks.json"), hooksPath);
|
|
113058
113297
|
const matches2 = hooks.filter((h2) => h2.key === wanted.key || h2.key === canonicalKey);
|
|
113059
|
-
|
|
113298
|
+
need5(matches2.length === 1, "AMBIGUOUS_IDENTITY");
|
|
113060
113299
|
const h = matches2[0];
|
|
113061
|
-
|
|
113062
|
-
|
|
113063
|
-
|
|
113300
|
+
need5(h.eventName === wanted.event && h.handlerType === "command" && h.command === wanted.command && h.matcher === null && h.timeoutSec === 15 && h.async === false && h.statusMessage === null && h.additionalContextLimit === null && [hooksPath, join61(home, ".codex/hooks.json")].includes(h.sourcePath) && h.source === "user" && h.pluginId === null && h.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(h.currentHash) && typeof h.enabled === "boolean", "NATIVE_IDENTITY_CHANGED");
|
|
113301
|
+
need5(hooks.filter((other) => other.command === wanted.command).length === 1, "AMBIGUOUS_IDENTITY");
|
|
113302
|
+
need5(["trusted", "untrusted", "modified"].includes(h.trustStatus), "UNKNOWN_TRUST_STATUS");
|
|
113064
113303
|
return { key: h.key, event: wanted.event, command: wanted.command, handlerType: h.handlerType, timeoutSec: h.timeoutSec, sourcePath: h.sourcePath, currentHash: h.currentHash, enabled: h.enabled, trustStatus: h.trustStatus };
|
|
113065
113304
|
});
|
|
113066
113305
|
const discovered = await list2(), admitted = admit(discovered);
|
|
113067
113306
|
const config2 = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
113068
113307
|
const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
113069
|
-
|
|
113070
|
-
|
|
113071
|
-
|
|
113308
|
+
need5(layers?.length === 1 && /^sha256:[a-f0-9]{64}$/.test(layers[0].version) && !layers[0].disabledReason && codexNativeConfigEqual(layers[0].config, original), "NATIVE_CONFIG_MISMATCH");
|
|
113309
|
+
need5(config2.config && config2.config.features?.hooks !== false, "NATIVE_HOOKS_DISABLED");
|
|
113310
|
+
need5(config2.layers.slice(0, config2.layers.indexOf(layers[0])).every((layer) => layer.config?.hooks === undefined && layer.config?.features?.hooks === undefined), "NATIVE_CONFIG_OVERRIDDEN");
|
|
113072
113311
|
const keys3 = admitted.map((h) => h.key), planned = admitted.filter((h) => !h.enabled || h.trustStatus !== "trusted");
|
|
113073
113312
|
const preservationText = codexTrustTextWitness(configFile.text, keys3);
|
|
113074
113313
|
const checkInputs = () => {
|
|
@@ -113083,16 +113322,16 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
113083
113322
|
const planDigest = makePlanDigest(digestInput);
|
|
113084
113323
|
const baseReceipt = { planDigest, skillsCli: skillsCli.receipt, nativeVersion: rpc.version, nativeConfigPath, ownedNativeProcessId: rpc.processId, agent: "codex", applied: false, planned, nativeEligible: planned.length === 0, bindingRefreshRequired: false, transport: "owned-stdio-process", existingSessionsReloaded: false, nativeExecutionVerified: false };
|
|
113085
113324
|
if (options.apply)
|
|
113086
|
-
|
|
113325
|
+
need5(options.reviewedPlanDigest === planDigest, "PLAN_CHANGED: run the dry-run again and pass its reviewed --plan-digest with --apply");
|
|
113087
113326
|
const parent = join61(dataDir3, "native-hook-trust");
|
|
113088
113327
|
const st = lstatSync32(parent, { throwIfNoEntry: false });
|
|
113089
113328
|
if (st) {
|
|
113090
|
-
|
|
113329
|
+
need5(st.isDirectory() && !st.isSymbolicLink() && st.uid === process.getuid() && (st.mode & 511) === 448, "UNSAFE_JOURNAL");
|
|
113091
113330
|
const priorJournals = readdirSync23(parent);
|
|
113092
|
-
|
|
113331
|
+
need5(priorJournals.length < 1000, "JOURNAL_BOUND");
|
|
113093
113332
|
for (const name of priorJournals) {
|
|
113094
|
-
|
|
113095
|
-
|
|
113333
|
+
need5(/^[a-f0-9-]{36}$/.test(name), "UNSAFE_JOURNAL");
|
|
113334
|
+
need5(lstatSync32(join61(parent, name, "receipt.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_REQUIRED: inspect the incomplete private journal before applying again");
|
|
113096
113335
|
}
|
|
113097
113336
|
}
|
|
113098
113337
|
if (!options.apply || !planned.length)
|
|
@@ -113105,7 +113344,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
113105
113344
|
save(join61(journal, "hooks.before.json"), hooksFile.bytes);
|
|
113106
113345
|
save(join61(journal, "policy.before.json"), policyFile.bytes);
|
|
113107
113346
|
save(join61(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook) }));
|
|
113108
|
-
|
|
113347
|
+
need5(isDeepStrictEqual15(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
|
|
113109
113348
|
checkInputs();
|
|
113110
113349
|
let result2;
|
|
113111
113350
|
try {
|
|
@@ -113113,22 +113352,22 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
113113
113352
|
} catch {
|
|
113114
113353
|
throw new Error("CODEX_HOOK_TRUST_NATIVE_WRITE_FAILED: reconcile the private journal before retrying");
|
|
113115
113354
|
}
|
|
113116
|
-
|
|
113355
|
+
need5(result2?.status === "ok" && result2.filePath === configPath && /^sha256:[a-f0-9]{64}$/.test(result2.version), "NATIVE_WRITE_OVERRIDDEN");
|
|
113117
113356
|
const after2 = snapshot(configPath, true), expectedConfig = structuredClone(original);
|
|
113118
113357
|
expectedConfig.hooks ??= {};
|
|
113119
113358
|
expectedConfig.hooks.state ??= {};
|
|
113120
113359
|
for (const h of planned)
|
|
113121
113360
|
expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
|
|
113122
|
-
|
|
113361
|
+
need5(isDeepStrictEqual15(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys3) === preservationText, "PRESERVATION_FAILED");
|
|
113123
113362
|
const savedConfig = snapshot(join61(journal, "config.before.toml"), true);
|
|
113124
|
-
|
|
113363
|
+
need5(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
|
|
113125
113364
|
const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
|
|
113126
113365
|
unchanged2(policyFile);
|
|
113127
113366
|
unchanged2(hooksFile);
|
|
113128
113367
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
|
|
113129
113368
|
const current = await list2(), confirmed = admit(current);
|
|
113130
|
-
|
|
113131
|
-
|
|
113369
|
+
need5(confirmed.every((h) => h.enabled && h.trustStatus === "trusted") && isDeepStrictEqual15(confirmed.map((h) => [h.key, h.currentHash]), admitted.map((h) => [h.key, h.currentHash])), "NATIVE_NOT_ELIGIBLE");
|
|
113370
|
+
need5(isDeepStrictEqual15(current.filter((h) => !keys3.includes(h.key)), discovered.filter((h) => !keys3.includes(h.key))), "UNRELATED_HOOK_CHANGED");
|
|
113132
113371
|
unchanged2(after2);
|
|
113133
113372
|
unchanged2(savedConfig);
|
|
113134
113373
|
unchanged2(hooksFile);
|
|
@@ -113291,7 +113530,7 @@ var exports_agent_integration = {};
|
|
|
113291
113530
|
__export(exports_agent_integration, {
|
|
113292
113531
|
registerAgentIntegration: () => registerAgentIntegration
|
|
113293
113532
|
});
|
|
113294
|
-
import { existsSync as existsSync36, lstatSync as lstatSync33, readFileSync as readFileSync41, writeFileSync as writeFileSync26 } from "fs";
|
|
113533
|
+
import { existsSync as existsSync36, lstatSync as lstatSync33, readFileSync as readFileSync41, readSync as readSync17, writeFileSync as writeFileSync26 } from "fs";
|
|
113295
113534
|
import { createHash as createHash32 } from "crypto";
|
|
113296
113535
|
import { homedir as homedir13 } from "os";
|
|
113297
113536
|
import { isAbsolute as isAbsolute29 } from "path";
|
|
@@ -113361,6 +113600,32 @@ function agents(value) {
|
|
|
113361
113600
|
}
|
|
113362
113601
|
function registerAgentIntegration(parent) {
|
|
113363
113602
|
const hook = parent.command("hook").description("Load selected Skills context through agent lifecycle hooks");
|
|
113603
|
+
hook.command("project-claude-settings").requiredOption("--expected-target-sha256 <sha256>", "SHA-256 of the exact target settings bytes supplied on stdin").option("--expected-source-sha256 <sha256>", "Source witness from the preceding projection, for revalidation").option("--json", "Return only command-leaf replacements and content witnesses", false).description("Read-only projection of verified Skills hooks into copied Claude settings supplied on stdin").action(async (options) => {
|
|
113604
|
+
try {
|
|
113605
|
+
const bytes = Buffer.alloc(1024 * 1024 + 1);
|
|
113606
|
+
let length = 0;
|
|
113607
|
+
while (length < bytes.length) {
|
|
113608
|
+
const count = readSync17(0, bytes, length, bytes.length - length, null);
|
|
113609
|
+
if (!count)
|
|
113610
|
+
break;
|
|
113611
|
+
length += count;
|
|
113612
|
+
}
|
|
113613
|
+
if (length > 1024 * 1024)
|
|
113614
|
+
throw new Error("CLAUDE_MANAGED_HOOK_TARGET_INVALID");
|
|
113615
|
+
const targetSettings = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length));
|
|
113616
|
+
const plan = planClaudeManagedHookProjection({
|
|
113617
|
+
targetSettings,
|
|
113618
|
+
expectedTargetSha256: options.expectedTargetSha256,
|
|
113619
|
+
expectedSourceSha256: options.expectedSourceSha256,
|
|
113620
|
+
projectDir: process.cwd()
|
|
113621
|
+
});
|
|
113622
|
+
await writeCliOutput(JSON.stringify(plan));
|
|
113623
|
+
} catch (error61) {
|
|
113624
|
+
const code2 = error61 instanceof Error && /^CLAUDE_MANAGED_HOOK_[A-Z_]+$/.test(error61.message) ? error61.message : "CLAUDE_MANAGED_HOOK_PROJECTION_FAILED";
|
|
113625
|
+
console.error(code2);
|
|
113626
|
+
process.exitCode = 1;
|
|
113627
|
+
}
|
|
113628
|
+
});
|
|
113364
113629
|
hook.command("native-catalog").requiredOption("--cwd <path>", "Absolute project directory observed by native Codex skills/list").requiredOption("--output <file>", "New private file for the projected native catalog").option("--codex-command <path>", "Installed Codex executable used by the native client", "codex").option("--json", "Output bounded capture metadata as JSON", false).description("Capture a reviewed Codex native skill catalog for exact hook enrollment").action(async (options) => {
|
|
113365
113630
|
try {
|
|
113366
113631
|
if (!isAbsolute29(options.cwd) || !isAbsolute29(options.output))
|
|
@@ -113772,7 +114037,7 @@ var exports_profiles = {};
|
|
|
113772
114037
|
__export(exports_profiles, {
|
|
113773
114038
|
registerProfiles: () => registerProfiles
|
|
113774
114039
|
});
|
|
113775
|
-
import { closeSync as closeSync21, constants as constants20, fstatSync as fstatSync21, openSync as openSync21, readSync as
|
|
114040
|
+
import { closeSync as closeSync21, constants as constants20, fstatSync as fstatSync21, openSync as openSync21, readSync as readSync18, writeFileSync as writeFileSync27 } from "fs";
|
|
113776
114041
|
function readProfileInput(path) {
|
|
113777
114042
|
const fd = openSync21(path, constants20.O_RDONLY | constants20.O_NONBLOCK);
|
|
113778
114043
|
try {
|
|
@@ -113782,7 +114047,7 @@ function readProfileInput(path) {
|
|
|
113782
114047
|
const bytes = Buffer.alloc(MAX_PROFILE_DOCUMENT_BYTES + 1);
|
|
113783
114048
|
let size2 = 0;
|
|
113784
114049
|
while (size2 < bytes.length) {
|
|
113785
|
-
const read3 =
|
|
114050
|
+
const read3 = readSync18(fd, bytes, size2, bytes.length - size2, null);
|
|
113786
114051
|
if (!read3)
|
|
113787
114052
|
break;
|
|
113788
114053
|
size2 += read3;
|
|
@@ -113846,7 +114111,7 @@ import {
|
|
|
113846
114111
|
closeSync as closeSync23,
|
|
113847
114112
|
fstatSync as fstatSync23,
|
|
113848
114113
|
openSync as openSync23,
|
|
113849
|
-
readSync as
|
|
114114
|
+
readSync as readSync19,
|
|
113850
114115
|
writeFileSync as writeFileSync28
|
|
113851
114116
|
} from "fs";
|
|
113852
114117
|
function readInput(path) {
|
|
@@ -113858,7 +114123,7 @@ function readInput(path) {
|
|
|
113858
114123
|
const bytes = Buffer.alloc(MAX_EXECUTION_GRANT_BYTES + 1);
|
|
113859
114124
|
let size2 = 0;
|
|
113860
114125
|
while (size2 < bytes.length) {
|
|
113861
|
-
const count =
|
|
114126
|
+
const count = readSync19(fd, bytes, size2, bytes.length - size2, null);
|
|
113862
114127
|
if (!count)
|
|
113863
114128
|
break;
|
|
113864
114129
|
size2 += count;
|