@hasna/skills 0.10.23 → 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/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.23",
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
  });
@@ -13381,27 +13432,62 @@ function mcpControls(path, read2) {
13381
13432
  const object5 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v));
13382
13433
  const textField = (v, empty = false) => typeof v === "string" && (empty || Boolean(v.trim())) && Buffer.byteLength(v) <= 16384 && !/[\x00-\x1f\x7f]/.test(v);
13383
13434
  const envName = (v) => typeof v === "string" && /^[A-Za-z_][A-Za-z0-9_]{0,255}$/.test(v);
13435
+ const optional = (v, valid) => v === undefined || v === null || valid(v);
13436
+ const stringList = (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => textField(item));
13437
+ const approval = (v) => ["auto", "prompt", "writes", "approve"].includes(v);
13438
+ const duration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
13439
+ const positiveInteger = (v) => Number.isSafeInteger(v) && v > 0;
13440
+ const fields = [
13441
+ "command",
13442
+ "args",
13443
+ "cwd",
13444
+ "env",
13445
+ "env_vars",
13446
+ "enabled",
13447
+ "required",
13448
+ "environment_id",
13449
+ "startup_readiness",
13450
+ "supports_parallel_tool_calls",
13451
+ "tool_input_schema_max_bytes",
13452
+ "startup_timeout_sec",
13453
+ "startup_timeout_ms",
13454
+ "tool_timeout_sec",
13455
+ "default_tools_approval_mode",
13456
+ "enabled_tools",
13457
+ "disabled_tools",
13458
+ "omit_tools_from",
13459
+ "tools",
13460
+ "name"
13461
+ ];
13384
13462
  if (!object5(value) || Object.keys(value).length !== 1 || !object5(value.mcpServers))
13385
13463
  refuse2();
13386
13464
  const servers = Object.entries(value.mcpServers);
13387
13465
  if (!servers.length || servers.length > 256)
13388
13466
  refuse2();
13389
13467
  for (const [name, server] of servers) {
13390
- if (!identifier2(name) || !object5(server) || !Object.keys(server).every((key) => ["command", "args", "cwd", "env_vars"].includes(key)) || !textField(server.command) || server.args !== undefined && (!Array.isArray(server.args) || server.args.length > 256 || !server.args.every((arg) => textField(arg, true))) || server.cwd !== undefined && !textField(server.cwd))
13468
+ if (!identifier2(name) || !object5(server) || !Object.keys(server).every((key) => fields.includes(key)) || !textField(server.command) || !optional(server.args, (v) => Array.isArray(v) && v.length <= 256 && v.every((arg) => textField(arg, true))) || !optional(server.cwd, textField) || !optional(server.env, (v) => object5(v) && Object.keys(v).length <= 256 && Object.entries(v).every(([key, value2]) => envName(key) && textField(value2, true))) || !["enabled", "required", "supports_parallel_tool_calls"].every((key) => optional(server[key], (v) => typeof v === "boolean")) || !optional(server.environment_id, (v) => v === "local") || !optional(server.startup_readiness, (v) => ["connection", "catalog"].includes(v)) || !optional(server.tool_input_schema_max_bytes, positiveInteger) || !["startup_timeout_sec", "tool_timeout_sec"].every((key) => optional(server[key], duration)) || !optional(server.startup_timeout_ms, (v) => Number.isSafeInteger(v) && v >= 0) || !optional(server.default_tools_approval_mode, approval) || !["enabled_tools", "disabled_tools"].every((key) => optional(server[key], stringList)) || !optional(server.omit_tools_from, (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => ["code_mode", "deferred", "direct"].includes(item))) || !optional(server.name, textField))
13391
13469
  refuse2();
13392
- if (server.env_vars !== undefined) {
13470
+ if (server.env_vars !== undefined && server.env_vars !== null) {
13393
13471
  if (!Array.isArray(server.env_vars) || server.env_vars.length > 256)
13394
13472
  refuse2();
13395
13473
  const names = server.env_vars.map((entry) => {
13396
13474
  if (envName(entry))
13397
13475
  return entry;
13398
- if (!object5(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || entry.source !== undefined && entry.source !== "local")
13476
+ if (!object5(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || !optional(entry.source, (v) => v === "local"))
13399
13477
  refuse2();
13400
13478
  return entry.name;
13401
13479
  });
13402
13480
  if (new Set(names).size !== names.length)
13403
13481
  refuse2();
13404
13482
  }
13483
+ if (server.tools !== undefined && server.tools !== null) {
13484
+ if (!object5(server.tools) || Object.keys(server.tools).length > 256)
13485
+ refuse2();
13486
+ for (const [tool, config] of Object.entries(server.tools)) {
13487
+ if (!textField(tool) || !object5(config) || !Object.keys(config).every((key) => ["approval_mode", "output_token_limit"].includes(key)) || !optional(config.approval_mode, approval) || !optional(config.output_token_limit, positiveInteger))
13488
+ refuse2();
13489
+ }
13490
+ }
13405
13491
  }
13406
13492
  return sha2562;
13407
13493
  }
@@ -13438,7 +13524,7 @@ function rootControls(root, read2) {
13438
13524
  const appSha256 = present ? appControls(app, read2) : undefined;
13439
13525
  return { namespace: manifest.name, manifestSha256, ...appSha256 ? { appSha256 } : {}, ...mcpSha256 ? { mcpSha256 } : {} };
13440
13526
  }
13441
- function identity3(document2, cache, read2) {
13527
+ function documentIdentity(document2, cache, read2) {
13442
13528
  if (!document2.startsWith(cache + sep4) || !document2.endsWith(sep4 + "SKILL.md"))
13443
13529
  refuse2();
13444
13530
  const text2 = read2(document2);
@@ -13466,11 +13552,33 @@ function identity3(document2, cache, read2) {
13466
13552
  const path = join14(root, ".codex-plugin", "plugin.json");
13467
13553
  if (!lstatSync12(path, { throwIfNoEntry: false }))
13468
13554
  continue;
13469
- const controls = rootControls(root, read2);
13470
- 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 };
13471
13572
  }
13472
13573
  refuse2();
13473
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
+ }
13474
13582
  function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
13475
13583
  if (!Array.isArray(controls) || controls.length > 4096)
13476
13584
  return false;
@@ -13508,11 +13616,13 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
13508
13616
  return false;
13509
13617
  }
13510
13618
  }
13511
- function reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2, rules) {
13619
+ function reviewCodexPluginControls(catalog, documents, cache, cwd, read2, rules, pluginSettings) {
13512
13620
  if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd)
13513
13621
  refuse2();
13514
13622
  const skills = projectCodexNativeSkillCatalog({ data: [{ cwd, errors: [], skills: catalog.skills }] }, cwd);
13515
- 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;
13516
13626
  let installedPlugins;
13517
13627
  try {
13518
13628
  installedPlugins = projectCodexInstalledPluginEntries(catalog.plugins ?? []);
@@ -13523,25 +13633,48 @@ function reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2, r
13523
13633
  for (const document2 of candidates) {
13524
13634
  if (!document2.startsWith(cache + sep4))
13525
13635
  continue;
13526
- const parsed = identity3(document2, cache, read2);
13527
- 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"))
13528
13638
  refuse2();
13529
- const cacheParts = relative4(cache, parsed.pluginParent).split(sep4);
13639
+ const cacheParts = relative4(cache, located.pluginParent).split(sep4);
13530
13640
  if (cacheParts.length !== 2 || cacheParts.some((part) => !identifier2(part)))
13531
13641
  refuse2();
13532
13642
  const [marketplace, pluginName] = cacheParts;
13533
- if (pluginName !== parsed.namespace)
13643
+ if (pluginName !== located.namespace)
13534
13644
  refuse2();
13535
13645
  const expectedPluginId = `${pluginName}@${marketplace}`;
13536
13646
  const listed = skills.filter((skill) => skill.path === document2);
13537
13647
  if (listed.length > 1)
13538
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();
13539
13673
  let remotePluginId;
13540
13674
  if (listed.length === 1) {
13541
13675
  if (listed[0].name !== parsed.name || listed[0].pluginId !== expectedPluginId)
13542
13676
  refuse2();
13543
13677
  } else {
13544
- const installed = installedPlugins.filter((plugin2) => plugin2.id === expectedPluginId && plugin2.name === pluginName);
13545
13678
  if (installed.length !== 1 || !installed[0].installed || !installed[0].enabled)
13546
13679
  refuse2();
13547
13680
  const plugin = installed[0];
@@ -13559,9 +13692,48 @@ function reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2, r
13559
13692
  }
13560
13693
  result.push({ ...parsed, pluginId: expectedPluginId, ...remotePluginId ? { remotePluginId } : {} });
13561
13694
  }
13562
- 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)
13563
13696
  refuse2();
13564
- 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
+ }
13565
13737
  }
13566
13738
  function isReviewedCodexPluginSkillDisabled(document2, cache, controls, rules, read2) {
13567
13739
  if (!document2.startsWith(cache + sep4) || !Array.isArray(controls) || controls.length > 4096)
@@ -14245,10 +14417,75 @@ function disableCodexBundledSkills(text2) {
14245
14417
  // src/lib/agent-integration.ts
14246
14418
  var sha3 = (data) => createHash12("sha256").update(data).digest("hex");
14247
14419
  var ROOTS = NATIVE_SKILL_ROOTS;
14420
+ function hasNoDarwinAcl(fd) {
14421
+ try {
14422
+ const { dlopen, read: read2 } = __require("bun:ffi");
14423
+ const library = dlopen("/usr/lib/libSystem.B.dylib", {
14424
+ acl_get_fd: { args: ["i32"], returns: "ptr" },
14425
+ acl_free: { args: ["ptr"], returns: "i32" },
14426
+ __error: { args: [], returns: "ptr" }
14427
+ });
14428
+ try {
14429
+ const errno = library.symbols.__error();
14430
+ if (!errno)
14431
+ return false;
14432
+ const acl = library.symbols.acl_get_fd(fd);
14433
+ if (acl) {
14434
+ library.symbols.acl_free(acl);
14435
+ return false;
14436
+ }
14437
+ return (acl === null || acl === 0) && read2.i32(errno) === 2;
14438
+ } finally {
14439
+ library.close();
14440
+ }
14441
+ } catch {
14442
+ return false;
14443
+ }
14444
+ }
14445
+ function isSystemRootAlias(path) {
14446
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(path))
14447
+ return false;
14448
+ const target = `/private${path}`, link = `private${path}`;
14449
+ const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
14450
+ try {
14451
+ const before = lstatSync15(path, { bigint: true });
14452
+ if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync4(path) !== link)
14453
+ return false;
14454
+ const directories = ["/", "/private", target].map((directory) => {
14455
+ const stat = lstatSync15(directory, { bigint: true });
14456
+ if (!stat.isDirectory() || stat.uid !== 0n)
14457
+ throw new Error("Untrusted system alias directory");
14458
+ if (directory !== "/private/tmp") {
14459
+ if ((stat.mode & 0o022n) !== 0n)
14460
+ throw new Error("Writable system alias directory");
14461
+ } else if ((stat.mode & 0o1000n) === 0n)
14462
+ throw new Error("Unprotected system temporary directory");
14463
+ const fd = openSync8(directory, constants7.O_RDONLY | constants7.O_DIRECTORY | constants7.O_NOFOLLOW);
14464
+ try {
14465
+ if (identity4(fstatSync8(fd, { bigint: true })) !== identity4(stat) || !hasNoDarwinAcl(fd) || identity4(fstatSync8(fd, { bigint: true })) !== identity4(stat))
14466
+ throw new Error("Unverified system alias directory ACL");
14467
+ } finally {
14468
+ closeSync8(fd);
14469
+ }
14470
+ return { directory, identity: identity4(stat) };
14471
+ });
14472
+ return realpathSync6(path) === target && readlinkSync4(path) === link && identity4(lstatSync15(path, { bigint: true })) === identity4(before) && directories.every((item) => identity4(lstatSync15(item.directory, { bigint: true })) === item.identity);
14473
+ } catch {
14474
+ return false;
14475
+ }
14476
+ }
14477
+ function canonicalSystemPath(path) {
14478
+ const absolute = resolve10(path), root = `/${absolute.split(sep5)[1]}`;
14479
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync15(root, { throwIfNoEntry: false })?.isSymbolicLink())
14480
+ return absolute;
14481
+ if (!isSystemRootAlias(root))
14482
+ throw new Error(`Refusing symlink path: ${root}`);
14483
+ return `/private${absolute}`;
14484
+ }
14248
14485
  function assertSafePath(path) {
14249
14486
  let cursor = resolve10(path);
14250
14487
  while (true) {
14251
- if (lstatSync15(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
14488
+ if (lstatSync15(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
14252
14489
  throw new Error(`Refusing symlink path: ${cursor}`);
14253
14490
  const parent = dirname11(cursor);
14254
14491
  if (parent === cursor)
@@ -14284,7 +14521,7 @@ function rootAliases(home, allowed = false) {
14284
14521
  return result;
14285
14522
  }
14286
14523
  function canonicalAgentPath(path, aliases) {
14287
- const absolute = resolve10(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
14524
+ const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
14288
14525
  return binding ? join17(binding.target, relative5(binding.alias, absolute)) : absolute;
14289
14526
  }
14290
14527
  function disabledCodexSkillPaths(config, aliases) {
@@ -14382,17 +14619,19 @@ function selectNativeMigrationTargets(inventory, manifest) {
14382
14619
  const identities = new Set;
14383
14620
  const selected = [];
14384
14621
  for (const target of manifest.targets) {
14385
- const projectRoot = resolve10(target.projectRoot), normalized = relative5(projectRoot, resolve10(projectRoot, target.path));
14386
- if (!isAbsolute11(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync8(projectRoot) || !lstatSync15(projectRoot).isDirectory())
14622
+ if (!isAbsolute11(target.projectRoot) || resolve10(target.projectRoot) !== target.projectRoot)
14623
+ throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
14624
+ const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot, resolve10(projectRoot, target.path));
14625
+ if (!existsSync8(projectRoot) || !lstatSync15(projectRoot).isDirectory())
14387
14626
  throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
14388
14627
  assertSafePath(projectRoot);
14389
14628
  if (isAbsolute11(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep5}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
14390
14629
  throw new Error(`Invalid native migration target: ${target.path}`);
14391
- const identity4 = `${target.agent}\x00${projectRoot}\x00${normalized}`;
14630
+ const identity4 = `${target.agent}\x00${canonicalSystemPath(projectRoot)}\x00${normalized}`;
14392
14631
  if (identities.has(identity4))
14393
14632
  throw new Error(`Duplicate native migration target: ${target.path}`);
14394
14633
  identities.add(identity4);
14395
- const expected = resolve10(target.projectRoot, target.path);
14634
+ const expected = canonicalSystemPath(resolve10(target.projectRoot, target.path));
14396
14635
  const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve10(entry2.path) === expected);
14397
14636
  if (matches.length !== 1)
14398
14637
  throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
@@ -14408,7 +14647,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
14408
14647
  function projectAncestorDirectories(projects) {
14409
14648
  const directories = new Set;
14410
14649
  for (const project of projects) {
14411
- for (let path = resolve10(project), depth = 0;; depth++) {
14650
+ for (let path = canonicalSystemPath(project), depth = 0;; depth++) {
14412
14651
  if (depth >= 100)
14413
14652
  throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
14414
14653
  directories.add(path);
@@ -14421,11 +14660,13 @@ function projectAncestorDirectories(projects) {
14421
14660
  return [...directories];
14422
14661
  }
14423
14662
  function inventoryNativeSkills(home = homedir2(), options = {}) {
14663
+ home = canonicalSystemPath(home);
14424
14664
  if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute11(options.reviewedCacheAlias) || resolve10(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
14425
14665
  throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
14426
14666
  if (options.disabledVendorPaths?.some((path) => !isAbsolute11(path) || resolve10(path) !== path))
14427
14667
  throw new Error("Disabled vendor paths must be exact absolute paths");
14428
- const disabledVendorPaths = new Set(options.disabledVendorPaths ?? []);
14668
+ const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
14669
+ const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
14429
14670
  const aliases = rootAliases(home, options.allowRootAliases);
14430
14671
  const selectedAgents = options.agents ? new Set(options.agents) : undefined;
14431
14672
  const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
@@ -14436,6 +14677,9 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
14436
14677
  for (const [agent, path] of rootDefinitions)
14437
14678
  roots.push([agent, canonicalAgentPath(join17(project, path), aliases)]);
14438
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");
14439
14683
  const entries = [], seen = new Set;
14440
14684
  const cacheScans = new Map, maxAliasProofEntries = 1e4;
14441
14685
  let discoveryEntries = 0, discoveryPathBytes = 0;
@@ -14448,8 +14692,11 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
14448
14692
  }
14449
14693
  let reviewedCacheAliasFound = false;
14450
14694
  function verifiedSiblingCacheAlias(agent, path, parent) {
14451
- const link = readlinkSync4(path), target = resolve10(parent, link);
14452
- if (dirname11(target) !== parent || (isAbsolute11(link) ? link !== target : dirname11(link) !== "."))
14695
+ const link = readlinkSync4(path), lexicalTarget = resolve10(parent, link);
14696
+ if (isAbsolute11(link) ? link !== lexicalTarget : dirname11(link) !== ".")
14697
+ return false;
14698
+ const target = canonicalSystemPath(lexicalTarget);
14699
+ if (dirname11(target) !== parent)
14453
14700
  return false;
14454
14701
  if (!lstatSync15(target, { throwIfNoEntry: false })?.isDirectory())
14455
14702
  return false;
@@ -14458,20 +14705,22 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
14458
14705
  return false;
14459
14706
  if (scan.hasSkills) {
14460
14707
  const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep5)));
14461
- if (path === options.reviewedCacheAlias && targetSkills.length > 0)
14708
+ if (path === reviewedCacheAlias && targetSkills.length > 0)
14462
14709
  reviewedCacheAliasFound = true;
14463
14710
  if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path) || options.disabledVendorSkill?.(entry) === true))
14464
14711
  return true;
14465
- if (path !== options.reviewedCacheAlias)
14712
+ if (path !== reviewedCacheAlias)
14466
14713
  return false;
14467
14714
  }
14468
14715
  return true;
14469
14716
  }
14470
- 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) {
14471
14718
  if (!admitted)
14472
14719
  admit(path);
14473
14720
  const scan = { complete: true, hasSkills: false, entries: 1 };
14474
14721
  assertSafePath(path);
14722
+ if (configuredRoot && agent === "codex" && installationInputs.includes(path))
14723
+ return scan;
14475
14724
  if (!existsSync8(path))
14476
14725
  return scan;
14477
14726
  const stat = lstatSync15(path);
@@ -14527,7 +14776,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
14527
14776
  continue;
14528
14777
  }
14529
14778
  }
14530
- const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true);
14779
+ const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true, configuredRoot);
14531
14780
  scan.complete &&= childScan.complete;
14532
14781
  scan.hasSkills ||= childScan.hasSkills;
14533
14782
  scan.entries += childScan.entries;
@@ -14536,10 +14785,10 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
14536
14785
  cacheScans.set(path, scan);
14537
14786
  return scan;
14538
14787
  }
14539
- function scanRoot(agent, path, vendor = false, pluginCache = false) {
14788
+ function scanRoot(agent, path, vendor = false, pluginCache = false, configuredRoot = false) {
14540
14789
  if (agent === "hermes" || options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`))
14541
14790
  assertNoHermesLegacyShadow(path);
14542
- if (!visit2(agent, path, vendor, 0, pluginCache).complete)
14791
+ if (!visit2(agent, path, vendor, 0, pluginCache, false, configuredRoot).complete)
14543
14792
  throw new Error(`Native skill discovery limit exceeded; review this root before continuing: ${path}`);
14544
14793
  }
14545
14794
  for (const [agent, path] of roots)
@@ -14555,7 +14804,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
14555
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 }))) : [];
14556
14805
  for (const root of [...options.agentRoots ?? [], ...configured])
14557
14806
  if (includesAgent(root.agent))
14558
- scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true);
14807
+ scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true, false, true);
14559
14808
  if (options.reviewedCacheAlias !== undefined && !reviewedCacheAliasFound)
14560
14809
  throw new Error("Reviewed cache alias was not an inventoried skill-containing sibling alias");
14561
14810
  recheckRootAliases(aliases);
@@ -14695,6 +14944,11 @@ enabled = false
14695
14944
  Bun.TOML.parse(result);
14696
14945
  return result;
14697
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
+ }
14698
14952
  function planAgentIntegration(options) {
14699
14953
  const home = options.home ?? homedir2(), dataDir2 = options.dataDir ?? getDataDirReadOnly();
14700
14954
  const aliases = rootAliases(home, options.allowRootAliases);
@@ -14733,11 +14987,16 @@ function planAgentIntegration(options) {
14733
14987
  const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(join17(home, ".codex", "config.toml"), aliases)) ?? "") : {};
14734
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) });
14735
14989
  const observedNativeSources = [];
14736
- 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) => {
14737
14991
  const bytes = readNativeBytes(path, 1024 * 1024);
14738
14992
  observedNativeSources.push({ path, sha256: sha3(bytes) });
14739
14993
  return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
14740
- }, 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));
14741
15000
  const changes = [];
14742
15001
  for (const agent of [...new Set(options.agents)]) {
14743
15002
  if (!INTEGRATION_AGENTS.includes(agent))
@@ -14802,7 +15061,16 @@ function planAgentIntegration(options) {
14802
15061
  changes.push({ path: configPath, before: previous, after: next });
14803
15062
  }
14804
15063
  }
14805
- 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
+ });
14806
15074
  const nextPolicy = { ...policy, version: 1, loading: "cli", profileId, bridge: {
14807
15075
  ...policy.bridge,
14808
15076
  version: CLI_BRIDGE_VERSION,
@@ -14815,7 +15083,7 @@ function planAgentIntegration(options) {
14815
15083
  profiles: Object.fromEntries([...new Set([...priorAgents, ...options.agents])].sort().map((agent) => [agent, bindings.get(agent)?.profileId ?? policy.bridge?.profiles?.[agent] ?? policy.profileId])),
14816
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 ?? [],
14817
15085
  rootAliases: aliases,
14818
- ...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 } : {},
14819
15087
  discovery: { ...policy.bridge?.discovery, ...Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) }
14820
15088
  } };
14821
15089
  const serializedPolicy = serializeManagedSkillPolicy(nextPolicy);
@@ -15306,7 +15574,12 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
15306
15574
  throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
15307
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))))
15308
15576
  throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
15309
- 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
+ };
15310
15583
  const disabledPaths = new Set(disabledCodexSkillPaths(codexConfig, aliases));
15311
15584
  const disabledVendorSkill = (entry) => {
15312
15585
  if (agent !== "codex" || !entry.vendor)
@@ -15343,7 +15616,15 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
15343
15616
  } catch (error) {
15344
15617
  throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
15345
15618
  }
15346
- 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 });
15347
15628
  const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledVendorSkill(entry));
15348
15629
  if (unexpected.length) {
15349
15630
  const paths = unexpected.slice(0, 3).map((entry) => {
@@ -18027,8 +18308,16 @@ function verifyAgentDiscovery(binding, codexRecovery) {
18027
18308
  throw new Error("Invalid native discovery binding");
18028
18309
  if (binding.agent === "hermes" && !binding.directories?.length)
18029
18310
  throw new Error("Hermes discovery requires directory membership coverage; run skills hook install with a fresh discovery review");
18030
- if (binding.directories !== undefined)
18031
- 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
+ }
18032
18321
  const budget = discoveryByteBudget();
18033
18322
  for (const source of binding.sources) {
18034
18323
  assertMarketplaceBinding(binding, source);
@@ -18038,6 +18327,10 @@ function verifyAgentDiscovery(binding, codexRecovery) {
18038
18327
  throw new Error("Invalid native discovery projection");
18039
18328
  if (source.sha256 !== null && !/^[a-f0-9]{64}$/.test(source.sha256))
18040
18329
  throw new Error("Invalid native discovery digest");
18330
+ if (installationInput(source.path)) {
18331
+ safe4(source.path);
18332
+ continue;
18333
+ }
18041
18334
  const current = projected(source, undefined, budget);
18042
18335
  if (current !== source.sha256 && !verifiesCodexHookDiscoverySource(binding, source, current, codexRecovery))
18043
18336
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
@@ -18045,6 +18338,27 @@ function verifyAgentDiscovery(binding, codexRecovery) {
18045
18338
  for (const root2 of binding.roots)
18046
18339
  safe4(root2);
18047
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
+ }
18048
18362
  function assertMarketplaceBinding(binding, source) {
18049
18363
  if ((source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2") && (binding.agent !== "codex" || binding.method !== "reviewed" || basename9(source.path) !== "config.toml"))
18050
18364
  throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
@@ -18055,8 +18369,11 @@ function assertMarketplaceBinding(binding, source) {
18055
18369
  }
18056
18370
  function rebindAgentDiscovery(binding, changes) {
18057
18371
  const budget = discoveryByteBudget();
18372
+ const installationRoots = codexInstallationRoots(binding);
18058
18373
  return { ...binding, sources: binding.sources.map((source) => {
18059
18374
  assertMarketplaceBinding(binding, source);
18375
+ if (!changes.has(source.path) && installationRoots.some((root2) => source.path.startsWith(root2 + sep6)))
18376
+ return source;
18060
18377
  const sha2562 = projected(source, changes, budget);
18061
18378
  if (!changes.has(source.path) && sha2562 !== source.sha256)
18062
18379
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);