@hasna/skills 0.10.24 → 0.10.26
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 +11 -0
- package/bin/index.js +248 -35
- package/bin/mcp.js +426 -312
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.js +245 -35
- package/dist/lib/agent-discovery-directories.d.ts +2 -2
- package/dist/lib/agent-discovery.d.ts +7 -0
- package/dist/lib/agent-integration.d.ts +1 -0
- package/dist/lib/codex-native-skill-catalog.d.ts +9 -1
- package/dist/lib/codex-plugin-skill-controls.d.ts +28 -1
- package/dist/sdk/index.js +245 -35
- package/package.json +1 -1
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
|
|
|
24122
24122
|
// package.json
|
|
24123
24123
|
var package_default = {
|
|
24124
24124
|
name: "@hasna/skills",
|
|
24125
|
-
version: "0.10.
|
|
24125
|
+
version: "0.10.26",
|
|
24126
24126
|
description: "Skills library for AI coding agents",
|
|
24127
24127
|
type: "module",
|
|
24128
24128
|
bin: {
|
package/bin/worker.js
CHANGED
|
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
24125
24125
|
// package.json
|
|
24126
24126
|
var package_default = {
|
|
24127
24127
|
name: "@hasna/skills",
|
|
24128
|
-
version: "0.10.
|
|
24128
|
+
version: "0.10.26",
|
|
24129
24129
|
description: "Skills library for AI coding agents",
|
|
24130
24130
|
type: "module",
|
|
24131
24131
|
bin: {
|
package/dist/index.js
CHANGED
|
@@ -9051,6 +9051,18 @@ function assertAgentPolicyCollections(policy) {
|
|
|
9051
9051
|
if (control.appSha256 !== undefined)
|
|
9052
9052
|
requireBound(typeof control.appSha256 === "string" && /^[a-f0-9]{64}$/.test(control.appSha256));
|
|
9053
9053
|
}
|
|
9054
|
+
if (bridge.codexInactivePlugins !== undefined) {
|
|
9055
|
+
const controls = array(bridge.codexInactivePlugins, 4096), ids = new Set;
|
|
9056
|
+
requireBound(controls.length === 0 || object(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === "codex-cli 0.160.0" && typeof bridge.codexPluginSkillReview.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(bridge.codexPluginSkillReview.catalogSha256));
|
|
9057
|
+
for (const control of controls) {
|
|
9058
|
+
requireBound(object(control) && Object.keys(control).every((key) => ["pluginId", "namespace", "pluginParent", "sourceType", "sourceSha256"].includes(key)));
|
|
9059
|
+
text(control.pluginId, 1024);
|
|
9060
|
+
text(control.namespace, 64);
|
|
9061
|
+
text(control.pluginParent);
|
|
9062
|
+
requireBound(!ids.has(control.pluginId) && isAbsolute2(control.pluginParent) && resolve2(control.pluginParent) === control.pluginParent && ["local", "git", "npm"].includes(control.sourceType) && typeof control.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(control.sourceSha256));
|
|
9063
|
+
ids.add(control.pluginId);
|
|
9064
|
+
}
|
|
9065
|
+
}
|
|
9054
9066
|
if (bridge.disabledBuiltins !== undefined)
|
|
9055
9067
|
for (const builtin of array(bridge.disabledBuiltins, AGENT_POLICY_LIMITS.builtinNames)) {
|
|
9056
9068
|
requireBound(object(builtin));
|
|
@@ -9063,6 +9075,25 @@ function assertAgentPolicyCollections(policy) {
|
|
|
9063
9075
|
text(agent, 128);
|
|
9064
9076
|
requireBound(object(value));
|
|
9065
9077
|
text(value.agent, 128);
|
|
9078
|
+
if (value.codexInstallationInputs !== undefined) {
|
|
9079
|
+
const proof = record(value.codexInstallationInputs, 4), inputs = array(proof.plugins, 4096), ids = new Set;
|
|
9080
|
+
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && proof.version === "codex-cli 0.160.0" && typeof proof.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(proof.catalogSha256) && inputs.length > 0 && object(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === proof.version && bridge.codexPluginSkillReview.catalogSha256 === proof.catalogSha256);
|
|
9081
|
+
for (const input of inputs) {
|
|
9082
|
+
requireBound(object(input) && Object.keys(input).every((key) => ["pluginId", "namespace", "pluginParent", "sourceRoot", "sourceSha256"].includes(key)));
|
|
9083
|
+
text(input.pluginId, 1024);
|
|
9084
|
+
text(input.namespace, 64);
|
|
9085
|
+
text(input.pluginParent);
|
|
9086
|
+
text(input.sourceRoot);
|
|
9087
|
+
requireBound(!ids.has(input.pluginId) && [input.pluginParent, input.sourceRoot].every((path) => isAbsolute2(path) && resolve2(path) === path) && typeof input.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(input.sourceSha256));
|
|
9088
|
+
ids.add(input.pluginId);
|
|
9089
|
+
}
|
|
9090
|
+
if (proof.directories !== undefined)
|
|
9091
|
+
for (const directory of array(proof.directories, AGENT_POLICY_LIMITS.discoveryDirectories)) {
|
|
9092
|
+
requireBound(object(directory));
|
|
9093
|
+
text(directory.path);
|
|
9094
|
+
requireBound(directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256));
|
|
9095
|
+
}
|
|
9096
|
+
}
|
|
9066
9097
|
for (const root of array(value.roots, AGENT_POLICY_LIMITS.discoveryRoots))
|
|
9067
9098
|
text(root);
|
|
9068
9099
|
for (const source of array(value.sources, AGENT_POLICY_LIMITS.discoverySources)) {
|
|
@@ -9384,7 +9415,7 @@ function safe(path) {
|
|
|
9384
9415
|
function same(before, after) {
|
|
9385
9416
|
return Boolean(after && after.isDirectory() && before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs);
|
|
9386
9417
|
}
|
|
9387
|
-
function capture(path, budget) {
|
|
9418
|
+
function capture(path, budget, excludedRoots) {
|
|
9388
9419
|
safe(path);
|
|
9389
9420
|
const root = lstatSync4(path, { throwIfNoEntry: false });
|
|
9390
9421
|
if (!root)
|
|
@@ -9413,7 +9444,7 @@ function capture(path, budget) {
|
|
|
9413
9444
|
budget.bytes += Buffer.byteLength(row);
|
|
9414
9445
|
need(budget.bytes <= AGENT_POLICY_LIMITS.discoveryDirectoryBytes, "Native discovery directory metadata limit exceeded");
|
|
9415
9446
|
hash.update(row);
|
|
9416
|
-
if (stat.isDirectory())
|
|
9447
|
+
if (stat.isDirectory() && !excludedRoots.includes(child))
|
|
9417
9448
|
walk(child, childRelative, stat, depth + 1);
|
|
9418
9449
|
}
|
|
9419
9450
|
need(same(before, lstatSync4(current, { throwIfNoEntry: false })), "Native discovery directory changed while reading");
|
|
@@ -9422,16 +9453,18 @@ function capture(path, budget) {
|
|
|
9422
9453
|
safe(path);
|
|
9423
9454
|
return { path, sha256: hash.digest("hex") };
|
|
9424
9455
|
}
|
|
9425
|
-
function captureDiscoveryDirectories(paths) {
|
|
9456
|
+
function captureDiscoveryDirectories(paths, excludedRoots = []) {
|
|
9426
9457
|
need(Array.isArray(paths) && paths.length <= AGENT_POLICY_LIMITS.discoveryDirectories && new Set(paths).size === paths.length, "Invalid native discovery directory collection");
|
|
9427
9458
|
const budget = { entries: 0, bytes: 0 };
|
|
9428
|
-
|
|
9459
|
+
for (const root of excludedRoots)
|
|
9460
|
+
safe(root);
|
|
9461
|
+
return paths.map((path) => capture(path, budget, excludedRoots));
|
|
9429
9462
|
}
|
|
9430
|
-
function verifyDiscoveryDirectories(directories) {
|
|
9463
|
+
function verifyDiscoveryDirectories(directories, excludedRoots = []) {
|
|
9431
9464
|
need(Array.isArray(directories) && directories.length <= AGENT_POLICY_LIMITS.discoveryDirectories, "Invalid native discovery directory collection");
|
|
9432
9465
|
for (const directory of directories)
|
|
9433
9466
|
need(directory && typeof directory === "object" && (directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256)), "Invalid native discovery directory digest");
|
|
9434
|
-
const current = captureDiscoveryDirectories(directories.map((directory) => directory.path));
|
|
9467
|
+
const current = captureDiscoveryDirectories(directories.map((directory) => directory.path), excludedRoots);
|
|
9435
9468
|
for (let i = 0;i < directories.length; i++)
|
|
9436
9469
|
need(current[i].sha256 === directories[i].sha256, `Native discovery directory membership changed; run skills hook install with a fresh discovery review: ${directories[i].path}`);
|
|
9437
9470
|
}
|
|
@@ -10881,7 +10914,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
|
|
|
10881
10914
|
// package.json
|
|
10882
10915
|
var package_default = {
|
|
10883
10916
|
name: "@hasna/skills",
|
|
10884
|
-
version: "0.10.
|
|
10917
|
+
version: "0.10.26",
|
|
10885
10918
|
description: "Skills library for AI coding agents",
|
|
10886
10919
|
type: "module",
|
|
10887
10920
|
bin: {
|
|
@@ -13197,6 +13230,10 @@ async function connectCodexHookRpc(options) {
|
|
|
13197
13230
|
}
|
|
13198
13231
|
|
|
13199
13232
|
// src/lib/codex-native-skill-catalog.ts
|
|
13233
|
+
function codexPluginSourceIsConfigControlled(plugin) {
|
|
13234
|
+
const remoteRealms = ["openai-curated-remote", "created-by-me-remote", "workspace-directory", "workspace-shared-with-me", "workspace-shared-with-me-private", "workspace-shared-with-me-unlisted"];
|
|
13235
|
+
return Boolean(plugin.sourceType && ["local", "git", "npm"].includes(plugin.sourceType) && plugin.sourceSha256 && /^[a-f0-9]{64}$/.test(plugin.sourceSha256) && !plugin.remotePluginId && !remoteRealms.includes(plugin.id.slice(plugin.id.lastIndexOf("@") + 1)));
|
|
13236
|
+
}
|
|
13200
13237
|
var MAX_SKILLS = 4096;
|
|
13201
13238
|
var MAX_NAME_BYTES = 1024;
|
|
13202
13239
|
var MAX_PATH_BYTES = 16384;
|
|
@@ -13208,6 +13245,15 @@ var object4 = (value) => value !== null && typeof value === "object" && !Array.i
|
|
|
13208
13245
|
var scalar = (value, max) => typeof value === "string" && value.length > 0 && value.trim() === value && Buffer.byteLength(value, "utf8") <= max && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
13209
13246
|
var remotePluginIdentifier = (value) => typeof value === "string" && /^[A-Za-z0-9_~-]{1,1024}$/.test(value);
|
|
13210
13247
|
var absolutePath2 = (value) => scalar(value, MAX_PATH_BYTES) && isAbsolute10(value);
|
|
13248
|
+
function projectPluginSource(source) {
|
|
13249
|
+
if (!object4(source))
|
|
13250
|
+
return {};
|
|
13251
|
+
if (source.type === "remote")
|
|
13252
|
+
return { sourceType: "remote" };
|
|
13253
|
+
const optionalText = (value) => value === undefined || value === null || scalar(value, MAX_PATH_BYTES);
|
|
13254
|
+
const valid = source.type === "local" ? Object.keys(source).every((key) => ["type", "path"].includes(key)) && absolutePath2(source.path) : source.type === "git" ? Object.keys(source).every((key) => ["type", "url", "path", "refName", "sha"].includes(key)) && scalar(source.url, MAX_PATH_BYTES) && [source.path, source.refName, source.sha].every(optionalText) : source.type === "npm" ? Object.keys(source).every((key) => ["type", "package", "version", "registry"].includes(key)) && scalar(source.package, MAX_NAME_BYTES) && [source.version, source.registry].every(optionalText) : false;
|
|
13255
|
+
return valid ? { sourceType: source.type, sourceSha256: hashNativeJsonControls(JSON.stringify(source)), ...source.type === "local" ? { sourcePath: source.path } : {} } : {};
|
|
13256
|
+
}
|
|
13211
13257
|
function projectCodexNativeSkillCatalog(response, cwd) {
|
|
13212
13258
|
if (!absolutePath2(cwd) || !object4(response) || !Array.isArray(response.data) || response.data.length !== 1)
|
|
13213
13259
|
refuse();
|
|
@@ -13237,7 +13283,10 @@ function projectCodexInstalledPlugins(response) {
|
|
|
13237
13283
|
plugins.push({
|
|
13238
13284
|
...value,
|
|
13239
13285
|
marketplace: marketplace.name,
|
|
13240
|
-
sourceType:
|
|
13286
|
+
sourceType: undefined,
|
|
13287
|
+
sourceSha256: undefined,
|
|
13288
|
+
sourcePath: undefined,
|
|
13289
|
+
...projectPluginSource(value.source)
|
|
13241
13290
|
});
|
|
13242
13291
|
}
|
|
13243
13292
|
}
|
|
@@ -13248,7 +13297,7 @@ function projectCodexInstalledPluginEntries(value) {
|
|
|
13248
13297
|
refuse();
|
|
13249
13298
|
const ids = new Set;
|
|
13250
13299
|
return value.map((item) => {
|
|
13251
|
-
if (!object4(item) || !scalar(item.id, MAX_PLUGIN_ID_BYTES) || !scalar(item.name, MAX_NAME_BYTES) || typeof item.installed !== "boolean" || typeof item.enabled !== "boolean" || item.localVersion !== null && !scalar(item.localVersion, MAX_NAME_BYTES) || item.remotePluginId !== undefined && item.remotePluginId !== null && !remotePluginIdentifier(item.remotePluginId) || item.sourceType !== undefined && item.sourceType !== "
|
|
13300
|
+
if (!object4(item) || !scalar(item.id, MAX_PLUGIN_ID_BYTES) || !scalar(item.name, MAX_NAME_BYTES) || typeof item.installed !== "boolean" || typeof item.enabled !== "boolean" || item.localVersion !== null && !scalar(item.localVersion, MAX_NAME_BYTES) || item.remotePluginId !== undefined && item.remotePluginId !== null && !remotePluginIdentifier(item.remotePluginId) || item.sourceType !== undefined && !["remote", "local", "git", "npm"].includes(item.sourceType) || (["local", "git", "npm"].includes(item.sourceType) ? typeof item.sourceSha256 !== "string" || !/^[a-f0-9]{64}$/.test(item.sourceSha256) : item.sourceSha256 !== undefined) || (item.sourceType === "local" ? !absolutePath2(item.sourcePath) || normalize2(item.sourcePath) !== item.sourcePath || item.sourceSha256 !== hashNativeJsonControls(JSON.stringify({ type: "local", path: item.sourcePath })) : item.sourcePath !== undefined))
|
|
13252
13301
|
refuse();
|
|
13253
13302
|
const at = item.id.lastIndexOf("@");
|
|
13254
13303
|
if (at < 1 || item.id.slice(0, at) !== item.name || at === item.id.length - 1 || ids.has(item.id))
|
|
@@ -13260,7 +13309,9 @@ function projectCodexInstalledPluginEntries(value) {
|
|
|
13260
13309
|
installed: item.installed,
|
|
13261
13310
|
enabled: item.enabled,
|
|
13262
13311
|
localVersion: item.localVersion ?? null,
|
|
13263
|
-
...item.sourceType ===
|
|
13312
|
+
...item.sourceType === undefined ? {} : { sourceType: item.sourceType },
|
|
13313
|
+
...item.sourceSha256 === undefined ? {} : { sourceSha256: item.sourceSha256 },
|
|
13314
|
+
...item.sourcePath === undefined ? {} : { sourcePath: item.sourcePath },
|
|
13264
13315
|
...item.remotePluginId == null ? {} : { remotePluginId: item.remotePluginId }
|
|
13265
13316
|
};
|
|
13266
13317
|
});
|
|
@@ -13473,7 +13524,7 @@ function rootControls(root, read2) {
|
|
|
13473
13524
|
const appSha256 = present ? appControls(app, read2) : undefined;
|
|
13474
13525
|
return { namespace: manifest.name, manifestSha256, ...appSha256 ? { appSha256 } : {}, ...mcpSha256 ? { mcpSha256 } : {} };
|
|
13475
13526
|
}
|
|
13476
|
-
function
|
|
13527
|
+
function documentIdentity(document2, cache, read2) {
|
|
13477
13528
|
if (!document2.startsWith(cache + sep4) || !document2.endsWith(sep4 + "SKILL.md"))
|
|
13478
13529
|
refuse2();
|
|
13479
13530
|
const text2 = read2(document2);
|
|
@@ -13501,11 +13552,33 @@ function identity3(document2, cache, read2) {
|
|
|
13501
13552
|
const path = join14(root, ".codex-plugin", "plugin.json");
|
|
13502
13553
|
if (!lstatSync12(path, { throwIfNoEntry: false }))
|
|
13503
13554
|
continue;
|
|
13504
|
-
const
|
|
13505
|
-
|
|
13555
|
+
const manifestFile = lstatSync12(path);
|
|
13556
|
+
if (!lstatSync12(join14(root, ".codex-plugin")).isDirectory() || !manifestFile.isFile() || manifestFile.size > 1024 * 1024)
|
|
13557
|
+
refuse2();
|
|
13558
|
+
let manifest;
|
|
13559
|
+
try {
|
|
13560
|
+
const text3 = read2(path);
|
|
13561
|
+
hashNativeJsonControls(text3);
|
|
13562
|
+
manifest = JSON.parse(text3);
|
|
13563
|
+
} catch {
|
|
13564
|
+
refuse2();
|
|
13565
|
+
}
|
|
13566
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest) || !identifier2(manifest.name))
|
|
13567
|
+
refuse2();
|
|
13568
|
+
const declaredVersion = manifest.version === undefined ? "" : typeof manifest.version === "string" ? manifest.version.trim() : null;
|
|
13569
|
+
if (declaredVersion === null || (declaredVersion ? declaredVersion !== basename5(root) : basename5(root) !== CODEX_DEFAULT_AGENT_PLUGIN_VERSION))
|
|
13570
|
+
refuse2();
|
|
13571
|
+
return { name: `${manifest.name}:${front.name}`, namespace: manifest.name, pluginParent: dirname10(root), root };
|
|
13506
13572
|
}
|
|
13507
13573
|
refuse2();
|
|
13508
13574
|
}
|
|
13575
|
+
function identity3(document2, cache, read2) {
|
|
13576
|
+
const parsed = documentIdentity(document2, cache, read2);
|
|
13577
|
+
const controls = rootControls(parsed.root, read2);
|
|
13578
|
+
if (controls.namespace !== parsed.namespace)
|
|
13579
|
+
refuse2();
|
|
13580
|
+
return { name: parsed.name, pluginParent: parsed.pluginParent, ...controls };
|
|
13581
|
+
}
|
|
13509
13582
|
function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
|
|
13510
13583
|
if (!Array.isArray(controls) || controls.length > 4096)
|
|
13511
13584
|
return false;
|
|
@@ -13543,11 +13616,13 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
|
|
|
13543
13616
|
return false;
|
|
13544
13617
|
}
|
|
13545
13618
|
}
|
|
13546
|
-
function
|
|
13619
|
+
function reviewCodexPluginControls(catalog, documents, cache, cwd, read2, rules, pluginSettings) {
|
|
13547
13620
|
if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd)
|
|
13548
13621
|
refuse2();
|
|
13549
13622
|
const skills = projectCodexNativeSkillCatalog({ data: [{ cwd, errors: [], skills: catalog.skills }] }, cwd);
|
|
13550
|
-
const allowed = new Set(documents), result = [];
|
|
13623
|
+
const allowed = new Set(documents), result = [], inertDocuments = new Set;
|
|
13624
|
+
const inactivePlugins = new Map;
|
|
13625
|
+
const sourceInputs = new Map;
|
|
13551
13626
|
let installedPlugins;
|
|
13552
13627
|
try {
|
|
13553
13628
|
installedPlugins = projectCodexInstalledPluginEntries(catalog.plugins ?? []);
|
|
@@ -13558,25 +13633,48 @@ function reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2, r
|
|
|
13558
13633
|
for (const document2 of candidates) {
|
|
13559
13634
|
if (!document2.startsWith(cache + sep4))
|
|
13560
13635
|
continue;
|
|
13561
|
-
const
|
|
13562
|
-
if (
|
|
13636
|
+
const located = documentIdentity(document2, cache, read2);
|
|
13637
|
+
if (located.name === "skills-cli" || located.name.endsWith(":skills-cli"))
|
|
13563
13638
|
refuse2();
|
|
13564
|
-
const cacheParts = relative4(cache,
|
|
13639
|
+
const cacheParts = relative4(cache, located.pluginParent).split(sep4);
|
|
13565
13640
|
if (cacheParts.length !== 2 || cacheParts.some((part) => !identifier2(part)))
|
|
13566
13641
|
refuse2();
|
|
13567
13642
|
const [marketplace, pluginName] = cacheParts;
|
|
13568
|
-
if (pluginName !==
|
|
13643
|
+
if (pluginName !== located.namespace)
|
|
13569
13644
|
refuse2();
|
|
13570
13645
|
const expectedPluginId = `${pluginName}@${marketplace}`;
|
|
13571
13646
|
const listed = skills.filter((skill) => skill.path === document2);
|
|
13572
13647
|
if (listed.length > 1)
|
|
13573
13648
|
refuse2();
|
|
13649
|
+
const installed = installedPlugins.filter((plugin) => plugin.id === expectedPluginId && plugin.name === pluginName);
|
|
13650
|
+
if (catalog.version === "codex-cli 0.160.0" && installed.length === 1 && installed[0].installed && installed[0].localVersion === basename5(located.root) && installed[0].sourceType === "local" && codexPluginSourceIsConfigControlled(installed[0])) {
|
|
13651
|
+
const plugin = installed[0], input = { pluginId: expectedPluginId, namespace: located.namespace, pluginParent: located.pluginParent, sourceRoot: plugin.sourcePath, sourceSha256: plugin.sourceSha256 };
|
|
13652
|
+
if (input.sourceRoot !== located.root && !input.sourceRoot.startsWith(cache + sep4) && (lstatSync12(input.sourceRoot, { throwIfNoEntry: false }) || documents.some((path) => path.startsWith(input.sourceRoot + sep4)))) {
|
|
13653
|
+
if (skills.some((skill) => skill.path === input.sourceRoot || skill.path.startsWith(input.sourceRoot + sep4)) || !sourceInputUnchanged(input, cache, read2))
|
|
13654
|
+
refuse2();
|
|
13655
|
+
sourceInputs.set(expectedPluginId, input);
|
|
13656
|
+
}
|
|
13657
|
+
}
|
|
13658
|
+
const settings = pluginSettings && typeof pluginSettings === "object" && !Array.isArray(pluginSettings) ? pluginSettings : undefined;
|
|
13659
|
+
if (catalog.version === "codex-cli 0.160.0" && !listed.length && !skills.some((skill) => skill.pluginId === expectedPluginId) && installed.length === 1 && installed[0].installed && !installed[0].enabled && codexPluginSourceIsConfigControlled(installed[0]) && settings && Object.hasOwn(settings, expectedPluginId) && settings[expectedPluginId]?.enabled === false) {
|
|
13660
|
+
if (!Array.isArray(rules))
|
|
13661
|
+
refuse2();
|
|
13662
|
+
const exactPathDeny = rules.some((rule) => rule?.path === document2 && rule?.enabled === false);
|
|
13663
|
+
if (!lstatSync12(document2).isFile() || realpathSync5(document2) !== document2 || realpathSync5(located.root) !== located.root || realpathSync5(located.pluginParent) !== located.pluginParent || realpathSync5(join14(located.root, ".codex-plugin/plugin.json")) !== join14(located.root, ".codex-plugin/plugin.json") || !exactPathDeny || rules.some((rule) => typeof rule?.name === "string" && rule.name.trim() === located.name && rule?.enabled !== false) || !isCodexNativeSkillDisabled({ name: located.name, path: document2, pluginId: expectedPluginId, enabled: true }, rules))
|
|
13664
|
+
refuse2();
|
|
13665
|
+
inertDocuments.add(document2);
|
|
13666
|
+
const plugin = installed[0];
|
|
13667
|
+
inactivePlugins.set(expectedPluginId, { pluginId: expectedPluginId, namespace: located.namespace, pluginParent: located.pluginParent, sourceType: plugin.sourceType, sourceSha256: plugin.sourceSha256 });
|
|
13668
|
+
continue;
|
|
13669
|
+
}
|
|
13670
|
+
const parsed = { name: located.name, pluginParent: located.pluginParent, ...rootControls(located.root, read2) };
|
|
13671
|
+
if (parsed.namespace !== located.namespace)
|
|
13672
|
+
refuse2();
|
|
13574
13673
|
let remotePluginId;
|
|
13575
13674
|
if (listed.length === 1) {
|
|
13576
13675
|
if (listed[0].name !== parsed.name || listed[0].pluginId !== expectedPluginId)
|
|
13577
13676
|
refuse2();
|
|
13578
13677
|
} else {
|
|
13579
|
-
const installed = installedPlugins.filter((plugin2) => plugin2.id === expectedPluginId && plugin2.name === pluginName);
|
|
13580
13678
|
if (installed.length !== 1 || !installed[0].installed || !installed[0].enabled)
|
|
13581
13679
|
refuse2();
|
|
13582
13680
|
const plugin = installed[0];
|
|
@@ -13594,9 +13692,48 @@ function reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2, r
|
|
|
13594
13692
|
}
|
|
13595
13693
|
result.push({ ...parsed, pluginId: expectedPluginId, ...remotePluginId ? { remotePluginId } : {} });
|
|
13596
13694
|
}
|
|
13597
|
-
if (!result.length && documents.some((document2) => document2.startsWith(cache + sep4)) || new Set(result.map((item) => item.name)).size !== result.length)
|
|
13695
|
+
if (!result.length && documents.some((document2) => document2.startsWith(cache + sep4) && !inertDocuments.has(document2)) || new Set(result.map((item) => item.name)).size !== result.length)
|
|
13598
13696
|
refuse2();
|
|
13599
|
-
return result;
|
|
13697
|
+
return { skills: result, inactivePlugins: [...inactivePlugins.values()], sourceInputs: [...sourceInputs.values()] };
|
|
13698
|
+
}
|
|
13699
|
+
function sourceInputUnchanged(input, cache, read2) {
|
|
13700
|
+
try {
|
|
13701
|
+
const parts = relative4(cache, input.pluginParent).split(sep4);
|
|
13702
|
+
if (parts.length !== 2 || parts.some((part) => !identifier2(part)) || parts[1] !== input.namespace || input.pluginId !== `${parts[1]}@${parts[0]}` || !codexPluginSourceIsConfigControlled({ id: input.pluginId, sourceType: "local", sourceSha256: input.sourceSha256 }) || input.sourceSha256 !== hashNativeJsonControls(JSON.stringify({ type: "local", path: input.sourceRoot })) || resolve9(input.sourceRoot) !== input.sourceRoot || !lstatSync12(input.sourceRoot).isDirectory() || realpathSync5(input.sourceRoot) !== input.sourceRoot || input.sourceRoot === cache || input.sourceRoot.startsWith(cache + sep4) || cache.startsWith(input.sourceRoot + sep4))
|
|
13703
|
+
return false;
|
|
13704
|
+
const path = join14(input.sourceRoot, ".codex-plugin/plugin.json"), stat = lstatSync12(path);
|
|
13705
|
+
if (!stat.isFile() || stat.size > 1024 * 1024 || realpathSync5(path) !== path)
|
|
13706
|
+
return false;
|
|
13707
|
+
const text2 = read2(path);
|
|
13708
|
+
hashNativeJsonControls(text2);
|
|
13709
|
+
const manifest = JSON.parse(text2);
|
|
13710
|
+
return Boolean(manifest && typeof manifest === "object" && !Array.isArray(manifest) && manifest.name === input.namespace);
|
|
13711
|
+
} catch {
|
|
13712
|
+
return false;
|
|
13713
|
+
}
|
|
13714
|
+
}
|
|
13715
|
+
function reviewedCodexPluginSourceRoots(cache, inputs, read2) {
|
|
13716
|
+
if (!Array.isArray(inputs) || inputs.length > 4096 || new Set(inputs.map((input) => input.pluginId)).size !== inputs.length || new Set(inputs.map((input) => input.sourceRoot)).size !== inputs.length || inputs.some((input) => !sourceInputUnchanged(input, cache, read2)))
|
|
13717
|
+
refuse2();
|
|
13718
|
+
const roots = inputs.map((input) => input.sourceRoot);
|
|
13719
|
+
if (roots.some((root) => roots.some((other) => root !== other && root.startsWith(other + sep4))))
|
|
13720
|
+
refuse2();
|
|
13721
|
+
return roots;
|
|
13722
|
+
}
|
|
13723
|
+
function isReviewedCodexPluginInactive(document2, cache, controls, pluginSettings, rules, read2) {
|
|
13724
|
+
if (!Array.isArray(controls) || controls.length > 4096 || !pluginSettings || typeof pluginSettings !== "object" || Array.isArray(pluginSettings) || !Array.isArray(rules))
|
|
13725
|
+
return false;
|
|
13726
|
+
try {
|
|
13727
|
+
const parsed = documentIdentity(document2, cache, read2), matches = controls.filter((control2) => control2.pluginParent === parsed.pluginParent && control2.namespace === parsed.namespace);
|
|
13728
|
+
if (matches.length !== 1 || !lstatSync12(document2).isFile() || realpathSync5(document2) !== document2 || realpathSync5(parsed.root) !== parsed.root || realpathSync5(parsed.pluginParent) !== parsed.pluginParent || realpathSync5(join14(parsed.root, ".codex-plugin/plugin.json")) !== join14(parsed.root, ".codex-plugin/plugin.json"))
|
|
13729
|
+
return false;
|
|
13730
|
+
const control = matches[0], parts = relative4(cache, parsed.pluginParent).split(sep4);
|
|
13731
|
+
if (parts.length !== 2 || parts.some((part) => !identifier2(part)) || parts[1] !== parsed.namespace || control.pluginId !== `${parts[1]}@${parts[0]}` || !codexPluginSourceIsConfigControlled({ ...control, id: control.pluginId }) || !Object.hasOwn(pluginSettings, control.pluginId) || pluginSettings[control.pluginId]?.enabled !== false)
|
|
13732
|
+
return false;
|
|
13733
|
+
return !rules.some((rule) => (rule?.path === document2 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule?.enabled !== false);
|
|
13734
|
+
} catch {
|
|
13735
|
+
return false;
|
|
13736
|
+
}
|
|
13600
13737
|
}
|
|
13601
13738
|
function isReviewedCodexPluginSkillDisabled(document2, cache, controls, rules, read2) {
|
|
13602
13739
|
if (!document2.startsWith(cache + sep4) || !Array.isArray(controls) || controls.length > 4096)
|
|
@@ -14540,6 +14677,9 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14540
14677
|
for (const [agent, path] of rootDefinitions)
|
|
14541
14678
|
roots.push([agent, canonicalAgentPath(join17(project, path), aliases)]);
|
|
14542
14679
|
}
|
|
14680
|
+
const installationInputs = options.codexInstallationInputRoots ?? [];
|
|
14681
|
+
if (installationInputs.some((input) => resolve10(input) !== input || realpathSync6(input) !== input || roots.some(([agent, path]) => agent === "codex" && (path === input || path.startsWith(input + sep5) || input.startsWith(path + sep5)))))
|
|
14682
|
+
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
14543
14683
|
const entries = [], seen = new Set;
|
|
14544
14684
|
const cacheScans = new Map, maxAliasProofEntries = 1e4;
|
|
14545
14685
|
let discoveryEntries = 0, discoveryPathBytes = 0;
|
|
@@ -14574,11 +14714,13 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14574
14714
|
}
|
|
14575
14715
|
return true;
|
|
14576
14716
|
}
|
|
14577
|
-
function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false) {
|
|
14717
|
+
function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false, configuredRoot = false) {
|
|
14578
14718
|
if (!admitted)
|
|
14579
14719
|
admit(path);
|
|
14580
14720
|
const scan = { complete: true, hasSkills: false, entries: 1 };
|
|
14581
14721
|
assertSafePath(path);
|
|
14722
|
+
if (configuredRoot && agent === "codex" && installationInputs.some((root) => path === root || path.startsWith(`${root}${sep5}`)))
|
|
14723
|
+
return scan;
|
|
14582
14724
|
if (!existsSync8(path))
|
|
14583
14725
|
return scan;
|
|
14584
14726
|
const stat = lstatSync15(path);
|
|
@@ -14634,7 +14776,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14634
14776
|
continue;
|
|
14635
14777
|
}
|
|
14636
14778
|
}
|
|
14637
|
-
const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true);
|
|
14779
|
+
const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true, configuredRoot);
|
|
14638
14780
|
scan.complete &&= childScan.complete;
|
|
14639
14781
|
scan.hasSkills ||= childScan.hasSkills;
|
|
14640
14782
|
scan.entries += childScan.entries;
|
|
@@ -14643,10 +14785,10 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14643
14785
|
cacheScans.set(path, scan);
|
|
14644
14786
|
return scan;
|
|
14645
14787
|
}
|
|
14646
|
-
function scanRoot(agent, path, vendor = false, pluginCache = false) {
|
|
14788
|
+
function scanRoot(agent, path, vendor = false, pluginCache = false, configuredRoot = false) {
|
|
14647
14789
|
if (agent === "hermes" || options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`))
|
|
14648
14790
|
assertNoHermesLegacyShadow(path);
|
|
14649
|
-
if (!visit2(agent, path, vendor, 0, pluginCache).complete)
|
|
14791
|
+
if (!visit2(agent, path, vendor, 0, pluginCache, false, configuredRoot).complete)
|
|
14650
14792
|
throw new Error(`Native skill discovery limit exceeded; review this root before continuing: ${path}`);
|
|
14651
14793
|
}
|
|
14652
14794
|
for (const [agent, path] of roots)
|
|
@@ -14662,7 +14804,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14662
14804
|
const configured = options.configured ? INTEGRATION_AGENTS.filter(includesAgent).flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
|
|
14663
14805
|
for (const root of [...options.agentRoots ?? [], ...configured])
|
|
14664
14806
|
if (includesAgent(root.agent))
|
|
14665
|
-
scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true);
|
|
14807
|
+
scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true, false, true);
|
|
14666
14808
|
if (options.reviewedCacheAlias !== undefined && !reviewedCacheAliasFound)
|
|
14667
14809
|
throw new Error("Reviewed cache alias was not an inventoried skill-containing sibling alias");
|
|
14668
14810
|
recheckRootAliases(aliases);
|
|
@@ -14802,6 +14944,11 @@ enabled = false
|
|
|
14802
14944
|
Bun.TOML.parse(result);
|
|
14803
14945
|
return result;
|
|
14804
14946
|
}
|
|
14947
|
+
function assertCodexInstallationInputs(home, projectDirs, inputs) {
|
|
14948
|
+
const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => resolve10(directory, path)));
|
|
14949
|
+
if (inputs.some((input) => nativeRoots.some((root) => root === input || root.startsWith(input + sep5) || input.startsWith(root + sep5))))
|
|
14950
|
+
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
14951
|
+
}
|
|
14805
14952
|
function planAgentIntegration(options) {
|
|
14806
14953
|
const home = options.home ?? homedir2(), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
14807
14954
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
@@ -14840,11 +14987,16 @@ function planAgentIntegration(options) {
|
|
|
14840
14987
|
const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(join17(home, ".codex", "config.toml"), aliases)) ?? "") : {};
|
|
14841
14988
|
const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, disabledVendorPaths: disabledCodexSkillPaths(codexConfig, aliases) });
|
|
14842
14989
|
const observedNativeSources = [];
|
|
14843
|
-
const
|
|
14990
|
+
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join17(entry.path, "SKILL.md")), canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), resolve10(options.projectDir ?? home), (path) => {
|
|
14844
14991
|
const bytes = readNativeBytes(path, 1024 * 1024);
|
|
14845
14992
|
observedNativeSources.push({ path, sha256: sha3(bytes) });
|
|
14846
14993
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
14847
|
-
}, codexConfig.skills?.config ?? []) : policy.bridge?.codexPluginSkills ?? [];
|
|
14994
|
+
}, codexConfig.skills?.config ?? [], codexConfig.plugins) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
|
|
14995
|
+
const codexPluginSkills = codexPluginControls.skills;
|
|
14996
|
+
const codexInactivePlugins = codexPluginControls.inactivePlugins;
|
|
14997
|
+
const codexDiscovery = discoveries.find((binding) => binding.agent === "codex");
|
|
14998
|
+
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot + sep5) || input.sourceRoot.startsWith(root + sep5)) && codexDiscovery.sources.some((source) => source.path === join17(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined));
|
|
14999
|
+
assertCodexInstallationInputs(home, [options.projectDir ?? home], codexPluginSourceInputs.map((input) => input.sourceRoot));
|
|
14848
15000
|
const changes = [];
|
|
14849
15001
|
for (const agent of [...new Set(options.agents)]) {
|
|
14850
15002
|
if (!INTEGRATION_AGENTS.includes(agent))
|
|
@@ -14909,7 +15061,16 @@ function planAgentIntegration(options) {
|
|
|
14909
15061
|
changes.push({ path: configPath, before: previous, after: next });
|
|
14910
15062
|
}
|
|
14911
15063
|
}
|
|
14912
|
-
const
|
|
15064
|
+
const codexPluginSkillReview = options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha3(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd), ...options.codexNativeCatalog.plugins === undefined ? {} : { plugins: projectCodexInstalledPluginEntries(options.codexNativeCatalog.plugins) } })), configSha256: changes.find((change) => change.path === canonicalAgentPath(join17(home, ".codex/config.toml"), aliases))?.before === null ? null : sha3(readOptional(canonicalAgentPath(join17(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview;
|
|
15065
|
+
const discoveryAfter = discoveries.map((binding) => {
|
|
15066
|
+
const rebound = rebindAgentDiscovery(binding, new Map(changes.map((change) => [change.path, change.after])));
|
|
15067
|
+
if (binding.agent !== "codex")
|
|
15068
|
+
return rebound;
|
|
15069
|
+
const { codexInstallationInputs: previousInputs, ...current } = rebound;
|
|
15070
|
+
const inputs = codexPluginSourceInputs.map((input) => input.sourceRoot);
|
|
15071
|
+
const directories = options.codexNativeCatalog || !previousInputs ? captureDiscoveryDirectories((binding.directories ?? []).filter((directory) => inputs.some((root) => root.startsWith(directory.path + sep5))).map((directory) => directory.path), inputs) : previousInputs.directories;
|
|
15072
|
+
return codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: "codex-cli 0.160.0", catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
|
|
15073
|
+
});
|
|
14913
15074
|
const nextPolicy = { ...policy, version: 1, loading: "cli", profileId, bridge: {
|
|
14914
15075
|
...policy.bridge,
|
|
14915
15076
|
version: CLI_BRIDGE_VERSION,
|
|
@@ -14922,7 +15083,7 @@ function planAgentIntegration(options) {
|
|
|
14922
15083
|
profiles: Object.fromEntries([...new Set([...priorAgents, ...options.agents])].sort().map((agent) => [agent, bindings.get(agent)?.profileId ?? policy.bridge?.profiles?.[agent] ?? policy.profileId])),
|
|
14923
15084
|
disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(join17(home, ".codex", "skills", ".system"), aliases) + sep5)).map((entry) => ({ path: entry.path, hash: entry.hash })) : policy.bridge?.disabledBuiltins ?? [],
|
|
14924
15085
|
rootAliases: aliases,
|
|
14925
|
-
...
|
|
15086
|
+
...options.codexNativeCatalog || codexPluginSkills.length || codexInactivePlugins.length || codexPluginSourceInputs.length ? { codexPluginSkills, codexInactivePlugins, codexPluginSkillReview } : {},
|
|
14926
15087
|
discovery: { ...policy.bridge?.discovery, ...Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) }
|
|
14927
15088
|
} };
|
|
14928
15089
|
const serializedPolicy = serializeManagedSkillPolicy(nextPolicy);
|
|
@@ -15413,7 +15574,12 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15413
15574
|
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
15414
15575
|
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024))))
|
|
15415
15576
|
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
|
|
15416
|
-
const disabledPlugin = (entry) =>
|
|
15577
|
+
const disabledPlugin = (entry) => {
|
|
15578
|
+
if (agent !== "codex" || !entry.vendor)
|
|
15579
|
+
return false;
|
|
15580
|
+
const document2 = join17(entry.path, "SKILL.md"), cache = canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024));
|
|
15581
|
+
return isReviewedCodexPluginInactive(document2, cache, binding.codexInactivePlugins ?? [], codexConfig.plugins, codexConfig.skills?.config ?? [], read2) || isReviewedCodexPluginSkillDisabled(document2, cache, binding.codexPluginSkills ?? [], codexConfig.skills?.config, read2);
|
|
15582
|
+
};
|
|
15417
15583
|
const disabledPaths = new Set(disabledCodexSkillPaths(codexConfig, aliases));
|
|
15418
15584
|
const disabledVendorSkill = (entry) => {
|
|
15419
15585
|
if (agent !== "codex" || !entry.vendor)
|
|
@@ -15450,7 +15616,15 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15450
15616
|
} catch (error) {
|
|
15451
15617
|
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
15452
15618
|
}
|
|
15453
|
-
|
|
15619
|
+
let installationInputRoots = [];
|
|
15620
|
+
if (agent === "codex") {
|
|
15621
|
+
try {
|
|
15622
|
+
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), discovery.codexInstallationInputs?.plugins ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
|
|
15623
|
+
} catch {
|
|
15624
|
+
throw new Error("NATIVE_SKILL_DRIFT: native installation input identity changed; review discovery and catalog");
|
|
15625
|
+
}
|
|
15626
|
+
}
|
|
15627
|
+
const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", agents: [agent], projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0, disabledVendorPaths: agent === "codex" ? disabledCodexSkillPaths(codexConfig, aliases) : [], disabledVendorSkill: disabledPlugin, codexInstallationInputRoots: installationInputRoots });
|
|
15454
15628
|
const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledVendorSkill(entry));
|
|
15455
15629
|
if (unexpected.length) {
|
|
15456
15630
|
const paths = unexpected.slice(0, 3).map((entry) => {
|
|
@@ -18134,8 +18308,16 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
18134
18308
|
throw new Error("Invalid native discovery binding");
|
|
18135
18309
|
if (binding.agent === "hermes" && !binding.directories?.length)
|
|
18136
18310
|
throw new Error("Hermes discovery requires directory membership coverage; run skills hook install with a fresh discovery review");
|
|
18137
|
-
|
|
18138
|
-
|
|
18311
|
+
const installationRoots = codexInstallationRoots(binding);
|
|
18312
|
+
const installationInput = (path) => installationRoots.some((root2) => path === root2 || path.startsWith(root2 + sep6));
|
|
18313
|
+
if (binding.directories !== undefined) {
|
|
18314
|
+
const projected2 = binding.codexInstallationInputs?.directories ?? [];
|
|
18315
|
+
const ancestors2 = binding.directories.filter((directory) => installationRoots.some((root2) => root2.startsWith(directory.path + sep6)));
|
|
18316
|
+
if (projected2.length !== ancestors2.length || projected2.some((directory) => !ancestors2.some((original) => original.path === directory.path)))
|
|
18317
|
+
throw new Error("Missing installation input directory projection");
|
|
18318
|
+
verifyDiscoveryDirectories(binding.directories.filter((directory) => !installationInput(directory.path) && !ancestors2.includes(directory)));
|
|
18319
|
+
verifyDiscoveryDirectories(projected2, installationRoots);
|
|
18320
|
+
}
|
|
18139
18321
|
const budget = discoveryByteBudget();
|
|
18140
18322
|
for (const source of binding.sources) {
|
|
18141
18323
|
assertMarketplaceBinding(binding, source);
|
|
@@ -18145,6 +18327,10 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
18145
18327
|
throw new Error("Invalid native discovery projection");
|
|
18146
18328
|
if (source.sha256 !== null && !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
18147
18329
|
throw new Error("Invalid native discovery digest");
|
|
18330
|
+
if (installationInput(source.path)) {
|
|
18331
|
+
safe4(source.path);
|
|
18332
|
+
continue;
|
|
18333
|
+
}
|
|
18148
18334
|
const current = projected(source, undefined, budget);
|
|
18149
18335
|
if (current !== source.sha256 && !verifiesCodexHookDiscoverySource(binding, source, current, codexRecovery))
|
|
18150
18336
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
@@ -18152,6 +18338,27 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
18152
18338
|
for (const root2 of binding.roots)
|
|
18153
18339
|
safe4(root2);
|
|
18154
18340
|
}
|
|
18341
|
+
function codexInstallationRoots(binding) {
|
|
18342
|
+
const proof = binding.codexInstallationInputs;
|
|
18343
|
+
if (proof === undefined)
|
|
18344
|
+
return [];
|
|
18345
|
+
if (binding.agent !== "codex" || binding.method !== "reviewed" || proof.version !== "codex-cli 0.160.0" || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
|
|
18346
|
+
throw new Error("Invalid native installation input proof");
|
|
18347
|
+
const cache = dirname17(dirname17(proof.plugins[0].pluginParent));
|
|
18348
|
+
const config = join27(dirname17(dirname17(cache)), "config.toml"), home = dirname17(dirname17(dirname17(cache)));
|
|
18349
|
+
const configSource = binding.sources.find((source) => source.path === config && source.sha256 !== null && (!source.format || source.format === "toml" && source.fields?.includes("plugins")));
|
|
18350
|
+
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => join27(home, path));
|
|
18351
|
+
if (!configSource || proof.plugins.some((input) => nativeRoots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot + sep6) || input.sourceRoot.startsWith(root2 + sep6)) || binding.sources.some((source) => basename9(source.path) === "config.toml" && (source.path === input.sourceRoot || source.path.startsWith(input.sourceRoot + sep6)))))
|
|
18352
|
+
throw new Error("Native installation input overlaps configuration or loading roots");
|
|
18353
|
+
if (proof.plugins.some((input) => dirname17(dirname17(input.pluginParent)) !== cache || !binding.sources.some((source) => source.path === join27(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined)))
|
|
18354
|
+
throw new Error("Missing native installation input source witness");
|
|
18355
|
+
return reviewedCodexPluginSourceRoots(cache, proof.plugins, (path) => {
|
|
18356
|
+
const text2 = read2(path);
|
|
18357
|
+
if (text2 === null)
|
|
18358
|
+
throw new Error("Missing native installation input");
|
|
18359
|
+
return text2;
|
|
18360
|
+
});
|
|
18361
|
+
}
|
|
18155
18362
|
function assertMarketplaceBinding(binding, source) {
|
|
18156
18363
|
if ((source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2") && (binding.agent !== "codex" || binding.method !== "reviewed" || basename9(source.path) !== "config.toml"))
|
|
18157
18364
|
throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
|
|
@@ -18162,8 +18369,11 @@ function assertMarketplaceBinding(binding, source) {
|
|
|
18162
18369
|
}
|
|
18163
18370
|
function rebindAgentDiscovery(binding, changes) {
|
|
18164
18371
|
const budget = discoveryByteBudget();
|
|
18372
|
+
const installationRoots = codexInstallationRoots(binding);
|
|
18165
18373
|
return { ...binding, sources: binding.sources.map((source) => {
|
|
18166
18374
|
assertMarketplaceBinding(binding, source);
|
|
18375
|
+
if (!changes.has(source.path) && installationRoots.some((root2) => source.path.startsWith(root2 + sep6)))
|
|
18376
|
+
return source;
|
|
18167
18377
|
const sha2562 = projected(source, changes, budget);
|
|
18168
18378
|
if (!changes.has(source.path) && sha2562 !== source.sha256)
|
|
18169
18379
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
@@ -4,5 +4,5 @@ export interface DiscoveryDirectory {
|
|
|
4
4
|
sha256: string | null;
|
|
5
5
|
}
|
|
6
6
|
/** Read-only capture; callers must review both membership and source bytes. */
|
|
7
|
-
export declare function captureDiscoveryDirectories(paths: string[]): DiscoveryDirectory[];
|
|
8
|
-
export declare function verifyDiscoveryDirectories(directories: DiscoveryDirectory[]): void;
|
|
7
|
+
export declare function captureDiscoveryDirectories(paths: string[], excludedRoots?: readonly string[]): DiscoveryDirectory[];
|
|
8
|
+
export declare function verifyDiscoveryDirectories(directories: DiscoveryDirectory[], excludedRoots?: readonly string[]): void;
|
|
@@ -2,6 +2,7 @@ import type { IntegrationAgent } from "./agent-adapters.js";
|
|
|
2
2
|
import { type DiscoveryDirectory } from "./agent-discovery-directories.js";
|
|
3
3
|
import { type ManagedPluginRegistrationWitness } from "./plugin-discovery.js";
|
|
4
4
|
import { type CodexHookDiscoveryRecovery } from "./codex-hook-discovery-recovery.js";
|
|
5
|
+
import { type CodexPluginSourceInput } from "./codex-plugin-skill-controls.js";
|
|
5
6
|
export { captureDiscoveryDirectories, type DiscoveryDirectory } from "./agent-discovery-directories.js";
|
|
6
7
|
export interface DiscoverySource {
|
|
7
8
|
path: string;
|
|
@@ -18,6 +19,12 @@ export interface AgentDiscoveryBinding {
|
|
|
18
19
|
directories?: DiscoveryDirectory[];
|
|
19
20
|
method: "automatic" | "reviewed";
|
|
20
21
|
builtinNames?: string[];
|
|
22
|
+
codexInstallationInputs?: {
|
|
23
|
+
version: "codex-cli 0.160.0";
|
|
24
|
+
catalogSha256: string;
|
|
25
|
+
plugins: CodexPluginSourceInput[];
|
|
26
|
+
directories?: DiscoveryDirectory[];
|
|
27
|
+
};
|
|
21
28
|
}
|
|
22
29
|
export interface ReviewedDiscoveryInputs {
|
|
23
30
|
version: 1;
|
|
@@ -117,6 +117,7 @@ export declare function inventoryNativeSkills(home?: string, options?: {
|
|
|
117
117
|
reviewedCacheAlias?: string;
|
|
118
118
|
disabledVendorPaths?: readonly string[];
|
|
119
119
|
disabledVendorSkill?: (entry: NativeSkillEntry) => boolean;
|
|
120
|
+
codexInstallationInputRoots?: readonly string[];
|
|
120
121
|
}): NativeSkillEntry[];
|
|
121
122
|
/** Planning is read-only; credentials and unrelated settings never appear in CLI output. */
|
|
122
123
|
export declare function planAgentIntegration(options: {
|
|
@@ -22,8 +22,16 @@ export interface CodexInstalledPlugin {
|
|
|
22
22
|
localVersion: string | null;
|
|
23
23
|
/** Native remote identity; never a guessed installed cache version. */
|
|
24
24
|
remotePluginId?: string;
|
|
25
|
-
sourceType?: "remote";
|
|
25
|
+
sourceType?: "remote" | "local" | "git" | "npm";
|
|
26
|
+
/** Fingerprint of the complete native nonremote source declaration. */
|
|
27
|
+
sourceSha256?: string;
|
|
28
|
+
/** Declared local installation input, distinct from the loaded cache root. */
|
|
29
|
+
sourcePath?: string;
|
|
26
30
|
}
|
|
31
|
+
/** Codex 0.160 derives remote installed keys from these native scope realms.
|
|
32
|
+
* A nonremote declaration in one of them cannot prove config-owned enablement:
|
|
33
|
+
* remote reconciliation may replace enabled even while config stays disabled. */
|
|
34
|
+
export declare function codexPluginSourceIsConfigControlled(plugin: Pick<CodexInstalledPlugin, "id" | "sourceType" | "sourceSha256" | "remotePluginId">): boolean;
|
|
27
35
|
export declare const remotePluginIdentifier: (value: unknown) => value is string;
|
|
28
36
|
/** Project the measured native skills/list contract without retaining descriptions,
|
|
29
37
|
* links, interfaces, errors, or any other untrusted native text. */
|