@hasna/skills 0.10.24 → 0.10.25

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/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.10.24",
10
+ version: "0.10.25",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
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.24",
24125
+ version: "0.10.25",
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.24",
24128
+ version: "0.10.25",
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
- return paths.map((path) => capture(path, budget));
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.24",
10917
+ version: "0.10.25",
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: object4(value.source) && value.source.type === "remote" ? "remote" : undefined
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 !== "remote")
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 === "remote" ? { sourceType: "remote" } : {},
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 identity3(document2, cache, read2) {
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 controls = rootControls(root, read2);
13505
- return { name: `${controls.namespace}:${front.name}`, pluginParent: dirname10(root), ...controls };
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 reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2, rules) {
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 parsed = identity3(document2, cache, read2);
13562
- if (parsed.name === "skills-cli" || parsed.name.endsWith(":skills-cli"))
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, parsed.pluginParent).split(sep4);
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 !== parsed.namespace)
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.includes(path))
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 codexPluginSkills = options.codexNativeCatalog ? reviewCodexPluginSkillControls(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) => {
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 discoveryAfter = discoveries.map((binding) => rebindAgentDiscovery(binding, new Map(changes.map((change) => [change.path, change.after]))));
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
- ...codexPluginSkills.length ? { codexPluginSkills, 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 } : {},
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) => agent === "codex" && entry.vendor && isReviewedCodexPluginSkillDisabled(join17(entry.path, "SKILL.md"), canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], codexConfig.skills?.config, (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
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
- 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 });
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
- if (binding.directories !== undefined)
18138
- verifyDiscoveryDirectories(binding.directories);
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. */