@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/README.md CHANGED
@@ -530,6 +530,17 @@ hooks, commands and unsupported MCP controls refuse. Referenced own-root
530
530
  tool, approval, timeout and environment metadata. Their complete fingerprints
531
531
  and presence remain bound; review never executes commands or resolves environment
532
532
  values. Name controls suppress Skills, not every plugin capability.
533
+ A Codex 0.160 catalog may also prove a nonremote plugin is installed but disabled.
534
+ With an exact current path deny and explicit witnessed plugin disable, its cache
535
+ can remain inert across versions; re-enable, ambiguous identity, conflicting
536
+ controls and unsafe paths refuse. This does not approve its hook declarations.
537
+ Remote-controlled, missing or unknown source identities do not qualify.
538
+
539
+ The catalog retains local source paths separately from installed cache identities.
540
+ When a reviewed discovery witness binds a positively mapped installation input,
541
+ runtime checks omit that input from plugin skill loading. Default home/project
542
+ roots and installed caches are still checked, and migration inventory remains
543
+ complete. Overlapping, linked, missing or contradictory mappings refuse.
533
544
  No prompt hook starts an app server to obtain this receipt:
534
545
  native startup can maintain caches. The explicit capture starts and closes only its
535
546
  own bounded native client; it does not reload an existing agent session.
package/bin/index.js CHANGED
@@ -36868,7 +36868,7 @@ var package_default;
36868
36868
  var init_package = __esm(() => {
36869
36869
  package_default = {
36870
36870
  name: "@hasna/skills",
36871
- version: "0.10.24",
36871
+ version: "0.10.25",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -39944,6 +39944,18 @@ function assertAgentPolicyCollections(policy) {
39944
39944
  if (control.appSha256 !== undefined)
39945
39945
  requireBound(typeof control.appSha256 === "string" && /^[a-f0-9]{64}$/.test(control.appSha256));
39946
39946
  }
39947
+ if (bridge.codexInactivePlugins !== undefined) {
39948
+ const controls = array(bridge.codexInactivePlugins, 4096), ids = new Set;
39949
+ 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));
39950
+ for (const control of controls) {
39951
+ requireBound(object(control) && Object.keys(control).every((key) => ["pluginId", "namespace", "pluginParent", "sourceType", "sourceSha256"].includes(key)));
39952
+ text(control.pluginId, 1024);
39953
+ text(control.namespace, 64);
39954
+ text(control.pluginParent);
39955
+ requireBound(!ids.has(control.pluginId) && isAbsolute2(control.pluginParent) && resolve3(control.pluginParent) === control.pluginParent && ["local", "git", "npm"].includes(control.sourceType) && typeof control.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(control.sourceSha256));
39956
+ ids.add(control.pluginId);
39957
+ }
39958
+ }
39947
39959
  if (bridge.disabledBuiltins !== undefined)
39948
39960
  for (const builtin of array(bridge.disabledBuiltins, AGENT_POLICY_LIMITS.builtinNames)) {
39949
39961
  requireBound(object(builtin));
@@ -39956,6 +39968,25 @@ function assertAgentPolicyCollections(policy) {
39956
39968
  text(agent, 128);
39957
39969
  requireBound(object(value));
39958
39970
  text(value.agent, 128);
39971
+ if (value.codexInstallationInputs !== undefined) {
39972
+ const proof = record(value.codexInstallationInputs, 4), inputs = array(proof.plugins, 4096), ids = new Set;
39973
+ 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);
39974
+ for (const input of inputs) {
39975
+ requireBound(object(input) && Object.keys(input).every((key) => ["pluginId", "namespace", "pluginParent", "sourceRoot", "sourceSha256"].includes(key)));
39976
+ text(input.pluginId, 1024);
39977
+ text(input.namespace, 64);
39978
+ text(input.pluginParent);
39979
+ text(input.sourceRoot);
39980
+ requireBound(!ids.has(input.pluginId) && [input.pluginParent, input.sourceRoot].every((path) => isAbsolute2(path) && resolve3(path) === path) && typeof input.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(input.sourceSha256));
39981
+ ids.add(input.pluginId);
39982
+ }
39983
+ if (proof.directories !== undefined)
39984
+ for (const directory of array(proof.directories, AGENT_POLICY_LIMITS.discoveryDirectories)) {
39985
+ requireBound(object(directory));
39986
+ text(directory.path);
39987
+ requireBound(directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256));
39988
+ }
39989
+ }
39959
39990
  for (const root of array(value.roots, AGENT_POLICY_LIMITS.discoveryRoots))
39960
39991
  text(root);
39961
39992
  for (const source of array(value.sources, AGENT_POLICY_LIMITS.discoverySources)) {
@@ -47235,7 +47266,7 @@ function safe(path) {
47235
47266
  function same(before2, after2) {
47236
47267
  return Boolean(after2 && after2.isDirectory() && before2.dev === after2.dev && before2.ino === after2.ino && before2.mtimeMs === after2.mtimeMs && before2.ctimeMs === after2.ctimeMs);
47237
47268
  }
47238
- function capture(path, budget) {
47269
+ function capture(path, budget, excludedRoots) {
47239
47270
  safe(path);
47240
47271
  const root = lstatSync4(path, { throwIfNoEntry: false });
47241
47272
  if (!root)
@@ -47264,7 +47295,7 @@ function capture(path, budget) {
47264
47295
  budget.bytes += Buffer.byteLength(row);
47265
47296
  need(budget.bytes <= AGENT_POLICY_LIMITS.discoveryDirectoryBytes, "Native discovery directory metadata limit exceeded");
47266
47297
  hash.update(row);
47267
- if (stat.isDirectory())
47298
+ if (stat.isDirectory() && !excludedRoots.includes(child))
47268
47299
  walk(child, childRelative, stat, depth + 1);
47269
47300
  }
47270
47301
  need(same(before2, lstatSync4(current, { throwIfNoEntry: false })), "Native discovery directory changed while reading");
@@ -47273,16 +47304,18 @@ function capture(path, budget) {
47273
47304
  safe(path);
47274
47305
  return { path, sha256: hash.digest("hex") };
47275
47306
  }
47276
- function captureDiscoveryDirectories(paths) {
47307
+ function captureDiscoveryDirectories(paths, excludedRoots = []) {
47277
47308
  need(Array.isArray(paths) && paths.length <= AGENT_POLICY_LIMITS.discoveryDirectories && new Set(paths).size === paths.length, "Invalid native discovery directory collection");
47278
47309
  const budget = { entries: 0, bytes: 0 };
47279
- return paths.map((path) => capture(path, budget));
47310
+ for (const root of excludedRoots)
47311
+ safe(root);
47312
+ return paths.map((path) => capture(path, budget, excludedRoots));
47280
47313
  }
47281
- function verifyDiscoveryDirectories(directories) {
47314
+ function verifyDiscoveryDirectories(directories, excludedRoots = []) {
47282
47315
  need(Array.isArray(directories) && directories.length <= AGENT_POLICY_LIMITS.discoveryDirectories, "Invalid native discovery directory collection");
47283
47316
  for (const directory of directories)
47284
47317
  need(directory && typeof directory === "object" && (directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256)), "Invalid native discovery directory digest");
47285
- const current = captureDiscoveryDirectories(directories.map((directory) => directory.path));
47318
+ const current = captureDiscoveryDirectories(directories.map((directory) => directory.path), excludedRoots);
47286
47319
  for (let i = 0;i < directories.length; i++)
47287
47320
  need(current[i].sha256 === directories[i].sha256, `Native discovery directory membership changed; run skills hook install with a fresh discovery review: ${directories[i].path}`);
47288
47321
  }
@@ -51059,9 +51092,22 @@ var init_codex_hook_rpc = __esm(() => {
51059
51092
  // src/lib/codex-native-skill-catalog.ts
51060
51093
  import { realpathSync as realpathSync4 } from "fs";
51061
51094
  import { isAbsolute as isAbsolute10, normalize as normalize2 } from "path";
51095
+ function codexPluginSourceIsConfigControlled(plugin) {
51096
+ 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"];
51097
+ 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)));
51098
+ }
51062
51099
  function refuse() {
51063
51100
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_INVALID");
51064
51101
  }
51102
+ function projectPluginSource(source) {
51103
+ if (!object4(source))
51104
+ return {};
51105
+ if (source.type === "remote")
51106
+ return { sourceType: "remote" };
51107
+ const optionalText = (value) => value === undefined || value === null || scalar(value, MAX_PATH_BYTES);
51108
+ 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;
51109
+ return valid ? { sourceType: source.type, sourceSha256: hashNativeJsonControls(JSON.stringify(source)), ...source.type === "local" ? { sourcePath: source.path } : {} } : {};
51110
+ }
51065
51111
  function projectCodexNativeSkillCatalog(response, cwd2) {
51066
51112
  if (!absolutePath2(cwd2) || !object4(response) || !Array.isArray(response.data) || response.data.length !== 1)
51067
51113
  refuse();
@@ -51091,7 +51137,10 @@ function projectCodexInstalledPlugins(response) {
51091
51137
  plugins.push({
51092
51138
  ...value,
51093
51139
  marketplace: marketplace.name,
51094
- sourceType: object4(value.source) && value.source.type === "remote" ? "remote" : undefined
51140
+ sourceType: undefined,
51141
+ sourceSha256: undefined,
51142
+ sourcePath: undefined,
51143
+ ...projectPluginSource(value.source)
51095
51144
  });
51096
51145
  }
51097
51146
  }
@@ -51102,7 +51151,7 @@ function projectCodexInstalledPluginEntries(value) {
51102
51151
  refuse();
51103
51152
  const ids = new Set;
51104
51153
  return value.map((item) => {
51105
- 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")
51154
+ 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))
51106
51155
  refuse();
51107
51156
  const at2 = item.id.lastIndexOf("@");
51108
51157
  if (at2 < 1 || item.id.slice(0, at2) !== item.name || at2 === item.id.length - 1 || ids.has(item.id))
@@ -51114,7 +51163,9 @@ function projectCodexInstalledPluginEntries(value) {
51114
51163
  installed: item.installed,
51115
51164
  enabled: item.enabled,
51116
51165
  localVersion: item.localVersion ?? null,
51117
- ...item.sourceType === "remote" ? { sourceType: "remote" } : {},
51166
+ ...item.sourceType === undefined ? {} : { sourceType: item.sourceType },
51167
+ ...item.sourceSha256 === undefined ? {} : { sourceSha256: item.sourceSha256 },
51168
+ ...item.sourcePath === undefined ? {} : { sourcePath: item.sourcePath },
51118
51169
  ...item.remotePluginId == null ? {} : { remotePluginId: item.remotePluginId }
51119
51170
  };
51120
51171
  });
@@ -51184,6 +51235,7 @@ var MAX_SKILLS = 4096, MAX_NAME_BYTES = 1024, MAX_PATH_BYTES = 16384, MAX_PLUGIN
51184
51235
  var init_codex_native_skill_catalog = __esm(() => {
51185
51236
  init_codex_hook_rpc();
51186
51237
  init_codex_native_compatibility();
51238
+ init_claude_settings_witness();
51187
51239
  });
51188
51240
 
51189
51241
  // src/lib/codex-plugin-skill-controls.ts
@@ -51332,7 +51384,7 @@ function rootControls(root, read2) {
51332
51384
  const appSha256 = present ? appControls(app, read2) : undefined;
51333
51385
  return { namespace: manifest.name, manifestSha256, ...appSha256 ? { appSha256 } : {}, ...mcpSha256 ? { mcpSha256 } : {} };
51334
51386
  }
51335
- function identity4(document3, cache3, read2) {
51387
+ function documentIdentity(document3, cache3, read2) {
51336
51388
  if (!document3.startsWith(cache3 + sep4) || !document3.endsWith(sep4 + "SKILL.md"))
51337
51389
  refuse2();
51338
51390
  const text2 = read2(document3);
@@ -51360,11 +51412,33 @@ function identity4(document3, cache3, read2) {
51360
51412
  const path = join18(root, ".codex-plugin", "plugin.json");
51361
51413
  if (!lstatSync12(path, { throwIfNoEntry: false }))
51362
51414
  continue;
51363
- const controls = rootControls(root, read2);
51364
- return { name: `${controls.namespace}:${front.name}`, pluginParent: dirname10(root), ...controls };
51415
+ const manifestFile = lstatSync12(path);
51416
+ if (!lstatSync12(join18(root, ".codex-plugin")).isDirectory() || !manifestFile.isFile() || manifestFile.size > 1024 * 1024)
51417
+ refuse2();
51418
+ let manifest;
51419
+ try {
51420
+ const text3 = read2(path);
51421
+ hashNativeJsonControls(text3);
51422
+ manifest = JSON.parse(text3);
51423
+ } catch {
51424
+ refuse2();
51425
+ }
51426
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest) || !identifier2(manifest.name))
51427
+ refuse2();
51428
+ const declaredVersion = manifest.version === undefined ? "" : typeof manifest.version === "string" ? manifest.version.trim() : null;
51429
+ if (declaredVersion === null || (declaredVersion ? declaredVersion !== basename5(root) : basename5(root) !== CODEX_DEFAULT_AGENT_PLUGIN_VERSION))
51430
+ refuse2();
51431
+ return { name: `${manifest.name}:${front.name}`, namespace: manifest.name, pluginParent: dirname10(root), root };
51365
51432
  }
51366
51433
  refuse2();
51367
51434
  }
51435
+ function identity4(document3, cache3, read2) {
51436
+ const parsed = documentIdentity(document3, cache3, read2);
51437
+ const controls = rootControls(parsed.root, read2);
51438
+ if (controls.namespace !== parsed.namespace)
51439
+ refuse2();
51440
+ return { name: parsed.name, pluginParent: parsed.pluginParent, ...controls };
51441
+ }
51368
51442
  function reviewedCodexPluginCapabilitiesUnchanged(cache3, controls, read2) {
51369
51443
  if (!Array.isArray(controls) || controls.length > 4096)
51370
51444
  return false;
@@ -51402,11 +51476,13 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache3, controls, read2) {
51402
51476
  return false;
51403
51477
  }
51404
51478
  }
51405
- function reviewCodexPluginSkillControls(catalog, documents, cache3, cwd2, read2, rules) {
51479
+ function reviewCodexPluginControls(catalog, documents, cache3, cwd2, read2, rules, pluginSettings) {
51406
51480
  if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd2)
51407
51481
  refuse2();
51408
51482
  const skills = projectCodexNativeSkillCatalog({ data: [{ cwd: cwd2, errors: [], skills: catalog.skills }] }, cwd2);
51409
- const allowed = new Set(documents), result2 = [];
51483
+ const allowed = new Set(documents), result2 = [], inertDocuments = new Set;
51484
+ const inactivePlugins = new Map;
51485
+ const sourceInputs = new Map;
51410
51486
  let installedPlugins;
51411
51487
  try {
51412
51488
  installedPlugins = projectCodexInstalledPluginEntries(catalog.plugins ?? []);
@@ -51417,25 +51493,48 @@ function reviewCodexPluginSkillControls(catalog, documents, cache3, cwd2, read2,
51417
51493
  for (const document3 of candidates) {
51418
51494
  if (!document3.startsWith(cache3 + sep4))
51419
51495
  continue;
51420
- const parsed = identity4(document3, cache3, read2);
51421
- if (parsed.name === "skills-cli" || parsed.name.endsWith(":skills-cli"))
51496
+ const located = documentIdentity(document3, cache3, read2);
51497
+ if (located.name === "skills-cli" || located.name.endsWith(":skills-cli"))
51422
51498
  refuse2();
51423
- const cacheParts = relative4(cache3, parsed.pluginParent).split(sep4);
51499
+ const cacheParts = relative4(cache3, located.pluginParent).split(sep4);
51424
51500
  if (cacheParts.length !== 2 || cacheParts.some((part) => !identifier2(part)))
51425
51501
  refuse2();
51426
51502
  const [marketplace, pluginName] = cacheParts;
51427
- if (pluginName !== parsed.namespace)
51503
+ if (pluginName !== located.namespace)
51428
51504
  refuse2();
51429
51505
  const expectedPluginId = `${pluginName}@${marketplace}`;
51430
51506
  const listed = skills.filter((skill) => skill.path === document3);
51431
51507
  if (listed.length > 1)
51432
51508
  refuse2();
51509
+ const installed = installedPlugins.filter((plugin) => plugin.id === expectedPluginId && plugin.name === pluginName);
51510
+ 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])) {
51511
+ const plugin = installed[0], input = { pluginId: expectedPluginId, namespace: located.namespace, pluginParent: located.pluginParent, sourceRoot: plugin.sourcePath, sourceSha256: plugin.sourceSha256 };
51512
+ if (input.sourceRoot !== located.root && !input.sourceRoot.startsWith(cache3 + sep4) && (lstatSync12(input.sourceRoot, { throwIfNoEntry: false }) || documents.some((path) => path.startsWith(input.sourceRoot + sep4)))) {
51513
+ if (skills.some((skill) => skill.path === input.sourceRoot || skill.path.startsWith(input.sourceRoot + sep4)) || !sourceInputUnchanged(input, cache3, read2))
51514
+ refuse2();
51515
+ sourceInputs.set(expectedPluginId, input);
51516
+ }
51517
+ }
51518
+ const settings = pluginSettings && typeof pluginSettings === "object" && !Array.isArray(pluginSettings) ? pluginSettings : undefined;
51519
+ 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) {
51520
+ if (!Array.isArray(rules))
51521
+ refuse2();
51522
+ const exactPathDeny = rules.some((rule) => rule?.path === document3 && rule?.enabled === false);
51523
+ if (!lstatSync12(document3).isFile() || realpathSync5(document3) !== document3 || realpathSync5(located.root) !== located.root || realpathSync5(located.pluginParent) !== located.pluginParent || realpathSync5(join18(located.root, ".codex-plugin/plugin.json")) !== join18(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: document3, pluginId: expectedPluginId, enabled: true }, rules))
51524
+ refuse2();
51525
+ inertDocuments.add(document3);
51526
+ const plugin = installed[0];
51527
+ inactivePlugins.set(expectedPluginId, { pluginId: expectedPluginId, namespace: located.namespace, pluginParent: located.pluginParent, sourceType: plugin.sourceType, sourceSha256: plugin.sourceSha256 });
51528
+ continue;
51529
+ }
51530
+ const parsed = { name: located.name, pluginParent: located.pluginParent, ...rootControls(located.root, read2) };
51531
+ if (parsed.namespace !== located.namespace)
51532
+ refuse2();
51433
51533
  let remotePluginId;
51434
51534
  if (listed.length === 1) {
51435
51535
  if (listed[0].name !== parsed.name || listed[0].pluginId !== expectedPluginId)
51436
51536
  refuse2();
51437
51537
  } else {
51438
- const installed = installedPlugins.filter((plugin2) => plugin2.id === expectedPluginId && plugin2.name === pluginName);
51439
51538
  if (installed.length !== 1 || !installed[0].installed || !installed[0].enabled)
51440
51539
  refuse2();
51441
51540
  const plugin = installed[0];
@@ -51453,9 +51552,48 @@ function reviewCodexPluginSkillControls(catalog, documents, cache3, cwd2, read2,
51453
51552
  }
51454
51553
  result2.push({ ...parsed, pluginId: expectedPluginId, ...remotePluginId ? { remotePluginId } : {} });
51455
51554
  }
51456
- if (!result2.length && documents.some((document3) => document3.startsWith(cache3 + sep4)) || new Set(result2.map((item) => item.name)).size !== result2.length)
51555
+ if (!result2.length && documents.some((document3) => document3.startsWith(cache3 + sep4) && !inertDocuments.has(document3)) || new Set(result2.map((item) => item.name)).size !== result2.length)
51457
51556
  refuse2();
51458
- return result2;
51557
+ return { skills: result2, inactivePlugins: [...inactivePlugins.values()], sourceInputs: [...sourceInputs.values()] };
51558
+ }
51559
+ function sourceInputUnchanged(input, cache3, read2) {
51560
+ try {
51561
+ const parts = relative4(cache3, input.pluginParent).split(sep4);
51562
+ 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 })) || resolve10(input.sourceRoot) !== input.sourceRoot || !lstatSync12(input.sourceRoot).isDirectory() || realpathSync5(input.sourceRoot) !== input.sourceRoot || input.sourceRoot === cache3 || input.sourceRoot.startsWith(cache3 + sep4) || cache3.startsWith(input.sourceRoot + sep4))
51563
+ return false;
51564
+ const path = join18(input.sourceRoot, ".codex-plugin/plugin.json"), stat = lstatSync12(path);
51565
+ if (!stat.isFile() || stat.size > 1024 * 1024 || realpathSync5(path) !== path)
51566
+ return false;
51567
+ const text2 = read2(path);
51568
+ hashNativeJsonControls(text2);
51569
+ const manifest = JSON.parse(text2);
51570
+ return Boolean(manifest && typeof manifest === "object" && !Array.isArray(manifest) && manifest.name === input.namespace);
51571
+ } catch {
51572
+ return false;
51573
+ }
51574
+ }
51575
+ function reviewedCodexPluginSourceRoots(cache3, inputs, read2) {
51576
+ 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, cache3, read2)))
51577
+ refuse2();
51578
+ const roots = inputs.map((input) => input.sourceRoot);
51579
+ if (roots.some((root) => roots.some((other) => root !== other && root.startsWith(other + sep4))))
51580
+ refuse2();
51581
+ return roots;
51582
+ }
51583
+ function isReviewedCodexPluginInactive(document3, cache3, controls, pluginSettings, rules, read2) {
51584
+ if (!Array.isArray(controls) || controls.length > 4096 || !pluginSettings || typeof pluginSettings !== "object" || Array.isArray(pluginSettings) || !Array.isArray(rules))
51585
+ return false;
51586
+ try {
51587
+ const parsed = documentIdentity(document3, cache3, read2), matches2 = controls.filter((control2) => control2.pluginParent === parsed.pluginParent && control2.namespace === parsed.namespace);
51588
+ if (matches2.length !== 1 || !lstatSync12(document3).isFile() || realpathSync5(document3) !== document3 || realpathSync5(parsed.root) !== parsed.root || realpathSync5(parsed.pluginParent) !== parsed.pluginParent || realpathSync5(join18(parsed.root, ".codex-plugin/plugin.json")) !== join18(parsed.root, ".codex-plugin/plugin.json"))
51589
+ return false;
51590
+ const control = matches2[0], parts = relative4(cache3, parsed.pluginParent).split(sep4);
51591
+ 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)
51592
+ return false;
51593
+ return !rules.some((rule) => (rule?.path === document3 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule?.enabled !== false);
51594
+ } catch {
51595
+ return false;
51596
+ }
51459
51597
  }
51460
51598
  function isReviewedCodexPluginSkillDisabled(document3, cache3, controls, rules, read2) {
51461
51599
  if (!document3.startsWith(cache3 + sep4) || !Array.isArray(controls) || controls.length > 4096)
@@ -52465,6 +52603,9 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
52465
52603
  for (const [agent, path] of rootDefinitions)
52466
52604
  roots.push([agent, canonicalAgentPath(join21(project, path), aliases)]);
52467
52605
  }
52606
+ const installationInputs = options.codexInstallationInputRoots ?? [];
52607
+ if (installationInputs.some((input) => resolve11(input) !== input || realpathSync6(input) !== input || roots.some(([agent, path]) => agent === "codex" && (path === input || path.startsWith(input + sep5) || input.startsWith(path + sep5)))))
52608
+ throw new Error("Native installation input overlaps an unconditional skill-loading root");
52468
52609
  const entries = [], seen = new Set;
52469
52610
  const cacheScans = new Map, maxAliasProofEntries = 1e4;
52470
52611
  let discoveryEntries = 0, discoveryPathBytes = 0;
@@ -52499,11 +52640,13 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
52499
52640
  }
52500
52641
  return true;
52501
52642
  }
52502
- function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false) {
52643
+ function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false, configuredRoot = false) {
52503
52644
  if (!admitted)
52504
52645
  admit(path);
52505
52646
  const scan = { complete: true, hasSkills: false, entries: 1 };
52506
52647
  assertSafePath(path);
52648
+ if (configuredRoot && agent === "codex" && installationInputs.includes(path))
52649
+ return scan;
52507
52650
  if (!existsSync12(path))
52508
52651
  return scan;
52509
52652
  const stat = lstatSync15(path);
@@ -52559,7 +52702,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
52559
52702
  continue;
52560
52703
  }
52561
52704
  }
52562
- const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true);
52705
+ const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true, configuredRoot);
52563
52706
  scan.complete &&= childScan.complete;
52564
52707
  scan.hasSkills ||= childScan.hasSkills;
52565
52708
  scan.entries += childScan.entries;
@@ -52568,10 +52711,10 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
52568
52711
  cacheScans.set(path, scan);
52569
52712
  return scan;
52570
52713
  }
52571
- function scanRoot(agent, path, vendor = false, pluginCache = false) {
52714
+ function scanRoot(agent, path, vendor = false, pluginCache = false, configuredRoot = false) {
52572
52715
  if (agent === "hermes" || options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`))
52573
52716
  assertNoHermesLegacyShadow(path);
52574
- if (!visit2(agent, path, vendor, 0, pluginCache).complete)
52717
+ if (!visit2(agent, path, vendor, 0, pluginCache, false, configuredRoot).complete)
52575
52718
  throw new Error(`Native skill discovery limit exceeded; review this root before continuing: ${path}`);
52576
52719
  }
52577
52720
  for (const [agent, path] of roots)
@@ -52587,7 +52730,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
52587
52730
  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 }))) : [];
52588
52731
  for (const root of [...options.agentRoots ?? [], ...configured])
52589
52732
  if (includesAgent(root.agent))
52590
- scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true);
52733
+ scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true, false, true);
52591
52734
  if (options.reviewedCacheAlias !== undefined && !reviewedCacheAliasFound)
52592
52735
  throw new Error("Reviewed cache alias was not an inventoried skill-containing sibling alias");
52593
52736
  recheckRootAliases(aliases);
@@ -52727,6 +52870,11 @@ enabled = false
52727
52870
  Bun.TOML.parse(result2);
52728
52871
  return result2;
52729
52872
  }
52873
+ function assertCodexInstallationInputs(home, projectDirs, inputs) {
52874
+ const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => resolve11(directory, path)));
52875
+ if (inputs.some((input) => nativeRoots.some((root) => root === input || root.startsWith(input + sep5) || input.startsWith(root + sep5))))
52876
+ throw new Error("Native installation input overlaps an unconditional skill-loading root");
52877
+ }
52730
52878
  function planAgentIntegration(options) {
52731
52879
  const home = options.home ?? homedir3(), dataDir3 = options.dataDir ?? getDataDirReadOnly();
52732
52880
  const aliases = rootAliases(home, options.allowRootAliases);
@@ -52765,11 +52913,16 @@ function planAgentIntegration(options) {
52765
52913
  const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(join21(home, ".codex", "config.toml"), aliases)) ?? "") : {};
52766
52914
  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) });
52767
52915
  const observedNativeSources = [];
52768
- const codexPluginSkills = options.codexNativeCatalog ? reviewCodexPluginSkillControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join21(entry.path, "SKILL.md")), canonicalAgentPath(join21(home, ".codex/plugins/cache"), aliases), resolve11(options.projectDir ?? home), (path) => {
52916
+ const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join21(entry.path, "SKILL.md")), canonicalAgentPath(join21(home, ".codex/plugins/cache"), aliases), resolve11(options.projectDir ?? home), (path) => {
52769
52917
  const bytes = readNativeBytes(path, 1024 * 1024);
52770
52918
  observedNativeSources.push({ path, sha256: sha3(bytes) });
52771
52919
  return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
52772
- }, codexConfig.skills?.config ?? []) : policy.bridge?.codexPluginSkills ?? [];
52920
+ }, codexConfig.skills?.config ?? [], codexConfig.plugins) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
52921
+ const codexPluginSkills = codexPluginControls.skills;
52922
+ const codexInactivePlugins = codexPluginControls.inactivePlugins;
52923
+ const codexDiscovery = discoveries.find((binding) => binding.agent === "codex");
52924
+ 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 === join21(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined));
52925
+ assertCodexInstallationInputs(home, [options.projectDir ?? home], codexPluginSourceInputs.map((input) => input.sourceRoot));
52773
52926
  const changes = [];
52774
52927
  for (const agent of [...new Set(options.agents)]) {
52775
52928
  if (!INTEGRATION_AGENTS.includes(agent))
@@ -52834,7 +52987,16 @@ function planAgentIntegration(options) {
52834
52987
  changes.push({ path: configPath, before: previous, after: next });
52835
52988
  }
52836
52989
  }
52837
- const discoveryAfter = discoveries.map((binding) => rebindAgentDiscovery(binding, new Map(changes.map((change) => [change.path, change.after]))));
52990
+ 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(join21(home, ".codex/config.toml"), aliases))?.before === null ? null : sha3(readOptional(canonicalAgentPath(join21(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview;
52991
+ const discoveryAfter = discoveries.map((binding) => {
52992
+ const rebound = rebindAgentDiscovery(binding, new Map(changes.map((change) => [change.path, change.after])));
52993
+ if (binding.agent !== "codex")
52994
+ return rebound;
52995
+ const { codexInstallationInputs: previousInputs, ...current } = rebound;
52996
+ const inputs = codexPluginSourceInputs.map((input) => input.sourceRoot);
52997
+ 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;
52998
+ return codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: "codex-cli 0.160.0", catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
52999
+ });
52838
53000
  const nextPolicy = { ...policy, version: 1, loading: "cli", profileId, bridge: {
52839
53001
  ...policy.bridge,
52840
53002
  version: CLI_BRIDGE_VERSION,
@@ -52847,7 +53009,7 @@ function planAgentIntegration(options) {
52847
53009
  profiles: Object.fromEntries([...new Set([...priorAgents, ...options.agents])].sort().map((agent) => [agent, bindings.get(agent)?.profileId ?? policy.bridge?.profiles?.[agent] ?? policy.profileId])),
52848
53010
  disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(join21(home, ".codex", "skills", ".system"), aliases) + sep5)).map((entry) => ({ path: entry.path, hash: entry.hash })) : policy.bridge?.disabledBuiltins ?? [],
52849
53011
  rootAliases: aliases,
52850
- ...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(join21(home, ".codex/config.toml"), aliases))?.before === null ? null : sha3(readOptional(canonicalAgentPath(join21(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview } : {},
53012
+ ...options.codexNativeCatalog || codexPluginSkills.length || codexInactivePlugins.length || codexPluginSourceInputs.length ? { codexPluginSkills, codexInactivePlugins, codexPluginSkillReview } : {},
52851
53013
  discovery: { ...policy.bridge?.discovery, ...Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) }
52852
53014
  } };
52853
53015
  const serializedPolicy = serializeManagedSkillPolicy(nextPolicy);
@@ -53257,7 +53419,12 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
53257
53419
  throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
53258
53420
  if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join21(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024))))
53259
53421
  throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
53260
- const disabledPlugin = (entry) => agent === "codex" && entry.vendor && isReviewedCodexPluginSkillDisabled(join21(entry.path, "SKILL.md"), canonicalAgentPath(join21(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], codexConfig.skills?.config, (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
53422
+ const disabledPlugin = (entry) => {
53423
+ if (agent !== "codex" || !entry.vendor)
53424
+ return false;
53425
+ const document3 = join21(entry.path, "SKILL.md"), cache3 = canonicalAgentPath(join21(home, ".codex/plugins/cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024));
53426
+ return isReviewedCodexPluginInactive(document3, cache3, binding.codexInactivePlugins ?? [], codexConfig.plugins, codexConfig.skills?.config ?? [], read2) || isReviewedCodexPluginSkillDisabled(document3, cache3, binding.codexPluginSkills ?? [], codexConfig.skills?.config, read2);
53427
+ };
53261
53428
  const disabledPaths = new Set(disabledCodexSkillPaths(codexConfig, aliases));
53262
53429
  const disabledVendorSkill = (entry) => {
53263
53430
  if (agent !== "codex" || !entry.vendor)
@@ -53294,7 +53461,15 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
53294
53461
  } catch (error) {
53295
53462
  throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
53296
53463
  }
53297
- 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 });
53464
+ let installationInputRoots = [];
53465
+ if (agent === "codex") {
53466
+ try {
53467
+ installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(join21(home, ".codex/plugins/cache"), aliases), discovery.codexInstallationInputs?.plugins ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
53468
+ } catch {
53469
+ throw new Error("NATIVE_SKILL_DRIFT: native installation input identity changed; review discovery and catalog");
53470
+ }
53471
+ }
53472
+ 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 });
53298
53473
  const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledVendorSkill(entry));
53299
53474
  if (unexpected.length) {
53300
53475
  const paths = unexpected.slice(0, 3).map((entry) => {
@@ -56666,8 +56841,16 @@ function verifyAgentDiscovery(binding, codexRecovery) {
56666
56841
  throw new Error("Invalid native discovery binding");
56667
56842
  if (binding.agent === "hermes" && !binding.directories?.length)
56668
56843
  throw new Error("Hermes discovery requires directory membership coverage; run skills hook install with a fresh discovery review");
56669
- if (binding.directories !== undefined)
56670
- verifyDiscoveryDirectories(binding.directories);
56844
+ const installationRoots = codexInstallationRoots(binding);
56845
+ const installationInput = (path) => installationRoots.some((root2) => path === root2 || path.startsWith(root2 + sep6));
56846
+ if (binding.directories !== undefined) {
56847
+ const projected2 = binding.codexInstallationInputs?.directories ?? [];
56848
+ const ancestors2 = binding.directories.filter((directory) => installationRoots.some((root2) => root2.startsWith(directory.path + sep6)));
56849
+ if (projected2.length !== ancestors2.length || projected2.some((directory) => !ancestors2.some((original) => original.path === directory.path)))
56850
+ throw new Error("Missing installation input directory projection");
56851
+ verifyDiscoveryDirectories(binding.directories.filter((directory) => !installationInput(directory.path) && !ancestors2.includes(directory)));
56852
+ verifyDiscoveryDirectories(projected2, installationRoots);
56853
+ }
56671
56854
  const budget = discoveryByteBudget();
56672
56855
  for (const source of binding.sources) {
56673
56856
  assertMarketplaceBinding(binding, source);
@@ -56677,6 +56860,10 @@ function verifyAgentDiscovery(binding, codexRecovery) {
56677
56860
  throw new Error("Invalid native discovery projection");
56678
56861
  if (source.sha256 !== null && !/^[a-f0-9]{64}$/.test(source.sha256))
56679
56862
  throw new Error("Invalid native discovery digest");
56863
+ if (installationInput(source.path)) {
56864
+ safe4(source.path);
56865
+ continue;
56866
+ }
56680
56867
  const current = projected(source, undefined, budget);
56681
56868
  if (current !== source.sha256 && !verifiesCodexHookDiscoverySource(binding, source, current, codexRecovery))
56682
56869
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
@@ -56684,6 +56871,27 @@ function verifyAgentDiscovery(binding, codexRecovery) {
56684
56871
  for (const root2 of binding.roots)
56685
56872
  safe4(root2);
56686
56873
  }
56874
+ function codexInstallationRoots(binding) {
56875
+ const proof = binding.codexInstallationInputs;
56876
+ if (proof === undefined)
56877
+ return [];
56878
+ 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)
56879
+ throw new Error("Invalid native installation input proof");
56880
+ const cache3 = dirname17(dirname17(proof.plugins[0].pluginParent));
56881
+ const config = join32(dirname17(dirname17(cache3)), "config.toml"), home = dirname17(dirname17(dirname17(cache3)));
56882
+ const configSource = binding.sources.find((source) => source.path === config && source.sha256 !== null && (!source.format || source.format === "toml" && source.fields?.includes("plugins")));
56883
+ const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => join32(home, path));
56884
+ 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)))))
56885
+ throw new Error("Native installation input overlaps configuration or loading roots");
56886
+ if (proof.plugins.some((input) => dirname17(dirname17(input.pluginParent)) !== cache3 || !binding.sources.some((source) => source.path === join32(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined)))
56887
+ throw new Error("Missing native installation input source witness");
56888
+ return reviewedCodexPluginSourceRoots(cache3, proof.plugins, (path) => {
56889
+ const text3 = read2(path);
56890
+ if (text3 === null)
56891
+ throw new Error("Missing native installation input");
56892
+ return text3;
56893
+ });
56894
+ }
56687
56895
  function assertMarketplaceBinding(binding, source) {
56688
56896
  if ((source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2") && (binding.agent !== "codex" || binding.method !== "reviewed" || basename9(source.path) !== "config.toml"))
56689
56897
  throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
@@ -56694,8 +56902,11 @@ function assertMarketplaceBinding(binding, source) {
56694
56902
  }
56695
56903
  function rebindAgentDiscovery(binding, changes) {
56696
56904
  const budget = discoveryByteBudget();
56905
+ const installationRoots = codexInstallationRoots(binding);
56697
56906
  return { ...binding, sources: binding.sources.map((source) => {
56698
56907
  assertMarketplaceBinding(binding, source);
56908
+ if (!changes.has(source.path) && installationRoots.some((root2) => source.path.startsWith(root2 + sep6)))
56909
+ return source;
56699
56910
  const sha2562 = projected(source, changes, budget);
56700
56911
  if (!changes.has(source.path) && sha2562 !== source.sha256)
56701
56912
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
@@ -56966,6 +57177,8 @@ var init_agent_discovery = __esm(() => {
56966
57177
  init_claude_settings_witness();
56967
57178
  init_codex_hook_discovery_recovery();
56968
57179
  init_codex_settings_witness();
57180
+ init_codex_plugin_skill_controls();
57181
+ init_native_discovery_roots();
56969
57182
  init_agent_discovery_directories();
56970
57183
  });
56971
57184