@hasna/skills 0.10.72 → 0.10.73
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 +270 -2
- package/bin/index.js +1687 -1238
- package/bin/mcp.js +116 -35
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.js +947 -696
- package/dist/lib/agent-discovery.d.ts +36 -0
- package/dist/lib/claude-installer-binding.d.ts +65 -0
- package/dist/lib/codex-node-modules.d.ts +11 -0
- package/dist/lib/codex-settings-witness-v1.d.ts +9 -0
- package/dist/lib/codex-settings-witness.d.ts +18 -0
- package/dist/runtime-prerequisites.js +1 -1
- package/dist/sdk/index.js +1056 -820
- package/docs/plugin-admission.md +50 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6988,8 +6988,8 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6988
6988
|
});
|
|
6989
6989
|
|
|
6990
6990
|
// src/lib/registry.ts
|
|
6991
|
-
import { existsSync as existsSync15, readFileSync as
|
|
6992
|
-
import { join as
|
|
6991
|
+
import { existsSync as existsSync15, readFileSync as readFileSync27, readdirSync as readdirSync14 } from "fs";
|
|
6992
|
+
import { join as join41 } from "path";
|
|
6993
6993
|
|
|
6994
6994
|
// src/lib/config.ts
|
|
6995
6995
|
import { closeSync, constants, existsSync as existsSync2, fstatSync, lstatSync, openSync, readFileSync, statSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -7266,7 +7266,7 @@ import {
|
|
|
7266
7266
|
statSync as statSync13,
|
|
7267
7267
|
writeFileSync as writeFileSync9
|
|
7268
7268
|
} from "fs";
|
|
7269
|
-
import { basename as basename17, dirname as
|
|
7269
|
+
import { basename as basename17, dirname as dirname28, isAbsolute as isAbsolute29, join as join40, normalize as normalize4, relative as relative11, resolve as resolve29, sep as sep17 } from "path";
|
|
7270
7270
|
|
|
7271
7271
|
// src/lib/registry-data/index.ts
|
|
7272
7272
|
var SKILLS = [];
|
|
@@ -8924,9 +8924,9 @@ var NATIVE_SKILL_ROOTS = [
|
|
|
8924
8924
|
];
|
|
8925
8925
|
|
|
8926
8926
|
// src/lib/authoring-path.ts
|
|
8927
|
-
import { lstatSync as
|
|
8927
|
+
import { lstatSync as lstatSync33, realpathSync as realpathSync14 } from "fs";
|
|
8928
8928
|
import { homedir as homedir6 } from "os";
|
|
8929
|
-
import { basename as basename16, dirname as
|
|
8929
|
+
import { basename as basename16, dirname as dirname27, join as join39, resolve as resolve28, sep as sep16 } from "path";
|
|
8930
8930
|
|
|
8931
8931
|
// src/lib/agent-adapters.ts
|
|
8932
8932
|
var INTEGRATION_AGENTS = ["claude", "codex", "gemini", "opencode", "cursor", "hermes", "sumi"];
|
|
@@ -9973,8 +9973,8 @@ function assertAgentPolicyCollections(policy) {
|
|
|
9973
9973
|
|
|
9974
9974
|
// src/lib/agent-discovery.ts
|
|
9975
9975
|
import { createHash as createHash30 } from "crypto";
|
|
9976
|
-
import { lstatSync as
|
|
9977
|
-
import { basename as basename15, dirname as
|
|
9976
|
+
import { lstatSync as lstatSync32, readFileSync as readFileSync26, readdirSync as readdirSync12, realpathSync as realpathSync13 } from "fs";
|
|
9977
|
+
import { basename as basename15, dirname as dirname26, isAbsolute as isAbsolute28, join as join38, relative as relative10, resolve as resolve27, sep as sep15 } from "path";
|
|
9978
9978
|
import { isDeepStrictEqual as isDeepStrictEqual9 } from "util";
|
|
9979
9979
|
|
|
9980
9980
|
// node_modules/yaml/dist/index.js
|
|
@@ -12430,6 +12430,10 @@ function canonical4(value, depth = 0, budget2 = { remaining: 65536 }) {
|
|
|
12430
12430
|
function hashCodexSettingsReplacement(text2, budget2 = { remaining: 256 * 1024 * 1024 }) {
|
|
12431
12431
|
need4(Buffer.byteLength(text2) <= 1024 * 1024 && budget2.remaining >= Buffer.byteLength(text2));
|
|
12432
12432
|
budget2.remaining -= Buffer.byteLength(text2);
|
|
12433
|
+
const prepared = prepareCodexSettingsV1(text2);
|
|
12434
|
+
return finishCodexSettingsV1(prepared.config, prepared.tokens);
|
|
12435
|
+
}
|
|
12436
|
+
function prepareCodexSettingsV1(text2) {
|
|
12433
12437
|
let config;
|
|
12434
12438
|
try {
|
|
12435
12439
|
config = Bun.TOML.parse(text2);
|
|
@@ -12437,6 +12441,9 @@ function hashCodexSettingsReplacement(text2, budget2 = { remaining: 256 * 1024 *
|
|
|
12437
12441
|
throw new Error("Invalid Codex settings witness");
|
|
12438
12442
|
}
|
|
12439
12443
|
need4(config && typeof config === "object" && !Array.isArray(config));
|
|
12444
|
+
return { config, tokens: numericTokens(text2) };
|
|
12445
|
+
}
|
|
12446
|
+
function finishCodexSettingsV1(config, tokens) {
|
|
12440
12447
|
if (Object.hasOwn(config, "model")) {
|
|
12441
12448
|
need4(typeof config.model === "string" && /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(config.model));
|
|
12442
12449
|
delete config.model;
|
|
@@ -12446,7 +12453,7 @@ function hashCodexSettingsReplacement(text2, budget2 = { remaining: 256 * 1024 *
|
|
|
12446
12453
|
need4(typeof config[key] === "string" && values.has(config[key]));
|
|
12447
12454
|
delete config[key];
|
|
12448
12455
|
}
|
|
12449
|
-
return sha(`hasna.skills.codex-settings.v1\x00${JSON.stringify([canonical4(config),
|
|
12456
|
+
return sha(`hasna.skills.codex-settings.v1\x00${JSON.stringify([canonical4(config), tokens])}`);
|
|
12450
12457
|
}
|
|
12451
12458
|
function captureCodexSettings(path, budget2 = { remaining: 256 * 1024 * 1024 }) {
|
|
12452
12459
|
const text2 = readNativeSettingsWitnessFile(path, budget2, "config.toml");
|
|
@@ -13160,7 +13167,7 @@ function projectStatusLine(config) {
|
|
|
13160
13167
|
if (!Object.keys(tui).length)
|
|
13161
13168
|
delete config.tui;
|
|
13162
13169
|
}
|
|
13163
|
-
function
|
|
13170
|
+
function prepareSettings(text2, version, dependencies) {
|
|
13164
13171
|
let config;
|
|
13165
13172
|
try {
|
|
13166
13173
|
config = Bun.TOML.parse(text2);
|
|
@@ -13199,18 +13206,87 @@ function settingsDigest2(text2, version = 2, dependencies) {
|
|
|
13199
13206
|
delete config.tui.model_availability_nux;
|
|
13200
13207
|
if (isRecord3(config.tui) && typeof config.tui.screen_reader_detection_done === "boolean")
|
|
13201
13208
|
delete config.tui.screen_reader_detection_done;
|
|
13202
|
-
config
|
|
13209
|
+
return { config: embedCodexNumericTokens(config, tokens), numericSource: text2, dependencies };
|
|
13210
|
+
}
|
|
13211
|
+
const mcp = version >= 3 ? projectRegularCodexMcp(config, text2) : { config, numericSource: text2 };
|
|
13212
|
+
return version >= 4 ? projectModelAvailabilityNux(mcp.config, mcp.numericSource) : mcp;
|
|
13213
|
+
}
|
|
13214
|
+
function finishSettings(prepared, version) {
|
|
13215
|
+
const config = prepared.config;
|
|
13216
|
+
if (version === 6) {
|
|
13203
13217
|
normalize2(config, 6);
|
|
13204
13218
|
if (isRecord3(config.tui) && !Object.keys(config.tui).length)
|
|
13205
13219
|
delete config.tui;
|
|
13206
|
-
return sha2(`hasna.skills.codex-settings.v6\x00${JSON.stringify([canonical5(config), canonical5(dependencies)])}`);
|
|
13220
|
+
return sha2(`hasna.skills.codex-settings.v6\x00${JSON.stringify([canonical5(config), canonical5(prepared.dependencies)])}`);
|
|
13207
13221
|
}
|
|
13208
|
-
|
|
13209
|
-
const
|
|
13210
|
-
normalize2(projected.config, version);
|
|
13211
|
-
const values = [canonical5(projected.config), numericTokens2(projected.numericSource)];
|
|
13222
|
+
normalize2(config, version);
|
|
13223
|
+
const values = [canonical5(config), numericTokens2(prepared.numericSource)];
|
|
13212
13224
|
return sha2(`hasna.skills.codex-settings.v${version}\x00${JSON.stringify(values)}`);
|
|
13213
13225
|
}
|
|
13226
|
+
function settingsDigest2(text2, version = 2, dependencies) {
|
|
13227
|
+
return finishSettings(prepareSettings(text2, version, dependencies), version);
|
|
13228
|
+
}
|
|
13229
|
+
var SCREEN_READER_RECEIPT = "screen_reader_detection_done";
|
|
13230
|
+
function cloneTree(value) {
|
|
13231
|
+
if (Array.isArray(value))
|
|
13232
|
+
return value.map(cloneTree);
|
|
13233
|
+
if (isRecord3(value) && [Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
13234
|
+
return Object.fromEntries(Object.entries(value).map(([key2, child2]) => [key2, cloneTree(child2)]));
|
|
13235
|
+
return value;
|
|
13236
|
+
}
|
|
13237
|
+
function receiptEdits(config) {
|
|
13238
|
+
const tui = config.tui;
|
|
13239
|
+
const set = (value) => (target) => {
|
|
13240
|
+
if (!isRecord3(target.tui))
|
|
13241
|
+
target.tui = {};
|
|
13242
|
+
target.tui[SCREEN_READER_RECEIPT] = value;
|
|
13243
|
+
};
|
|
13244
|
+
if (tui === undefined || isRecord3(tui) && !Object.hasOwn(tui, SCREEN_READER_RECEIPT))
|
|
13245
|
+
return [set(true), set(false)];
|
|
13246
|
+
if (!isRecord3(tui) || typeof tui[SCREEN_READER_RECEIPT] !== "boolean")
|
|
13247
|
+
return [];
|
|
13248
|
+
const remove = (dropEmpty) => (target) => {
|
|
13249
|
+
delete target.tui[SCREEN_READER_RECEIPT];
|
|
13250
|
+
if (dropEmpty && !Object.keys(target.tui).length)
|
|
13251
|
+
delete target.tui;
|
|
13252
|
+
};
|
|
13253
|
+
return [remove(true), ...Object.keys(tui).length === 1 ? [remove(false)] : [], set(!tui[SCREEN_READER_RECEIPT])];
|
|
13254
|
+
}
|
|
13255
|
+
function codexSettingsToleratesScreenReaderReceipt(text2, hashMode, expected) {
|
|
13256
|
+
try {
|
|
13257
|
+
if (Buffer.byteLength(text2) > 1024 * 1024 || !/^[a-f0-9]{64}$/.test(expected))
|
|
13258
|
+
return false;
|
|
13259
|
+
if (hashMode === "codex-settings-v1") {
|
|
13260
|
+
const prepared2 = prepareCodexSettingsV1(text2);
|
|
13261
|
+
return receiptEdits(prepared2.config).some((edit) => {
|
|
13262
|
+
const candidate = cloneTree(prepared2.config);
|
|
13263
|
+
edit(candidate);
|
|
13264
|
+
return finishCodexSettingsV1(candidate, prepared2.tokens) === expected;
|
|
13265
|
+
});
|
|
13266
|
+
}
|
|
13267
|
+
const version = Number(hashMode.slice("codex-settings-v".length));
|
|
13268
|
+
if (![2, 3, 4, 5].includes(version))
|
|
13269
|
+
return false;
|
|
13270
|
+
const prepared = prepareSettings(text2, version);
|
|
13271
|
+
return receiptEdits(prepared.config).some((edit) => {
|
|
13272
|
+
const candidate = cloneTree(prepared.config);
|
|
13273
|
+
edit(candidate);
|
|
13274
|
+
if (version >= 4 && isRecord3(candidate.tui) && !Object.keys(candidate.tui).length)
|
|
13275
|
+
delete candidate.tui;
|
|
13276
|
+
return finishSettings({ ...prepared, config: candidate }, version) === expected;
|
|
13277
|
+
});
|
|
13278
|
+
} catch {
|
|
13279
|
+
return false;
|
|
13280
|
+
}
|
|
13281
|
+
}
|
|
13282
|
+
function codexSettingsWitnessMatch(path, hashMode, expected, budget2 = { remaining: 256 * 1024 * 1024 }) {
|
|
13283
|
+
const text2 = readNativeSettingsWitnessFile(path, budget2, "config.toml");
|
|
13284
|
+
need7(Buffer.byteLength(text2) <= 1024 * 1024);
|
|
13285
|
+
const exact = hashMode === "codex-settings-v1" ? hashCodexSettingsReplacement(text2) : settingsDigest2(text2, Number(hashMode.slice("codex-settings-v".length)));
|
|
13286
|
+
if (exact === expected)
|
|
13287
|
+
return "exact";
|
|
13288
|
+
return codexSettingsToleratesScreenReaderReceipt(text2, hashMode, expected) ? "screen-reader-receipt" : null;
|
|
13289
|
+
}
|
|
13214
13290
|
function hashCodexSettingsReplacementV2(text2, budget2 = { remaining: 256 * 1024 * 1024 }) {
|
|
13215
13291
|
need7(Buffer.byteLength(text2) <= 1024 * 1024 && budget2.remaining >= Buffer.byteLength(text2));
|
|
13216
13292
|
budget2.remaining -= Buffer.byteLength(text2);
|
|
@@ -13925,6 +14001,8 @@ import { execFile, spawn as spawn2 } from "child_process";
|
|
|
13925
14001
|
import { realpathSync as realpathSync4, statSync as statSync8 } from "fs";
|
|
13926
14002
|
import { isAbsolute as isAbsolute14 } from "path";
|
|
13927
14003
|
import { promisify } from "util";
|
|
14004
|
+
var CODEX_CONFIG_FALLBACK_SUMMARY = "Invalid configuration; using defaults.";
|
|
14005
|
+
var CODEX_CONFIG_INVALID = "CODEX_HOOK_TRUST_NATIVE_CONFIG_INVALID: Codex could not load its configuration and fell back to defaults; repair config.toml (run codex once to see the error) and retry";
|
|
13928
14006
|
async function connectCodexHookRpc(options) {
|
|
13929
14007
|
const timeoutMs = options.timeoutMs ?? 20000;
|
|
13930
14008
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 20000)
|
|
@@ -13953,7 +14031,7 @@ async function connectCodexHookRpc(options) {
|
|
|
13953
14031
|
else
|
|
13954
14032
|
env.CODEX_HOME = options.codexHome;
|
|
13955
14033
|
const corpusLease = acquireCodexCorpusWrite([options.codexHome ?? join17(options.home, ".codex")], { codexCommand: binary });
|
|
13956
|
-
const child2 = spawn2(binary, ["app-server", "--
|
|
14034
|
+
const child2 = spawn2(binary, ["app-server", "--stdio"], {
|
|
13957
14035
|
cwd: options.home,
|
|
13958
14036
|
env,
|
|
13959
14037
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -13965,12 +14043,12 @@ async function connectCodexHookRpc(options) {
|
|
|
13965
14043
|
resolve15();
|
|
13966
14044
|
}));
|
|
13967
14045
|
const pending = new Map;
|
|
13968
|
-
let nextId = 1, buffer = Buffer.alloc(0), stopped = false, diagnosticBytes = 0;
|
|
14046
|
+
let nextId = 1, buffer = Buffer.alloc(0), stopped = false, diagnosticBytes = 0, configInvalid = false;
|
|
13969
14047
|
const fail2 = () => {
|
|
13970
14048
|
stopped = true;
|
|
13971
14049
|
for (const value of pending.values()) {
|
|
13972
14050
|
clearTimeout(value.timer);
|
|
13973
|
-
value.reject(new Error("CODEX_HOOK_TRUST_NATIVE_RPC_FAILED"));
|
|
14051
|
+
value.reject(new Error(configInvalid ? CODEX_CONFIG_INVALID : "CODEX_HOOK_TRUST_NATIVE_RPC_FAILED"));
|
|
13974
14052
|
}
|
|
13975
14053
|
pending.clear();
|
|
13976
14054
|
};
|
|
@@ -14010,6 +14088,12 @@ async function connectCodexHookRpc(options) {
|
|
|
14010
14088
|
child2.kill();
|
|
14011
14089
|
return;
|
|
14012
14090
|
}
|
|
14091
|
+
if (!("id" in value) && value.method === "configWarning" && value.params?.summary === CODEX_CONFIG_FALLBACK_SUMMARY) {
|
|
14092
|
+
configInvalid = true;
|
|
14093
|
+
fail2();
|
|
14094
|
+
child2.kill();
|
|
14095
|
+
return;
|
|
14096
|
+
}
|
|
14013
14097
|
const waiting = pending.get(value.id);
|
|
14014
14098
|
if (!waiting)
|
|
14015
14099
|
continue;
|
|
@@ -14026,7 +14110,7 @@ async function connectCodexHookRpc(options) {
|
|
|
14026
14110
|
processId: child2.pid,
|
|
14027
14111
|
request(method, params) {
|
|
14028
14112
|
if (stopped)
|
|
14029
|
-
return Promise.reject(new Error("CODEX_HOOK_TRUST_NATIVE_RPC_CLOSED"));
|
|
14113
|
+
return Promise.reject(new Error(configInvalid ? CODEX_CONFIG_INVALID : "CODEX_HOOK_TRUST_NATIVE_RPC_CLOSED"));
|
|
14030
14114
|
return new Promise((resolve15, reject) => {
|
|
14031
14115
|
const id = nextId++;
|
|
14032
14116
|
const timer = setTimeout(() => {
|
|
@@ -14069,7 +14153,7 @@ async function connectCodexHookRpc(options) {
|
|
|
14069
14153
|
return rpc;
|
|
14070
14154
|
} catch {
|
|
14071
14155
|
await rpc.close();
|
|
14072
|
-
throw new Error("CODEX_HOOK_TRUST_NATIVE_UNSUPPORTED: native hooks/list and versioned config writes are required");
|
|
14156
|
+
throw new Error(configInvalid ? CODEX_CONFIG_INVALID : "CODEX_HOOK_TRUST_NATIVE_UNSUPPORTED: native hooks/list and versioned config writes are required");
|
|
14073
14157
|
}
|
|
14074
14158
|
}
|
|
14075
14159
|
|
|
@@ -15053,14 +15137,14 @@ function projectReviewedClaudePluginManifest(text2, reviewedSha256) {
|
|
|
15053
15137
|
}
|
|
15054
15138
|
|
|
15055
15139
|
// src/lib/plugin-discovery.ts
|
|
15056
|
-
import { closeSync as closeSync18, constants as constants19, fstatSync as fstatSync18, lstatSync as
|
|
15057
|
-
import { basename as basename13, dirname as
|
|
15140
|
+
import { closeSync as closeSync18, constants as constants19, fstatSync as fstatSync18, lstatSync as lstatSync30, openSync as openSync18, opendirSync as opendirSync5, readSync as readSync13 } from "fs";
|
|
15141
|
+
import { basename as basename13, dirname as dirname24, isAbsolute as isAbsolute26, join as join37, resolve as resolve25 } from "path";
|
|
15058
15142
|
|
|
15059
15143
|
// src/lib/plugin-admission.ts
|
|
15060
15144
|
import { homedir as homedir5 } from "os";
|
|
15061
|
-
import { isAbsolute as isAbsolute25, join as
|
|
15145
|
+
import { isAbsolute as isAbsolute25, join as join36, resolve as resolve24 } from "path";
|
|
15062
15146
|
// package.json
|
|
15063
|
-
var version = "0.10.
|
|
15147
|
+
var version = "0.10.73";
|
|
15064
15148
|
var package_default = {
|
|
15065
15149
|
name: "@hasna/skills",
|
|
15066
15150
|
version,
|
|
@@ -16905,22 +16989,22 @@ async function prepareSkillDependencies(skillPath, env, timeoutMs = DEFAULT_PREP
|
|
|
16905
16989
|
}
|
|
16906
16990
|
|
|
16907
16991
|
// src/lib/skillinfo.ts
|
|
16908
|
-
import { existsSync as existsSync13, readFileSync as
|
|
16909
|
-
import { join as
|
|
16992
|
+
import { existsSync as existsSync13, readFileSync as readFileSync25 } from "fs";
|
|
16993
|
+
import { join as join35 } from "path";
|
|
16910
16994
|
|
|
16911
16995
|
// src/lib/installer.ts
|
|
16912
|
-
import { existsSync as existsSync12, readFileSync as
|
|
16913
|
-
import { join as
|
|
16996
|
+
import { existsSync as existsSync12, readFileSync as readFileSync23, rmSync as rmSync3 } from "fs";
|
|
16997
|
+
import { join as join34 } from "path";
|
|
16914
16998
|
import { homedir as homedir4 } from "os";
|
|
16915
16999
|
|
|
16916
17000
|
// src/lib/agent-sync.ts
|
|
16917
17001
|
import {
|
|
16918
17002
|
cpSync as cpSync2,
|
|
16919
17003
|
existsSync as existsSync10,
|
|
16920
|
-
lstatSync as
|
|
17004
|
+
lstatSync as lstatSync29,
|
|
16921
17005
|
mkdirSync as mkdirSync9,
|
|
16922
17006
|
mkdtempSync,
|
|
16923
|
-
readFileSync as
|
|
17007
|
+
readFileSync as readFileSync20,
|
|
16924
17008
|
readdirSync as readdirSync11,
|
|
16925
17009
|
renameSync as renameSync5,
|
|
16926
17010
|
rmSync as rmSync2,
|
|
@@ -16928,7 +17012,7 @@ import {
|
|
|
16928
17012
|
writeFileSync as writeFileSync7
|
|
16929
17013
|
} from "fs";
|
|
16930
17014
|
import { homedir as homedir3 } from "os";
|
|
16931
|
-
import { basename as basename12, dirname as
|
|
17015
|
+
import { basename as basename12, dirname as dirname23, join as join32 } from "path";
|
|
16932
17016
|
|
|
16933
17017
|
// src/lib/codex-native-skill-policy.ts
|
|
16934
17018
|
import { createHash as createHash21, randomUUID as randomUUID3 } from "crypto";
|
|
@@ -18270,16 +18354,59 @@ function assertCodexSemanticCacheWitnessUnchanged(previous, current) {
|
|
|
18270
18354
|
refuse4();
|
|
18271
18355
|
}
|
|
18272
18356
|
|
|
18357
|
+
// src/lib/codex-node-modules.ts
|
|
18358
|
+
import { lstatSync as lstatSync21, readFileSync as readFileSync16 } from "fs";
|
|
18359
|
+
import { dirname as dirname19, join as join26, resolve as resolve19, sep as sep12 } from "path";
|
|
18360
|
+
var PLUGIN_MANIFESTS = Object.freeze(["plugin.json", ".codex-plugin/plugin.json", ".claude-plugin/plugin.json", ".cursor-plugin/plugin.json"]);
|
|
18361
|
+
var PLUGIN_ROOT_DEPTH = 3;
|
|
18362
|
+
function codexPluginCacheSkillTree(path, depth) {
|
|
18363
|
+
if (depth <= PLUGIN_ROOT_DEPTH)
|
|
18364
|
+
return true;
|
|
18365
|
+
let root = path;
|
|
18366
|
+
for (let level = depth;level > PLUGIN_ROOT_DEPTH; level--)
|
|
18367
|
+
root = dirname19(root);
|
|
18368
|
+
try {
|
|
18369
|
+
const skillRoots = [join26(root, "skills")];
|
|
18370
|
+
for (const name of PLUGIN_MANIFESTS) {
|
|
18371
|
+
const manifest = join26(root, name), stat2 = lstatSync21(manifest, { throwIfNoEntry: false });
|
|
18372
|
+
if (!stat2)
|
|
18373
|
+
continue;
|
|
18374
|
+
if (!stat2.isFile() || stat2.size > 1024 * 1024)
|
|
18375
|
+
return true;
|
|
18376
|
+
const text2 = new TextDecoder("utf-8", { fatal: true }).decode(readFileSync16(manifest));
|
|
18377
|
+
hashNativeJsonControls(text2);
|
|
18378
|
+
const value = JSON.parse(text2);
|
|
18379
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
18380
|
+
return true;
|
|
18381
|
+
const declared = value.skills;
|
|
18382
|
+
if (declared === undefined)
|
|
18383
|
+
continue;
|
|
18384
|
+
const paths = typeof declared === "string" ? [declared] : declared;
|
|
18385
|
+
if (!Array.isArray(paths) || paths.some((item) => typeof item !== "string"))
|
|
18386
|
+
return true;
|
|
18387
|
+
for (const item of paths) {
|
|
18388
|
+
const target = resolve19(root, item);
|
|
18389
|
+
if (target !== root && !target.startsWith(root + sep12))
|
|
18390
|
+
return true;
|
|
18391
|
+
skillRoots.push(target.endsWith(`${sep12}SKILL.md`) ? dirname19(target) : target);
|
|
18392
|
+
}
|
|
18393
|
+
}
|
|
18394
|
+
return skillRoots.some((skillRoot) => path === skillRoot || path.startsWith(skillRoot + sep12) || skillRoot.startsWith(path + sep12));
|
|
18395
|
+
} catch {
|
|
18396
|
+
return true;
|
|
18397
|
+
}
|
|
18398
|
+
}
|
|
18399
|
+
|
|
18273
18400
|
// src/lib/agent-integration.ts
|
|
18274
18401
|
import { createHash as createHash27, randomUUID as randomUUID4 } from "crypto";
|
|
18275
18402
|
import { isDeepStrictEqual as isDeepStrictEqual8 } from "util";
|
|
18276
|
-
import { existsSync as existsSync9, lstatSync as
|
|
18277
|
-
import { basename as basename11, dirname as
|
|
18403
|
+
import { existsSync as existsSync9, lstatSync as lstatSync28, statSync as statSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync19, readdirSync as readdirSync10, opendirSync as opendirSync4, readlinkSync as readlinkSync7, realpathSync as realpathSync12, renameSync as renameSync4, rmdirSync as rmdirSync3, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5, chmodSync, openSync as openSync17, closeSync as closeSync17, fsyncSync as fsyncSync7, fstatSync as fstatSync17, readSync as readSync12, constants as constants18, linkSync as linkSync3 } from "fs";
|
|
18404
|
+
import { basename as basename11, dirname as dirname22, join as join31, resolve as resolve23, relative as relative9, isAbsolute as isAbsolute24, sep as sep14 } from "path";
|
|
18278
18405
|
import { homedir as homedir2 } from "os";
|
|
18279
18406
|
|
|
18280
18407
|
// src/lib/managed-policy.ts
|
|
18281
|
-
import { closeSync as closeSync13, constants as constants13, fstatSync as fstatSync13, lstatSync as
|
|
18282
|
-
import { join as
|
|
18408
|
+
import { closeSync as closeSync13, constants as constants13, fstatSync as fstatSync13, lstatSync as lstatSync22, openSync as openSync13, readSync as readSync9 } from "fs";
|
|
18409
|
+
import { join as join27 } from "path";
|
|
18283
18410
|
function refusal(reason2 = "is unreadable") {
|
|
18284
18411
|
return new SkillSelectionError("INVALID_AGENT_POLICY", `The Skills agent loading policy ${reason2}; refusing legacy fallback.`);
|
|
18285
18412
|
}
|
|
@@ -18310,10 +18437,10 @@ function serializeManagedSkillPolicy(value) {
|
|
|
18310
18437
|
return text2;
|
|
18311
18438
|
}
|
|
18312
18439
|
function readManagedSkillPolicySnapshot(dataDir2 = getDataDirReadOnly()) {
|
|
18313
|
-
const path =
|
|
18440
|
+
const path = join27(dataDir2, "agent-policy.json");
|
|
18314
18441
|
let descriptor, observed = false;
|
|
18315
18442
|
try {
|
|
18316
|
-
const stat2 =
|
|
18443
|
+
const stat2 = lstatSync22(path);
|
|
18317
18444
|
observed = true;
|
|
18318
18445
|
if (!stat2.isFile() || stat2.isSymbolicLink())
|
|
18319
18446
|
throw refusal();
|
|
@@ -18339,7 +18466,7 @@ function readManagedSkillPolicySnapshot(dataDir2 = getDataDirReadOnly()) {
|
|
|
18339
18466
|
return { text: text2, value: parseManagedSkillPolicy(text2) };
|
|
18340
18467
|
} catch (error) {
|
|
18341
18468
|
if (error.code === "ENOENT" && !observed) {
|
|
18342
|
-
if (
|
|
18469
|
+
if (lstatSync22(path, { throwIfNoEntry: false }) === undefined)
|
|
18343
18470
|
return null;
|
|
18344
18471
|
}
|
|
18345
18472
|
if (error instanceof SkillSelectionError)
|
|
@@ -18359,8 +18486,8 @@ function requiresCliSkillLoading(dataDir2 = getDataDirReadOnly()) {
|
|
|
18359
18486
|
|
|
18360
18487
|
// src/lib/agent-bridge.ts
|
|
18361
18488
|
import { createHash as createHash23 } from "crypto";
|
|
18362
|
-
import { lstatSync as
|
|
18363
|
-
import { join as
|
|
18489
|
+
import { lstatSync as lstatSync23, readFileSync as readFileSync17, readdirSync as readdirSync9 } from "fs";
|
|
18490
|
+
import { join as join28 } from "path";
|
|
18364
18491
|
var CLI_BRIDGE_NAME = "skills-cli";
|
|
18365
18492
|
var CLI_BRIDGE_VERSION = 1;
|
|
18366
18493
|
var markdown = `---
|
|
@@ -18387,13 +18514,13 @@ function isOwnedCliBridge(path, expectedPaths) {
|
|
|
18387
18514
|
if (!expectedPaths.includes(path))
|
|
18388
18515
|
return false;
|
|
18389
18516
|
try {
|
|
18390
|
-
if (!
|
|
18517
|
+
if (!lstatSync23(path).isDirectory())
|
|
18391
18518
|
return false;
|
|
18392
18519
|
if (JSON.stringify(readdirSync9(path).sort()) !== JSON.stringify(Object.keys(CLI_BRIDGE_FILES).sort()))
|
|
18393
18520
|
return false;
|
|
18394
18521
|
return Object.entries(CLI_BRIDGE_FILES).every(([name, content]) => {
|
|
18395
|
-
const file =
|
|
18396
|
-
return stat2.isFile() && !stat2.isSymbolicLink() && stat2.size === Buffer.byteLength(content) &&
|
|
18522
|
+
const file = join28(path, name), stat2 = lstatSync23(file);
|
|
18523
|
+
return stat2.isFile() && !stat2.isSymbolicLink() && stat2.size === Buffer.byteLength(content) && readFileSync17(file, "utf8") === content;
|
|
18397
18524
|
});
|
|
18398
18525
|
} catch {
|
|
18399
18526
|
return false;
|
|
@@ -18670,8 +18797,8 @@ function disableCodexBundledSkills(text2) {
|
|
|
18670
18797
|
|
|
18671
18798
|
// src/lib/agent-sumi.ts
|
|
18672
18799
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
18673
|
-
import { accessSync as accessSync2, constants as constants14, existsSync as existsSync8, lstatSync as
|
|
18674
|
-
import { delimiter as delimiter2, isAbsolute as isAbsolute20, join as
|
|
18800
|
+
import { accessSync as accessSync2, constants as constants14, existsSync as existsSync8, lstatSync as lstatSync24, realpathSync as realpathSync10, statSync as statSync10 } from "fs";
|
|
18801
|
+
import { delimiter as delimiter2, isAbsolute as isAbsolute20, join as join29, resolve as resolve20 } from "path";
|
|
18675
18802
|
|
|
18676
18803
|
// src/lib/hook-diagnostics.ts
|
|
18677
18804
|
var CONTEXT_ERROR_CODES = new Set([
|
|
@@ -18760,7 +18887,7 @@ function recordWithKeys(value, keys) {
|
|
|
18760
18887
|
return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key2) => Object.hasOwn(value, key2));
|
|
18761
18888
|
}
|
|
18762
18889
|
function absolutePath3(value) {
|
|
18763
|
-
return typeof value === "string" && value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute20(value) &&
|
|
18890
|
+
return typeof value === "string" && value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute20(value) && resolve20(value) === value;
|
|
18764
18891
|
}
|
|
18765
18892
|
function absoluteInput(value) {
|
|
18766
18893
|
return value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute20(value);
|
|
@@ -18771,7 +18898,7 @@ function resolveSumiPathsCommand(pathValue) {
|
|
|
18771
18898
|
for (const directory2 of pathValue.split(delimiter2)) {
|
|
18772
18899
|
if (!directory2 || /[\x00-\x1f\x7f]/.test(directory2))
|
|
18773
18900
|
continue;
|
|
18774
|
-
const candidate =
|
|
18901
|
+
const candidate = resolve20(directory2, "sumi-paths");
|
|
18775
18902
|
try {
|
|
18776
18903
|
accessSync2(candidate, constants14.X_OK);
|
|
18777
18904
|
if (statSync10(candidate).isFile())
|
|
@@ -18783,7 +18910,7 @@ function resolveSumiPathsCommand(pathValue) {
|
|
|
18783
18910
|
function validPathPlan(value, home, cwd) {
|
|
18784
18911
|
if (!recordWithKeys(value, ["schemaVersion", "kind", "home", "cwd", "roots", "legacyRoots", "configFiles", "skillRoots"]))
|
|
18785
18912
|
return false;
|
|
18786
|
-
if (value.schemaVersion !== 1 || value.kind !== "sumi-paths" || value.home !==
|
|
18913
|
+
if (value.schemaVersion !== 1 || value.kind !== "sumi-paths" || value.home !== resolve20(home) || value.cwd !== resolve20(cwd))
|
|
18787
18914
|
return false;
|
|
18788
18915
|
if (!recordWithKeys(value.roots, SUMI_PATH_KEYS) || !recordWithKeys(value.legacyRoots, SUMI_PATH_KEYS))
|
|
18789
18916
|
return false;
|
|
@@ -18805,11 +18932,11 @@ function validPathPlan(value, home, cwd) {
|
|
|
18805
18932
|
const legacyConfigRoot = value.legacyRoots.config;
|
|
18806
18933
|
if (typeof configRoot !== "string" || legacyConfigRoot !== null && typeof legacyConfigRoot !== "string")
|
|
18807
18934
|
return false;
|
|
18808
|
-
if (value.configFiles.canonical !==
|
|
18935
|
+
if (value.configFiles.canonical !== join29(configRoot, "sumi.json") || value.skillRoots.canonical !== join29(configRoot, "skills"))
|
|
18809
18936
|
return false;
|
|
18810
18937
|
if (legacyConfigRoot === null)
|
|
18811
18938
|
return value.configFiles.legacy === null && value.skillRoots.legacy === null;
|
|
18812
|
-
return value.configFiles.legacy ===
|
|
18939
|
+
return value.configFiles.legacy === join29(legacyConfigRoot, "sumi.json") && value.skillRoots.legacy === join29(legacyConfigRoot, "skills");
|
|
18813
18940
|
}
|
|
18814
18941
|
function sumiPathPlan(home, cwd = process.cwd(), env = process.env) {
|
|
18815
18942
|
for (const key2 of ["SUMI_CONFIG", "SUMI_CONFIG_CONTENT"])
|
|
@@ -18827,8 +18954,8 @@ function sumiPathPlan(home, cwd = process.cwd(), env = process.env) {
|
|
|
18827
18954
|
childEnv[key2] = env[key2];
|
|
18828
18955
|
let child2;
|
|
18829
18956
|
try {
|
|
18830
|
-
child2 = spawnSync4(executable2, ["--json", "--home",
|
|
18831
|
-
cwd:
|
|
18957
|
+
child2 = spawnSync4(executable2, ["--json", "--home", resolve20(home), "--cwd", resolve20(cwd)], {
|
|
18958
|
+
cwd: resolve20(cwd),
|
|
18832
18959
|
env: childEnv,
|
|
18833
18960
|
encoding: "buffer",
|
|
18834
18961
|
timeout: 3000,
|
|
@@ -18860,7 +18987,7 @@ function selectedConfigDirectory(plan) {
|
|
|
18860
18987
|
const canonical9 = plan.roots.config, legacy = plan.legacyRoots.config;
|
|
18861
18988
|
if (legacy === null)
|
|
18862
18989
|
return existsSync8(canonical9) ? realpathSync10(canonical9) : canonical9;
|
|
18863
|
-
const canonicalStat =
|
|
18990
|
+
const canonicalStat = lstatSync24(canonical9, { throwIfNoEntry: false }), legacyStat = lstatSync24(legacy, { throwIfNoEntry: false });
|
|
18864
18991
|
if (canonicalStat && legacyStat && realpathSync10(canonical9) !== realpathSync10(legacy))
|
|
18865
18992
|
throw new Error("Sumi config roots conflict; complete native home adoption before Skills integration");
|
|
18866
18993
|
const selected = canonicalStat ? canonical9 : legacyStat ? legacy : canonical9;
|
|
@@ -18876,13 +19003,13 @@ function sumiConfigDirectory(home, env = process.env, cwd = process.cwd()) {
|
|
|
18876
19003
|
}
|
|
18877
19004
|
function sumiSkillRoots(home, cwd = process.cwd(), env = process.env) {
|
|
18878
19005
|
const plan = sumiPathPlan(home, cwd, env);
|
|
18879
|
-
return [
|
|
19006
|
+
return [join29(selectedConfigDirectory(plan), "skills")];
|
|
18880
19007
|
}
|
|
18881
19008
|
function sumiBridgeRoot(home, cwd = process.cwd(), env = process.env) {
|
|
18882
19009
|
return sumiSkillRoots(home, cwd, env)[0];
|
|
18883
19010
|
}
|
|
18884
19011
|
function sumiConfigPath(home, cwd = process.cwd(), env = process.env) {
|
|
18885
|
-
return
|
|
19012
|
+
return join29(sumiConfigDirectory(home, env, cwd), "sumi.json");
|
|
18886
19013
|
}
|
|
18887
19014
|
function renderSumiPlugin(command, profile) {
|
|
18888
19015
|
return renderSumiPluginVersion(command, profile, 3);
|
|
@@ -18987,9 +19114,9 @@ var SUMI_SKILL_PERMISSIONS = [
|
|
|
18987
19114
|
];
|
|
18988
19115
|
|
|
18989
19116
|
// src/lib/codex-hook-trust-files.ts
|
|
18990
|
-
import { closeSync as closeSync14, constants as constants15, fstatSync as fstatSync14, fsyncSync as fsyncSync4, lstatSync as
|
|
19117
|
+
import { closeSync as closeSync14, constants as constants15, fstatSync as fstatSync14, fsyncSync as fsyncSync4, lstatSync as lstatSync25, openSync as openSync14, readlinkSync as readlinkSync5, readSync as readSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
18991
19118
|
import { createHash as createHash24 } from "crypto";
|
|
18992
|
-
import { dirname as
|
|
19119
|
+
import { dirname as dirname20, isAbsolute as isAbsolute21, resolve as resolve21 } from "path";
|
|
18993
19120
|
function need8(value, code) {
|
|
18994
19121
|
if (!value)
|
|
18995
19122
|
throw new Error(`CODEX_HOOK_TRUST_${code}`);
|
|
@@ -18999,11 +19126,11 @@ function trustedAncestorOwner(uid2) {
|
|
|
18999
19126
|
return BigInt(uid2) === 0n || BigInt(uid2) === BigInt(process.getuid());
|
|
19000
19127
|
}
|
|
19001
19128
|
function safeParents(file) {
|
|
19002
|
-
for (let p =
|
|
19003
|
-
const s =
|
|
19129
|
+
for (let p = dirname20(file);; p = dirname20(p)) {
|
|
19130
|
+
const s = lstatSync25(p);
|
|
19004
19131
|
need8(s.isDirectory() && !s.isSymbolicLink() && trustedAncestorOwner(s.uid), "UNSAFE_PARENT");
|
|
19005
19132
|
need8((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
19006
|
-
if (
|
|
19133
|
+
if (dirname20(p) === p)
|
|
19007
19134
|
break;
|
|
19008
19135
|
}
|
|
19009
19136
|
}
|
|
@@ -19018,12 +19145,12 @@ function directAliasTarget(link) {
|
|
|
19018
19145
|
const names = parts.slice(first);
|
|
19019
19146
|
if (!names.length || names.some((part) => part === "" || part === "." || part === ".."))
|
|
19020
19147
|
return;
|
|
19021
|
-
return
|
|
19148
|
+
return resolve21(dirname20(link), text2);
|
|
19022
19149
|
}
|
|
19023
19150
|
function snapshot(file, privateFile = false, options = {}) {
|
|
19024
19151
|
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
19025
19152
|
safeParents(file);
|
|
19026
|
-
const initial =
|
|
19153
|
+
const initial = lstatSync25(file, { bigint: true });
|
|
19027
19154
|
need8(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
|
|
19028
19155
|
need8(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
19029
19156
|
const fd = openSync14(file, constants15.O_RDONLY | constants15.O_NOFOLLOW | constants15.O_NONBLOCK);
|
|
@@ -19038,7 +19165,7 @@ function snapshot(file, privateFile = false, options = {}) {
|
|
|
19038
19165
|
break;
|
|
19039
19166
|
length += n2;
|
|
19040
19167
|
}
|
|
19041
|
-
need8(length === Number(before.size) && sameStat(before, fstatSync14(fd, { bigint: true })) && sameStat(before,
|
|
19168
|
+
need8(length === Number(before.size) && sameStat(before, fstatSync14(fd, { bigint: true })) && sameStat(before, lstatSync25(file, { bigint: true })), "INPUT_CHANGED");
|
|
19042
19169
|
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
19043
19170
|
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash24("sha256").update(content).digest("hex") };
|
|
19044
19171
|
} finally {
|
|
@@ -19052,7 +19179,7 @@ function unchanged2(before) {
|
|
|
19052
19179
|
|
|
19053
19180
|
// src/cli/commands/runtime-launcher.ts
|
|
19054
19181
|
import { createHash as createHash25 } from "crypto";
|
|
19055
|
-
import { closeSync as closeSync15, constants as constants16, fchmodSync as fchmodSync2, fstatSync as fstatSync15, fsyncSync as fsyncSync5, lstatSync as
|
|
19182
|
+
import { closeSync as closeSync15, constants as constants16, fchmodSync as fchmodSync2, fstatSync as fstatSync15, fsyncSync as fsyncSync5, lstatSync as lstatSync26, openSync as openSync15, readFileSync as readFileSync18, readlinkSync as readlinkSync6, realpathSync as realpathSync11, symlinkSync, writeSync as writeSync3 } from "fs";
|
|
19056
19183
|
import { isAbsolute as isAbsolute22 } from "path";
|
|
19057
19184
|
|
|
19058
19185
|
// src/server/launcher-environment-policy.ts
|
|
@@ -19259,9 +19386,9 @@ function parsePinnedLauncher(text2) {
|
|
|
19259
19386
|
var PINNED_LAUNCHER_MAX_BYTES = 16 * 1024;
|
|
19260
19387
|
function resolveLauncherCommand(path) {
|
|
19261
19388
|
const physical = realpathSync11(path);
|
|
19262
|
-
const stat2 =
|
|
19389
|
+
const stat2 = lstatSync26(physical);
|
|
19263
19390
|
if (stat2.isFile() && stat2.size <= PINNED_LAUNCHER_MAX_BYTES) {
|
|
19264
|
-
const text2 =
|
|
19391
|
+
const text2 = readFileSync18(physical, "utf8");
|
|
19265
19392
|
const binding = parsePinnedLauncher(text2);
|
|
19266
19393
|
if (binding) {
|
|
19267
19394
|
let entry;
|
|
@@ -19281,7 +19408,7 @@ function assertPinnedLauncherRuntimeSafe(runtime) {
|
|
|
19281
19408
|
assertLauncherPath(runtime, "LAUNCHER_RUNTIME_PATH_INVALID");
|
|
19282
19409
|
let stat2;
|
|
19283
19410
|
try {
|
|
19284
|
-
stat2 =
|
|
19411
|
+
stat2 = lstatSync26(runtime);
|
|
19285
19412
|
} catch {
|
|
19286
19413
|
throw new Error("LAUNCHER_RUNTIME_UNSAFE");
|
|
19287
19414
|
}
|
|
@@ -19293,8 +19420,8 @@ function assertPinnedLauncherRuntimeSafe(runtime) {
|
|
|
19293
19420
|
|
|
19294
19421
|
// src/lib/retained-review-artifacts.ts
|
|
19295
19422
|
import { createHash as createHash26 } from "crypto";
|
|
19296
|
-
import { constants as constants17, closeSync as closeSync16, fstatSync as fstatSync16, fsyncSync as fsyncSync6, lstatSync as
|
|
19297
|
-
import { dirname as
|
|
19423
|
+
import { constants as constants17, closeSync as closeSync16, fstatSync as fstatSync16, fsyncSync as fsyncSync6, lstatSync as lstatSync27, mkdirSync as mkdirSync7, openSync as openSync16, readSync as readSync11, writeSync as writeSync4 } from "fs";
|
|
19424
|
+
import { dirname as dirname21, isAbsolute as isAbsolute23, join as join30, resolve as resolve22, sep as sep13 } from "path";
|
|
19298
19425
|
var sha3 = (bytes) => createHash26("sha256").update(bytes).digest("hex");
|
|
19299
19426
|
var hex = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
19300
19427
|
var refuse5 = () => {
|
|
@@ -19308,19 +19435,19 @@ function keys(value, names) {
|
|
|
19308
19435
|
return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === names.length && names.every((name) => Object.hasOwn(value, name)));
|
|
19309
19436
|
}
|
|
19310
19437
|
function safe4(path) {
|
|
19311
|
-
need9(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute23(path) &&
|
|
19312
|
-
for (let cursor = path;; cursor =
|
|
19313
|
-
const stat2 =
|
|
19438
|
+
need9(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute23(path) && resolve22(path) === path && !/[\x00-\x1f\x7f]/.test(path));
|
|
19439
|
+
for (let cursor = path;; cursor = dirname21(cursor)) {
|
|
19440
|
+
const stat2 = lstatSync27(cursor, { throwIfNoEntry: false });
|
|
19314
19441
|
need9(!stat2?.isSymbolicLink());
|
|
19315
19442
|
if (cursor !== path && stat2)
|
|
19316
19443
|
need9(stat2.isDirectory());
|
|
19317
|
-
if (
|
|
19444
|
+
if (dirname21(cursor) === cursor)
|
|
19318
19445
|
break;
|
|
19319
19446
|
}
|
|
19320
19447
|
}
|
|
19321
19448
|
function readExact(path) {
|
|
19322
19449
|
safe4(path);
|
|
19323
|
-
const before =
|
|
19450
|
+
const before = lstatSync27(path, { throwIfNoEntry: false });
|
|
19324
19451
|
need9(before?.isFile() && before.nlink === 1 && before.size <= AGENT_POLICY_LIMITS.discoveryRawSourceBytes && (before.mode & 63) === 0 && (process.getuid === undefined || before.uid === process.getuid()));
|
|
19325
19452
|
const fd = openSync16(path, constants17.O_RDONLY | constants17.O_NOFOLLOW | constants17.O_NONBLOCK);
|
|
19326
19453
|
try {
|
|
@@ -19334,7 +19461,7 @@ function readExact(path) {
|
|
|
19334
19461
|
offset += count;
|
|
19335
19462
|
}
|
|
19336
19463
|
need9(readSync11(fd, Buffer.alloc(1), 0, 1, null) === 0);
|
|
19337
|
-
const after = fstatSync16(fd), current =
|
|
19464
|
+
const after = fstatSync16(fd), current = lstatSync27(path, { throwIfNoEntry: false });
|
|
19338
19465
|
safe4(path);
|
|
19339
19466
|
need9(current && opened.dev === current.dev && opened.ino === current.ino && opened.mode === current.mode && opened.uid === current.uid && opened.size === current.size && opened.mtimeMs === current.mtimeMs && opened.ctimeMs === current.ctimeMs && current.nlink === 1 && opened.mode === after.mode && opened.uid === after.uid && after.nlink === 1 && opened.size === after.size && opened.mtimeMs === after.mtimeMs && opened.ctimeMs === after.ctimeMs);
|
|
19340
19467
|
const text2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
@@ -19360,24 +19487,24 @@ function verifyReviewArtifactOriginal(path, expectedSha256) {
|
|
|
19360
19487
|
need9(hex(expectedSha256) && sha3(readExact(path)) === expectedSha256);
|
|
19361
19488
|
}
|
|
19362
19489
|
function retainedArtifactPath(metadata) {
|
|
19363
|
-
return
|
|
19490
|
+
return join30(metadata.storeRoot, "agent-discovery", "artifacts", metadata.sha256, "catalog.json");
|
|
19364
19491
|
}
|
|
19365
19492
|
function validRetainedArtifact(value) {
|
|
19366
|
-
return keys(value, ["version", "kind", "storeRoot", "originalPath", "sha256", "catalogSha256"]) && value.version === 1 && value.kind === "codex-native-catalog" && hex(value.sha256) && hex(value.catalogSha256) && [value.storeRoot, value.originalPath].every((path) => typeof path === "string" && isAbsolute23(path) &&
|
|
19493
|
+
return keys(value, ["version", "kind", "storeRoot", "originalPath", "sha256", "catalogSha256"]) && value.version === 1 && value.kind === "codex-native-catalog" && hex(value.sha256) && hex(value.catalogSha256) && [value.storeRoot, value.originalPath].every((path) => typeof path === "string" && isAbsolute23(path) && resolve22(path) === path && path.length <= AGENT_POLICY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(path));
|
|
19367
19494
|
}
|
|
19368
19495
|
function privateStore(storeRoot) {
|
|
19369
19496
|
safe4(storeRoot);
|
|
19370
|
-
const stat2 =
|
|
19497
|
+
const stat2 = lstatSync27(storeRoot, { throwIfNoEntry: false });
|
|
19371
19498
|
need9(!stat2 || stat2.isDirectory() && (stat2.mode & 18) === 0 && (process.getuid === undefined || stat2.uid === process.getuid()));
|
|
19372
19499
|
}
|
|
19373
19500
|
function verifyRetainedReviewArtifact(source, storeRoot) {
|
|
19374
19501
|
const artifact = source.reviewArtifact;
|
|
19375
|
-
need9(validRetainedArtifact(artifact) && source.hashMode === "bytes" && source.sha256 === artifact.sha256 && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && source.path === retainedArtifactPath(artifact) && (storeRoot === undefined || artifact.storeRoot ===
|
|
19502
|
+
need9(validRetainedArtifact(artifact) && source.hashMode === "bytes" && source.sha256 === artifact.sha256 && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && source.path === retainedArtifactPath(artifact) && (storeRoot === undefined || artifact.storeRoot === resolve22(storeRoot)));
|
|
19376
19503
|
privateStore(artifact.storeRoot);
|
|
19377
19504
|
const text2 = readExact(source.path);
|
|
19378
19505
|
need9(sha3(text2) === artifact.sha256 && catalogDigest(text2) === artifact.catalogSha256);
|
|
19379
|
-
for (let parent =
|
|
19380
|
-
const stat2 =
|
|
19506
|
+
for (let parent = dirname21(source.path);parent !== artifact.storeRoot; parent = dirname21(parent)) {
|
|
19507
|
+
const stat2 = lstatSync27(parent);
|
|
19381
19508
|
need9(stat2.isDirectory() && (stat2.mode & 63) === 0 && (process.getuid === undefined || stat2.uid === process.getuid()));
|
|
19382
19509
|
}
|
|
19383
19510
|
}
|
|
@@ -19385,7 +19512,7 @@ function planReviewArtifactRetention(binding, artifacts, dataDir2, home) {
|
|
|
19385
19512
|
if (artifacts === undefined)
|
|
19386
19513
|
return { binding, changes: [], observed: [] };
|
|
19387
19514
|
need9(binding.agent === "codex" && binding.method === "reviewed" && Array.isArray(artifacts) && artifacts.length > 0 && artifacts.length <= 32);
|
|
19388
|
-
privateStore(
|
|
19515
|
+
privateStore(resolve22(dataDir2));
|
|
19389
19516
|
const paths = new Set;
|
|
19390
19517
|
const changes = [], observed = [];
|
|
19391
19518
|
let totalBytes = 0;
|
|
@@ -19394,19 +19521,19 @@ function planReviewArtifactRetention(binding, artifacts, dataDir2, home) {
|
|
|
19394
19521
|
need9(keys(declaration, ["kind", "path", "sha256"]) && declaration.kind === "codex-native-catalog" && hex(declaration.sha256) && !paths.has(declaration.path));
|
|
19395
19522
|
paths.add(declaration.path);
|
|
19396
19523
|
safe4(declaration.path);
|
|
19397
|
-
const liveRoots = [
|
|
19398
|
-
need9(!liveRoots.some((root) => declaration.path === root || declaration.path.startsWith(root +
|
|
19524
|
+
const liveRoots = [join30(home, ".codex"), ...binding.roots, ...(binding.directories ?? []).map((item) => item.path)];
|
|
19525
|
+
need9(!liveRoots.some((root) => declaration.path === root || declaration.path.startsWith(root + sep13)));
|
|
19399
19526
|
const matches = sources.filter((source2) => source2.path === declaration.path);
|
|
19400
19527
|
need9(matches.length === 1 && matches[0].sha256 === declaration.sha256 && matches[0].reviewArtifact === undefined && [undefined, "bytes"].includes(matches[0].hashMode) && matches[0].format === undefined && matches[0].fields === undefined && matches[0].managedPlugins === undefined);
|
|
19401
19528
|
const text2 = readExact(declaration.path);
|
|
19402
19529
|
need9(sha3(text2) === declaration.sha256);
|
|
19403
19530
|
totalBytes += Buffer.byteLength(text2);
|
|
19404
19531
|
need9(totalBytes <= AGENT_POLICY_LIMITS.discoveryRawTotalBytes);
|
|
19405
|
-
const metadata = { version: 1, kind: declaration.kind, storeRoot:
|
|
19532
|
+
const metadata = { version: 1, kind: declaration.kind, storeRoot: resolve22(dataDir2), originalPath: declaration.path, sha256: declaration.sha256, catalogSha256: catalogDigest(text2) };
|
|
19406
19533
|
const path = retainedArtifactPath(metadata);
|
|
19407
19534
|
safe4(path);
|
|
19408
19535
|
const source = { path, sha256: metadata.sha256, hashMode: "bytes", reviewArtifact: metadata };
|
|
19409
|
-
if (
|
|
19536
|
+
if (lstatSync27(path, { throwIfNoEntry: false }))
|
|
19410
19537
|
verifyRetainedReviewArtifact(source, dataDir2);
|
|
19411
19538
|
else if (!changes.some((change) => change.path === path))
|
|
19412
19539
|
changes.push({ path, before: null, after: text2, artifact: metadata });
|
|
@@ -19420,14 +19547,14 @@ function writeReviewArtifactExclusive(change) {
|
|
|
19420
19547
|
need9(change.before === null && validRetainedArtifact(change.artifact) && change.path === retainedArtifactPath(change.artifact) && sha3(change.after) === change.artifact.sha256 && catalogDigest(change.after) === change.artifact.catalogSha256);
|
|
19421
19548
|
privateStore(change.artifact.storeRoot);
|
|
19422
19549
|
const missing2 = [];
|
|
19423
|
-
for (let parent =
|
|
19550
|
+
for (let parent = dirname21(change.path);!lstatSync27(parent, { throwIfNoEntry: false }); parent = dirname21(parent))
|
|
19424
19551
|
missing2.push(parent);
|
|
19425
19552
|
for (const parent of missing2.reverse())
|
|
19426
19553
|
mkdirSync7(parent, { mode: 448 });
|
|
19427
19554
|
safe4(change.path);
|
|
19428
19555
|
privateStore(change.artifact.storeRoot);
|
|
19429
|
-
for (let parent =
|
|
19430
|
-
const stat2 =
|
|
19556
|
+
for (let parent = dirname21(change.path);parent !== change.artifact.storeRoot; parent = dirname21(parent)) {
|
|
19557
|
+
const stat2 = lstatSync27(parent);
|
|
19431
19558
|
need9(stat2.isDirectory() && (stat2.mode & 63) === 0 && (process.getuid === undefined || stat2.uid === process.getuid()));
|
|
19432
19559
|
}
|
|
19433
19560
|
const fd = openSync16(change.path, constants17.O_WRONLY | constants17.O_CREAT | constants17.O_EXCL | constants17.O_NOFOLLOW, 384);
|
|
@@ -19444,7 +19571,7 @@ function writeReviewArtifactExclusive(change) {
|
|
|
19444
19571
|
closeSync16(fd);
|
|
19445
19572
|
}
|
|
19446
19573
|
need9(readExact(change.path) === change.after);
|
|
19447
|
-
const directory2 = openSync16(
|
|
19574
|
+
const directory2 = openSync16(dirname21(change.path), constants17.O_RDONLY | constants17.O_DIRECTORY | constants17.O_NOFOLLOW);
|
|
19448
19575
|
try {
|
|
19449
19576
|
fsyncSync6(directory2);
|
|
19450
19577
|
} finally {
|
|
@@ -19461,7 +19588,7 @@ function nativeAgentRoot(home, agent, cwd = process.cwd()) {
|
|
|
19461
19588
|
const root = AGENT_ADAPTERS[agent].root;
|
|
19462
19589
|
if (typeof root !== "string")
|
|
19463
19590
|
throw new Error(`Native skill root is unavailable for ${agent}`);
|
|
19464
|
-
return
|
|
19591
|
+
return join31(home, root);
|
|
19465
19592
|
}
|
|
19466
19593
|
function nativeAgentConfig(home, agent, cwd = process.cwd()) {
|
|
19467
19594
|
if (agent === "sumi")
|
|
@@ -19469,7 +19596,7 @@ function nativeAgentConfig(home, agent, cwd = process.cwd()) {
|
|
|
19469
19596
|
const config = AGENT_ADAPTERS[agent].config;
|
|
19470
19597
|
if (typeof config !== "string")
|
|
19471
19598
|
throw new Error(`Native configuration path is unavailable for ${agent}`);
|
|
19472
|
-
return
|
|
19599
|
+
return join31(home, config);
|
|
19473
19600
|
}
|
|
19474
19601
|
function hasTrustedDarwinAcl(fd, allowDenyOnly = false) {
|
|
19475
19602
|
try {
|
|
@@ -19521,11 +19648,11 @@ function isSystemRootAlias(path) {
|
|
|
19521
19648
|
const identity5 = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}:${stat2.uid}:${stat2.mode}`;
|
|
19522
19649
|
const directoryIdentity = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.uid}:${stat2.mode}`;
|
|
19523
19650
|
try {
|
|
19524
|
-
const before =
|
|
19651
|
+
const before = lstatSync28(path, { bigint: true });
|
|
19525
19652
|
if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync7(path) !== link)
|
|
19526
19653
|
return false;
|
|
19527
19654
|
const directories = ["/", "/private", target].map((directory2) => {
|
|
19528
|
-
const stat2 =
|
|
19655
|
+
const stat2 = lstatSync28(directory2, { bigint: true });
|
|
19529
19656
|
if (!stat2.isDirectory() || stat2.uid !== 0n)
|
|
19530
19657
|
throw new Error("Untrusted system alias directory");
|
|
19531
19658
|
if (directory2 !== "/private/tmp") {
|
|
@@ -19542,25 +19669,25 @@ function isSystemRootAlias(path) {
|
|
|
19542
19669
|
}
|
|
19543
19670
|
return { directory: directory2, identity: directoryIdentity(stat2) };
|
|
19544
19671
|
});
|
|
19545
|
-
return realpathSync12(path) === target && readlinkSync7(path) === link && identity5(
|
|
19672
|
+
return realpathSync12(path) === target && readlinkSync7(path) === link && identity5(lstatSync28(path, { bigint: true })) === identity5(before) && directories.every((item) => directoryIdentity(lstatSync28(item.directory, { bigint: true })) === item.identity);
|
|
19546
19673
|
} catch {
|
|
19547
19674
|
return false;
|
|
19548
19675
|
}
|
|
19549
19676
|
}
|
|
19550
19677
|
function canonicalSystemPath(path) {
|
|
19551
|
-
const absolute =
|
|
19552
|
-
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !
|
|
19678
|
+
const absolute = resolve23(path), root = `/${absolute.split(sep14)[1]}`;
|
|
19679
|
+
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync28(root, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
19553
19680
|
return absolute;
|
|
19554
19681
|
if (!isSystemRootAlias(root))
|
|
19555
19682
|
throw new Error(`Refusing symlink path: ${root}`);
|
|
19556
19683
|
return `/private${absolute}`;
|
|
19557
19684
|
}
|
|
19558
19685
|
function assertSafePath(path) {
|
|
19559
|
-
let cursor =
|
|
19686
|
+
let cursor = resolve23(path);
|
|
19560
19687
|
while (true) {
|
|
19561
|
-
if (
|
|
19688
|
+
if (lstatSync28(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
|
|
19562
19689
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
19563
|
-
const parent =
|
|
19690
|
+
const parent = dirname22(cursor);
|
|
19564
19691
|
if (parent === cursor)
|
|
19565
19692
|
return;
|
|
19566
19693
|
cursor = parent;
|
|
@@ -19568,17 +19695,17 @@ function assertSafePath(path) {
|
|
|
19568
19695
|
}
|
|
19569
19696
|
function readRootAlias(home, agent) {
|
|
19570
19697
|
assertSafePath(home);
|
|
19571
|
-
const realHome = realpathSync12(home), alias =
|
|
19572
|
-
const stat2 =
|
|
19698
|
+
const realHome = realpathSync12(home), alias = join31(realHome, `.${agent}`);
|
|
19699
|
+
const stat2 = lstatSync28(alias, { throwIfNoEntry: false, bigint: true });
|
|
19573
19700
|
if (!stat2?.isSymbolicLink())
|
|
19574
19701
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
19575
|
-
const link = readlinkSync7(alias), targetPath =
|
|
19702
|
+
const link = readlinkSync7(alias), targetPath = resolve23(dirname22(alias), link);
|
|
19576
19703
|
assertSafePath(targetPath);
|
|
19577
|
-
const targetStat =
|
|
19704
|
+
const targetStat = lstatSync28(targetPath, { throwIfNoEntry: false });
|
|
19578
19705
|
if (!targetStat?.isDirectory())
|
|
19579
19706
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
19580
19707
|
const target = realpathSync12(targetPath), inside = relative9(realHome, target);
|
|
19581
|
-
if (!inside || inside === ".." || inside.startsWith(`..${
|
|
19708
|
+
if (!inside || inside === ".." || inside.startsWith(`..${sep14}`) || isAbsolute24(inside))
|
|
19582
19709
|
throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
|
|
19583
19710
|
return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
|
|
19584
19711
|
}
|
|
@@ -19587,15 +19714,15 @@ function rootAliases(home, allowed = false) {
|
|
|
19587
19714
|
return [];
|
|
19588
19715
|
const result = [];
|
|
19589
19716
|
for (const agent of ["claude", "codex"])
|
|
19590
|
-
if (
|
|
19717
|
+
if (lstatSync28(join31(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
19591
19718
|
result.push(readRootAlias(home, agent));
|
|
19592
|
-
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target +
|
|
19719
|
+
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep14) || other.target.startsWith(item.target + sep14))))
|
|
19593
19720
|
throw new Error("Agent root alias targets must not overlap");
|
|
19594
19721
|
return result;
|
|
19595
19722
|
}
|
|
19596
19723
|
function canonicalAgentPath(path, aliases) {
|
|
19597
|
-
const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias +
|
|
19598
|
-
return binding ?
|
|
19724
|
+
const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep14));
|
|
19725
|
+
return binding ? join31(binding.target, relative9(binding.alias, absolute)) : absolute;
|
|
19599
19726
|
}
|
|
19600
19727
|
function disabledCodexSkillPaths(config, aliases) {
|
|
19601
19728
|
const skills = config.skills;
|
|
@@ -19618,15 +19745,15 @@ function disabledCodexSkillPaths(config, aliases) {
|
|
|
19618
19745
|
} catch {
|
|
19619
19746
|
continue;
|
|
19620
19747
|
}
|
|
19621
|
-
if (!document2.endsWith(`${
|
|
19748
|
+
if (!document2.endsWith(`${sep14}SKILL.md`))
|
|
19622
19749
|
continue;
|
|
19623
19750
|
settings.set(document2, [...settings.get(document2) ?? [], entry.enabled === false]);
|
|
19624
19751
|
}
|
|
19625
|
-
return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) =>
|
|
19752
|
+
return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) => dirname22(document2));
|
|
19626
19753
|
}
|
|
19627
19754
|
function recheckRootAliases(aliases) {
|
|
19628
19755
|
for (const binding of aliases) {
|
|
19629
|
-
if (!["claude", "codex"].includes(binding.agent) || binding.alias !==
|
|
19756
|
+
if (!["claude", "codex"].includes(binding.agent) || binding.alias !== join31(binding.home, `.${binding.agent}`))
|
|
19630
19757
|
throw new Error("Invalid agent root alias binding");
|
|
19631
19758
|
const current = readRootAlias(binding.home, binding.agent);
|
|
19632
19759
|
if (JSON.stringify(current) !== JSON.stringify(binding))
|
|
@@ -19635,11 +19762,11 @@ function recheckRootAliases(aliases) {
|
|
|
19635
19762
|
}
|
|
19636
19763
|
function nativeSourceDigest(path) {
|
|
19637
19764
|
assertSafePath(path);
|
|
19638
|
-
return
|
|
19765
|
+
return lstatSync28(path, { throwIfNoEntry: false }) ? sha4(readNativeBytes(path, 1024 * 1024)) : null;
|
|
19639
19766
|
}
|
|
19640
19767
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
19641
19768
|
assertSafePath(path);
|
|
19642
|
-
const before =
|
|
19769
|
+
const before = lstatSync28(path);
|
|
19643
19770
|
if (!before.isFile() || before.size > maximum)
|
|
19644
19771
|
throw new Error("Unsupported or oversized native skill file");
|
|
19645
19772
|
const descriptor = openSync17(path, constants18.O_RDONLY | constants18.O_NOFOLLOW | constants18.O_NONBLOCK);
|
|
@@ -19655,7 +19782,7 @@ function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
|
19655
19782
|
break;
|
|
19656
19783
|
length += count;
|
|
19657
19784
|
}
|
|
19658
|
-
const after = fstatSync17(descriptor), current =
|
|
19785
|
+
const after = fstatSync17(descriptor), current = lstatSync28(path);
|
|
19659
19786
|
if (current.dev !== opened.dev || current.ino !== opened.ino || current.size !== after.size || current.mtimeMs !== after.mtimeMs || current.ctimeMs !== after.ctimeMs || length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
|
|
19660
19787
|
throw new Error("Native skill file changed while reading");
|
|
19661
19788
|
return bytes.subarray(0, length);
|
|
@@ -19667,14 +19794,14 @@ function treeHash(root) {
|
|
|
19667
19794
|
const hash3 = createHash27("sha256");
|
|
19668
19795
|
let bytes = 0, files = 0;
|
|
19669
19796
|
function visit2(path) {
|
|
19670
|
-
const stat2 =
|
|
19797
|
+
const stat2 = lstatSync28(path);
|
|
19671
19798
|
if (stat2.isSymbolicLink())
|
|
19672
19799
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
19673
19800
|
const rel = relative9(root, path);
|
|
19674
19801
|
if (stat2.isDirectory()) {
|
|
19675
19802
|
hash3.update(`d\x00${rel}\x00`);
|
|
19676
19803
|
for (const name of readdirSync10(path).sort())
|
|
19677
|
-
visit2(
|
|
19804
|
+
visit2(join31(path, name));
|
|
19678
19805
|
return;
|
|
19679
19806
|
}
|
|
19680
19807
|
if (!stat2.isFile())
|
|
@@ -19696,20 +19823,20 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
19696
19823
|
const identities = new Set;
|
|
19697
19824
|
const selected = [];
|
|
19698
19825
|
for (const target of manifest.targets) {
|
|
19699
|
-
if (!isAbsolute24(target.projectRoot) ||
|
|
19826
|
+
if (!isAbsolute24(target.projectRoot) || resolve23(target.projectRoot) !== target.projectRoot)
|
|
19700
19827
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
19701
|
-
const projectRoot = canonicalSystemPath(target.projectRoot), normalized2 = relative9(projectRoot,
|
|
19702
|
-
if (!existsSync9(projectRoot) || !
|
|
19828
|
+
const projectRoot = canonicalSystemPath(target.projectRoot), normalized2 = relative9(projectRoot, resolve23(projectRoot, target.path));
|
|
19829
|
+
if (!existsSync9(projectRoot) || !lstatSync28(projectRoot).isDirectory())
|
|
19703
19830
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
19704
19831
|
assertSafePath(projectRoot);
|
|
19705
|
-
if (isAbsolute24(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized2 || normalized2 === ".." || normalized2.startsWith(`..${
|
|
19832
|
+
if (isAbsolute24(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized2 || normalized2 === ".." || normalized2.startsWith(`..${sep14}`) || normalized2 !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
|
|
19706
19833
|
throw new Error(`Invalid native migration target: ${target.path}`);
|
|
19707
19834
|
const identity5 = `${target.agent}\x00${canonicalSystemPath(projectRoot)}\x00${normalized2}`;
|
|
19708
19835
|
if (identities.has(identity5))
|
|
19709
19836
|
throw new Error(`Duplicate native migration target: ${target.path}`);
|
|
19710
19837
|
identities.add(identity5);
|
|
19711
|
-
const expected = canonicalSystemPath(
|
|
19712
|
-
const matches = inventory.filter((entry2) => entry2.agent === target.agent &&
|
|
19838
|
+
const expected = canonicalSystemPath(resolve23(target.projectRoot, target.path));
|
|
19839
|
+
const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve23(entry2.path) === expected);
|
|
19713
19840
|
if (matches.length !== 1)
|
|
19714
19841
|
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
19715
19842
|
const entry = matches[0];
|
|
@@ -19728,7 +19855,7 @@ function projectAncestorDirectories(projects) {
|
|
|
19728
19855
|
if (depth >= 100)
|
|
19729
19856
|
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
19730
19857
|
directories.add(path);
|
|
19731
|
-
const parent =
|
|
19858
|
+
const parent = dirname22(path);
|
|
19732
19859
|
if (parent === path)
|
|
19733
19860
|
break;
|
|
19734
19861
|
path = parent;
|
|
@@ -19737,17 +19864,17 @@ function projectAncestorDirectories(projects) {
|
|
|
19737
19864
|
return [...directories];
|
|
19738
19865
|
}
|
|
19739
19866
|
function isCodexSkillDiscoveryInput(path) {
|
|
19740
|
-
for (let cursor = canonicalSystemPath(path);cursor !==
|
|
19741
|
-
if (ROOTS.some(([agent, root]) => agent === "codex" && cursor.endsWith(
|
|
19867
|
+
for (let cursor = canonicalSystemPath(path);cursor !== dirname22(cursor); cursor = dirname22(cursor)) {
|
|
19868
|
+
if (ROOTS.some(([agent, root]) => agent === "codex" && cursor.endsWith(join31(sep14, root))))
|
|
19742
19869
|
return true;
|
|
19743
19870
|
}
|
|
19744
19871
|
return false;
|
|
19745
19872
|
}
|
|
19746
19873
|
function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
19747
19874
|
home = canonicalSystemPath(home);
|
|
19748
|
-
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute24(options.reviewedCacheAlias) ||
|
|
19875
|
+
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute24(options.reviewedCacheAlias) || resolve23(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
|
|
19749
19876
|
throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
|
|
19750
|
-
if (options.disabledVendorPaths?.some((path) => !isAbsolute24(path) ||
|
|
19877
|
+
if (options.disabledVendorPaths?.some((path) => !isAbsolute24(path) || resolve23(path) !== path))
|
|
19751
19878
|
throw new Error("Disabled vendor paths must be exact absolute paths");
|
|
19752
19879
|
const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
|
|
19753
19880
|
const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
|
|
@@ -19755,25 +19882,25 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19755
19882
|
const selectedAgents = options.agents ? new Set(options.agents) : undefined;
|
|
19756
19883
|
const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
|
|
19757
19884
|
const rootDefinitions = selectedAgents ? ROOTS.filter(([agent, path]) => agent !== "sumi" && selectedAgents.has(agent) || selectedAgents.has("hermes") && agent === "codex" && path === ".agents/skills") : ROOTS.filter(([agent]) => agent !== "sumi");
|
|
19758
|
-
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(
|
|
19885
|
+
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(join31(home, path), aliases)]);
|
|
19759
19886
|
const includesSumi = !selectedAgents || selectedAgents.has("sumi");
|
|
19760
19887
|
if (includesSumi)
|
|
19761
19888
|
for (const path of sumiSkillRoots(home))
|
|
19762
19889
|
roots.push(["sumi", canonicalAgentPath(path, aliases)]);
|
|
19763
|
-
const bridgePaths = Object.entries(AGENT_ADAPTERS).filter(([agent]) => agent !== "sumi" || includesSumi).map(([agent]) => canonicalAgentPath(
|
|
19890
|
+
const bridgePaths = Object.entries(AGENT_ADAPTERS).filter(([agent]) => agent !== "sumi" || includesSumi).map(([agent]) => canonicalAgentPath(join31(nativeAgentRoot(home, agent), CLI_BRIDGE_NAME), aliases));
|
|
19764
19891
|
for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
|
|
19765
19892
|
for (const [agent, path] of rootDefinitions)
|
|
19766
|
-
roots.push([agent, canonicalAgentPath(
|
|
19893
|
+
roots.push([agent, canonicalAgentPath(join31(project, path), aliases)]);
|
|
19767
19894
|
if (selectedAgents?.has("sumi")) {
|
|
19768
19895
|
for (const path of sumiSkillRoots(home, project))
|
|
19769
19896
|
roots.push(["sumi", canonicalAgentPath(path, aliases)]);
|
|
19770
19897
|
for (const directory2 of [".claude", ".agents"])
|
|
19771
19898
|
for (const name of ["skill", "skills"])
|
|
19772
|
-
roots.push(["sumi", canonicalAgentPath(
|
|
19899
|
+
roots.push(["sumi", canonicalAgentPath(join31(project, directory2, name), aliases)]);
|
|
19773
19900
|
}
|
|
19774
19901
|
}
|
|
19775
19902
|
const installationInputs = options.codexInstallationInputRoots ?? [];
|
|
19776
|
-
if (installationInputs.some((input) =>
|
|
19903
|
+
if (installationInputs.some((input) => resolve23(input) !== input || realpathSync12(input) !== input || roots.some(([agent, path]) => agent === "codex" && (path === input || path.startsWith(input + sep14) || input.startsWith(path + sep14)))))
|
|
19777
19904
|
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
19778
19905
|
const entries = [], seen = new Set;
|
|
19779
19906
|
const cacheScans = new Map, maxAliasProofEntries = 1e4;
|
|
@@ -19787,19 +19914,19 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19787
19914
|
}
|
|
19788
19915
|
let reviewedCacheAliasFound = false;
|
|
19789
19916
|
function verifiedSiblingCacheAlias(agent, path, parent) {
|
|
19790
|
-
const link = readlinkSync7(path), lexicalTarget =
|
|
19791
|
-
if (isAbsolute24(link) ? link !== lexicalTarget :
|
|
19917
|
+
const link = readlinkSync7(path), lexicalTarget = resolve23(parent, link);
|
|
19918
|
+
if (isAbsolute24(link) ? link !== lexicalTarget : dirname22(link) !== ".")
|
|
19792
19919
|
return false;
|
|
19793
19920
|
const target = canonicalSystemPath(lexicalTarget);
|
|
19794
|
-
if (
|
|
19921
|
+
if (dirname22(target) !== parent)
|
|
19795
19922
|
return false;
|
|
19796
|
-
if (!
|
|
19923
|
+
if (!lstatSync28(target, { throwIfNoEntry: false })?.isDirectory())
|
|
19797
19924
|
return false;
|
|
19798
19925
|
const scan = cacheScans.get(target);
|
|
19799
19926
|
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
19800
19927
|
return false;
|
|
19801
19928
|
if (scan.hasSkills) {
|
|
19802
|
-
const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target +
|
|
19929
|
+
const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep14)));
|
|
19803
19930
|
if (path === reviewedCacheAlias && targetSkills.length > 0)
|
|
19804
19931
|
reviewedCacheAliasFound = true;
|
|
19805
19932
|
if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path) || options.disabledVendorSkill?.(entry) === true))
|
|
@@ -19814,11 +19941,11 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19814
19941
|
admit(path);
|
|
19815
19942
|
const scan = { complete: true, hasSkills: false, entries: 1 };
|
|
19816
19943
|
assertSafePath(path);
|
|
19817
|
-
if (configuredRoot && agent === "codex" && installationInputs.some((root) => path === root || path.startsWith(`${root}${
|
|
19944
|
+
if (configuredRoot && agent === "codex" && installationInputs.some((root) => path === root || path.startsWith(`${root}${sep14}`)))
|
|
19818
19945
|
return scan;
|
|
19819
19946
|
if (!existsSync9(path))
|
|
19820
19947
|
return scan;
|
|
19821
|
-
const stat2 =
|
|
19948
|
+
const stat2 = lstatSync28(path);
|
|
19822
19949
|
if (stat2.isFile()) {
|
|
19823
19950
|
if (agent === "sumi" && depth === 1 && path.endsWith(".md"))
|
|
19824
19951
|
throw new Error("NATIVE_SKILL_DRIFT: flat Sumi skill documents require a dedicated migration adapter");
|
|
@@ -19826,21 +19953,21 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19826
19953
|
}
|
|
19827
19954
|
if (!stat2.isDirectory())
|
|
19828
19955
|
throw new Error(`Unsupported native discovery entry: ${path}`);
|
|
19829
|
-
if (existsSync9(
|
|
19956
|
+
if (existsSync9(join31(path, "SKILL.md"))) {
|
|
19830
19957
|
scan.hasSkills = true;
|
|
19831
19958
|
if (seen.has(path))
|
|
19832
19959
|
return scan;
|
|
19833
19960
|
seen.add(path);
|
|
19834
19961
|
let managed = false;
|
|
19835
|
-
const marker =
|
|
19962
|
+
const marker = join31(path, ".hasna-skills.json");
|
|
19836
19963
|
if (existsSync9(marker)) {
|
|
19837
19964
|
try {
|
|
19838
|
-
managed = JSON.parse(
|
|
19965
|
+
managed = JSON.parse(readFileSync19(marker, "utf8")).managedBy === "@hasna/skills";
|
|
19839
19966
|
} catch {}
|
|
19840
19967
|
}
|
|
19841
19968
|
const bridge = (!vendor || agent === "sumi") && isOwnedCliBridge(path, bridgePaths);
|
|
19842
|
-
const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target +
|
|
19843
|
-
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" || isCodexSkillDiscoveryInput(path) ? { codexHome: process.env.CODEX_HOME ??
|
|
19969
|
+
const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep14));
|
|
19970
|
+
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" || isCodexSkillDiscoveryInput(path) ? { codexHome: process.env.CODEX_HOME ?? join31(home, ".codex") } : {}, ...agent === "codex" && path.startsWith(canonicalAgentPath(join31(home, ".codex", "skills", ".system"), aliases) + sep14) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve23(home) } : {}, ...rootAlias ? { rootAlias } : {} });
|
|
19844
19971
|
return scan;
|
|
19845
19972
|
}
|
|
19846
19973
|
if (depth > (agent === "sumi" ? 32 : vendor ? 32 : 3))
|
|
@@ -19849,7 +19976,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19849
19976
|
try {
|
|
19850
19977
|
let child2;
|
|
19851
19978
|
while ((child2 = directory2.readSync()) !== null) {
|
|
19852
|
-
admit(
|
|
19979
|
+
admit(join31(path, child2.name));
|
|
19853
19980
|
children.push(child2);
|
|
19854
19981
|
}
|
|
19855
19982
|
} finally {
|
|
@@ -19859,15 +19986,15 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19859
19986
|
if (pluginCache)
|
|
19860
19987
|
children.sort((a, b) => Number(a.isSymbolicLink()) - Number(b.isSymbolicLink()));
|
|
19861
19988
|
for (const { name } of children) {
|
|
19862
|
-
if (name === "node_modules")
|
|
19989
|
+
if (name === "node_modules" && !(agent === "codex" && (!pluginCache || codexPluginCacheSkillTree(join31(path, name), depth + 1))))
|
|
19863
19990
|
continue;
|
|
19864
|
-
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && agent !== "sumi" && !(options.guardHermes && `${path}${
|
|
19991
|
+
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && agent !== "sumi" && !(options.guardHermes && `${path}${sep14}`.includes(`${sep14}.agents${sep14}skills${sep14}`)))
|
|
19865
19992
|
continue;
|
|
19866
19993
|
const isVendor = vendor || name === ".system";
|
|
19867
19994
|
if (isVendor && !options.includeVendor)
|
|
19868
19995
|
continue;
|
|
19869
|
-
const child2 =
|
|
19870
|
-
if (isVendor &&
|
|
19996
|
+
const child2 = join31(path, name);
|
|
19997
|
+
if (isVendor && lstatSync28(child2, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
19871
19998
|
const target = statSync11(child2, { throwIfNoEntry: false });
|
|
19872
19999
|
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child2, path)) {
|
|
19873
20000
|
scan.entries++;
|
|
@@ -19884,7 +20011,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19884
20011
|
return scan;
|
|
19885
20012
|
}
|
|
19886
20013
|
function scanRoot(agent, path, vendor = false, pluginCache = false, configuredRoot = false) {
|
|
19887
|
-
if (agent === "hermes" || options.guardHermes && `${path}${
|
|
20014
|
+
if (agent === "hermes" || options.guardHermes && `${path}${sep14}`.includes(`${sep14}.agents${sep14}skills${sep14}`))
|
|
19888
20015
|
assertNoHermesLegacyShadow(path);
|
|
19889
20016
|
if (!visit2(agent, path, vendor, 0, pluginCache, false, configuredRoot).complete)
|
|
19890
20017
|
throw new Error(`Native skill discovery limit exceeded; review this root before continuing: ${path}`);
|
|
@@ -19893,11 +20020,11 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19893
20020
|
scanRoot(agent, path);
|
|
19894
20021
|
if (options.includeVendor) {
|
|
19895
20022
|
for (const agent of ["codex", "claude"].filter(includesAgent))
|
|
19896
|
-
scanRoot(agent, canonicalAgentPath(
|
|
20023
|
+
scanRoot(agent, canonicalAgentPath(join31(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
|
|
19897
20024
|
if (includesAgent("claude"))
|
|
19898
|
-
scanRoot("claude", canonicalAgentPath(
|
|
20025
|
+
scanRoot("claude", canonicalAgentPath(join31(home, ".claude", "plugins", "synced"), aliases), true);
|
|
19899
20026
|
if (includesAgent("gemini"))
|
|
19900
|
-
scanRoot("gemini",
|
|
20027
|
+
scanRoot("gemini", join31(home, ".gemini", "extensions"), true);
|
|
19901
20028
|
}
|
|
19902
20029
|
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 }))) : [];
|
|
19903
20030
|
for (const root of [...options.agentRoots ?? [], ...configured])
|
|
@@ -19910,7 +20037,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19910
20037
|
}
|
|
19911
20038
|
function readOptional(path) {
|
|
19912
20039
|
assertSafePath(path);
|
|
19913
|
-
return existsSync9(path) ?
|
|
20040
|
+
return existsSync9(path) ? readFileSync19(path, "utf8") : null;
|
|
19914
20041
|
}
|
|
19915
20042
|
function jsonObject(text2, path) {
|
|
19916
20043
|
if (text2 === null)
|
|
@@ -20010,19 +20137,19 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
20010
20137
|
function disableCodexSkills(text2, skills, aliases, bridgePath) {
|
|
20011
20138
|
const bundled = disableCodexBundledSkills(text2);
|
|
20012
20139
|
let result = normalizeCodexInlinePathConfig(bundled);
|
|
20013
|
-
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [
|
|
20140
|
+
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve23(bridgePath), resolve23(join31(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
|
|
20014
20141
|
const previousConfig = Bun.TOML.parse(bundled).skills?.config;
|
|
20015
20142
|
if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
|
|
20016
20143
|
throw new Error("Codex skill controls require one path or name selector per [[skills.config]] entry; review ambiguous entries before running skills hook install");
|
|
20017
20144
|
const bridgeNeedsRepair = previousConfig?.some((entry) => entry.enabled === false && bridgeSelector(entry));
|
|
20018
20145
|
const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
|
|
20019
20146
|
for (const skill of selections) {
|
|
20020
|
-
const path =
|
|
20147
|
+
const path = join31(skill.path, "SKILL.md");
|
|
20021
20148
|
let found = false;
|
|
20022
20149
|
result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
|
|
20023
20150
|
const parsed = Bun.TOML.parse(section);
|
|
20024
20151
|
const declared = parsed.skills?.config?.[0];
|
|
20025
|
-
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [
|
|
20152
|
+
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve23(path), resolve23(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
|
|
20026
20153
|
return section;
|
|
20027
20154
|
found = true;
|
|
20028
20155
|
return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
|
|
@@ -20043,8 +20170,8 @@ enabled = false
|
|
|
20043
20170
|
return result;
|
|
20044
20171
|
}
|
|
20045
20172
|
function assertCodexInstallationInputs(home, projectDirs, inputs) {
|
|
20046
|
-
const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory2) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) =>
|
|
20047
|
-
if (inputs.some((input) => nativeRoots.some((root) => root === input || root.startsWith(input +
|
|
20173
|
+
const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory2) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => resolve23(directory2, path)));
|
|
20174
|
+
if (inputs.some((input) => nativeRoots.some((root) => root === input || root.startsWith(input + sep14) || input.startsWith(root + sep14))))
|
|
20048
20175
|
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
20049
20176
|
}
|
|
20050
20177
|
function planAgentIntegration(options) {
|
|
@@ -20054,7 +20181,7 @@ function planAgentIntegration(options) {
|
|
|
20054
20181
|
throw new Error("Explicit Codex skill denials require a fresh native catalog and discovery review");
|
|
20055
20182
|
const home = options.home ?? homedir2(), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20056
20183
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
20057
|
-
const policyPath =
|
|
20184
|
+
const policyPath = join31(dataDir2, "agent-policy.json");
|
|
20058
20185
|
assertSafePath(policyPath);
|
|
20059
20186
|
const priorSnapshot = readManagedSkillPolicySnapshot(dataDir2), previousPolicy = priorSnapshot?.text ?? null, policy = priorSnapshot?.value ?? {};
|
|
20060
20187
|
if (policy.bridge !== undefined && (!policy.bridge || typeof policy.bridge !== "object" || Array.isArray(policy.bridge)))
|
|
@@ -20091,28 +20218,28 @@ function planAgentIntegration(options) {
|
|
|
20091
20218
|
}
|
|
20092
20219
|
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
|
|
20093
20220
|
});
|
|
20094
|
-
const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(
|
|
20221
|
+
const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases)) : null;
|
|
20095
20222
|
const codexConfig = Bun.TOML.parse(codexConfigBefore ?? "");
|
|
20096
20223
|
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) });
|
|
20097
|
-
const codexPathConfig = options.agents.includes("codex") ? disableCodexSkills(codexConfigBefore ?? "", nativeSkills, aliases, canonicalAgentPath(
|
|
20224
|
+
const codexPathConfig = options.agents.includes("codex") ? disableCodexSkills(codexConfigBefore ?? "", nativeSkills, aliases, canonicalAgentPath(join31(home, ".codex/skills", CLI_BRIDGE_NAME), aliases)) : undefined;
|
|
20098
20225
|
const plannedCodexRules = codexPathConfig === undefined ? undefined : Bun.TOML.parse(codexPathConfig).skills?.config ?? [];
|
|
20099
20226
|
const observedNativeSources = [];
|
|
20100
|
-
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) =>
|
|
20227
|
+
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join31(entry.path, "SKILL.md")), canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), resolve23(options.projectDir ?? home), (path) => {
|
|
20101
20228
|
const bytes = readNativeBytes(path, 1024 * 1024);
|
|
20102
20229
|
observedNativeSources.push({ path, sha256: sha4(bytes) });
|
|
20103
20230
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
20104
20231
|
}, codexConfig.skills?.config ?? [], codexConfig.plugins, plannedCodexRules, options.codexSkillDenials) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
|
|
20105
|
-
const cache = canonicalAgentPath(
|
|
20232
|
+
const cache = canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases);
|
|
20106
20233
|
const retired = (policy.bridge?.codexPluginSkills ?? []).filter((control) => absentDisabledCodexPluginParent(cache, control.pluginParent, policy.bridge.codexPluginSkills, codexConfig.skills?.config ?? []));
|
|
20107
20234
|
for (const denial of options.codexSkillDenials ?? []) {
|
|
20108
20235
|
const control = codexPluginControls.skills.find((item) => item.name === denial.name);
|
|
20109
20236
|
if (!control)
|
|
20110
20237
|
throw new Error("Explicit Codex denial did not resolve a reviewed identity");
|
|
20111
|
-
const root =
|
|
20238
|
+
const root = join31(control.pluginParent, relative9(control.pluginParent, denial.path).split(sep14)[0]);
|
|
20112
20239
|
const review = options.discoveryInputs.agents.find((item) => item.agent === "codex");
|
|
20113
|
-
for (const path of [
|
|
20240
|
+
for (const path of [join31(root, ".app.json"), join31(root, ".mcp.json"), join31(root, "hooks")])
|
|
20114
20241
|
observedNativeSources.push({ path, sha256: nativeSourceDigest(path) });
|
|
20115
|
-
const files = [
|
|
20242
|
+
const files = [join31(root, ".codex-plugin/plugin.json"), ...control.appSha256 ? [join31(root, ".app.json")] : [], ...control.mcpSha256 ? [join31(root, ".mcp.json")] : []];
|
|
20116
20243
|
for (const path of files)
|
|
20117
20244
|
if (!review?.sources.some((source) => source.path === path && source.format === undefined && source.fields === undefined && [undefined, "bytes"].includes(source.hashMode) && source.sha256 === sha4(readNativeBytes(path, 1024 * 1024))))
|
|
20118
20245
|
throw new Error("Explicit Codex denial requires current full manifest and capability discovery witnesses");
|
|
@@ -20121,12 +20248,12 @@ function planAgentIntegration(options) {
|
|
|
20121
20248
|
for (const discovery of discoveries)
|
|
20122
20249
|
if (discovery.agent === "codex" && discovery.method === "reviewed")
|
|
20123
20250
|
discovery.codexDisabledPluginSkills = codexPluginSkills;
|
|
20124
|
-
if (options.agents.includes("codex") && !options.codexNativeCatalog && codexPluginSkills.length && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(
|
|
20251
|
+
if (options.agents.includes("codex") && !options.codexNativeCatalog && codexPluginSkills.length && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), codexPluginSkills, (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), codexConfig.skills?.config ?? [])) {
|
|
20125
20252
|
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; provide a fresh native catalog and discovery review");
|
|
20126
20253
|
}
|
|
20127
20254
|
const codexInactivePlugins = codexPluginControls.inactivePlugins;
|
|
20128
20255
|
const codexDiscovery = discoveries.find((binding) => binding.agent === "codex");
|
|
20129
|
-
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot +
|
|
20256
|
+
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot + sep14) || input.sourceRoot.startsWith(root + sep14)) && codexDiscovery.sources.some((source) => source.path === join31(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined));
|
|
20130
20257
|
assertCodexInstallationInputs(home, [options.projectDir ?? home], codexPluginSourceInputs.map((input) => input.sourceRoot));
|
|
20131
20258
|
const changes = [];
|
|
20132
20259
|
let observedSettings;
|
|
@@ -20134,19 +20261,19 @@ function planAgentIntegration(options) {
|
|
|
20134
20261
|
if (!INTEGRATION_AGENTS.includes(agent))
|
|
20135
20262
|
throw new Error(`Unsupported agent: ${agent}`);
|
|
20136
20263
|
const { command, profileId: profileId2 } = bindings.get(agent);
|
|
20137
|
-
const bridgePath = canonicalAgentPath(
|
|
20264
|
+
const bridgePath = canonicalAgentPath(join31(nativeAgentRoot(home, agent, options.projectDir ?? process.cwd()), CLI_BRIDGE_NAME), aliases);
|
|
20138
20265
|
assertSafePath(bridgePath);
|
|
20139
20266
|
if (existsSync9(bridgePath) && !isOwnedCliBridge(bridgePath, [bridgePath]))
|
|
20140
20267
|
throw new Error(`Refusing to overwrite an unrecognized or modified Skills bridge: ${bridgePath}`);
|
|
20141
20268
|
for (const [name, after2] of Object.entries(CLI_BRIDGE_FILES)) {
|
|
20142
|
-
const path2 =
|
|
20269
|
+
const path2 = join31(bridgePath, name), before2 = readOptional(path2);
|
|
20143
20270
|
if (before2 !== after2)
|
|
20144
20271
|
changes.push({ path: path2, before: before2, after: after2 });
|
|
20145
20272
|
}
|
|
20146
20273
|
const path = canonicalAgentPath(nativeAgentConfig(home, agent, options.projectDir ?? process.cwd()), aliases);
|
|
20147
20274
|
const before = readOptional(path);
|
|
20148
20275
|
if (agent === "hermes") {
|
|
20149
|
-
const supervisorPath =
|
|
20276
|
+
const supervisorPath = join31(dataDir2, "agent-hooks", "hermes.js"), supervisorBefore = readOptional(supervisorPath), supervisorAfter = renderHermesSupervisor(command, profileId2);
|
|
20150
20277
|
const priorCommand = policy.bridge?.commands?.hermes, priorProfile = policy.bridge?.profiles?.hermes ?? policy.profileId;
|
|
20151
20278
|
const previous = policy.bridge?.supervisors?.hermes;
|
|
20152
20279
|
if (supervisorBefore !== null && supervisorBefore !== supervisorAfter && !(previous?.path === supervisorPath && typeof priorCommand === "string" && typeof priorProfile === "string" && supervisorBefore === renderHermesSupervisor(priorCommand, priorProfile)))
|
|
@@ -20157,7 +20284,7 @@ function planAgentIntegration(options) {
|
|
|
20157
20284
|
const after2 = configureHermesHooks(before, supervisor, previous);
|
|
20158
20285
|
if (before !== after2)
|
|
20159
20286
|
changes.push({ path, before, after: after2 });
|
|
20160
|
-
const optOut =
|
|
20287
|
+
const optOut = join31(home, ".hermes", HERMES_OPT_OUT), optOutBefore = readOptional(optOut);
|
|
20161
20288
|
if (optOutBefore === null)
|
|
20162
20289
|
changes.push({ path: optOut, before: null, after: `Managed by @hasna/skills: bundled native skill reseeding is disabled.
|
|
20163
20290
|
` });
|
|
@@ -20168,7 +20295,7 @@ function planAgentIntegration(options) {
|
|
|
20168
20295
|
if (config.permissions !== undefined && (!Array.isArray(config.permissions) || config.permissions.some((rule) => !rule || typeof rule.action !== "string" || typeof rule.resource !== "string" || !["allow", "deny", "ask"].includes(rule.effect))))
|
|
20169
20296
|
throw new Error("Expected Sumi native permission rules");
|
|
20170
20297
|
config.permissions = [...(config.permissions ?? []).filter((rule) => rule.action !== "skill"), ...SUMI_SKILL_PERMISSIONS];
|
|
20171
|
-
const pluginPath =
|
|
20298
|
+
const pluginPath = join31(sumiConfigDirectory(home, process.env, options.projectDir ?? process.cwd()), "plugins", "skills-cli.js"), pluginBefore = readOptional(pluginPath);
|
|
20172
20299
|
const pluginAfter = renderSumiPlugin(command, profileId2);
|
|
20173
20300
|
const priorCommand = policy.bridge?.commands?.sumi, priorProfile = policy.bridge?.profiles?.sumi ?? policy.profileId;
|
|
20174
20301
|
if (pluginBefore !== null && pluginBefore !== pluginAfter && !(typeof priorCommand === "string" && typeof priorProfile === "string" && isManagedSumiPlugin(pluginBefore, priorCommand, priorProfile)))
|
|
@@ -20180,7 +20307,7 @@ function planAgentIntegration(options) {
|
|
|
20180
20307
|
if (!permission || typeof permission !== "object" || Array.isArray(permission))
|
|
20181
20308
|
throw new Error("Expected OpenCode permission object");
|
|
20182
20309
|
config.permission = { ...permission, skill: { "*": "deny", [CLI_BRIDGE_NAME]: "allow" } };
|
|
20183
|
-
const pluginPath =
|
|
20310
|
+
const pluginPath = join31(home, ".config", "opencode", "plugins", "skills-cli.js"), pluginBefore = readOptional(pluginPath);
|
|
20184
20311
|
const pluginAfter = renderOpenCodePlugin(command, profileId2);
|
|
20185
20312
|
const priorCommand = policy.bridge?.commands?.opencode;
|
|
20186
20313
|
const ownedPrevious = typeof priorCommand === "string" && typeof policy.profileId === "string" && pluginBefore === renderOpenCodePlugin(priorCommand, policy.bridge?.profiles?.opencode ?? policy.profileId);
|
|
@@ -20197,7 +20324,7 @@ function planAgentIntegration(options) {
|
|
|
20197
20324
|
if (before !== after)
|
|
20198
20325
|
changes.push({ path, before, after });
|
|
20199
20326
|
if (agent === "codex") {
|
|
20200
|
-
const configPath = canonicalAgentPath(
|
|
20327
|
+
const configPath = canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases), previous = readOptional(configPath);
|
|
20201
20328
|
if (previous !== codexConfigBefore)
|
|
20202
20329
|
throw new Error(`Configuration changed during planning: ${configPath}`);
|
|
20203
20330
|
const next = disableReviewedCodexPluginNames(codexPathConfig, codexPluginSkills);
|
|
@@ -20209,28 +20336,28 @@ function planAgentIntegration(options) {
|
|
|
20209
20336
|
}
|
|
20210
20337
|
}
|
|
20211
20338
|
}
|
|
20212
|
-
const codexPluginSkillReview = options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha4(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(
|
|
20339
|
+
const codexPluginSkillReview = options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha4(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(join31(home, ".codex/config.toml"), aliases))?.before === null ? null : sha4(readOptional(canonicalAgentPath(join31(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview;
|
|
20213
20340
|
const discoveryAfter = discoveries.map((binding) => {
|
|
20214
20341
|
const rebound = rebindAgentDiscovery(binding, new Map(changes.map((change) => [change.path, change.after])));
|
|
20215
20342
|
if (binding.agent !== "codex")
|
|
20216
20343
|
return rebound;
|
|
20217
20344
|
const { codexInstallationInputs: previousInputs, ...current } = rebound;
|
|
20218
20345
|
const inputs = codexPluginSourceInputs.map((input) => input.sourceRoot);
|
|
20219
|
-
const directories = options.codexNativeCatalog || !previousInputs ? captureDiscoveryDirectories((binding.directories ?? []).filter((directory2) => inputs.some((root) => root.startsWith(directory2.path +
|
|
20346
|
+
const directories = options.codexNativeCatalog || !previousInputs ? captureDiscoveryDirectories((binding.directories ?? []).filter((directory2) => inputs.some((root) => root.startsWith(directory2.path + sep14))).map((directory2) => directory2.path), inputs) : previousInputs.directories;
|
|
20220
20347
|
const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: codexPluginSkillReview.version, catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
|
|
20221
|
-
return captureRetiredCodexDiscovery(installed, codexPluginSkills, Bun.TOML.parse(changes.find((change) => change.path === canonicalAgentPath(
|
|
20348
|
+
return captureRetiredCodexDiscovery(installed, codexPluginSkills, Bun.TOML.parse(changes.find((change) => change.path === canonicalAgentPath(join31(home, ".codex/config.toml"), aliases))?.after ?? codexConfigBefore ?? "").skills?.config ?? []);
|
|
20222
20349
|
});
|
|
20223
20350
|
const nextPolicy = { ...policy, version: 1, loading: "cli", profileId, bridge: {
|
|
20224
20351
|
...policy.bridge,
|
|
20225
20352
|
version: CLI_BRIDGE_VERSION,
|
|
20226
20353
|
digest: CLI_BRIDGE_DIGEST,
|
|
20227
20354
|
agents: [...new Set([...priorAgents, ...options.agents])].sort(),
|
|
20228
|
-
home:
|
|
20355
|
+
home: resolve23(home),
|
|
20229
20356
|
includeVendor: true,
|
|
20230
|
-
...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path:
|
|
20357
|
+
...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path: join31(dataDir2, "agent-hooks", "hermes.js"), runtime: process.execPath, sha256: sha4(renderHermesSupervisor(bindings.get("hermes").command, bindings.get("hermes").profileId)) } } } : {},
|
|
20231
20358
|
commands: { ...policy.bridge?.commands, ...Object.fromEntries([...bindings].map(([agent, binding]) => [agent, binding.command])) },
|
|
20232
20359
|
profiles: Object.fromEntries([...new Set([...priorAgents, ...options.agents])].sort().map((agent) => [agent, bindings.get(agent)?.profileId ?? policy.bridge?.profiles?.[agent] ?? policy.profileId])),
|
|
20233
|
-
disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(
|
|
20360
|
+
disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(join31(home, ".codex", "skills", ".system"), aliases) + sep14)).map((entry) => ({ path: entry.path, hash: entry.hash })) : policy.bridge?.disabledBuiltins ?? [],
|
|
20234
20361
|
rootAliases: aliases,
|
|
20235
20362
|
...options.codexNativeCatalog || codexPluginSkills.length || codexInactivePlugins.length || codexPluginSourceInputs.length ? { codexPluginSkills, codexInactivePlugins, codexPluginSkillReview } : {},
|
|
20236
20363
|
discovery: { ...policy.bridge?.discovery, ...Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) }
|
|
@@ -20273,7 +20400,7 @@ function projectedHookAcl(path, observed) {
|
|
|
20273
20400
|
const identity5 = observed.isDirectory() ? (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.uid}:${stat2.mode}` : (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}:${stat2.uid}:${stat2.mode}:${stat2.nlink}:${stat2.size}:${stat2.mtimeNs}`;
|
|
20274
20401
|
const fd = openSync17(path, constants18.O_RDONLY | constants18.O_NOFOLLOW | constants18.O_NONBLOCK | (observed.isDirectory() ? constants18.O_DIRECTORY : 0));
|
|
20275
20402
|
try {
|
|
20276
|
-
if (identity5(fstatSync17(fd, { bigint: true })) !== identity5(observed) || !hasTrustedDarwinAcl(fd, true) || identity5(fstatSync17(fd, { bigint: true })) !== identity5(observed) || identity5(
|
|
20403
|
+
if (identity5(fstatSync17(fd, { bigint: true })) !== identity5(observed) || !hasTrustedDarwinAcl(fd, true) || identity5(fstatSync17(fd, { bigint: true })) !== identity5(observed) || identity5(lstatSync28(path, { bigint: true })) !== identity5(observed))
|
|
20277
20404
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20278
20405
|
} finally {
|
|
20279
20406
|
closeSync17(fd);
|
|
@@ -20281,23 +20408,23 @@ function projectedHookAcl(path, observed) {
|
|
|
20281
20408
|
}
|
|
20282
20409
|
function projectedHookExecutable(command, expectedResolved) {
|
|
20283
20410
|
try {
|
|
20284
|
-
if (!isAbsolute24(command) || /[\0\r\n]/.test(command) ||
|
|
20411
|
+
if (!isAbsolute24(command) || /[\0\r\n]/.test(command) || resolve23(command) !== command)
|
|
20285
20412
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20286
20413
|
command = canonicalSystemPath(command);
|
|
20287
20414
|
const parents2 = (file = command) => {
|
|
20288
20415
|
const result = [];
|
|
20289
|
-
for (let path =
|
|
20290
|
-
const stat2 =
|
|
20416
|
+
for (let path = dirname22(file);; path = dirname22(path)) {
|
|
20417
|
+
const stat2 = lstatSync28(path, { bigint: true });
|
|
20291
20418
|
if (!stat2.isDirectory() || !trustedAncestorOwner(stat2.uid) || (stat2.mode & 0o022n) !== 0n && !(stat2.uid === 0n && (stat2.mode & 0o1000n) !== 0n))
|
|
20292
20419
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20293
20420
|
projectedHookAcl(path, stat2);
|
|
20294
20421
|
result.push(`${path}:${stat2.dev}:${stat2.ino}:${stat2.uid}:${stat2.mode}`);
|
|
20295
|
-
if (
|
|
20422
|
+
if (dirname22(path) === path)
|
|
20296
20423
|
break;
|
|
20297
20424
|
}
|
|
20298
20425
|
return result;
|
|
20299
20426
|
};
|
|
20300
|
-
const parentIdentity = parents2(), link =
|
|
20427
|
+
const parentIdentity = parents2(), link = lstatSync28(command, { bigint: true });
|
|
20301
20428
|
if (link.uid !== BigInt(process.getuid()) || !link.isFile() && !link.isSymbolicLink())
|
|
20302
20429
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20303
20430
|
const linkIdentity = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.mode}:${stat2.uid}:${stat2.ctimeNs}`;
|
|
@@ -20317,18 +20444,18 @@ function projectedHookExecutable(command, expectedResolved) {
|
|
|
20317
20444
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20318
20445
|
const path = target.pinned.runtime;
|
|
20319
20446
|
assertPinnedLauncherRuntimeSafe(path);
|
|
20320
|
-
if (
|
|
20447
|
+
if (resolve23(path) !== path)
|
|
20321
20448
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20322
|
-
runtime = { path, parents: parents2(path), stat:
|
|
20449
|
+
runtime = { path, parents: parents2(path), stat: lstatSync28(path, { bigint: true }) };
|
|
20323
20450
|
if (!runtime.stat.isFile())
|
|
20324
20451
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20325
20452
|
projectedHookAcl(path, runtime.stat);
|
|
20326
20453
|
}
|
|
20327
20454
|
const resolvedParentIdentity = parents2(resolved);
|
|
20328
|
-
const manifest = snapshot(
|
|
20455
|
+
const manifest = snapshot(join31(dirname22(dirname22(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
20329
20456
|
projectedHookAcl(manifest.file, manifest.stat);
|
|
20330
20457
|
const pkg = JSON.parse(manifest.text);
|
|
20331
|
-
if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !==
|
|
20458
|
+
if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !== join31(dirname22(manifest.file), "bin/index.js"))
|
|
20332
20459
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20333
20460
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
20334
20461
|
projectedHookAcl(cli.file, cli.stat);
|
|
@@ -20337,7 +20464,7 @@ function projectedHookExecutable(command, expectedResolved) {
|
|
|
20337
20464
|
const recheck = () => {
|
|
20338
20465
|
try {
|
|
20339
20466
|
const again = resolveLauncherCommand(command);
|
|
20340
|
-
if (again.entry !== resolved || again.physical !== target.physical || linkIdentity(
|
|
20467
|
+
if (again.entry !== resolved || again.physical !== target.physical || linkIdentity(lstatSync28(command, { bigint: true })) !== linkIdentity(link) || !direct() || !isDeepStrictEqual8(parents2(), parentIdentity) || !isDeepStrictEqual8(parents2(resolved), resolvedParentIdentity))
|
|
20341
20468
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20342
20469
|
if (launcher) {
|
|
20343
20470
|
if (!isDeepStrictEqual8(parents2(target.physical), launcherParentIdentity))
|
|
@@ -20377,8 +20504,8 @@ function planClaudeManagedHookProjection(options) {
|
|
|
20377
20504
|
} catch {
|
|
20378
20505
|
hookProjectionRefusal("TARGET_INVALID");
|
|
20379
20506
|
}
|
|
20380
|
-
const home =
|
|
20381
|
-
const policyPath =
|
|
20507
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20508
|
+
const policyPath = join31(dataDir2, "agent-policy.json");
|
|
20382
20509
|
let policyBytes, settingsBytes, settingsPath, command, profile;
|
|
20383
20510
|
try {
|
|
20384
20511
|
policyBytes = readNativeBytes(policyPath, 1024 * 1024);
|
|
@@ -20386,7 +20513,7 @@ function planClaudeManagedHookProjection(options) {
|
|
|
20386
20513
|
if (!snapshot2 || snapshot2.text !== policyBytes.toString("utf8"))
|
|
20387
20514
|
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
20388
20515
|
const binding = snapshot2.value.bridge;
|
|
20389
|
-
settingsPath = canonicalAgentPath(
|
|
20516
|
+
settingsPath = canonicalAgentPath(join31(home, AGENT_ADAPTERS.claude.config), binding?.rootAliases ?? []);
|
|
20390
20517
|
settingsBytes = readNativeBytes(settingsPath, 1024 * 1024);
|
|
20391
20518
|
command = binding?.commands?.claude;
|
|
20392
20519
|
profile = binding?.profiles?.claude;
|
|
@@ -20472,7 +20599,7 @@ function claudeSettingsConsumers(home, path, discoveries, aliases) {
|
|
|
20472
20599
|
const canonical9 = (value) => canonicalAgentPath(value, aliases);
|
|
20473
20600
|
const containsSettings = (root) => {
|
|
20474
20601
|
const distance = relative9(canonical9(root), path);
|
|
20475
|
-
return distance === "" || distance !== ".." && !distance.startsWith(`..${
|
|
20602
|
+
return distance === "" || distance !== ".." && !distance.startsWith(`..${sep14}`) && !isAbsolute24(distance);
|
|
20476
20603
|
};
|
|
20477
20604
|
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || binding.agent !== "sumi" && canonical9(nativeAgentConfig(home, binding.agent)) === path || binding.sources.some((source) => canonical9(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory2) => containsSettings(directory2.path))).map((binding) => binding.agent);
|
|
20478
20605
|
}
|
|
@@ -20490,8 +20617,8 @@ function verifyCoordinatedDiscovery(binding, home, aliases, executable2) {
|
|
|
20490
20617
|
}
|
|
20491
20618
|
}
|
|
20492
20619
|
function planClaudeHookEventsUpdate(options) {
|
|
20493
|
-
const home =
|
|
20494
|
-
const policyPath =
|
|
20620
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20621
|
+
const policyPath = join31(dataDir2, "agent-policy.json");
|
|
20495
20622
|
assertSafePath(policyPath);
|
|
20496
20623
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
20497
20624
|
if (!snapshot2)
|
|
@@ -20502,9 +20629,9 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
20502
20629
|
}
|
|
20503
20630
|
const aliases = bridge.rootAliases ?? [];
|
|
20504
20631
|
recheckRootAliases(aliases);
|
|
20505
|
-
const path = canonicalAgentPath(
|
|
20632
|
+
const path = canonicalAgentPath(join31(home, ".claude", "settings.json"), aliases);
|
|
20506
20633
|
assertSafePath(path);
|
|
20507
|
-
const before =
|
|
20634
|
+
const before = lstatSync28(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
20508
20635
|
if ((before === null ? "absent" : sha4(before)) !== options.expectedSettingsSha256)
|
|
20509
20636
|
throw new Error("Claude settings preimage changed");
|
|
20510
20637
|
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
@@ -20560,7 +20687,7 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
20560
20687
|
}
|
|
20561
20688
|
function atomicWrite2(path, content) {
|
|
20562
20689
|
assertSafePath(path);
|
|
20563
|
-
mkdirSync8(
|
|
20690
|
+
mkdirSync8(dirname22(path), { recursive: true, mode: 448 });
|
|
20564
20691
|
const temporary = `${path}.skills-${randomUUID4()}`;
|
|
20565
20692
|
try {
|
|
20566
20693
|
writeFileSync6(temporary, content, { mode: 384, flag: "wx" });
|
|
@@ -20575,7 +20702,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
20575
20702
|
throw new Error("Invalid Codex witness target version");
|
|
20576
20703
|
if (options.targetClaudeVersion !== undefined && (options.agent !== "claude" || ![3, 4].includes(options.targetClaudeVersion)))
|
|
20577
20704
|
throw new Error("Invalid Claude witness target version");
|
|
20578
|
-
const home =
|
|
20705
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20579
20706
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
20580
20707
|
if (!snapshot2 || sha4(snapshot2.text) !== options.expectedPolicySha256)
|
|
20581
20708
|
throw new Error("Managed policy preimage changed");
|
|
@@ -20584,7 +20711,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
20584
20711
|
const binding = policy.bridge?.discovery?.[options.agent];
|
|
20585
20712
|
if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
|
|
20586
20713
|
throw new Error("Settings witness upgrade requires an existing reviewed native binding");
|
|
20587
|
-
const configPath = canonicalAgentPath(options.agent === "sumi" ? nativeAgentConfig(home, "sumi", options.projectDir ?? process.cwd()) :
|
|
20714
|
+
const configPath = canonicalAgentPath(options.agent === "sumi" ? nativeAgentConfig(home, "sumi", options.projectDir ?? process.cwd()) : join31(home, options.agent === "claude" ? ".claude/settings.json" : ".codex/config.toml"), aliases);
|
|
20588
20715
|
const settings = readOptional(configPath);
|
|
20589
20716
|
if (settings === null || sha4(settings) !== options.expectedSettingsSha256)
|
|
20590
20717
|
throw new Error("Native settings preimage changed");
|
|
@@ -20635,12 +20762,12 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
20635
20762
|
const replacement = { ...binding, sources: rebound };
|
|
20636
20763
|
verifyAgentDiscovery(replacement, undefined, aliases);
|
|
20637
20764
|
const after = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
|
|
20638
|
-
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path:
|
|
20765
|
+
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text, after }], nativeSkills: [], observedPolicy: { path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], ...policy.bridge.discoveryExecutables ? { discoveryExecutables: policy.bridge.discoveryExecutables } : {}, rootAliases: aliases, managedAgentChecks: { home, agents: [options.agent] }, settingsWitnessUpgrade: { agent: options.agent, path: configPath, fromHashMode: previous.hashMode ?? "bytes", fromSha256: previous.sha256, toHashMode: next.hashMode, toSha256: next.sha256, reviewedPreimage: options.reviewedPreimage, currentSettingsSha256: options.expectedSettingsSha256, replacedWitnesses: replaced.map((source) => ({ hashMode: source.hashMode ?? "bytes", sha256: source.sha256 })) } };
|
|
20639
20766
|
}
|
|
20640
20767
|
function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
20641
20768
|
if (options.agent !== "codex" || !/^[a-f0-9]{64}$/.test(options.expectedPolicySha256) || !/^[a-f0-9]{64}$/.test(options.expectedSettingsSha256) || options.expectedCacheWitnessSha256 !== undefined && !/^[a-f0-9]{64}$/.test(options.expectedCacheWitnessSha256) || !Array.isArray(options.appOnlyParents) || options.appOnlyParents.length > 4096 || new Set(options.appOnlyParents).size !== options.appOnlyParents.length || options.reviewedHookParents !== undefined && (!Array.isArray(options.reviewedHookParents) || options.reviewedHookParents.length > 4096 || new Set(options.reviewedHookParents).size !== options.reviewedHookParents.length))
|
|
20642
20769
|
throw new Error("Invalid Codex semantic cache upgrade request");
|
|
20643
|
-
const home =
|
|
20770
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20644
20771
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
20645
20772
|
if (!snapshot2 || sha4(snapshot2.text) !== options.expectedPolicySha256)
|
|
20646
20773
|
throw new Error("Managed policy preimage changed");
|
|
@@ -20654,10 +20781,10 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20654
20781
|
throw new Error("Cache upgrade requires prior installed-plugin review");
|
|
20655
20782
|
if (!controls.length || !isDeepStrictEqual8(controls, binding.codexDisabledPluginSkills ?? controls))
|
|
20656
20783
|
throw new Error("Cache upgrade requires unchanged enrolled plugin controls");
|
|
20657
|
-
const cacheRoot = canonicalAgentPath(
|
|
20658
|
-
if (controls.some((control) =>
|
|
20784
|
+
const cacheRoot = canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases);
|
|
20785
|
+
if (controls.some((control) => dirname22(dirname22(control.pluginParent)) !== cacheRoot))
|
|
20659
20786
|
throw new Error("Cache upgrade names another enrolled cache");
|
|
20660
|
-
const configPath = canonicalAgentPath(
|
|
20787
|
+
const configPath = canonicalAgentPath(join31(home, ".codex/config.toml"), aliases), settings = readOptional(configPath);
|
|
20661
20788
|
if (settings === null || sha4(settings) !== options.expectedSettingsSha256)
|
|
20662
20789
|
throw new Error("Native settings preimage changed");
|
|
20663
20790
|
const boundedRead = (path) => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024));
|
|
@@ -20680,13 +20807,13 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20680
20807
|
return unchangedFullSource(path);
|
|
20681
20808
|
};
|
|
20682
20809
|
const appOnlyParents = options.appOnlyParents.map((parent) => {
|
|
20683
|
-
const parts = relative9(cacheRoot, parent).split(
|
|
20684
|
-
if (!isAbsolute24(parent) ||
|
|
20810
|
+
const parts = relative9(cacheRoot, parent).split(sep14);
|
|
20811
|
+
if (!isAbsolute24(parent) || resolve23(parent) !== parent || parts.length !== 2 || parts.some((part) => !/^[A-Za-z0-9_-]{1,64}$/.test(part)) || controls.some((control) => control.pluginParent === parent))
|
|
20685
20812
|
throw new Error("Unsupported app-only parent role");
|
|
20686
|
-
const receipt = JSON.parse(unchangedFullSource(
|
|
20813
|
+
const receipt = JSON.parse(unchangedFullSource(join31(parent, ".codex-remote-plugin-install.json")));
|
|
20687
20814
|
if (!receipt || Object.keys(receipt).length !== 2 || receipt.schema_version !== 1 || typeof receipt.remote_plugin_id !== "string")
|
|
20688
20815
|
throw new Error("Invalid reviewed app-only parent identity");
|
|
20689
|
-
const manifests = binding.sources.filter((source) => source.path.startsWith(parent +
|
|
20816
|
+
const manifests = binding.sources.filter((source) => source.path.startsWith(parent + sep14) && source.path.endsWith(join31(sep14, ".codex-plugin/plugin.json")));
|
|
20690
20817
|
if (!manifests.length)
|
|
20691
20818
|
throw new Error("App-only bootstrap lacks prior full manifests");
|
|
20692
20819
|
for (const source of manifests)
|
|
@@ -20694,10 +20821,10 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20694
20821
|
return { role: "app-only", pluginId: `${parts[1]}@${parts[0]}`, namespace: parts[1], pluginParent: parent, remotePluginId: receipt.remote_plugin_id };
|
|
20695
20822
|
}).sort((a, b) => a.pluginParent.localeCompare(b.pluginParent));
|
|
20696
20823
|
const reviewedHookParents = (options.reviewedHookParents ?? []).map((parent) => {
|
|
20697
|
-
const parts = relative9(cacheRoot, parent).split(
|
|
20698
|
-
if (!isAbsolute24(parent) ||
|
|
20824
|
+
const parts = relative9(cacheRoot, parent).split(sep14);
|
|
20825
|
+
if (!isAbsolute24(parent) || resolve23(parent) !== parent || parts.length !== 2 || parts.some((part) => !/^[A-Za-z0-9_-]{1,64}$/.test(part)) || controls.some((control) => control.pluginParent === parent) || appOnlyParents.some((role) => role.pluginParent === parent))
|
|
20699
20826
|
throw new Error("Unsupported reviewed native-hook parent role");
|
|
20700
|
-
const receiptPath =
|
|
20827
|
+
const receiptPath = join31(parent, ".codex-remote-plugin-install.json"), receiptStat = lstatSync28(receiptPath, { throwIfNoEntry: false });
|
|
20701
20828
|
let identity5;
|
|
20702
20829
|
if (receiptStat) {
|
|
20703
20830
|
if (!receiptStat.isFile() || receiptStat.isSymbolicLink() || receiptStat.size > 16 * 1024)
|
|
@@ -20734,11 +20861,11 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20734
20861
|
for (const version2 of versions) {
|
|
20735
20862
|
if (!/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/.test(version2))
|
|
20736
20863
|
throw new Error("Invalid reviewed native-hook version identity");
|
|
20737
|
-
const root =
|
|
20864
|
+
const root = join31(parent, version2), stat2 = lstatSync28(root, { throwIfNoEntry: false });
|
|
20738
20865
|
if (!stat2?.isDirectory() || stat2.isSymbolicLink() || realpathSync12(root) !== root)
|
|
20739
20866
|
throw new Error("Invalid reviewed native-hook materialization");
|
|
20740
|
-
unchangedReviewedHookSource(
|
|
20741
|
-
unchangedReviewedHookSource(
|
|
20867
|
+
unchangedReviewedHookSource(join31(root, ".codex-plugin/plugin.json"));
|
|
20868
|
+
unchangedReviewedHookSource(join31(root, "hooks/hooks.json"));
|
|
20742
20869
|
}
|
|
20743
20870
|
return identity5;
|
|
20744
20871
|
}).sort((a, b) => a.pluginParent.localeCompare(b.pluginParent));
|
|
@@ -20748,10 +20875,10 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20748
20875
|
assertCodexSemanticCacheHookSafety(cacheRoot, parents2, configPath, hookRootAlias);
|
|
20749
20876
|
const supersededSources = binding.sources.filter((source) => codexSemanticCacheSourceRole(source, cacheRoot, parents2, reviewedHookParents.map((parent) => parent.pluginParent)));
|
|
20750
20877
|
for (const source of supersededSources)
|
|
20751
|
-
if ([".app.json", ".mcp.json"].includes(source.path.split(
|
|
20878
|
+
if ([".app.json", ".mcp.json"].includes(source.path.split(sep14).at(-1)))
|
|
20752
20879
|
unchangedFullSource(source.path);
|
|
20753
20880
|
const supersededDirectories = (binding.directories ?? []).filter((directory2) => codexSemanticCacheDirectoryRole(directory2.path, cacheRoot));
|
|
20754
|
-
if (!supersededDirectories.some((directory2) => directory2.path === cacheRoot) || (binding.directories ?? []).some((directory2) => cacheRoot.startsWith(directory2.path +
|
|
20881
|
+
if (!supersededDirectories.some((directory2) => directory2.path === cacheRoot) || (binding.directories ?? []).some((directory2) => cacheRoot.startsWith(directory2.path + sep14)))
|
|
20755
20882
|
throw new Error("Cache upgrade requires exact cache directory coverage without an unproved ancestor projection");
|
|
20756
20883
|
const rules = Bun.TOML.parse(settings).skills?.config ?? [];
|
|
20757
20884
|
const witness = captureCodexSemanticCacheWitness({ cacheRoot, controls, appOnlyParents, reviewedHookParents, rules, read: boundedRead });
|
|
@@ -20774,8 +20901,8 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20774
20901
|
dataDir: dataDir2,
|
|
20775
20902
|
profileId: policy.profileId,
|
|
20776
20903
|
nativeSkills: [],
|
|
20777
|
-
changes: [{ path:
|
|
20778
|
-
observedPolicy: { path:
|
|
20904
|
+
changes: [{ path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text, after }],
|
|
20905
|
+
observedPolicy: { path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text },
|
|
20779
20906
|
observedSettings: { path: configPath, before: settings },
|
|
20780
20907
|
discoveryBefore: [replacement],
|
|
20781
20908
|
discoveryAfter: [replacement],
|
|
@@ -20788,7 +20915,7 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20788
20915
|
function planReviewedArtifactMigration(options) {
|
|
20789
20916
|
if (options.kind !== "codex-native-catalog" || !/^[a-f0-9]{64}$/.test(options.expectedPolicySha256) || !/^[a-f0-9]{64}$/.test(options.expectedSourceSha256))
|
|
20790
20917
|
throw new Error("REVIEW_ARTIFACT_INVALID");
|
|
20791
|
-
const home =
|
|
20918
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = resolve23(options.dataDir ?? getDataDirReadOnly());
|
|
20792
20919
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
20793
20920
|
if (!snapshot2 || sha4(snapshot2.text) !== options.expectedPolicySha256)
|
|
20794
20921
|
throw new Error("REVIEW_ARTIFACT_POLICY_CHANGED");
|
|
@@ -20805,8 +20932,8 @@ function planReviewedArtifactMigration(options) {
|
|
|
20805
20932
|
dataDir: dataDir2,
|
|
20806
20933
|
profileId: snapshot2.value.profileId,
|
|
20807
20934
|
nativeSkills: [],
|
|
20808
|
-
changes: [...retained.changes, { path:
|
|
20809
|
-
observedPolicy: { path:
|
|
20935
|
+
changes: [...retained.changes, { path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text, after: serializeManagedSkillPolicy(next) }],
|
|
20936
|
+
observedPolicy: { path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text },
|
|
20810
20937
|
reviewArtifactSources: retained.observed,
|
|
20811
20938
|
retainedReviewArtifacts: retained.changes,
|
|
20812
20939
|
reviewArtifactMigration: { ...options, home, dataDir: dataDir2 },
|
|
@@ -20832,12 +20959,12 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20832
20959
|
throw new Error("Native identity source changed after planning");
|
|
20833
20960
|
if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
|
|
20834
20961
|
throw new Error("Native settings changed after witness planning");
|
|
20835
|
-
const policyPath =
|
|
20962
|
+
const policyPath = join31(plan.dataDir, "agent-policy.json");
|
|
20836
20963
|
assertSafePath(policyPath);
|
|
20837
|
-
const currentText = (path) =>
|
|
20964
|
+
const currentText = (path) => resolve23(path) === resolve23(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
|
|
20838
20965
|
readManagedSkillPolicySnapshot(plan.dataDir);
|
|
20839
20966
|
for (const change of plan.changes)
|
|
20840
|
-
if (
|
|
20967
|
+
if (resolve23(change.path) === resolve23(policyPath))
|
|
20841
20968
|
parseManagedSkillPolicy(change.after);
|
|
20842
20969
|
const aliases = plan.rootAliases ?? [];
|
|
20843
20970
|
assertCurrent();
|
|
@@ -20882,7 +21009,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20882
21009
|
for (const change of plan.changes)
|
|
20883
21010
|
if (currentText(change.path) !== change.before)
|
|
20884
21011
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
20885
|
-
const backupRoot =
|
|
21012
|
+
const backupRoot = join31(plan.dataDir, "migration", randomUUID4()), backups = [], written = [], createdDirectories = new Set;
|
|
20886
21013
|
if (!plan.changes.length) {
|
|
20887
21014
|
assertCurrent();
|
|
20888
21015
|
recheckRootAliases(aliases);
|
|
@@ -20893,9 +21020,9 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20893
21020
|
chmodSync(backupRoot, 448);
|
|
20894
21021
|
for (const [index, change] of plan.changes.entries())
|
|
20895
21022
|
if (change.before !== null) {
|
|
20896
|
-
const backup =
|
|
21023
|
+
const backup = join31(backupRoot, `${index}-${sha4(change.path).slice(0, 12)}.backup`);
|
|
20897
21024
|
writeFileSync6(backup, change.before, { mode: 384, flag: "wx" });
|
|
20898
|
-
if (
|
|
21025
|
+
if (readFileSync19(backup, "utf8") !== change.before)
|
|
20899
21026
|
throw new Error("Native configuration preservation readback failed");
|
|
20900
21027
|
backups.push(backup);
|
|
20901
21028
|
}
|
|
@@ -20905,7 +21032,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20905
21032
|
recheckRootAliases(aliases);
|
|
20906
21033
|
if (currentText(change.path) !== change.before)
|
|
20907
21034
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
20908
|
-
for (let directory2 =
|
|
21035
|
+
for (let directory2 = dirname22(change.path);!existsSync9(directory2); directory2 = dirname22(directory2))
|
|
20909
21036
|
createdDirectories.add(directory2);
|
|
20910
21037
|
const after = change.after;
|
|
20911
21038
|
const immutable = plan.retainedReviewArtifacts?.find((item) => item.path === change.path);
|
|
@@ -20935,7 +21062,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20935
21062
|
}
|
|
20936
21063
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
20937
21064
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.projectDir ?? plan.managedAgentChecks.home });
|
|
20938
|
-
atomicWrite2(
|
|
21065
|
+
atomicWrite2(join31(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha4(change.before), afterHash: sha4(change.after) })), backups, ...plan.cacheWitnessUpgrade ? { cacheWitnessUpgrade: plan.cacheWitnessUpgrade } : {}, ...plan.retainedReviewArtifacts?.length ? { retainedReviewArtifacts: plan.retainedReviewArtifacts.map((item) => ({ path: item.path, ...item.artifact })) } : {}, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
|
20939
21066
|
`);
|
|
20940
21067
|
} catch (error) {
|
|
20941
21068
|
assertCurrent();
|
|
@@ -20986,11 +21113,11 @@ function writeArchiveJournal(path, value) {
|
|
|
20986
21113
|
closeSync17(descriptor);
|
|
20987
21114
|
descriptor = undefined;
|
|
20988
21115
|
renameSync4(temporary, path);
|
|
20989
|
-
syncArchiveDirectory(
|
|
21116
|
+
syncArchiveDirectory(dirname22(path));
|
|
20990
21117
|
} finally {
|
|
20991
21118
|
if (descriptor !== undefined)
|
|
20992
21119
|
closeSync17(descriptor);
|
|
20993
|
-
if (
|
|
21120
|
+
if (lstatSync28(temporary, { throwIfNoEntry: false }))
|
|
20994
21121
|
unlinkSync5(temporary);
|
|
20995
21122
|
}
|
|
20996
21123
|
}
|
|
@@ -20998,7 +21125,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
20998
21125
|
const selected = options.targetManifest ? selectNativeMigrationTargets(inventory, options.targetManifest) : inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
|
|
20999
21126
|
const roots = selected.flatMap((entry) => {
|
|
21000
21127
|
const root = (entry.rootAlias?.agent === "codex" ? entry.rootAlias.alias : undefined) ?? codexCorpusRootForPath(entry.path);
|
|
21001
|
-
const owner2 = entry.codexHome ?? (entry.agent === "codex" && entry.bridgeHome ?
|
|
21128
|
+
const owner2 = entry.codexHome ?? (entry.agent === "codex" && entry.bridgeHome ? join31(entry.bridgeHome, ".codex") : undefined);
|
|
21002
21129
|
if (!root && !owner2 && (entry.agent === "codex" || isCodexSkillDiscoveryInput(entry.path)))
|
|
21003
21130
|
throw new Error("CODEX_CORPUS_ROOT_UNVERIFIED");
|
|
21004
21131
|
return [...new Set([root, owner2].filter((value) => value !== undefined))];
|
|
@@ -21013,7 +21140,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21013
21140
|
recheckRootAliases(aliases);
|
|
21014
21141
|
for (const entry of inventory.filter((entry2) => entry2.bridge)) {
|
|
21015
21142
|
const adapter = AGENT_ADAPTERS[entry.agent];
|
|
21016
|
-
const expected = adapter && entry.bridgeHome ? canonicalAgentPath(
|
|
21143
|
+
const expected = adapter && entry.bridgeHome ? canonicalAgentPath(join31(nativeAgentRoot(entry.bridgeHome, entry.agent), CLI_BRIDGE_NAME), aliases) : undefined;
|
|
21017
21144
|
if (!expected || treeHash(entry.path) !== entry.hash || !isOwnedCliBridge(entry.path, [expected]))
|
|
21018
21145
|
throw new Error(`Skills bridge changed after planning: ${entry.path}`);
|
|
21019
21146
|
}
|
|
@@ -21024,11 +21151,11 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21024
21151
|
const targetManifest = options.targetManifest ? { schema: options.targetManifest.schema, digest: options.targetManifest.digest, targetCount: options.targetManifest.targets.length } : undefined;
|
|
21025
21152
|
if (!selected.length)
|
|
21026
21153
|
return { entries: [], ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
21027
|
-
const operationId = randomUUID4(), archiveRoot =
|
|
21154
|
+
const operationId = randomUUID4(), archiveRoot = join31(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join31(archiveRoot, "receipt.json");
|
|
21028
21155
|
const entries = selected.map((entry, index) => ({
|
|
21029
|
-
source: entry.vendor ?
|
|
21030
|
-
archive:
|
|
21031
|
-
hash: entry.vendor ? sha4(readNativeBytes(
|
|
21156
|
+
source: entry.vendor ? join31(entry.path, "SKILL.md") : entry.path,
|
|
21157
|
+
archive: join31(archiveRoot, `${index}-${sha4(entry.path).slice(0, 12)}`),
|
|
21158
|
+
hash: entry.vendor ? sha4(readNativeBytes(join31(entry.path, "SKILL.md"))) : entry.hash,
|
|
21032
21159
|
...entry.vendor ? { discoveryOnly: true } : {},
|
|
21033
21160
|
status: "planned"
|
|
21034
21161
|
}));
|
|
@@ -21036,7 +21163,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21036
21163
|
const moved = [];
|
|
21037
21164
|
const verifyArchive = (entry) => {
|
|
21038
21165
|
assertSafePath(entry.archive);
|
|
21039
|
-
const stat2 =
|
|
21166
|
+
const stat2 = lstatSync28(entry.archive);
|
|
21040
21167
|
if (entry.discoveryOnly ? !stat2.isFile() : !stat2.isDirectory())
|
|
21041
21168
|
throw new Error("Archive type changed");
|
|
21042
21169
|
if ((entry.discoveryOnly ? sha4(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
|
|
@@ -21044,14 +21171,14 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21044
21171
|
};
|
|
21045
21172
|
assertSafePath(archiveRoot);
|
|
21046
21173
|
const created = [];
|
|
21047
|
-
for (let path = archiveRoot;!existsSync9(path); path =
|
|
21174
|
+
for (let path = archiveRoot;!existsSync9(path); path = dirname22(path))
|
|
21048
21175
|
created.push(path);
|
|
21049
21176
|
mkdirSync8(archiveRoot, { recursive: true, mode: 448 });
|
|
21050
21177
|
try {
|
|
21051
21178
|
for (const directory2 of created)
|
|
21052
21179
|
syncArchiveDirectory(directory2);
|
|
21053
21180
|
if (created.length)
|
|
21054
|
-
syncArchiveDirectory(
|
|
21181
|
+
syncArchiveDirectory(dirname22(created.at(-1)));
|
|
21055
21182
|
writeArchiveJournal(receiptPath, journal);
|
|
21056
21183
|
for (const [index, entry] of selected.entries()) {
|
|
21057
21184
|
assertCurrent();
|
|
@@ -21064,7 +21191,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21064
21191
|
writeArchiveJournal(receiptPath, journal);
|
|
21065
21192
|
renameSync4(move.source, move.archive);
|
|
21066
21193
|
moved.push(move);
|
|
21067
|
-
syncArchiveDirectory(
|
|
21194
|
+
syncArchiveDirectory(dirname22(move.source));
|
|
21068
21195
|
syncArchiveDirectory(archiveRoot);
|
|
21069
21196
|
verifyArchive(move);
|
|
21070
21197
|
move.status = "archived";
|
|
@@ -21087,7 +21214,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21087
21214
|
recheckRootAliases(aliases);
|
|
21088
21215
|
assertSafePath(entry.source);
|
|
21089
21216
|
assertSafePath(entry.archive);
|
|
21090
|
-
if (
|
|
21217
|
+
if (lstatSync28(entry.source, { throwIfNoEntry: false })) {
|
|
21091
21218
|
entry.status = "recovery-required";
|
|
21092
21219
|
entry.conflict = "source-occupied";
|
|
21093
21220
|
continue;
|
|
@@ -21101,10 +21228,10 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21101
21228
|
}
|
|
21102
21229
|
if (entry.discoveryOnly) {
|
|
21103
21230
|
linkSync3(entry.archive, entry.source);
|
|
21104
|
-
syncArchiveDirectory(
|
|
21231
|
+
syncArchiveDirectory(dirname22(entry.source));
|
|
21105
21232
|
unlinkSync5(entry.archive);
|
|
21106
21233
|
} else {
|
|
21107
|
-
if (
|
|
21234
|
+
if (lstatSync28(entry.source, { throwIfNoEntry: false })) {
|
|
21108
21235
|
entry.status = "recovery-required";
|
|
21109
21236
|
entry.conflict = "source-occupied";
|
|
21110
21237
|
continue;
|
|
@@ -21112,7 +21239,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21112
21239
|
renameSync4(entry.archive, entry.source);
|
|
21113
21240
|
}
|
|
21114
21241
|
entry.status = "restored";
|
|
21115
|
-
syncArchiveDirectory(
|
|
21242
|
+
syncArchiveDirectory(dirname22(entry.source));
|
|
21116
21243
|
syncArchiveDirectory(archiveRoot);
|
|
21117
21244
|
} catch {
|
|
21118
21245
|
entry.status = "recovery-required";
|
|
@@ -21135,12 +21262,12 @@ function classifiedCodexPluginCacheCopy(entry, cache, read2, admit) {
|
|
|
21135
21262
|
try {
|
|
21136
21263
|
if (entry.agent !== "codex" || !entry.vendor || entry.bridge || entry.system)
|
|
21137
21264
|
return false;
|
|
21138
|
-
const document2 =
|
|
21265
|
+
const document2 = join31(entry.path, "SKILL.md");
|
|
21139
21266
|
const inside = relative9(cache, entry.path);
|
|
21140
|
-
if (!inside || isAbsolute24(inside) || inside.split(
|
|
21267
|
+
if (!inside || isAbsolute24(inside) || inside.split(sep14).some((segment) => segment === ".."))
|
|
21141
21268
|
return false;
|
|
21142
21269
|
assertSafePath(entry.path);
|
|
21143
|
-
if (!
|
|
21270
|
+
if (!lstatSync28(cache).isDirectory() || !lstatSync28(entry.path).isDirectory() || !lstatSync28(document2).isFile())
|
|
21144
21271
|
return false;
|
|
21145
21272
|
if (realpathSync12.native(cache) !== cache || realpathSync12.native(entry.path) !== entry.path || realpathSync12.native(document2) !== document2)
|
|
21146
21273
|
return false;
|
|
@@ -21159,7 +21286,7 @@ function isInertCodexPluginCacheCopy(entry, cache, allowedHostPaths, read2) {
|
|
|
21159
21286
|
}
|
|
21160
21287
|
function isInertCodexVendorPluginCopy(entry, cache, rules, read2) {
|
|
21161
21288
|
try {
|
|
21162
|
-
if (entry.managed ||
|
|
21289
|
+
if (entry.managed || lstatSync28(join31(entry.path, ".hasna-skills.json"), { throwIfNoEntry: false }))
|
|
21163
21290
|
return false;
|
|
21164
21291
|
const marketplace = codexVendorMarketplace(cache, entry.path);
|
|
21165
21292
|
if (!marketplace)
|
|
@@ -21177,7 +21304,7 @@ function isInertCodexVendorPluginCopy(entry, cache, rules, read2) {
|
|
|
21177
21304
|
try {
|
|
21178
21305
|
return realpathSync12.native(path);
|
|
21179
21306
|
} catch {
|
|
21180
|
-
return
|
|
21307
|
+
return resolve23(path);
|
|
21181
21308
|
}
|
|
21182
21309
|
};
|
|
21183
21310
|
const matches = (rule) => typeof rule?.name === "string" && rule.name.trim() === identity5.name || typeof rule?.path === "string" && [document2, entry.path].includes(canonical9(rule.path));
|
|
@@ -21197,12 +21324,78 @@ function nativeSkillPathList(paths, invisible = false) {
|
|
|
21197
21324
|
const remaining = paths.length - shown.length;
|
|
21198
21325
|
return `${shown.join(", ")}${remaining ? `; ${remaining} more` : ""}`;
|
|
21199
21326
|
}
|
|
21327
|
+
function nativeSkillRemediation(agent, blocking, context) {
|
|
21328
|
+
const quote2 = (value) => nativeSkillPathList([value], true);
|
|
21329
|
+
const codex = agent === "codex";
|
|
21330
|
+
const system = blocking.filter((entry) => codex && entry.system === true);
|
|
21331
|
+
const cacheCopies = blocking.filter((entry) => codex && !system.includes(entry) && entry.path.startsWith(context.cache + sep14));
|
|
21332
|
+
const nameRule = (entry) => {
|
|
21333
|
+
if (context.strict || codexVendorMarketplace(context.cache, entry.path) === null)
|
|
21334
|
+
return null;
|
|
21335
|
+
if (context.rules !== undefined && !Array.isArray(context.rules))
|
|
21336
|
+
return null;
|
|
21337
|
+
try {
|
|
21338
|
+
const identity5 = classifyCodexPluginCacheDocument(join31(entry.path, "SKILL.md"), context.cache, context.read);
|
|
21339
|
+
if (!identity5)
|
|
21340
|
+
return null;
|
|
21341
|
+
const rules = [...context.rules ?? [], { name: identity5.name, enabled: false }];
|
|
21342
|
+
return isInertCodexVendorPluginCopy(entry, context.cache, rules, context.read) ? identity5.name : null;
|
|
21343
|
+
} catch {
|
|
21344
|
+
return null;
|
|
21345
|
+
}
|
|
21346
|
+
};
|
|
21347
|
+
const named = cacheCopies.flatMap((entry) => {
|
|
21348
|
+
const name = nameRule(entry);
|
|
21349
|
+
return name ? [{ entry, name }] : [];
|
|
21350
|
+
});
|
|
21351
|
+
const unnamed = cacheCopies.filter((entry) => !named.some((item) => item.entry === entry));
|
|
21352
|
+
const refresh = unnamed.filter((entry) => context.strict || context.toleratedChange || context.enrolled(entry));
|
|
21353
|
+
const byPath = unnamed.filter((entry) => !refresh.includes(entry));
|
|
21354
|
+
const otherVendor = blocking.filter((entry) => entry.vendor && !system.includes(entry) && !cacheCopies.includes(entry));
|
|
21355
|
+
const user = blocking.filter((entry) => !entry.vendor && !system.includes(entry) && !cacheCopies.includes(entry));
|
|
21356
|
+
const install = (catalog) => {
|
|
21357
|
+
const fresh = [...context.automatic ? [] : ["discovery review"], ...catalog ? ["native catalog"] : []];
|
|
21358
|
+
return `skills hook install --agent codex${catalog ? " --codex-native-catalog <reviewed-catalog>" : ""}${context.automatic ? "" : " --discovery-inputs <reviewed-file>"} --apply${fresh.length ? `, with a fresh ${fresh.join(" and ")}` : ""}`;
|
|
21359
|
+
};
|
|
21360
|
+
const followUp = (rows, entries, removesRules = false) => context.automatic || removesRules || !context.keepsConfig(rows) ? ` ${context.automatic ? "This automatic binding" : "The reviewed binding"} records that file, so follow the edit with ${install(entries.some(context.enrolled))}.` : "";
|
|
21361
|
+
const row = (selector) => `[[skills.config]]
|
|
21362
|
+
${selector}
|
|
21363
|
+
enabled = false
|
|
21364
|
+
`;
|
|
21365
|
+
const steps = [];
|
|
21366
|
+
if (user.length)
|
|
21367
|
+
steps.push(`For user and project copies, review skills migrate native --agent ${agent} --project <working-directory> --include-unmanaged${context.aliases ? " --allow-root-aliases" : ""} --json before continuing; it inventories that directory and its ancestors. Use --apply after reviewing the archive plan.`);
|
|
21368
|
+
if (named.length) {
|
|
21369
|
+
const names = [...new Set(named.map((item) => item.name))];
|
|
21370
|
+
const rows = names.map((name) => row(`name = ${JSON.stringify(name)}`));
|
|
21371
|
+
steps.push(`Codex refreshes OpenAI marketplace copies on its own, so disable each in Codex instead: add [[skills.config]] with name = ${nativeSkillPathList(names, true)}${names.length > 1 ? " (one rule per name)" : ""} and enabled = false to ${quote2(context.config)}. Codex then never loads it and the Codex prompt hook accepts it.${followUp(rows, named.map((item) => item.entry))}`);
|
|
21372
|
+
}
|
|
21373
|
+
if (byPath.length) {
|
|
21374
|
+
const rules = Array.isArray(context.rules) ? context.rules : [];
|
|
21375
|
+
const enabling = rules.some((rule) => rule?.enabled !== false && (typeof rule?.name === "string" || typeof rule?.path === "string" && byPath.some((entry) => [entry.path, join31(entry.path, "SKILL.md")].includes(resolve23(rule.path)))));
|
|
21376
|
+
const rows = byPath.map((entry) => row(`path = ${JSON.stringify(join31(entry.path, "SKILL.md"))}`));
|
|
21377
|
+
steps.push(`For other Codex plugin-cache copies, add [[skills.config]] with the exact path of each SKILL.md and enabled = false to ${quote2(context.config)}, or remove the plugin in Codex.${enabling ? " First remove the [[skills.config]] rules there that enable skills; no path rule counts while one does." : ""}${followUp(rows, byPath, enabling)}`);
|
|
21378
|
+
}
|
|
21379
|
+
if (system.length || refresh.length) {
|
|
21380
|
+
const reasons = [
|
|
21381
|
+
...system.length ? ["Codex system skills are never archived by skills migrate native"] : [],
|
|
21382
|
+
...context.strict && refresh.length ? ["this check, unlike the live Codex prompt hook, does not accept a hand-added Codex name rule for a plugin-cache copy"] : [],
|
|
21383
|
+
...!context.strict && context.toleratedChange && refresh.length ? ["plugin-cache copies keep their exact-path exemption only while the recorded configuration is unchanged"] : [],
|
|
21384
|
+
...refresh.some(context.enrolled) ? ["only a fresh native catalog review exempts a skill of a plugin Skills has enrolled"] : []
|
|
21385
|
+
];
|
|
21386
|
+
const sentence = reasons.join("; ");
|
|
21387
|
+
steps.push(`${sentence[0].toUpperCase()}${sentence.slice(1)}, so refresh the binding instead: ${install(refresh.some(context.enrolled))}. It disables and records each copy.`);
|
|
21388
|
+
}
|
|
21389
|
+
if (otherVendor.length)
|
|
21390
|
+
steps.push(`For other vendor copies, review skills migrate native --agent ${agent} --project <working-directory> --include-vendor --discovery-inputs <reviewed-file> --json; --include-vendor requires a reviewed --discovery-inputs file when a configured plugin declares hooks.`);
|
|
21391
|
+
return steps.join(" ");
|
|
21392
|
+
}
|
|
21200
21393
|
function assertManagedAgentBridge(agent, options = {}) {
|
|
21201
21394
|
return assertManagedAgentBridgeWithDiscovery(agent, options);
|
|
21202
21395
|
}
|
|
21203
21396
|
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
21204
|
-
const home =
|
|
21205
|
-
assertSafePath(
|
|
21397
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
21398
|
+
assertSafePath(join31(dataDir2, "agent-policy.json"));
|
|
21206
21399
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
21207
21400
|
if (!snapshot2)
|
|
21208
21401
|
throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
|
|
@@ -21213,11 +21406,11 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21213
21406
|
if (!Array.isArray(aliases))
|
|
21214
21407
|
throw new Error("NATIVE_SKILL_DRIFT: invalid root alias binding");
|
|
21215
21408
|
recheckRootAliases(aliases);
|
|
21216
|
-
const expected = canonicalAgentPath(
|
|
21409
|
+
const expected = canonicalAgentPath(join31(nativeAgentRoot(home, agent, options.projectDir ?? process.cwd()), CLI_BRIDGE_NAME), aliases);
|
|
21217
21410
|
if (!isOwnedCliBridge(expected, [expected]))
|
|
21218
21411
|
throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
|
|
21219
21412
|
const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
|
|
21220
|
-
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${
|
|
21413
|
+
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep14}.agents${sep14}skills${sep14}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
|
|
21221
21414
|
const discovery = provenDiscovery ?? binding.discovery?.[agent];
|
|
21222
21415
|
assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
|
|
21223
21416
|
for (const source of discovery?.sources ?? [])
|
|
@@ -21234,14 +21427,14 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21234
21427
|
throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
|
|
21235
21428
|
if (agent === "hermes") {
|
|
21236
21429
|
const supervisor = binding.supervisors?.hermes;
|
|
21237
|
-
if (supervisor?.path !==
|
|
21430
|
+
if (supervisor?.path !== join31(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha4(renderHermesSupervisor(command, profile)))
|
|
21238
21431
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
21239
21432
|
assertHermesProtection(home, config, command, profile, supervisor);
|
|
21240
21433
|
} else if (agent === "sumi") {
|
|
21241
|
-
if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || !isManagedSumiPlugin(readOptional(
|
|
21434
|
+
if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || !isManagedSumiPlugin(readOptional(join31(sumiConfigDirectory(home, process.env, options.projectDir ?? process.cwd()), "plugins", "skills-cli.js")), command, profile))
|
|
21242
21435
|
throw new Error("NATIVE_SKILL_DRIFT: Sumi bridge protection changed; run skills hook install");
|
|
21243
21436
|
} else if (agent === "opencode") {
|
|
21244
|
-
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(
|
|
21437
|
+
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(join31(home, ".config", "opencode", "plugins", "skills-cli.js")) !== renderOpenCodePlugin(command, profile))
|
|
21245
21438
|
throw new Error("NATIVE_SKILL_DRIFT: OpenCode bridge protection changed; run skills hook install");
|
|
21246
21439
|
} else {
|
|
21247
21440
|
const required = {};
|
|
@@ -21258,8 +21451,8 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21258
21451
|
if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
|
|
21259
21452
|
throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
|
|
21260
21453
|
}
|
|
21261
|
-
const codexPath = canonicalAgentPath(
|
|
21262
|
-
const vendorCache = canonicalAgentPath(
|
|
21454
|
+
const codexPath = canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases);
|
|
21455
|
+
const vendorCache = canonicalAgentPath(join31(home, ".codex", "plugins", "cache"), aliases);
|
|
21263
21456
|
const witnessTolerance = agent === "codex" && options.codexPromptHook === true ? {
|
|
21264
21457
|
changes: [],
|
|
21265
21458
|
acceptsInput: (source) => source.path === codexPath && codexVendorConfigChangeOnly(source, vendorCache),
|
|
@@ -21270,40 +21463,40 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21270
21463
|
const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
|
|
21271
21464
|
if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
|
|
21272
21465
|
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
21273
|
-
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path,
|
|
21466
|
+
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join31(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
21274
21467
|
if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
21275
21468
|
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
21276
|
-
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(
|
|
21469
|
+
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), codexConfig.skills?.config ?? []))
|
|
21277
21470
|
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
|
|
21278
21471
|
const disabledPlugin = (entry) => {
|
|
21279
21472
|
if (agent !== "codex" || !entry.vendor)
|
|
21280
21473
|
return false;
|
|
21281
|
-
const document2 =
|
|
21474
|
+
const document2 = join31(entry.path, "SKILL.md"), cache = canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024));
|
|
21282
21475
|
return isReviewedCodexPluginInactive(document2, cache, binding.codexInactivePlugins ?? [], codexConfig.plugins, codexConfig.skills?.config ?? [], read2) || isReviewedCodexPluginSkillDisabled(document2, cache, binding.codexPluginSkills ?? [], codexConfig.skills?.config, read2);
|
|
21283
21476
|
};
|
|
21284
|
-
const inertVendorCopy = (entry) => agent === "codex" && options.codexPromptHook === true && entry.vendor && entry.path.startsWith(vendorCache +
|
|
21285
|
-
const vendorOnlyCacheCopy = (entry) => (witnessTolerance?.changes.length ?? 0) > 0 && entry.path.startsWith(vendorCache +
|
|
21477
|
+
const inertVendorCopy = (entry) => agent === "codex" && options.codexPromptHook === true && entry.vendor && entry.path.startsWith(vendorCache + sep14) && isInertCodexVendorPluginCopy(entry, vendorCache, codexConfig.skills?.config, (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
|
|
21478
|
+
const vendorOnlyCacheCopy = (entry) => (witnessTolerance?.changes.length ?? 0) > 0 && entry.path.startsWith(vendorCache + sep14);
|
|
21286
21479
|
const disabledPaths = new Set(disabledCodexSkillPaths(codexConfig, aliases));
|
|
21287
21480
|
const disabledVendorSkill = (entry) => {
|
|
21288
21481
|
if (agent !== "codex" || !entry.vendor)
|
|
21289
21482
|
return false;
|
|
21290
21483
|
if (vendorOnlyCacheCopy(entry))
|
|
21291
21484
|
return false;
|
|
21292
|
-
if ((binding.codexPluginSkills ?? []).some((control) => entry.path.startsWith(control.pluginParent +
|
|
21485
|
+
if ((binding.codexPluginSkills ?? []).some((control) => entry.path.startsWith(control.pluginParent + sep14)))
|
|
21293
21486
|
return disabledPlugin(entry);
|
|
21294
21487
|
if (disabledPlugin(entry))
|
|
21295
21488
|
return true;
|
|
21296
21489
|
if (!disabledPaths.has(entry.path))
|
|
21297
21490
|
return false;
|
|
21298
|
-
if (entry.path.startsWith(canonicalAgentPath(
|
|
21491
|
+
if (entry.path.startsWith(canonicalAgentPath(join31(home, ".codex", "plugins", "cache"), aliases) + sep14))
|
|
21299
21492
|
return true;
|
|
21300
|
-
const runtimeCache = canonicalAgentPath(
|
|
21301
|
-
if (entry.path.startsWith(runtimeCache +
|
|
21493
|
+
const runtimeCache = canonicalAgentPath(join31(home, ".cache", "codex-runtimes"), aliases);
|
|
21494
|
+
if (entry.path.startsWith(runtimeCache + sep14) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep14)))
|
|
21302
21495
|
return true;
|
|
21303
|
-
const bundledMarketplaces = canonicalAgentPath(
|
|
21304
|
-
if (entry.path.startsWith(bundledMarketplaces +
|
|
21496
|
+
const bundledMarketplaces = canonicalAgentPath(join31(home, ".codex", ".tmp", "bundled-marketplaces"), aliases);
|
|
21497
|
+
if (entry.path.startsWith(bundledMarketplaces + sep14) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep14)))
|
|
21305
21498
|
return true;
|
|
21306
|
-
if (!entry.path.startsWith(canonicalAgentPath(
|
|
21499
|
+
if (!entry.path.startsWith(canonicalAgentPath(join31(home, ".codex", "skills", ".system"), aliases) + sep14))
|
|
21307
21500
|
return false;
|
|
21308
21501
|
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
21309
21502
|
};
|
|
@@ -21325,7 +21518,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21325
21518
|
let installationInputRoots = [];
|
|
21326
21519
|
if (agent === "codex") {
|
|
21327
21520
|
try {
|
|
21328
|
-
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(
|
|
21521
|
+
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), discovery.codexInstallationInputs?.plugins ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
|
|
21329
21522
|
} catch {
|
|
21330
21523
|
throw new Error("NATIVE_SKILL_DRIFT: native installation input identity changed; review discovery and catalog");
|
|
21331
21524
|
}
|
|
@@ -21335,7 +21528,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21335
21528
|
let adapterRefusal, inert = [];
|
|
21336
21529
|
if (agent === "codex" && unexpected.length && options.codexNativePolicy) {
|
|
21337
21530
|
const { inspector, helperRunner, deadlineMs, ...envelope } = options.codexNativePolicy;
|
|
21338
|
-
const cache = canonicalAgentPath(
|
|
21531
|
+
const cache = canonicalAgentPath(join31(home, ".codex", "plugins", "cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), bridgeDocument = join31(expected, "SKILL.md");
|
|
21339
21532
|
try {
|
|
21340
21533
|
const claimed = parseCodexNativeHookEnvelope(envelope, bridgeDocument).policy;
|
|
21341
21534
|
inert = unexpected.filter((entry) => isInertCodexPluginCacheCopy(entry, cache, claimed.allowedHostPaths, read2));
|
|
@@ -21354,7 +21547,18 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21354
21547
|
}
|
|
21355
21548
|
if (unexpected.length > inert.length + inertVendor.length) {
|
|
21356
21549
|
const blocking = unexpected.filter((entry) => !inertVendor.includes(entry));
|
|
21357
|
-
|
|
21550
|
+
const remediation = nativeSkillRemediation(agent, blocking, { cache: vendorCache, config: codexPath, aliases: aliases.length > 0, strict: options.codexPromptHook !== true, automatic: discovery.method === "automatic", toleratedChange: (witnessTolerance?.changes.length ?? 0) > 0, rules: codexConfig.skills?.config, enrolled: (entry) => (binding.codexPluginSkills ?? []).some((control) => entry.path.startsWith(control.pluginParent + sep14)), keepsConfig: (rows) => {
|
|
21551
|
+
try {
|
|
21552
|
+
const text2 = readOptional(codexPath) ?? "";
|
|
21553
|
+
return discoveryTextKeepsWitness(discovery, codexPath, `${text2.trimEnd()}${text2.trim() ? `
|
|
21554
|
+
|
|
21555
|
+
` : ""}${rows.join(`
|
|
21556
|
+
`)}`);
|
|
21557
|
+
} catch {
|
|
21558
|
+
return false;
|
|
21559
|
+
}
|
|
21560
|
+
}, read: (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)) });
|
|
21561
|
+
throw new Error(`NATIVE_SKILL_DRIFT: ${blocking.length} unexpected native skill copies were found: ${nativeSkillPathList(blocking.map((entry) => entry.path))}. ${remediation}${adapterRefusal ? ` Native policy adapter refused: ${adapterRefusal}` : ""}`);
|
|
21358
21562
|
}
|
|
21359
21563
|
recheckRootAliases(aliases);
|
|
21360
21564
|
const discoveryChanges = (witnessTolerance?.changes ?? []).filter((change, index, all) => all.findIndex((other) => other.kind === change.kind && other.path === change.path) === index);
|
|
@@ -21378,11 +21582,11 @@ function isSkillsOwnershipMarker(marker) {
|
|
|
21378
21582
|
return typeof marker === "object" && marker !== null && Object.hasOwn(marker, "managedBy") && marker.managedBy === SYNC_MARKER_MANAGED_BY;
|
|
21379
21583
|
}
|
|
21380
21584
|
function hasSkillsOwnershipMarker(dir) {
|
|
21381
|
-
const path =
|
|
21585
|
+
const path = join32(dir, SYNC_MARKER_FILE);
|
|
21382
21586
|
try {
|
|
21383
|
-
if (!
|
|
21587
|
+
if (!lstatSync29(path).isFile())
|
|
21384
21588
|
return false;
|
|
21385
|
-
const marker = JSON.parse(
|
|
21589
|
+
const marker = JSON.parse(readFileSync20(path, "utf8"));
|
|
21386
21590
|
return isSkillsOwnershipMarker(marker);
|
|
21387
21591
|
} catch {
|
|
21388
21592
|
return false;
|
|
@@ -21402,9 +21606,9 @@ function resolveSyncAgents(arg) {
|
|
|
21402
21606
|
function agentGlobalSkillsDir(agent, homeDir2 = homedir3()) {
|
|
21403
21607
|
switch (agent) {
|
|
21404
21608
|
case "opencode":
|
|
21405
|
-
return
|
|
21609
|
+
return join32(homeDir2, ".config", "opencode", "skills");
|
|
21406
21610
|
default:
|
|
21407
|
-
return
|
|
21611
|
+
return join32(homeDir2, `.${agent}`, "skills");
|
|
21408
21612
|
}
|
|
21409
21613
|
}
|
|
21410
21614
|
function adaptSkillMdForAgent(skillMd, agent) {
|
|
@@ -21463,7 +21667,7 @@ function resolveSyncCorpus(options = {}) {
|
|
|
21463
21667
|
function packageSourceRoots(source) {
|
|
21464
21668
|
const roots = [];
|
|
21465
21669
|
for (const sub of ["skills"]) {
|
|
21466
|
-
const candidate =
|
|
21670
|
+
const candidate = join32(source, sub);
|
|
21467
21671
|
if (existsSync10(candidate) && isDirectory(candidate))
|
|
21468
21672
|
roots.push(candidate);
|
|
21469
21673
|
}
|
|
@@ -21479,10 +21683,10 @@ function containsSkillDirectories(path) {
|
|
|
21479
21683
|
return false;
|
|
21480
21684
|
}
|
|
21481
21685
|
return entries.some((entry) => {
|
|
21482
|
-
const candidate =
|
|
21686
|
+
const candidate = join32(path, entry);
|
|
21483
21687
|
if (!isDirectory(candidate))
|
|
21484
21688
|
return false;
|
|
21485
|
-
return existsSync10(
|
|
21689
|
+
return existsSync10(join32(candidate, "SKILL.md")) || existsSync10(join32(candidate, "skill.json")) || existsSync10(join32(candidate, "package.json"));
|
|
21486
21690
|
});
|
|
21487
21691
|
}
|
|
21488
21692
|
function isDirectory(path) {
|
|
@@ -21522,7 +21726,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
21522
21726
|
actions.push({
|
|
21523
21727
|
skill: name,
|
|
21524
21728
|
agent,
|
|
21525
|
-
path:
|
|
21729
|
+
path: join32(agentGlobalSkillsDir(agent, homeDir2), name, "SKILL.md"),
|
|
21526
21730
|
action: "skip",
|
|
21527
21731
|
reason: "not found in this machine's corpus"
|
|
21528
21732
|
});
|
|
@@ -21554,7 +21758,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
21554
21758
|
}
|
|
21555
21759
|
function writeManagedAgentSkill(params) {
|
|
21556
21760
|
const homeDir2 = params.homeDir ?? homedir3();
|
|
21557
|
-
const dir =
|
|
21761
|
+
const dir = join32(agentGlobalSkillsDir(params.agent, homeDir2), params.skill);
|
|
21558
21762
|
const result = writeManagedSkillDir(dir, params.skillMd, {
|
|
21559
21763
|
skill: params.skill,
|
|
21560
21764
|
source: params.source,
|
|
@@ -21577,8 +21781,8 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
21577
21781
|
return withCodexCorpusWrite(root ? [root] : [], (assertCurrent) => writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent), options);
|
|
21578
21782
|
}
|
|
21579
21783
|
function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
21580
|
-
const skillMdPath =
|
|
21581
|
-
const markerPath =
|
|
21784
|
+
const skillMdPath = join32(dir, "SKILL.md");
|
|
21785
|
+
const markerPath = join32(dir, SYNC_MARKER_FILE);
|
|
21582
21786
|
const dirExists = existsSync10(dir);
|
|
21583
21787
|
const managed = hasSkillsOwnershipMarker(dir);
|
|
21584
21788
|
const hasSkillMd = existsSync10(skillMdPath);
|
|
@@ -21599,7 +21803,7 @@ function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
|
21599
21803
|
if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
|
|
21600
21804
|
let existingIsStub = false;
|
|
21601
21805
|
try {
|
|
21602
|
-
existingIsStub = isPointerSkillMd(
|
|
21806
|
+
existingIsStub = isPointerSkillMd(readFileSync20(skillMdPath, "utf-8"));
|
|
21603
21807
|
} catch {
|
|
21604
21808
|
existingIsStub = false;
|
|
21605
21809
|
}
|
|
@@ -21614,13 +21818,13 @@ function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
|
21614
21818
|
const action = dirExists ? "update" : "create";
|
|
21615
21819
|
if (options.dryRun)
|
|
21616
21820
|
return { action, path: skillMdPath };
|
|
21617
|
-
const parentDir =
|
|
21821
|
+
const parentDir = dirname23(dir);
|
|
21618
21822
|
mkdirSync9(parentDir, { recursive: true });
|
|
21619
|
-
const transactionDir = mkdtempSync(
|
|
21620
|
-
const candidateDir =
|
|
21621
|
-
const backupDir =
|
|
21622
|
-
const candidateSkillMdPath =
|
|
21623
|
-
const candidateMarkerPath =
|
|
21823
|
+
const transactionDir = mkdtempSync(join32(parentDir, `.hasna-skills-write-${basename12(dir)}-`));
|
|
21824
|
+
const candidateDir = join32(transactionDir, "candidate");
|
|
21825
|
+
const backupDir = join32(transactionDir, "backup");
|
|
21826
|
+
const candidateSkillMdPath = join32(candidateDir, "SKILL.md");
|
|
21827
|
+
const candidateMarkerPath = join32(candidateDir, SYNC_MARKER_FILE);
|
|
21624
21828
|
const marker = {
|
|
21625
21829
|
managedBy: SYNC_MARKER_MANAGED_BY,
|
|
21626
21830
|
skill: options.skill,
|
|
@@ -21649,7 +21853,7 @@ function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
|
21649
21853
|
assertCurrent();
|
|
21650
21854
|
renameDirectory(candidateDir, dir);
|
|
21651
21855
|
assertCurrent();
|
|
21652
|
-
if (
|
|
21856
|
+
if (readFileSync20(skillMdPath, "utf8") !== (skillMd.endsWith(`
|
|
21653
21857
|
`) ? skillMd : `${skillMd}
|
|
21654
21858
|
`))
|
|
21655
21859
|
throw new Error("Managed skill readback failed");
|
|
@@ -21678,12 +21882,12 @@ function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
|
21678
21882
|
return { action, path: skillMdPath };
|
|
21679
21883
|
}
|
|
21680
21884
|
function removeManagedAgentSkill(skill, agent, homeDir2 = homedir3(), options = {}) {
|
|
21681
|
-
const dir =
|
|
21885
|
+
const dir = join32(agentGlobalSkillsDir(agent, homeDir2), skill);
|
|
21682
21886
|
const root = codexCorpusRootForPath(dir);
|
|
21683
21887
|
return withCodexCorpusWrite(root ? [root] : [], () => removeManagedAgentSkillUnlocked(skill, agent, homeDir2, options), options);
|
|
21684
21888
|
}
|
|
21685
21889
|
function removeManagedAgentSkillUnlocked(skill, agent, homeDir2 = homedir3(), options = {}) {
|
|
21686
|
-
const dir =
|
|
21890
|
+
const dir = join32(agentGlobalSkillsDir(agent, homeDir2), skill);
|
|
21687
21891
|
if (!hasSkillsOwnershipMarker(dir))
|
|
21688
21892
|
return false;
|
|
21689
21893
|
rmSync2(dir, { recursive: true, force: true });
|
|
@@ -21691,9 +21895,9 @@ function removeManagedAgentSkillUnlocked(skill, agent, homeDir2 = homedir3(), op
|
|
|
21691
21895
|
}
|
|
21692
21896
|
function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
|
|
21693
21897
|
if (kind === undefined || kind === "instruction" || preferBundledDocs) {
|
|
21694
|
-
const skillMdPath =
|
|
21898
|
+
const skillMdPath = join32(skillPath, "SKILL.md");
|
|
21695
21899
|
if (existsSync10(skillMdPath))
|
|
21696
|
-
return
|
|
21900
|
+
return readFileSync20(skillMdPath, "utf-8");
|
|
21697
21901
|
}
|
|
21698
21902
|
return pointerSkillMd(name, description);
|
|
21699
21903
|
}
|
|
@@ -21733,8 +21937,8 @@ function resolveSkillAlias(name) {
|
|
|
21733
21937
|
}
|
|
21734
21938
|
|
|
21735
21939
|
// src/lib/project-state.ts
|
|
21736
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as
|
|
21737
|
-
import { join as
|
|
21940
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync21, writeFileSync as writeFileSync8 } from "fs";
|
|
21941
|
+
import { join as join33 } from "path";
|
|
21738
21942
|
var VALID_PIN_SOURCES = [
|
|
21739
21943
|
"official",
|
|
21740
21944
|
"custom",
|
|
@@ -21749,17 +21953,17 @@ var SKILLS_PROJECT_DIR = ".skills";
|
|
|
21749
21953
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
21750
21954
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
21751
21955
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
21752
|
-
return
|
|
21956
|
+
return join33(targetDir, SKILLS_PROJECT_DIR);
|
|
21753
21957
|
}
|
|
21754
21958
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
21755
|
-
return
|
|
21959
|
+
return join33(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
21756
21960
|
}
|
|
21757
21961
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
21758
21962
|
const path = getProjectConfigPath(targetDir);
|
|
21759
21963
|
if (!existsSync11(path))
|
|
21760
21964
|
return null;
|
|
21761
21965
|
try {
|
|
21762
|
-
return normalizeProjectConfig(JSON.parse(
|
|
21966
|
+
return normalizeProjectConfig(JSON.parse(readFileSync21(path, "utf-8")));
|
|
21763
21967
|
} catch {
|
|
21764
21968
|
return null;
|
|
21765
21969
|
}
|
|
@@ -21881,7 +22085,7 @@ function getSkillPath(name) {
|
|
|
21881
22085
|
const extensionPath = findExtensionSkillPath(skillName);
|
|
21882
22086
|
if (extensionPath)
|
|
21883
22087
|
return extensionPath;
|
|
21884
|
-
return
|
|
22088
|
+
return join34(getPortableSkillsRoot(), skillName);
|
|
21885
22089
|
}
|
|
21886
22090
|
function getCanonicalSkillName(name) {
|
|
21887
22091
|
return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
|
|
@@ -21944,9 +22148,9 @@ function createLocalSkillManifest(name, generateSkillMd) {
|
|
|
21944
22148
|
if (!existsSync12(sourcePath))
|
|
21945
22149
|
return null;
|
|
21946
22150
|
let skillMd = "";
|
|
21947
|
-
const skillMdPath =
|
|
22151
|
+
const skillMdPath = join34(sourcePath, "SKILL.md");
|
|
21948
22152
|
if (existsSync12(skillMdPath)) {
|
|
21949
|
-
skillMd =
|
|
22153
|
+
skillMd = readFileSync23(skillMdPath, "utf-8");
|
|
21950
22154
|
} else if (generateSkillMd) {
|
|
21951
22155
|
skillMd = generateSkillMd(name) ?? "";
|
|
21952
22156
|
} else {
|
|
@@ -22018,16 +22222,16 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
|
22018
22222
|
const base = projectDir || process.cwd();
|
|
22019
22223
|
switch (agent) {
|
|
22020
22224
|
case "pi":
|
|
22021
|
-
return scope === "project" ?
|
|
22225
|
+
return scope === "project" ? join34(base, ".pi", "skills") : join34(homedir4(), ".pi", "agent", "skills");
|
|
22022
22226
|
case "opencode":
|
|
22023
|
-
return scope === "project" ?
|
|
22227
|
+
return scope === "project" ? join34(base, ".opencode", "skills") : join34(homedir4(), ".config", "opencode", "skills");
|
|
22024
22228
|
default:
|
|
22025
|
-
return scope === "project" ?
|
|
22229
|
+
return scope === "project" ? join34(base, `.${agent}`, "skills") : join34(homedir4(), `.${agent}`, "skills");
|
|
22026
22230
|
}
|
|
22027
22231
|
}
|
|
22028
22232
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
22029
22233
|
const skillName = normalizeSkillName(getCanonicalSkillName(name));
|
|
22030
|
-
return
|
|
22234
|
+
return join34(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
22031
22235
|
}
|
|
22032
22236
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
22033
22237
|
assertNativeExportAllowed();
|
|
@@ -22070,9 +22274,9 @@ function removeSkillForAgentUnlocked(name, options) {
|
|
|
22070
22274
|
}
|
|
22071
22275
|
function resolveAgentSkillMd(name, generateSkillMd) {
|
|
22072
22276
|
const sourcePath = getSkillPath(name);
|
|
22073
|
-
const skillMdPath =
|
|
22277
|
+
const skillMdPath = join34(sourcePath, "SKILL.md");
|
|
22074
22278
|
if (existsSync12(skillMdPath))
|
|
22075
|
-
return
|
|
22279
|
+
return readFileSync23(skillMdPath, "utf-8");
|
|
22076
22280
|
if (generateSkillMd)
|
|
22077
22281
|
return generateSkillMd(name);
|
|
22078
22282
|
return generateMinimalSkillMd(name);
|
|
@@ -22105,7 +22309,7 @@ function generateMinimalSkillMd(name) {
|
|
|
22105
22309
|
"---",
|
|
22106
22310
|
""
|
|
22107
22311
|
].filter(Boolean);
|
|
22108
|
-
const fallbackDoc = readFileIfExists(
|
|
22312
|
+
const fallbackDoc = readFileIfExists(join34(sourcePath, "README.md")) || readFileIfExists(join34(sourcePath, "CLAUDE.md"));
|
|
22109
22313
|
if (fallbackDoc)
|
|
22110
22314
|
return `${frontmatter.join(`
|
|
22111
22315
|
`)}${fallbackDoc.trim()}
|
|
@@ -22124,18 +22328,18 @@ skills run ${canonicalName}
|
|
|
22124
22328
|
`;
|
|
22125
22329
|
}
|
|
22126
22330
|
function readBundledSkillVersion(name) {
|
|
22127
|
-
const pkgPath =
|
|
22331
|
+
const pkgPath = join34(getSkillPath(name), "package.json");
|
|
22128
22332
|
if (!existsSync12(pkgPath))
|
|
22129
22333
|
return "unknown";
|
|
22130
22334
|
try {
|
|
22131
|
-
const pkg = JSON.parse(
|
|
22335
|
+
const pkg = JSON.parse(readFileSync23(pkgPath, "utf-8"));
|
|
22132
22336
|
return pkg.version || "unknown";
|
|
22133
22337
|
} catch {
|
|
22134
22338
|
return "unknown";
|
|
22135
22339
|
}
|
|
22136
22340
|
}
|
|
22137
22341
|
function readFileIfExists(path) {
|
|
22138
|
-
return existsSync12(path) ?
|
|
22342
|
+
return existsSync12(path) ? readFileSync23(path, "utf-8") : null;
|
|
22139
22343
|
}
|
|
22140
22344
|
function loadProjectConfigCompat(targetDir) {
|
|
22141
22345
|
return loadProjectConfig(targetDir);
|
|
@@ -22179,11 +22383,11 @@ function selectSkillDoc(docs, file) {
|
|
|
22179
22383
|
function isInstructionSkillDir(skillPath, meta) {
|
|
22180
22384
|
if (meta?.kind === "instruction")
|
|
22181
22385
|
return true;
|
|
22182
|
-
const skillMdPath =
|
|
22386
|
+
const skillMdPath = join35(skillPath, "SKILL.md");
|
|
22183
22387
|
if (!existsSync13(skillMdPath))
|
|
22184
22388
|
return false;
|
|
22185
22389
|
try {
|
|
22186
|
-
return parseSkillFrontmatter(
|
|
22390
|
+
return parseSkillFrontmatter(readFileSync25(skillMdPath, "utf-8"))?.kind === "instruction";
|
|
22187
22391
|
} catch {
|
|
22188
22392
|
return false;
|
|
22189
22393
|
}
|
|
@@ -22209,9 +22413,9 @@ function getSkillDocs(name) {
|
|
|
22209
22413
|
if (!existsSync13(skillPath))
|
|
22210
22414
|
return null;
|
|
22211
22415
|
return {
|
|
22212
|
-
skillMd: readIfExists(
|
|
22213
|
-
readme: readIfExists(
|
|
22214
|
-
claudeMd: readIfExists(
|
|
22416
|
+
skillMd: readIfExists(join35(skillPath, "SKILL.md")),
|
|
22417
|
+
readme: readIfExists(join35(skillPath, "README.md")),
|
|
22418
|
+
claudeMd: readIfExists(join35(skillPath, "CLAUDE.md"))
|
|
22215
22419
|
};
|
|
22216
22420
|
}
|
|
22217
22421
|
function getSkillBestDoc(name, file) {
|
|
@@ -22227,14 +22431,14 @@ function getSkillRequirements(name) {
|
|
|
22227
22431
|
return null;
|
|
22228
22432
|
const texts = [];
|
|
22229
22433
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
22230
|
-
const content = readIfExists(
|
|
22434
|
+
const content = readIfExists(join35(skillPath, file));
|
|
22231
22435
|
if (content)
|
|
22232
22436
|
texts.push(content);
|
|
22233
22437
|
}
|
|
22234
22438
|
const meta = getSkill(name);
|
|
22235
22439
|
let dependencies = {};
|
|
22236
22440
|
try {
|
|
22237
|
-
dependencies = JSON.parse(
|
|
22441
|
+
dependencies = JSON.parse(readFileSync25(join35(skillPath, "package.json"), "utf8")).dependencies || {};
|
|
22238
22442
|
} catch {}
|
|
22239
22443
|
return getSkillRequirementsFromContent(name, texts, dependencies, meta);
|
|
22240
22444
|
}
|
|
@@ -22296,13 +22500,13 @@ async function runSkill(name, args, options = {}) {
|
|
|
22296
22500
|
error: `Skill '${name}' is an instruction skill (kind: instruction) and is not runnable. Instruction skills are consumed by coding agents via SKILL.md, not executed with 'skills run'.`
|
|
22297
22501
|
};
|
|
22298
22502
|
}
|
|
22299
|
-
const pkgPath =
|
|
22503
|
+
const pkgPath = join35(skillPath, "package.json");
|
|
22300
22504
|
if (!existsSync13(pkgPath)) {
|
|
22301
22505
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
22302
22506
|
}
|
|
22303
22507
|
let entryPoint2;
|
|
22304
22508
|
try {
|
|
22305
|
-
const pkg = JSON.parse(
|
|
22509
|
+
const pkg = JSON.parse(readFileSync25(pkgPath, "utf-8"));
|
|
22306
22510
|
if (pkg.bin) {
|
|
22307
22511
|
const binValues = Object.values(pkg.bin);
|
|
22308
22512
|
entryPoint2 = binValues[0];
|
|
@@ -22316,12 +22520,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
22316
22520
|
} catch {
|
|
22317
22521
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
22318
22522
|
}
|
|
22319
|
-
const entryPath =
|
|
22523
|
+
const entryPath = join35(skillPath, entryPoint2);
|
|
22320
22524
|
if (!existsSync13(entryPath)) {
|
|
22321
22525
|
return { exitCode: 1, error: `Entry point '${entryPoint2}' not found in skill '${name}'` };
|
|
22322
22526
|
}
|
|
22323
22527
|
const env = { ...process.env, ...options.env };
|
|
22324
|
-
if (!existsSync13(
|
|
22528
|
+
if (!existsSync13(join35(skillPath, "node_modules")) || hasDependencyPreparationMarker(skillPath)) {
|
|
22325
22529
|
const failure = await prepareSkillDependencies(skillPath, env, options.preparationTimeoutMs);
|
|
22326
22530
|
if (failure)
|
|
22327
22531
|
return failure;
|
|
@@ -22399,13 +22603,13 @@ function generateSkillMd(name) {
|
|
|
22399
22603
|
"---"
|
|
22400
22604
|
].join(`
|
|
22401
22605
|
`);
|
|
22402
|
-
const readme = readIfExists(
|
|
22403
|
-
const claudeMd = readIfExists(
|
|
22606
|
+
const readme = readIfExists(join35(skillPath, "README.md"));
|
|
22607
|
+
const claudeMd = readIfExists(join35(skillPath, "CLAUDE.md"));
|
|
22404
22608
|
let cliCommand = null;
|
|
22405
|
-
const pkgPath =
|
|
22609
|
+
const pkgPath = join35(skillPath, "package.json");
|
|
22406
22610
|
if (existsSync13(pkgPath)) {
|
|
22407
22611
|
try {
|
|
22408
|
-
const pkg = JSON.parse(
|
|
22612
|
+
const pkg = JSON.parse(readFileSync25(pkgPath, "utf-8"));
|
|
22409
22613
|
if (pkg.bin) {
|
|
22410
22614
|
const binKeys = Object.keys(pkg.bin);
|
|
22411
22615
|
if (binKeys.length > 0)
|
|
@@ -22479,7 +22683,7 @@ function extractEnvVars(text2) {
|
|
|
22479
22683
|
function readIfExists(path) {
|
|
22480
22684
|
try {
|
|
22481
22685
|
if (existsSync13(path)) {
|
|
22482
|
-
return
|
|
22686
|
+
return readFileSync25(path, "utf-8");
|
|
22483
22687
|
}
|
|
22484
22688
|
} catch {}
|
|
22485
22689
|
return null;
|
|
@@ -22631,7 +22835,7 @@ function describeEntries(entries) {
|
|
|
22631
22835
|
|
|
22632
22836
|
// src/lib/plugin-admission.ts
|
|
22633
22837
|
function pluginAdmissionRoot() {
|
|
22634
|
-
return
|
|
22838
|
+
return join36(homedir5(), ".hasna", "skills", "plugin-admission");
|
|
22635
22839
|
}
|
|
22636
22840
|
function validatePluginTarget(value) {
|
|
22637
22841
|
pluginKeys(value, ["schemaVersion", "pluginId", "registrations", "native", "resolver"]);
|
|
@@ -22642,7 +22846,7 @@ function validatePluginTarget(value) {
|
|
|
22642
22846
|
for (const registration of value.registrations) {
|
|
22643
22847
|
pluginKeys(registration, ["scope", "projectPath"]);
|
|
22644
22848
|
pluginNeed(registration.scope === "user" || registration.scope === "project", "Unsupported plugin registration scope");
|
|
22645
|
-
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute25(registration.projectPath) &&
|
|
22849
|
+
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute25(registration.projectPath) && resolve24(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
|
|
22646
22850
|
const key2 = JSON.stringify([registration.scope, registration.projectPath]);
|
|
22647
22851
|
pluginNeed(!scopes.has(key2), "Duplicate plugin registration scope");
|
|
22648
22852
|
scopes.add(key2);
|
|
@@ -22650,7 +22854,7 @@ function validatePluginTarget(value) {
|
|
|
22650
22854
|
for (const key2 of ["native", "resolver"]) {
|
|
22651
22855
|
pluginKeys(value[key2], key2 === "native" ? ["version", "executable", "digest"] : ["executable", "digest"]);
|
|
22652
22856
|
pluginText(value[key2].executable);
|
|
22653
|
-
pluginNeed(isAbsolute25(value[key2].executable) &&
|
|
22857
|
+
pluginNeed(isAbsolute25(value[key2].executable) && resolve24(value[key2].executable) === value[key2].executable, "Plugin executable must have a canonical absolute path");
|
|
22654
22858
|
pluginDigest(value[key2].digest);
|
|
22655
22859
|
}
|
|
22656
22860
|
pluginNeed(["2.1.274", "2.1.276"].includes(value.native.version), "Plugin admission requires a certified Claude command-source runtime (2.1.274 or 2.1.276)");
|
|
@@ -22802,17 +23006,17 @@ function pluginResolverCommand(binding) {
|
|
|
22802
23006
|
}
|
|
22803
23007
|
function root(options) {
|
|
22804
23008
|
const path = options.storeRoot ?? pluginAdmissionRoot();
|
|
22805
|
-
pluginNeed(isAbsolute25(path) &&
|
|
23009
|
+
pluginNeed(isAbsolute25(path) && resolve24(path) === path, "Invalid plugin admission store root");
|
|
22806
23010
|
return path;
|
|
22807
23011
|
}
|
|
22808
23012
|
function pluginBindingPath(storeRoot, id) {
|
|
22809
23013
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
22810
|
-
return
|
|
23014
|
+
return join36(storeRoot, "bindings", `${id}.json`);
|
|
22811
23015
|
}
|
|
22812
23016
|
function pluginReceiptPath(storeRoot, id, digest2) {
|
|
22813
23017
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
22814
23018
|
pluginDigest(digest2);
|
|
22815
|
-
return
|
|
23019
|
+
return join36(storeRoot, "receipts", id, `${digest2.slice(7)}.json`);
|
|
22816
23020
|
}
|
|
22817
23021
|
function readPluginBinding(storeRoot, id) {
|
|
22818
23022
|
const value = readPluginJson(pluginBindingPath(storeRoot, id));
|
|
@@ -22832,7 +23036,7 @@ function readPluginAdmissionReceipt(storeRoot, id, digest2) {
|
|
|
22832
23036
|
pluginNeed(receipt.plan.bindingId === id && receipt.plan.planDigest === digest2, "Plugin receipt identity differs from its filename");
|
|
22833
23037
|
const binding = readPluginBinding(storeRoot, id);
|
|
22834
23038
|
pluginNeed(canonicalJson(binding) === canonicalJson(receipt.plan.binding) && receipt.plan.sourceCommand === pluginResolverCommand(binding), "Plugin receipt binding changed");
|
|
22835
|
-
pluginNeed(receipt.materializedPath ===
|
|
23039
|
+
pluginNeed(receipt.materializedPath === join36(storeRoot, "objects", id, digest2.slice(7)), "Plugin receipt points outside its immutable materialization");
|
|
22836
23040
|
pluginText(receipt.admittedAt, 64);
|
|
22837
23041
|
pluginNeed(Number.isFinite(Date.parse(receipt.admittedAt)), "Invalid plugin admission time");
|
|
22838
23042
|
return receipt;
|
|
@@ -22973,7 +23177,7 @@ async function admitPlugin(spec, profileId, target, approvedDigest, approvedEvid
|
|
|
22973
23177
|
const { plan, projection } = await candidate(spec, profileId, target, options);
|
|
22974
23178
|
pluginNeed(plan.planDigest === approvedDigest, "The plugin plan changed since review; obtain and review a new plan");
|
|
22975
23179
|
pluginNeed(plan.evidenceDigest === approvedEvidenceDigest, "The plugin routing evidence changed since review; obtain and review a new plan");
|
|
22976
|
-
const store = root(options), materializedPath =
|
|
23180
|
+
const store = root(options), materializedPath = join36(store, "objects", plan.bindingId, plan.planDigest.slice(7));
|
|
22977
23181
|
materializePluginTree(materializedPath, projection.files);
|
|
22978
23182
|
writePluginJsonImmutable(pluginBindingPath(store, plan.bindingId), plan.binding);
|
|
22979
23183
|
const path = pluginReceiptPath(store, plan.bindingId, plan.planDigest), existing = readPluginJson(path);
|
|
@@ -23007,21 +23211,21 @@ function validateManagedPluginWitnesses(value) {
|
|
|
23007
23211
|
pluginNeed(typeof item.bindingId === "string" && /^[a-f0-9]{64}$/.test(item.bindingId) && !seen.has(item.bindingId), "Invalid or duplicate managed plugin witness");
|
|
23008
23212
|
seen.add(item.bindingId);
|
|
23009
23213
|
pluginText(item.storeRoot);
|
|
23010
|
-
pluginNeed(isAbsolute26(item.storeRoot) &&
|
|
23214
|
+
pluginNeed(isAbsolute26(item.storeRoot) && resolve25(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
|
|
23011
23215
|
}
|
|
23012
23216
|
}
|
|
23013
23217
|
function safe5(path) {
|
|
23014
23218
|
pluginText(path);
|
|
23015
|
-
pluginNeed(isAbsolute26(path) &&
|
|
23016
|
-
for (let at = path;; at =
|
|
23017
|
-
pluginNeed(!
|
|
23018
|
-
if (at ===
|
|
23219
|
+
pluginNeed(isAbsolute26(path) && resolve25(path) === path, "Invalid plugin registry path");
|
|
23220
|
+
for (let at = path;; at = dirname24(at)) {
|
|
23221
|
+
pluginNeed(!lstatSync30(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
23222
|
+
if (at === dirname24(at))
|
|
23019
23223
|
break;
|
|
23020
23224
|
}
|
|
23021
23225
|
}
|
|
23022
23226
|
function readRegistry(path) {
|
|
23023
23227
|
safe5(path);
|
|
23024
|
-
const initial =
|
|
23228
|
+
const initial = lstatSync30(path);
|
|
23025
23229
|
pluginNeed(initial.isFile() && initial.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
|
|
23026
23230
|
const fd = openSync18(path, constants19.O_RDONLY | constants19.O_NOFOLLOW | constants19.O_NONBLOCK);
|
|
23027
23231
|
try {
|
|
@@ -23035,7 +23239,7 @@ function readRegistry(path) {
|
|
|
23035
23239
|
break;
|
|
23036
23240
|
length += count;
|
|
23037
23241
|
}
|
|
23038
|
-
const after = fstatSync18(fd), current =
|
|
23242
|
+
const after = fstatSync18(fd), current = lstatSync30(path);
|
|
23039
23243
|
safe5(path);
|
|
23040
23244
|
pluginNeed(length <= 1024 * 1024 && length === opened.size && after.size === opened.size && after.mtimeMs === opened.mtimeMs && after.ctimeMs === opened.ctimeMs && current.dev === opened.dev && current.ino === opened.ino && current.mtimeMs === opened.mtimeMs && current.ctimeMs === opened.ctimeMs, "Plugin registry changed during read");
|
|
23041
23245
|
let value;
|
|
@@ -23065,7 +23269,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
23065
23269
|
seen.add(target.pluginId);
|
|
23066
23270
|
const rows = plugins[target.pluginId];
|
|
23067
23271
|
pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
|
|
23068
|
-
const [name, market] = target.pluginId.split("@"), cacheRoot =
|
|
23272
|
+
const [name, market] = target.pluginId.split("@"), cacheRoot = join37(dirname24(path), "cache", market, name);
|
|
23069
23273
|
const scopes = new Set, approvals = new Map, currentFiles = new Map;
|
|
23070
23274
|
plugins[target.pluginId] = rows.map((row) => {
|
|
23071
23275
|
pluginNeed(pluginObject(row), "Invalid managed plugin registration");
|
|
@@ -23078,7 +23282,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
23078
23282
|
pluginText(row.sourceProducerPath);
|
|
23079
23283
|
pluginText(row.lastUpdated, 64);
|
|
23080
23284
|
pluginNeed(Number.isFinite(Date.parse(row.lastUpdated)), "Invalid managed plugin update timestamp");
|
|
23081
|
-
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath ===
|
|
23285
|
+
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join37(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
|
|
23082
23286
|
const approval = (producer) => {
|
|
23083
23287
|
pluginText(producer);
|
|
23084
23288
|
const digest2 = `sha256:${basename13(producer)}`;
|
|
@@ -23108,7 +23312,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
23108
23312
|
return Object.fromEntries(Object.entries(row).filter(([key2]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key2)));
|
|
23109
23313
|
});
|
|
23110
23314
|
safe5(cacheRoot);
|
|
23111
|
-
const before =
|
|
23315
|
+
const before = lstatSync30(cacheRoot);
|
|
23112
23316
|
pluginNeed(before.isDirectory(), "Invalid managed plugin cache root");
|
|
23113
23317
|
const directory2 = opendirSync5(cacheRoot), names = [];
|
|
23114
23318
|
try {
|
|
@@ -23121,17 +23325,17 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
23121
23325
|
directory2.closeSync();
|
|
23122
23326
|
}
|
|
23123
23327
|
for (const version2 of names.sort()) {
|
|
23124
|
-
const files = pluginFileWitnesses(snapshotPluginTree(
|
|
23328
|
+
const files = pluginFileWitnesses(snapshotPluginTree(join37(cacheRoot, version2), { nativeRuntime: `claude-${target.native.version}` }));
|
|
23125
23329
|
verifiedBytes += files.reduce((total, file) => total + file.size, 0);
|
|
23126
23330
|
pluginNeed(verifiedBytes <= 256 * 1024 * 1024, "Managed plugin verification exceeds its aggregate byte budget");
|
|
23127
23331
|
const serialized = JSON.stringify(files);
|
|
23128
|
-
const expected = currentFiles.get(
|
|
23332
|
+
const expected = currentFiles.get(join37(cacheRoot, version2));
|
|
23129
23333
|
pluginNeed(expected === undefined || serialized === expected, "Current native cache differs from its exact registered producer receipt");
|
|
23130
23334
|
pluginNeed([...approvals.values()].some((receipt) => nativeVersionMatches(version2, receipt) && JSON.stringify(receipt.plan.files) === serialized), "A retained native plugin cache version differs from every approved projection");
|
|
23131
23335
|
}
|
|
23132
23336
|
for (const row of rows)
|
|
23133
23337
|
pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
|
|
23134
|
-
const after =
|
|
23338
|
+
const after = lstatSync30(cacheRoot);
|
|
23135
23339
|
safe5(cacheRoot);
|
|
23136
23340
|
pluginNeed(before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs, "Managed cache version membership changed during verification");
|
|
23137
23341
|
}
|
|
@@ -23144,24 +23348,24 @@ function captureManagedPluginRegistry(path, managedPlugins) {
|
|
|
23144
23348
|
|
|
23145
23349
|
// src/lib/claude-marketplace-registry.ts
|
|
23146
23350
|
import { createHash as createHash28 } from "crypto";
|
|
23147
|
-
import { closeSync as closeSync19, constants as constants20, fstatSync as fstatSync19, lstatSync as
|
|
23148
|
-
import { basename as basename14, dirname as
|
|
23351
|
+
import { closeSync as closeSync19, constants as constants20, fstatSync as fstatSync19, lstatSync as lstatSync31, openSync as openSync19, readSync as readSync14 } from "fs";
|
|
23352
|
+
import { basename as basename14, dirname as dirname25, isAbsolute as isAbsolute27, resolve as resolve26 } from "path";
|
|
23149
23353
|
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
23150
23354
|
function need10(value, reason2) {
|
|
23151
23355
|
if (!value)
|
|
23152
23356
|
throw new Error(`Claude marketplace registry ${reason2}`);
|
|
23153
23357
|
}
|
|
23154
23358
|
function absolutePath4(value) {
|
|
23155
|
-
need10(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute27(value) &&
|
|
23359
|
+
need10(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute27(value) && resolve26(value) === value, "requires a normalized absolute path");
|
|
23156
23360
|
}
|
|
23157
23361
|
var sameFile2 = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs;
|
|
23158
23362
|
function parents2(path) {
|
|
23159
23363
|
const result = [];
|
|
23160
|
-
for (let at =
|
|
23161
|
-
const stat2 =
|
|
23364
|
+
for (let at = dirname25(path);; at = dirname25(at)) {
|
|
23365
|
+
const stat2 = lstatSync31(at, { bigint: true });
|
|
23162
23366
|
need10(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
23163
23367
|
result.push([at, stat2]);
|
|
23164
|
-
if (at ===
|
|
23368
|
+
if (at === dirname25(at))
|
|
23165
23369
|
return result;
|
|
23166
23370
|
}
|
|
23167
23371
|
}
|
|
@@ -23169,7 +23373,7 @@ function readRegistry2(path, budget2) {
|
|
|
23169
23373
|
absolutePath4(path);
|
|
23170
23374
|
need10(basename14(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
23171
23375
|
need10(Number.isSafeInteger(budget2.remaining) && budget2.remaining >= 0, "has an invalid byte budget");
|
|
23172
|
-
const ancestors2 = parents2(path), initial =
|
|
23376
|
+
const ancestors2 = parents2(path), initial = lstatSync31(path, { bigint: true });
|
|
23173
23377
|
need10(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
23174
23378
|
need10(initial.size <= BigInt(budget2.remaining), "exceeds its aggregate byte limit");
|
|
23175
23379
|
const fd = openSync19(path, constants20.O_RDONLY | constants20.O_NOFOLLOW | constants20.O_NONBLOCK);
|
|
@@ -23184,10 +23388,10 @@ function readRegistry2(path, budget2) {
|
|
|
23184
23388
|
break;
|
|
23185
23389
|
length += count;
|
|
23186
23390
|
}
|
|
23187
|
-
const after = fstatSync19(fd, { bigint: true }), current =
|
|
23391
|
+
const after = fstatSync19(fd, { bigint: true }), current = lstatSync31(path, { bigint: true });
|
|
23188
23392
|
need10(current.isFile() && sameFile2(opened, after) && sameFile2(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
23189
23393
|
for (const [ancestor, before] of ancestors2) {
|
|
23190
|
-
const now =
|
|
23394
|
+
const now = lstatSync31(ancestor, { bigint: true });
|
|
23191
23395
|
need10(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
23192
23396
|
}
|
|
23193
23397
|
budget2.remaining -= length;
|
|
@@ -23380,7 +23584,7 @@ function discoveryExecutable(agent) {
|
|
|
23380
23584
|
const found = command ? Bun.which(command) : null;
|
|
23381
23585
|
if (!command || !found)
|
|
23382
23586
|
return null;
|
|
23383
|
-
const path =
|
|
23587
|
+
const path = resolve27(found), target = realpathOrNull(path);
|
|
23384
23588
|
return target === null ? null : { command, path, target };
|
|
23385
23589
|
}
|
|
23386
23590
|
function recordedDiscoveryExecutable(agent, recorded) {
|
|
@@ -23404,10 +23608,10 @@ function parseConfig(text2, path, toml = false) {
|
|
|
23404
23608
|
function safe6(path) {
|
|
23405
23609
|
if (!isAbsolute28(path) || path.includes("\x00"))
|
|
23406
23610
|
throw new Error("Expected an absolute native discovery path");
|
|
23407
|
-
for (let cursor = path;; cursor =
|
|
23408
|
-
if (
|
|
23611
|
+
for (let cursor = path;; cursor = dirname26(cursor)) {
|
|
23612
|
+
if (lstatSync32(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
23409
23613
|
throw new Error(`Refusing symlink native discovery input: ${cursor}`);
|
|
23410
|
-
if (
|
|
23614
|
+
if (dirname26(cursor) === cursor)
|
|
23411
23615
|
break;
|
|
23412
23616
|
}
|
|
23413
23617
|
}
|
|
@@ -23415,19 +23619,19 @@ function read2(path, changes) {
|
|
|
23415
23619
|
safe6(path);
|
|
23416
23620
|
if (changes?.has(path))
|
|
23417
23621
|
return changes.get(path);
|
|
23418
|
-
const stat2 =
|
|
23622
|
+
const stat2 = lstatSync32(path, { throwIfNoEntry: false });
|
|
23419
23623
|
if (!stat2)
|
|
23420
23624
|
return null;
|
|
23421
23625
|
if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
|
|
23422
23626
|
throw new Error(`Unsupported or oversized native discovery input: ${path}`);
|
|
23423
|
-
return
|
|
23627
|
+
return readFileSync26(path, "utf8");
|
|
23424
23628
|
}
|
|
23425
23629
|
function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
23426
23630
|
const enabledIds = Object.entries(config.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id]) => id);
|
|
23427
23631
|
if (enabledIds.length === 0)
|
|
23428
23632
|
return;
|
|
23429
|
-
const registryPath = canonical9(
|
|
23430
|
-
const cacheAliasRoot =
|
|
23633
|
+
const registryPath = canonical9(join38(home, ".claude/plugins/installed_plugins.json"));
|
|
23634
|
+
const cacheAliasRoot = join38(home, ".claude/plugins/cache");
|
|
23431
23635
|
const cacheRoot = canonical9(cacheAliasRoot);
|
|
23432
23636
|
const registryText = read2(registryPath);
|
|
23433
23637
|
if (registryText === null)
|
|
@@ -23435,7 +23639,7 @@ function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
|
23435
23639
|
const registry = parseConfig(registryText, registryPath);
|
|
23436
23640
|
if (registry.version !== 2 || !registry.plugins || typeof registry.plugins !== "object" || Array.isArray(registry.plugins))
|
|
23437
23641
|
throw new Error("Reviewed Claude installed plugin registrations are invalid");
|
|
23438
|
-
const cachePath = (path) => typeof path === "string" && isAbsolute28(path) &&
|
|
23642
|
+
const cachePath = (path) => typeof path === "string" && isAbsolute28(path) && resolve27(path) === path && [cacheAliasRoot, cacheRoot].some((root2) => path === root2 || path.startsWith(root2 + sep15));
|
|
23439
23643
|
const activeRoots = new Set;
|
|
23440
23644
|
for (const id of enabledIds) {
|
|
23441
23645
|
const rows = registry.plugins[id];
|
|
@@ -23447,11 +23651,11 @@ function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
|
23447
23651
|
if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
|
|
23448
23652
|
throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
|
|
23449
23653
|
}
|
|
23450
|
-
if ([cacheAliasRoot, cacheRoot].some((root2) => row.installPath === root2 || row.installPath.startsWith(root2 +
|
|
23654
|
+
if ([cacheAliasRoot, cacheRoot].some((root2) => row.installPath === root2 || row.installPath.startsWith(root2 + sep15))) {
|
|
23451
23655
|
if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename15(row.installPath) !== row.version)) {
|
|
23452
23656
|
throw new Error("Reviewed Claude discovery has a malformed active cache registration");
|
|
23453
23657
|
}
|
|
23454
|
-
if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute28(row.projectPath) &&
|
|
23658
|
+
if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute28(row.projectPath) && resolve27(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
|
|
23455
23659
|
throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
|
|
23456
23660
|
activeRoots.add(canonical9(row.installPath));
|
|
23457
23661
|
}
|
|
@@ -23469,11 +23673,11 @@ function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
|
23469
23673
|
};
|
|
23470
23674
|
for (const root2 of activeRoots) {
|
|
23471
23675
|
safe6(root2);
|
|
23472
|
-
const rootStat =
|
|
23676
|
+
const rootStat = lstatSync32(root2, { throwIfNoEntry: false });
|
|
23473
23677
|
if (!rootStat?.isDirectory() || realpathSync13(root2) !== root2)
|
|
23474
23678
|
throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
|
|
23475
|
-
const manifestPath =
|
|
23476
|
-
const manifestStat =
|
|
23679
|
+
const manifestPath = join38(root2, ".claude-plugin/plugin.json");
|
|
23680
|
+
const manifestStat = lstatSync32(manifestPath, { throwIfNoEntry: false });
|
|
23477
23681
|
let manifest;
|
|
23478
23682
|
if (manifestStat) {
|
|
23479
23683
|
if (!manifestStat.isFile())
|
|
@@ -23488,30 +23692,30 @@ function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
|
23488
23692
|
} else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
|
|
23489
23693
|
throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
|
|
23490
23694
|
}
|
|
23491
|
-
const roots = new Set([
|
|
23492
|
-
if (
|
|
23695
|
+
const roots = new Set([join38(root2, "skills")]);
|
|
23696
|
+
if (lstatSync32(join38(root2, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
|
|
23493
23697
|
roots.add(root2);
|
|
23494
23698
|
const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
23495
23699
|
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
23496
23700
|
throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
|
|
23497
23701
|
for (const value of declared) {
|
|
23498
|
-
const path =
|
|
23499
|
-
if (path !== root2 && !path.startsWith(root2 +
|
|
23702
|
+
const path = resolve27(root2, value);
|
|
23703
|
+
if (path !== root2 && !path.startsWith(root2 + sep15))
|
|
23500
23704
|
throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
|
|
23501
|
-
roots.add(path.endsWith(`${
|
|
23705
|
+
roots.add(path.endsWith(`${sep15}SKILL.md`) ? dirname26(path) : path);
|
|
23502
23706
|
}
|
|
23503
23707
|
for (const skillRoot of roots)
|
|
23504
23708
|
if (!review.roots.includes(skillRoot))
|
|
23505
23709
|
throw new Error("Reviewed Claude active cache plugin skill root is not covered");
|
|
23506
|
-
const hooksPath =
|
|
23507
|
-
const hooksStat =
|
|
23710
|
+
const hooksPath = join38(root2, "hooks/hooks.json");
|
|
23711
|
+
const hooksStat = lstatSync32(hooksPath, { throwIfNoEntry: false });
|
|
23508
23712
|
if (hooksStat) {
|
|
23509
23713
|
if (!hooksStat.isFile())
|
|
23510
23714
|
throw new Error("Reviewed Claude plugin hook manifest is unsupported");
|
|
23511
23715
|
requireSource(hooksPath, "hook manifest");
|
|
23512
23716
|
}
|
|
23513
23717
|
if (typeof manifest?.hooks === "string")
|
|
23514
|
-
requireSource(
|
|
23718
|
+
requireSource(resolve27(root2, manifest.hooks), "manifest-declared hook source");
|
|
23515
23719
|
}
|
|
23516
23720
|
}
|
|
23517
23721
|
function projectNativeDiscoveryFields(text2, format, fields2, path) {
|
|
@@ -23564,10 +23768,9 @@ function projected(source, changes, budget2 = discoveryByteBudget()) {
|
|
|
23564
23768
|
if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4" || source.hashMode === "codex-settings-v5")) {
|
|
23565
23769
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
23566
23770
|
throw new Error("Codex settings witnesses require exact typed metadata");
|
|
23567
|
-
|
|
23568
|
-
if (current !== source.sha256)
|
|
23771
|
+
if (codexSettingsWitnessMatch(source.path, source.hashMode, source.sha256, budget2) === null)
|
|
23569
23772
|
throw new NativeDiscoveryWitnessChange("input", source.path, `Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
23570
|
-
return changes?.has(source.path) ? (source.hashMode === "codex-settings-v5" ? hashCodexSettingsReplacementV5 : source.hashMode === "codex-settings-v4" ? hashCodexSettingsReplacementV4 : source.hashMode === "codex-settings-v3" ? hashCodexSettingsReplacementV3 : source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget2) :
|
|
23773
|
+
return changes?.has(source.path) ? (source.hashMode === "codex-settings-v5" ? hashCodexSettingsReplacementV5 : source.hashMode === "codex-settings-v4" ? hashCodexSettingsReplacementV4 : source.hashMode === "codex-settings-v3" ? hashCodexSettingsReplacementV3 : source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget2) : source.sha256;
|
|
23571
23774
|
}
|
|
23572
23775
|
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4")) {
|
|
23573
23776
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
@@ -23643,18 +23846,18 @@ function absentCodexSkillRoots(binding, tolerance) {
|
|
|
23643
23846
|
return [];
|
|
23644
23847
|
if (binding.agent !== "codex" || !Array.isArray(controls) || controls.length > 4096)
|
|
23645
23848
|
throw new Error("Invalid disabled Codex discovery proof");
|
|
23646
|
-
if (binding.method !== "reviewed" || controls.every((control) =>
|
|
23849
|
+
if (binding.method !== "reviewed" || controls.every((control) => lstatSync32(control.pluginParent, { throwIfNoEntry: false })))
|
|
23647
23850
|
return [];
|
|
23648
|
-
const cache =
|
|
23851
|
+
const cache = dirname26(dirname26(controls[0].pluginParent)), config = join38(dirname26(dirname26(cache)), "config.toml");
|
|
23649
23852
|
const source = binding.sources.find((source2) => source2.path === config && source2.sha256 !== null && source2.format === undefined && source2.fields === undefined);
|
|
23650
23853
|
if (!source || !codexConfigWitnessCurrent(source, tolerance))
|
|
23651
23854
|
throw new Error("Missing current disabled Codex configuration witness");
|
|
23652
23855
|
const rules = Bun.TOML.parse(read2(config)).skills?.config ?? [];
|
|
23653
23856
|
return [...new Set(controls.map((control) => control.pluginParent))].filter((parent) => !codexDirectHookSelects(cache, parent) && absentDisabledCodexPluginParent(cache, parent, controls, rules));
|
|
23654
23857
|
}
|
|
23655
|
-
function codexDirectHookSelects(cache, root2, configPath =
|
|
23656
|
-
const codex =
|
|
23657
|
-
const hooks = read2(
|
|
23858
|
+
function codexDirectHookSelects(cache, root2, configPath = join38(dirname26(dirname26(cache)), "config.toml")) {
|
|
23859
|
+
const codex = dirname26(configPath), config = Bun.TOML.parse(read2(configPath) ?? "");
|
|
23860
|
+
const hooks = read2(join38(codex, "hooks.json"));
|
|
23658
23861
|
if (hooks !== null)
|
|
23659
23862
|
hashNativeJsonControls(hooks);
|
|
23660
23863
|
return JSON.stringify(config.hooks ?? {}).includes(root2) || hooks !== null && JSON.stringify(JSON.parse(hooks)).includes(root2);
|
|
@@ -23666,7 +23869,7 @@ function retiredCodexRoots(binding, tolerance) {
|
|
|
23666
23869
|
const controls = binding.codexDisabledPluginSkills;
|
|
23667
23870
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || !Array.isArray(controls) || !controls.length || !Array.isArray(proof.roots) || proof.roots.length > AGENT_POLICY_LIMITS.discoveryRoots || new Set(proof.roots).size !== proof.roots.length || !Array.isArray(proof.directories) || proof.parents !== undefined && (!Array.isArray(proof.parents) || proof.parents.length > AGENT_POLICY_LIMITS.discoveryRoots || new Set(proof.parents).size !== proof.parents.length) || !proof.roots.length && !proof.parents?.length)
|
|
23668
23871
|
throw new Error("Invalid retired Codex materialization proof");
|
|
23669
|
-
const cache =
|
|
23872
|
+
const cache = dirname26(dirname26(controls[0].pluginParent)), config = join38(dirname26(dirname26(cache)), "config.toml");
|
|
23670
23873
|
const source = binding.sources.find((source2) => source2.path === config && source2.sha256 !== null && source2.format === undefined && source2.fields === undefined);
|
|
23671
23874
|
if (!source || !codexConfigWitnessCurrent(source, tolerance))
|
|
23672
23875
|
throw new Error("Missing current retired Codex configuration witness");
|
|
@@ -23674,7 +23877,7 @@ function retiredCodexRoots(binding, tolerance) {
|
|
|
23674
23877
|
if ((proof.parents ?? []).some((parent) => codexDirectHookSelects(cache, parent) || !reviewedDisabledCodexPluginParent(cache, parent, controls, rules)) || !reviewedCodexPluginCapabilitiesUnchanged(cache, controls, (path) => read2(path), rules))
|
|
23675
23878
|
throw new Error("Retired Codex parent capabilities changed");
|
|
23676
23879
|
for (const root2 of proof.roots) {
|
|
23677
|
-
const manifest = binding.sources.find((source2) => source2.path ===
|
|
23880
|
+
const manifest = binding.sources.find((source2) => source2.path === join38(root2, ".codex-plugin/plugin.json") && source2.sha256 !== null && source2.format === undefined && source2.fields === undefined && [undefined, "bytes"].includes(source2.hashMode));
|
|
23678
23881
|
if (!manifest || codexDirectHookSelects(cache, root2) || !reviewedRetiredCodexRoot(cache, root2, controls, rules, (path) => read2(path), true))
|
|
23679
23882
|
throw new Error("Retired Codex materialization changed");
|
|
23680
23883
|
}
|
|
@@ -23683,23 +23886,35 @@ function retiredCodexRoots(binding, tolerance) {
|
|
|
23683
23886
|
function captureRetiredCodexDiscovery(binding, controls, rules) {
|
|
23684
23887
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || !controls.length)
|
|
23685
23888
|
return binding;
|
|
23686
|
-
const cache =
|
|
23687
|
-
const candidates = binding.sources.filter((source) => source.path.endsWith(
|
|
23889
|
+
const cache = dirname26(dirname26(controls[0].pluginParent));
|
|
23890
|
+
const candidates = binding.sources.filter((source) => source.path.endsWith(sep15 + ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined && [undefined, "bytes"].includes(source.hashMode)).map((source) => dirname26(dirname26(source.path)));
|
|
23688
23891
|
const roots = [...new Set(candidates)].filter((root2) => !codexDirectHookSelects(cache, root2) && reviewedRetiredCodexRoot(cache, root2, controls, rules, (path) => read2(path), binding.codexRetiredMaterializations?.roots.includes(root2) ?? false)).sort();
|
|
23689
23892
|
const parents3 = reviewedCodexPluginCapabilitiesUnchanged(cache, controls, (path) => read2(path), rules) ? [...new Set(controls.map((control) => control.pluginParent))].filter((parent) => !codexDirectHookSelects(cache, parent) && reviewedDisabledCodexPluginParent(cache, parent, controls, rules)).sort() : [];
|
|
23690
23893
|
const { codexRetiredMaterializations: previous, ...current } = binding;
|
|
23691
23894
|
if (!roots.length && !parents3.length)
|
|
23692
23895
|
return current;
|
|
23693
23896
|
const installation = codexInstallationRoots(binding);
|
|
23694
|
-
const directories = (binding.directories ?? []).filter((directory2) => !installation.some((root2) => directory2.path === root2 || directory2.path.startsWith(root2 +
|
|
23897
|
+
const directories = (binding.directories ?? []).filter((directory2) => !installation.some((root2) => directory2.path === root2 || directory2.path.startsWith(root2 + sep15) || root2.startsWith(directory2.path + sep15)));
|
|
23695
23898
|
if (previous && isDeepStrictEqual9(previous.roots, roots) && isDeepStrictEqual9(previous.parents, parents3) && isDeepStrictEqual9(previous.directories.map((item) => item.path), directories.map((item) => item.path)))
|
|
23696
23899
|
return { ...current, codexRetiredMaterializations: previous };
|
|
23697
23900
|
return { ...current, codexRetiredMaterializations: { roots, parents: parents3, directories: captureDiscoveryDirectoryProjection(directories.map((item) => item.path), roots) } };
|
|
23698
23901
|
}
|
|
23699
|
-
function
|
|
23902
|
+
function retainableAbsentWitness(source) {
|
|
23700
23903
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined)
|
|
23701
|
-
return
|
|
23702
|
-
|
|
23904
|
+
return null;
|
|
23905
|
+
if ([undefined, "bytes"].includes(source.hashMode))
|
|
23906
|
+
return "bytes";
|
|
23907
|
+
if (source.hashMode === "claude-plugin-manifest-v1" && source.sha256 !== null && source.path.endsWith(join38(sep15, ".claude-plugin", "plugin.json")))
|
|
23908
|
+
return "claude-plugin-manifest-v1";
|
|
23909
|
+
if (source.hashMode === CLAUDE_ORPHAN_MARKER_MODE && source.sha256 !== null && isClaudePluginCacheOrphanMarker(source.path))
|
|
23910
|
+
return CLAUDE_ORPHAN_MARKER_MODE;
|
|
23911
|
+
return null;
|
|
23912
|
+
}
|
|
23913
|
+
function retainableAbsentSource(source) {
|
|
23914
|
+
return retainableAbsentWitness(source) !== null;
|
|
23915
|
+
}
|
|
23916
|
+
function retainedAbsentRoot(absentRoots, source) {
|
|
23917
|
+
return retainableAbsentSource(source) ? absentRoots.find((root2) => source.path.startsWith(root2 + sep15)) : undefined;
|
|
23703
23918
|
}
|
|
23704
23919
|
function absentRetiredClaudeRoots(binding) {
|
|
23705
23920
|
if (binding.agent !== "claude" || binding.method !== "reviewed")
|
|
@@ -23708,17 +23923,17 @@ function absentRetiredClaudeRoots(binding) {
|
|
|
23708
23923
|
for (const source of binding.sources) {
|
|
23709
23924
|
if (!retainableAbsentSource(source) || source.sha256 === null)
|
|
23710
23925
|
continue;
|
|
23711
|
-
const marker =
|
|
23926
|
+
const marker = sep15 + ".claude" + sep15 + "plugins" + sep15 + "cache" + sep15, at = source.path.indexOf(marker);
|
|
23712
23927
|
if (at < 0 || source.path.indexOf(marker, at + 1) >= 0)
|
|
23713
23928
|
continue;
|
|
23714
|
-
const cache = source.path.slice(0, at + marker.length - 1), parts = source.path.slice(at + marker.length).split(
|
|
23929
|
+
const cache = source.path.slice(0, at + marker.length - 1), parts = source.path.slice(at + marker.length).split(sep15);
|
|
23715
23930
|
if (parts.length < 4 || parts.slice(0, 3).some((part) => !part || part === "." || part === ".."))
|
|
23716
23931
|
continue;
|
|
23717
|
-
const root2 =
|
|
23932
|
+
const root2 = join38(cache, ...parts.slice(0, 3));
|
|
23718
23933
|
safe6(root2);
|
|
23719
|
-
if (
|
|
23934
|
+
if (lstatSync32(root2, { throwIfNoEntry: false }))
|
|
23720
23935
|
continue;
|
|
23721
|
-
const claude =
|
|
23936
|
+
const claude = dirname26(dirname26(cache)), settings = join38(claude, "settings.json"), registry = join38(claude, "plugins/installed_plugins.json");
|
|
23722
23937
|
const full = (path) => binding.sources.find((item) => item.path === path && item.sha256 !== null && item.format === undefined && item.fields === undefined);
|
|
23723
23938
|
const configSource = full(settings), registrySource = full(registry);
|
|
23724
23939
|
if (!configSource || !registrySource || projected(configSource) !== configSource.sha256 || projected(registrySource) !== registrySource.sha256)
|
|
@@ -23735,15 +23950,15 @@ function absentRetiredClaudeRoots(binding) {
|
|
|
23735
23950
|
if (!row || typeof row !== "object" || Array.isArray(row))
|
|
23736
23951
|
return null;
|
|
23737
23952
|
const value = row;
|
|
23738
|
-
if (typeof value.installPath !== "string" || /[\x00-\x1f\x7f]/.test(value.installPath) || !isAbsolute28(value.installPath) ||
|
|
23953
|
+
if (typeof value.installPath !== "string" || /[\x00-\x1f\x7f]/.test(value.installPath) || !isAbsolute28(value.installPath) || resolve27(value.installPath) !== value.installPath || value.installPath.length > AGENT_POLICY_LIMITS.pathCharacters)
|
|
23739
23954
|
return null;
|
|
23740
23955
|
if (value.scope === "user")
|
|
23741
23956
|
return value.projectPath === undefined || value.projectPath === null ? "user\x00" : null;
|
|
23742
23957
|
if (value.scope === "project")
|
|
23743
|
-
return typeof value.projectPath === "string" && !/[\x00-\x1f\x7f]/.test(value.projectPath) && value.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute28(value.projectPath) &&
|
|
23958
|
+
return typeof value.projectPath === "string" && !/[\x00-\x1f\x7f]/.test(value.projectPath) && value.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute28(value.projectPath) && resolve27(value.projectPath) === value.projectPath ? `project\x00${value.projectPath}` : null;
|
|
23744
23959
|
return null;
|
|
23745
23960
|
};
|
|
23746
|
-
if (Object.values(installed.plugins).some((value) => !Array.isArray(value) || value.some((row) => !row || typeof row.installPath !== "string" || row.installPath === root2 || row.installPath.startsWith(root2 +
|
|
23961
|
+
if (Object.values(installed.plugins).some((value) => !Array.isArray(value) || value.some((row) => !row || typeof row.installPath !== "string" || row.installPath === root2 || row.installPath.startsWith(root2 + sep15) || root2.startsWith(row.installPath + sep15))))
|
|
23747
23962
|
continue;
|
|
23748
23963
|
const rows = installed.plugins[id];
|
|
23749
23964
|
if (!Array.isArray(rows) || rows.length < 1)
|
|
@@ -23761,9 +23976,9 @@ function absentRetiredClaudeRoots(binding) {
|
|
|
23761
23976
|
if (!targetValid || !targetScopes.has("user\x00"))
|
|
23762
23977
|
continue;
|
|
23763
23978
|
const active = rows[0]?.installPath;
|
|
23764
|
-
if (typeof active !== "string" || rows[0]?.version !== basename15(active) ||
|
|
23979
|
+
if (typeof active !== "string" || rows[0]?.version !== basename15(active) || dirname26(active) !== dirname26(root2) || active === root2 || !lstatSync32(active, { throwIfNoEntry: false })?.isDirectory() || realpathSync13(active) !== active)
|
|
23765
23980
|
continue;
|
|
23766
|
-
const activeManifest = full(
|
|
23981
|
+
const activeManifest = full(join38(active, ".claude-plugin/plugin.json"));
|
|
23767
23982
|
if (!activeManifest || projected(activeManifest) !== activeManifest.sha256)
|
|
23768
23983
|
continue;
|
|
23769
23984
|
result.add(root2);
|
|
@@ -23771,24 +23986,24 @@ function absentRetiredClaudeRoots(binding) {
|
|
|
23771
23986
|
return [...result];
|
|
23772
23987
|
}
|
|
23773
23988
|
function codexSemanticCacheSourceRole(source, cacheRoot, parents3, hookParents = []) {
|
|
23774
|
-
if (!isAbsolute28(source.path) ||
|
|
23989
|
+
if (!isAbsolute28(source.path) || resolve27(source.path) !== source.path || /[\x00-\x1f\x7f]/.test(source.path) || source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || source.hashMode !== undefined && source.hashMode !== "bytes")
|
|
23775
23990
|
return false;
|
|
23776
|
-
const parts = relative10(cacheRoot, source.path).split(
|
|
23777
|
-
if (parts.length < 3 || !parents3.includes(
|
|
23991
|
+
const parts = relative10(cacheRoot, source.path).split(sep15);
|
|
23992
|
+
if (parts.length < 3 || !parents3.includes(join38(cacheRoot, parts[0], parts[1])))
|
|
23778
23993
|
return false;
|
|
23779
23994
|
const tail = parts.slice(3).join("/");
|
|
23780
|
-
const parent =
|
|
23995
|
+
const parent = join38(cacheRoot, parts[0], parts[1]);
|
|
23781
23996
|
return parts.length === 5 && tail === ".codex-plugin/plugin.json" && source.sha256 !== null || parts.length === 4 && [".app.json", ".mcp.json"].includes(tail) && source.sha256 !== null || parts.length === 5 && tail === "hooks/hooks.json" && hookParents.includes(parent) && source.sha256 !== null || source.sha256 === null && (parts.length === 4 && tail === "SKILL.md" || parts.length === 5 && tail === "hooks/hooks.json");
|
|
23782
23997
|
}
|
|
23783
23998
|
function codexSemanticCacheDirectoryRole(path, cacheRoot) {
|
|
23784
|
-
return isAbsolute28(path) &&
|
|
23999
|
+
return isAbsolute28(path) && resolve27(path) === path && !/[\x00-\x1f\x7f]/.test(path) && (path === cacheRoot || path.startsWith(cacheRoot + sep15));
|
|
23785
24000
|
}
|
|
23786
24001
|
function assertCodexSemanticCacheHookSafety(cacheRoot, parents3, configPath, hookRootAlias = null) {
|
|
23787
24002
|
const selectionRoots = [cacheRoot, ...parents3];
|
|
23788
24003
|
if (hookRootAlias !== null) {
|
|
23789
|
-
if (!hookRootAlias || Object.keys(hookRootAlias).length !== 2 || [hookRootAlias.alias, hookRootAlias.target].some((path) => typeof path !== "string" || !isAbsolute28(path) ||
|
|
24004
|
+
if (!hookRootAlias || Object.keys(hookRootAlias).length !== 2 || [hookRootAlias.alias, hookRootAlias.target].some((path) => typeof path !== "string" || !isAbsolute28(path) || resolve27(path) !== path || /[\x00-\x1f\x7f]/.test(path)) || hookRootAlias.target !== dirname26(configPath) || cacheRoot !== join38(hookRootAlias.target, "plugins/cache") || !lstatSync32(hookRootAlias.alias, { throwIfNoEntry: false })?.isSymbolicLink() || realpathSync13(hookRootAlias.alias) !== hookRootAlias.target || realpathSync13(hookRootAlias.target) !== hookRootAlias.target)
|
|
23790
24005
|
throw new Error("Reviewed semantic cache hook alias changed");
|
|
23791
|
-
selectionRoots.push(...selectionRoots.map((root2) =>
|
|
24006
|
+
selectionRoots.push(...selectionRoots.map((root2) => join38(hookRootAlias.alias, relative10(hookRootAlias.target, root2))));
|
|
23792
24007
|
}
|
|
23793
24008
|
selectionRoots.push("plugins/cache");
|
|
23794
24009
|
if (selectionRoots.some((parent) => codexDirectHookSelects(cacheRoot, parent, configPath)))
|
|
@@ -23808,7 +24023,7 @@ function verifyCodexSemanticCacheDiscovery(binding, reviewedRootAliases) {
|
|
|
23808
24023
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || !controls?.length || proof.schema !== "hasna.codex-semantic-cache-discovery.v1" || !/^[a-f0-9]{64}$/.test(proof.previousPolicySha256) || !Array.isArray(proof.appOnlyParents) || !Array.isArray(proof.supersededSources) || !Array.isArray(proof.supersededDirectories))
|
|
23809
24024
|
throw new Error("Invalid Codex semantic cache discovery proof");
|
|
23810
24025
|
const cacheRoot = proof.witness.cacheRoot;
|
|
23811
|
-
if (controls.some((control) =>
|
|
24026
|
+
if (controls.some((control) => dirname26(dirname26(control.pluginParent)) !== cacheRoot))
|
|
23812
24027
|
throw new Error("Codex semantic cache names another enrolled cache");
|
|
23813
24028
|
const reviewedHookParents = proof.reviewedHookParents ?? [];
|
|
23814
24029
|
if (!Array.isArray(reviewedHookParents) || reviewedHookParents.length > 4096 || reviewedHookParents.some((parent) => !parent || !(parent.installation === "remote-receipt" && Object.keys(parent).sort().join(",") === "installation,namespace,pluginId,pluginParent,remotePluginId,role" && typeof parent.remotePluginId === "string" || parent.installation === "local-marketplace" && Object.keys(parent).sort().join(",") === "installation,marketplaceSha256,marketplaceSourcePath,namespace,pluginId,pluginParent,role" && typeof parent.marketplaceSourcePath === "string" && /^[a-f0-9]{64}$/.test(parent.marketplaceSha256 ?? "")) || parent.role !== "reviewed-hook" || typeof parent.pluginId !== "string" || typeof parent.namespace !== "string" || typeof parent.pluginParent !== "string") || new Set(reviewedHookParents.map((parent) => parent.pluginParent)).size !== reviewedHookParents.length || !isDeepStrictEqual9((proof.witness.hookParents ?? []).map(({ manifestSha256: _manifest, hooksSha256: _hooks, receiptSha256: _receipt, ...parent }) => parent), reviewedHookParents)) {
|
|
@@ -23824,7 +24039,7 @@ function verifyCodexSemanticCacheDiscovery(binding, reviewedRootAliases) {
|
|
|
23824
24039
|
if (!directories.length || !isDeepStrictEqual9(sources, proof.supersededSources) || !isDeepStrictEqual9(directories, proof.supersededDirectories))
|
|
23825
24040
|
throw new Error("Codex semantic cache provenance changed");
|
|
23826
24041
|
const configPath = proof.configPath;
|
|
23827
|
-
if (!isAbsolute28(configPath) ||
|
|
24042
|
+
if (!isAbsolute28(configPath) || resolve27(configPath) !== configPath || basename15(configPath) !== "config.toml" || /[\x00-\x1f\x7f]/.test(configPath))
|
|
23828
24043
|
throw new Error("Invalid semantic cache configuration source");
|
|
23829
24044
|
const configSource = binding.sources.find((source) => source.path === configPath && source.sha256 !== null && source.format === undefined && source.fields === undefined);
|
|
23830
24045
|
if (!configSource || projected(configSource) !== configSource.sha256)
|
|
@@ -23846,10 +24061,10 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23846
24061
|
const semanticDirectory = (path) => Boolean(semanticCache && codexSemanticCacheDirectoryRole(path, semanticCache.witness.cacheRoot));
|
|
23847
24062
|
const installationRoots = codexInstallationRoots(binding);
|
|
23848
24063
|
const retiredRoots = retiredCodexRoots(binding, witnessTolerance);
|
|
23849
|
-
const installationInput = (path) => installationRoots.some((root2) => path === root2 || path.startsWith(root2 +
|
|
24064
|
+
const installationInput = (path) => installationRoots.some((root2) => path === root2 || path.startsWith(root2 + sep15));
|
|
23850
24065
|
if (binding.directories !== undefined) {
|
|
23851
24066
|
const projected2 = binding.codexInstallationInputs?.directories ?? [];
|
|
23852
|
-
const ancestors2 = binding.directories.filter((directory2) => installationRoots.some((root2) => root2.startsWith(directory2.path +
|
|
24067
|
+
const ancestors2 = binding.directories.filter((directory2) => installationRoots.some((root2) => root2.startsWith(directory2.path + sep15)));
|
|
23853
24068
|
if (projected2.length !== ancestors2.length || projected2.some((directory2) => !ancestors2.some((original) => original.path === directory2.path)))
|
|
23854
24069
|
throw new Error("Missing installation input directory projection");
|
|
23855
24070
|
const ordinary = binding.directories.filter((directory2) => !installationInput(directory2.path) && !ancestors2.includes(directory2) && !semanticDirectory(directory2.path));
|
|
@@ -23857,7 +24072,7 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23857
24072
|
const projection = binding.codexRetiredMaterializations.directories.filter((directory2) => !semanticDirectory(directory2.path));
|
|
23858
24073
|
if (projection.length !== ordinary.length || projection.some((item, index) => item.path !== ordinary[index]?.path))
|
|
23859
24074
|
throw new Error("Missing retired Codex directory projection");
|
|
23860
|
-
const omitted = [...retiredRoots, ...(binding.codexRetiredMaterializations.parents ?? []).filter((parent) => !
|
|
24075
|
+
const omitted = [...retiredRoots, ...(binding.codexRetiredMaterializations.parents ?? []).filter((parent) => !lstatSync32(parent, { throwIfNoEntry: false }))];
|
|
23861
24076
|
if (projection.every((item) => Array.isArray(item.entries)))
|
|
23862
24077
|
verifyDiscoveryDirectoryProjection(projection, omitted, witnessTolerance);
|
|
23863
24078
|
else {
|
|
@@ -23874,7 +24089,7 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23874
24089
|
verifyDiscoveryDirectories(ordinary, [], witnessTolerance);
|
|
23875
24090
|
verifyDiscoveryDirectories(projected2, installationRoots, witnessTolerance);
|
|
23876
24091
|
}
|
|
23877
|
-
const absentRoots = [...absentCodexSkillRoots(binding, witnessTolerance), ...absentRetiredClaudeRoots(binding), ...retiredRoots.filter((root2) => !
|
|
24092
|
+
const absentRoots = [...absentCodexSkillRoots(binding, witnessTolerance), ...absentRetiredClaudeRoots(binding), ...retiredRoots.filter((root2) => !lstatSync32(root2, { throwIfNoEntry: false }))];
|
|
23878
24093
|
const budget2 = discoveryByteBudget();
|
|
23879
24094
|
for (const source of binding.sources) {
|
|
23880
24095
|
assertMarketplaceBinding(binding, source);
|
|
@@ -23892,7 +24107,7 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23892
24107
|
safe6(source.path);
|
|
23893
24108
|
continue;
|
|
23894
24109
|
}
|
|
23895
|
-
if (absentRoots
|
|
24110
|
+
if (retainedAbsentRoot(absentRoots, source) !== undefined) {
|
|
23896
24111
|
safe6(source.path);
|
|
23897
24112
|
continue;
|
|
23898
24113
|
}
|
|
@@ -23912,23 +24127,25 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23912
24127
|
safe6(root2);
|
|
23913
24128
|
for (const root2 of absentRoots) {
|
|
23914
24129
|
safe6(root2);
|
|
23915
|
-
if (
|
|
24130
|
+
if (lstatSync32(root2, { throwIfNoEntry: false }))
|
|
23916
24131
|
throw new Error("Retired native materialization changed during discovery verification");
|
|
23917
24132
|
}
|
|
23918
24133
|
}
|
|
24134
|
+
var CLAUDE_CONTROL_WITNESS_MODES = new Set(["claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-marketplace-registry", "claude-marketplace-registry-v2", "claude-marketplace-entry-v1", "claude-plugin-registry"]);
|
|
24135
|
+
var CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
|
|
23919
24136
|
function codexInstallationRoots(binding) {
|
|
23920
24137
|
const proof = binding.codexInstallationInputs;
|
|
23921
24138
|
if (proof === undefined)
|
|
23922
24139
|
return [];
|
|
23923
24140
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || !supportsCodexNativeCapability(proof.version, "installed-plugin-review") || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
|
|
23924
24141
|
throw new Error("Invalid native installation input proof");
|
|
23925
|
-
const cache =
|
|
23926
|
-
const config =
|
|
24142
|
+
const cache = dirname26(dirname26(proof.plugins[0].pluginParent));
|
|
24143
|
+
const config = join38(dirname26(dirname26(cache)), "config.toml"), home = dirname26(dirname26(dirname26(cache)));
|
|
23927
24144
|
const configSource = binding.sources.find((source) => source.path === config && source.sha256 !== null && (!source.format || source.format === "toml" && source.fields?.includes("plugins")));
|
|
23928
|
-
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) =>
|
|
23929
|
-
if (!configSource || proof.plugins.some((input) => nativeRoots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot +
|
|
24145
|
+
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => join38(home, path));
|
|
24146
|
+
if (!configSource || proof.plugins.some((input) => nativeRoots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot + sep15) || input.sourceRoot.startsWith(root2 + sep15)) || binding.sources.some((source) => basename15(source.path) === "config.toml" && (source.path === input.sourceRoot || source.path.startsWith(input.sourceRoot + sep15)))))
|
|
23930
24147
|
throw new Error("Native installation input overlaps configuration or loading roots");
|
|
23931
|
-
if (proof.plugins.some((input) =>
|
|
24148
|
+
if (proof.plugins.some((input) => dirname26(dirname26(input.pluginParent)) !== cache || !binding.sources.some((source) => source.path === join38(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined)))
|
|
23932
24149
|
throw new Error("Missing native installation input source witness");
|
|
23933
24150
|
return reviewedCodexPluginSourceRoots(cache, proof.plugins, (path) => {
|
|
23934
24151
|
const text2 = read2(path);
|
|
@@ -23944,7 +24161,7 @@ function assertMarketplaceBinding(binding, source) {
|
|
|
23944
24161
|
throw new Error("Provider executable witnesses require explicit reviewed Codex V6");
|
|
23945
24162
|
if (source.hashMode === "codex-settings-v6" && (new Set(binding.sources.map((row) => row.path)).size !== binding.sources.length || !Array.isArray(source.hookSources) || source.hookSources.some((hook) => !binding.sources.some((row) => row.path === hook.path && row.sha256 === hook.sha256 && [undefined, "bytes"].includes(row.hashMode) && row.format === undefined && row.fields === undefined))))
|
|
23946
24163
|
throw new Error("Missing independently reviewed Codex hook source population");
|
|
23947
|
-
if (source.hashMode === "claude-plugin-manifest-v1" && (binding.agent !== "claude" || basename15(source.path) !== "plugin.json" || !source.path.endsWith(
|
|
24164
|
+
if (source.hashMode === "claude-plugin-manifest-v1" && (binding.agent !== "claude" || basename15(source.path) !== "plugin.json" || !source.path.endsWith(join38(sep15, ".claude-plugin", "plugin.json"))))
|
|
23948
24165
|
throw new Error("Claude plugin manifest witnesses require Claude plugin discovery");
|
|
23949
24166
|
if (source.hashMode === "sumi-settings-v1" && (binding.agent !== "sumi" || binding.method !== "reviewed" || basename15(source.path) !== "sumi.json"))
|
|
23950
24167
|
throw new Error("Sumi settings witnesses require explicit reviewed Sumi configuration");
|
|
@@ -23957,10 +24174,11 @@ function assertMarketplaceBinding(binding, source) {
|
|
|
23957
24174
|
if (source.hashMode === "claude-marketplace-entry-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || !claudeMarketplaceEntrySourceValid(source)))
|
|
23958
24175
|
throw new Error("Claude marketplace entry witnesses require explicit reviewed Claude discovery of one named entry");
|
|
23959
24176
|
}
|
|
24177
|
+
var ADOPTABLE_SETTINGS_MATCHES = Object.freeze(["exact", "screen-reader-receipt"]);
|
|
23960
24178
|
function rebindAgentDiscovery(binding, changes) {
|
|
23961
24179
|
const semanticCache = verifyCodexSemanticCacheDiscovery(binding);
|
|
23962
24180
|
const budget2 = discoveryByteBudget();
|
|
23963
|
-
const installationRoots = codexInstallationRoots(binding), absentRoots = [...absentCodexSkillRoots(binding), ...absentRetiredClaudeRoots(binding), ...retiredCodexRoots(binding).filter((root2) => !
|
|
24181
|
+
const installationRoots = codexInstallationRoots(binding), absentRoots = [...absentCodexSkillRoots(binding), ...absentRetiredClaudeRoots(binding), ...retiredCodexRoots(binding).filter((root2) => !lstatSync32(root2, { throwIfNoEntry: false }))];
|
|
23964
24182
|
const rebound = { ...binding, sources: binding.sources.map((source) => {
|
|
23965
24183
|
assertMarketplaceBinding(binding, source);
|
|
23966
24184
|
if (semanticCache?.supersededSources.some((previous) => isDeepStrictEqual9(previous, source))) {
|
|
@@ -23968,26 +24186,28 @@ function rebindAgentDiscovery(binding, changes) {
|
|
|
23968
24186
|
throw new Error("Codex semantic cache sources cannot be written by hook rendering");
|
|
23969
24187
|
return source;
|
|
23970
24188
|
}
|
|
23971
|
-
if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 +
|
|
24189
|
+
if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 + sep15)) || retainedAbsentRoot(absentRoots, source) !== undefined))
|
|
23972
24190
|
return source;
|
|
23973
24191
|
const sha2563 = projected(source, changes, budget2);
|
|
23974
24192
|
if (!changes.has(source.path) && !(source.hashMode === "codex-settings-v6" && source.hookSources.some((hook) => changes.has(hook.path))) && sha2563 !== source.sha256)
|
|
23975
24193
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
24194
|
+
if (changes.has(source.path) && (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4" || source.hashMode === "codex-settings-v5") && !ADOPTABLE_SETTINGS_MATCHES.includes(codexSettingsWitnessMatch(source.path, source.hashMode, source.sha256, budget2)))
|
|
24195
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
23976
24196
|
return source.hashMode === "codex-settings-v6" ? { ...source, ...captureCodexSettingsV6(source.path, source.hookSources, budget2, changes) } : { ...source, sha256: sha2563 };
|
|
23977
24197
|
}) };
|
|
23978
24198
|
for (const root2 of absentRoots) {
|
|
23979
24199
|
safe6(root2);
|
|
23980
|
-
if (
|
|
24200
|
+
if (lstatSync32(root2, { throwIfNoEntry: false }))
|
|
23981
24201
|
throw new Error("Retired native materialization changed during discovery rebinding");
|
|
23982
24202
|
}
|
|
23983
24203
|
return rebound;
|
|
23984
24204
|
}
|
|
23985
24205
|
function hashReviewedExecutable(path, budget2) {
|
|
23986
|
-
const before =
|
|
24206
|
+
const before = lstatSync32(path, { bigint: true });
|
|
23987
24207
|
if (before.size < 0n || before.size > BigInt(budget2.remaining))
|
|
23988
24208
|
throw new Error("Discovery aggregate executable byte limit exceeded");
|
|
23989
24209
|
const digest3 = pluginExecutableDigest(path).slice(7);
|
|
23990
|
-
const after =
|
|
24210
|
+
const after = lstatSync32(path, { bigint: true });
|
|
23991
24211
|
for (const key2 of ["dev", "ino", "size", "mode", "uid", "gid", "nlink", "mtimeNs", "ctimeNs"]) {
|
|
23992
24212
|
if (before[key2] !== after[key2])
|
|
23993
24213
|
throw new Error("Discovery executable changed during budgeted capture");
|
|
@@ -24013,12 +24233,13 @@ function captureDiscoveryPathSources(paths) {
|
|
|
24013
24233
|
const budget2 = discoveryByteBudget();
|
|
24014
24234
|
return paths.map((path) => isClaudePluginCacheOrphanMarker(path) ? { path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(path, budget2) } : { path, hashMode: "path-bytes", sha256: hashDiscoveryPathFile(path, budget2) });
|
|
24015
24235
|
}
|
|
24016
|
-
|
|
24236
|
+
var CLAUDE_PROJECT_DISCOVERY_KEYS = Object.freeze(["enabledPlugins", "extraKnownMarketplaces", "additionalMarketplaces", "skillOverrides"]);
|
|
24237
|
+
function assertProjectDiscovery(agent, directories, home, canonical9 = resolve27, reviewed) {
|
|
24017
24238
|
const homeConfigPath = canonical9(agentDiscoveryConfigPath(home, agent));
|
|
24018
24239
|
const v6 = agent === "codex" && reviewed?.agent === "codex" && reviewed.method === "reviewed" ? reviewed.sources.find((source) => source.hashMode === "codex-settings-v6" && source.path === homeConfigPath) : undefined;
|
|
24019
24240
|
const referenced = v6 ? new Set(captureCodexHookMcpDependencies(v6.path, v6.hookSources).servers) : undefined;
|
|
24020
24241
|
for (const directory2 of directories) {
|
|
24021
|
-
const isHome =
|
|
24242
|
+
const isHome = resolve27(directory2) === resolve27(home);
|
|
24022
24243
|
if (agent === "hermes") {
|
|
24023
24244
|
assertHermesEnvironment(home);
|
|
24024
24245
|
continue;
|
|
@@ -24027,7 +24248,7 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24027
24248
|
continue;
|
|
24028
24249
|
const paths = agent === "claude" ? [...isHome ? [] : [".claude/settings.json"], ".claude/settings.local.json"] : agent === "codex" ? [".codex/config.toml"] : agent === "gemini" ? [".gemini/settings.json"] : agent === "opencode" ? ["opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc"] : agent === "sumi" ? ["sumi.json", "sumi.jsonc", ".sumi/sumi.json", ".sumi/sumi.jsonc"] : [".cursor/hooks.json"];
|
|
24029
24250
|
for (const suffix of paths) {
|
|
24030
|
-
const path = canonical9(
|
|
24251
|
+
const path = canonical9(join38(directory2, suffix)), raw = read2(path);
|
|
24031
24252
|
if (raw === null)
|
|
24032
24253
|
continue;
|
|
24033
24254
|
const isRootAliasedHomeConfig = agent === "claude" && suffix === ".claude/settings.json" || agent === "codex" && suffix === ".codex/config.toml";
|
|
@@ -24042,7 +24263,7 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24042
24263
|
if (declaredHooks || providerOverride)
|
|
24043
24264
|
throw new Error(`NATIVE_SKILL_DRIFT: higher-precedence project hook dependency requires review: ${path}`);
|
|
24044
24265
|
}
|
|
24045
|
-
const keys2 = agent === "claude" ?
|
|
24266
|
+
const keys2 = agent === "claude" ? CLAUDE_PROJECT_DISCOVERY_KEYS : agent === "codex" ? ["plugins", "marketplaces", "skills"] : agent === "gemini" ? ["skills", "extensions"] : agent === "opencode" ? ["plugin", "skills"] : agent === "sumi" ? ["plugin", "plugins", "skills", "permissions"] : ["hooks"];
|
|
24046
24267
|
let admittedPlugins = false;
|
|
24047
24268
|
if (agent === "claude" && reviewed?.agent === agent && reviewed.method === "reviewed" && config.enabledPlugins && typeof config.enabledPlugins === "object" && !Array.isArray(config.enabledPlugins)) {
|
|
24048
24269
|
const exact = reviewed.sources.find((source) => source.path === path && source.format === undefined && source.fields === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
|
|
@@ -24056,8 +24277,8 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24056
24277
|
throw new Error(`NATIVE_SKILL_DRIFT: higher-precedence project skill or hook configuration requires review: ${path}`);
|
|
24057
24278
|
}
|
|
24058
24279
|
if (referenced) {
|
|
24059
|
-
const path = canonical9(
|
|
24060
|
-
if (path !== canonical9(
|
|
24280
|
+
const path = canonical9(join38(directory2, ".codex/hooks.json"));
|
|
24281
|
+
if (path !== canonical9(join38(dirname26(homeConfigPath), "hooks.json"))) {
|
|
24061
24282
|
const raw = read2(path);
|
|
24062
24283
|
if (raw !== null) {
|
|
24063
24284
|
const hooks = parseConfig(raw, path, false);
|
|
@@ -24069,15 +24290,15 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24069
24290
|
}
|
|
24070
24291
|
if (agent === "sumi")
|
|
24071
24292
|
for (const name of ["plugin", "plugins"]) {
|
|
24072
|
-
const path = canonical9(
|
|
24293
|
+
const path = canonical9(join38(directory2, ".sumi", name));
|
|
24073
24294
|
safe6(path);
|
|
24074
|
-
if (
|
|
24295
|
+
if (lstatSync32(path, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(path).length)
|
|
24075
24296
|
throw new Error(`NATIVE_SKILL_DRIFT: project Sumi plugins require a dedicated discovery review: ${path}`);
|
|
24076
24297
|
}
|
|
24077
24298
|
if (agent === "claude") {
|
|
24078
|
-
const commands = canonical9(
|
|
24299
|
+
const commands = canonical9(join38(directory2, ".claude/commands"));
|
|
24079
24300
|
safe6(commands);
|
|
24080
|
-
if (
|
|
24301
|
+
if (lstatSync32(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(commands).length)
|
|
24081
24302
|
throw new Error("NATIVE_SKILL_DRIFT: legacy project command discovery requires a dedicated format adapter");
|
|
24082
24303
|
}
|
|
24083
24304
|
}
|
|
@@ -24085,7 +24306,7 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24085
24306
|
function agentDiscoveryConfigPath(home, agent) {
|
|
24086
24307
|
if (agent === "sumi")
|
|
24087
24308
|
return sumiConfigPath(home);
|
|
24088
|
-
return
|
|
24309
|
+
return join38(home, agent === "hermes" ? ".hermes/config.yaml" : agent === "opencode" ? ".config/opencode/opencode.json" : `.${agent}/${agent === "codex" ? "config.toml" : agent === "cursor" ? "hooks.json" : "settings.json"}`);
|
|
24089
24310
|
}
|
|
24090
24311
|
function verifyAutomaticDiscoveryClosure(binding, options) {
|
|
24091
24312
|
const command = DISCOVERY_RUNTIME_COMMANDS[binding.agent], recorded = options.executable;
|
|
@@ -24145,7 +24366,8 @@ function codexVendorConfigChangeOnly(source, cache) {
|
|
|
24145
24366
|
if (text2 === null)
|
|
24146
24367
|
return false;
|
|
24147
24368
|
const rowsAffectDigest = !["codex-settings-v2", "codex-settings-v3", "codex-settings-v4", "codex-settings-v5"].includes(source.hashMode ?? "");
|
|
24148
|
-
|
|
24369
|
+
const typed = source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4" || source.hashMode === "codex-settings-v5" ? source.hashMode : undefined;
|
|
24370
|
+
return codexVendorConfigCandidates(text2, cache, { rowsAffectDigest }).some((candidate2) => replacementDigest(source, candidate2) === source.sha256 || typed !== undefined && codexSettingsToleratesScreenReaderReceipt(candidate2, typed, source.sha256));
|
|
24149
24371
|
} catch {
|
|
24150
24372
|
return false;
|
|
24151
24373
|
}
|
|
@@ -24170,8 +24392,22 @@ function replacementDigest(source, text2) {
|
|
|
24170
24392
|
return hashCodexSettingsReplacementV5(text2, budget2);
|
|
24171
24393
|
return null;
|
|
24172
24394
|
}
|
|
24395
|
+
function discoveryTextKeepsWitness(binding, path, text2) {
|
|
24396
|
+
if (binding.codexSemanticCache?.configPath === path)
|
|
24397
|
+
return false;
|
|
24398
|
+
return binding.sources.filter((source) => source.path === path).every((source) => {
|
|
24399
|
+
if (source.reviewArtifact !== undefined || source.managedPlugins !== undefined || source.hookSources !== undefined || typeof source.sha256 !== "string")
|
|
24400
|
+
return false;
|
|
24401
|
+
const typed = source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4" || source.hashMode === "codex-settings-v5" ? source.hashMode : undefined;
|
|
24402
|
+
try {
|
|
24403
|
+
return replacementDigest(source, text2) === source.sha256 || typed !== undefined && codexSettingsToleratesScreenReaderReceipt(text2, typed, source.sha256);
|
|
24404
|
+
} catch {
|
|
24405
|
+
return false;
|
|
24406
|
+
}
|
|
24407
|
+
});
|
|
24408
|
+
}
|
|
24173
24409
|
function resolveAgentDiscovery(options) {
|
|
24174
|
-
const canonical9 = options.canonical ??
|
|
24410
|
+
const canonical9 = options.canonical ?? resolve27, home = resolve27(options.home), agent = options.agent;
|
|
24175
24411
|
const sources = [], roots = new Set, builtinNames = [];
|
|
24176
24412
|
const witness = (path, format, fields2, hashMode) => {
|
|
24177
24413
|
const source = { path: canonical9(path), sha256: null, ...format ? { format, fields: fields2 } : {}, ...hashMode ? { hashMode } : {} };
|
|
@@ -24192,42 +24428,42 @@ function resolveAgentDiscovery(options) {
|
|
|
24192
24428
|
const review = options.reviewed?.agents.find((item) => item.agent === agent);
|
|
24193
24429
|
if (agent === "sumi") {
|
|
24194
24430
|
const directory2 = sumiConfigDirectory(home);
|
|
24195
|
-
if (witness(
|
|
24431
|
+
if (witness(join38(directory2, "sumi.jsonc")) !== null)
|
|
24196
24432
|
unresolved("JSONC configuration requires a supported format adapter");
|
|
24197
|
-
for (const root2 of [directory2,
|
|
24433
|
+
for (const root2 of [directory2, join38(home, ".claude"), join38(home, ".agents")])
|
|
24198
24434
|
for (const name of ["skill", "skills"])
|
|
24199
|
-
roots.add(canonical9(
|
|
24435
|
+
roots.add(canonical9(join38(root2, name)));
|
|
24200
24436
|
if (config.skills !== undefined && (!Array.isArray(config.skills) || config.skills.some((path) => typeof path !== "string")))
|
|
24201
24437
|
unresolved("skills must be a string array");
|
|
24202
24438
|
for (const value of config.skills ?? []) {
|
|
24203
24439
|
if (typeof value !== "string" || /[\0$]/.test(value) || !value || !(isAbsolute28(value) || value.startsWith("~/")))
|
|
24204
24440
|
unresolved("remote or relative skill sources need a dedicated discovery adapter");
|
|
24205
|
-
roots.add(canonical9(value.startsWith("~/") ?
|
|
24441
|
+
roots.add(canonical9(value.startsWith("~/") ? join38(home, value.slice(2)) : value));
|
|
24206
24442
|
}
|
|
24207
24443
|
for (const key2 of ["plugin", "plugins"])
|
|
24208
24444
|
if (config[key2] !== undefined && !Array.isArray(config[key2]))
|
|
24209
24445
|
unresolved(`${key2} must be an array`);
|
|
24210
|
-
const plugins = [
|
|
24446
|
+
const plugins = [join38(directory2, "plugin"), join38(directory2, "plugins")];
|
|
24211
24447
|
for (const path of plugins) {
|
|
24212
24448
|
safe6(path);
|
|
24213
|
-
if (
|
|
24449
|
+
if (lstatSync32(path, { throwIfNoEntry: false })?.isDirectory())
|
|
24214
24450
|
for (const name of readdirSync12(path).sort()) {
|
|
24215
24451
|
if (name === "skills-cli.js" && path === plugins[1])
|
|
24216
24452
|
continue;
|
|
24217
|
-
const target =
|
|
24218
|
-
if (!
|
|
24453
|
+
const target = join38(path, name);
|
|
24454
|
+
if (!lstatSync32(target).isFile())
|
|
24219
24455
|
unresolved("local plugin packages need a dedicated discovery adapter");
|
|
24220
24456
|
witness(target);
|
|
24221
24457
|
}
|
|
24222
24458
|
}
|
|
24223
24459
|
for (const root2 of roots) {
|
|
24224
24460
|
safe6(root2);
|
|
24225
|
-
if (
|
|
24461
|
+
if (lstatSync32(root2, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(root2).some((name) => name.endsWith(".md")))
|
|
24226
24462
|
unresolved("flat markdown skills require a dedicated native migration adapter");
|
|
24227
24463
|
}
|
|
24228
24464
|
}
|
|
24229
24465
|
if (agent === "hermes") {
|
|
24230
|
-
const profile = witness(
|
|
24466
|
+
const profile = witness(join38(home, ".hermes/active_profile"));
|
|
24231
24467
|
if (profile?.trim() && profile.trim() !== "default")
|
|
24232
24468
|
unresolved("a non-default profile is active");
|
|
24233
24469
|
const declared = config.skills?.external_dirs ?? [], paths = typeof declared === "string" ? [declared] : declared;
|
|
@@ -24241,7 +24477,7 @@ function resolveAgentDiscovery(options) {
|
|
|
24241
24477
|
continue;
|
|
24242
24478
|
if (normalized2.startsWith("~") && normalized2 !== "~" && !normalized2.startsWith("~/"))
|
|
24243
24479
|
unresolved("user-specific tilde expansion requires a separate discovery adapter");
|
|
24244
|
-
const path = normalized2 === "~" ? home : normalized2.startsWith("~/") ?
|
|
24480
|
+
const path = normalized2 === "~" ? home : normalized2.startsWith("~/") ? join38(home, normalized2.slice(2)) : resolve27(home, ".hermes", normalized2);
|
|
24245
24481
|
safe6(path);
|
|
24246
24482
|
roots.add(path);
|
|
24247
24483
|
}
|
|
@@ -24250,24 +24486,24 @@ function resolveAgentDiscovery(options) {
|
|
|
24250
24486
|
const executable2 = options.executable === undefined ? discoveryExecutable(agent) : options.executable;
|
|
24251
24487
|
if (executable2) {
|
|
24252
24488
|
let packageRoot;
|
|
24253
|
-
for (let directory2 =
|
|
24254
|
-
const path =
|
|
24489
|
+
for (let directory2 = dirname26(executable2.target), depth = 0;depth < 8; directory2 = dirname26(directory2), depth++) {
|
|
24490
|
+
const path = join38(directory2, "package.json"), raw = read2(path);
|
|
24255
24491
|
if (raw !== null && parseConfig(raw, path).name === "@google/gemini-cli") {
|
|
24256
24492
|
packageRoot = directory2;
|
|
24257
24493
|
witness(path);
|
|
24258
24494
|
break;
|
|
24259
24495
|
}
|
|
24260
|
-
if (
|
|
24496
|
+
if (dirname26(directory2) === directory2)
|
|
24261
24497
|
break;
|
|
24262
24498
|
}
|
|
24263
24499
|
if (!packageRoot)
|
|
24264
24500
|
unresolved("installed Gemini package layout is not recognized");
|
|
24265
|
-
const builtinRoot =
|
|
24501
|
+
const builtinRoot = join38(packageRoot, "bundle/builtin");
|
|
24266
24502
|
safe6(builtinRoot);
|
|
24267
|
-
if (!
|
|
24503
|
+
if (!lstatSync32(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
|
|
24268
24504
|
unresolved("installed Gemini builtin root is missing");
|
|
24269
24505
|
for (const name of readdirSync12(builtinRoot).sort()) {
|
|
24270
|
-
const text2 = witness(
|
|
24506
|
+
const text2 = witness(join38(builtinRoot, name, "SKILL.md"));
|
|
24271
24507
|
const match = text2?.match(/^---\r?\n[\s\S]*?^name:\s*([a-z0-9][a-z0-9-]*)\s*$/m);
|
|
24272
24508
|
if (!match?.[1] || builtinNames.includes(match[1]))
|
|
24273
24509
|
unresolved("installed Gemini builtin name is missing or ambiguous");
|
|
@@ -24310,10 +24546,10 @@ function resolveAgentDiscovery(options) {
|
|
|
24310
24546
|
if (agent === "claude" && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && source.reviewArtifact === undefined && [undefined, "bytes", "path-bytes"].includes(source.hashMode) && isClaudePluginCacheOrphanMarker(source.path)) {
|
|
24311
24547
|
return { path: source.path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(source.path) };
|
|
24312
24548
|
}
|
|
24313
|
-
if (agent !== "claude" || source.sha256 === null || source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.hashMode !== undefined && source.hashMode !== "bytes" || basename15(source.path) !== "plugin.json" || !source.path.endsWith(
|
|
24549
|
+
if (agent !== "claude" || source.sha256 === null || source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.hashMode !== undefined && source.hashMode !== "bytes" || basename15(source.path) !== "plugin.json" || !source.path.endsWith(join38(sep15, ".claude-plugin", "plugin.json")))
|
|
24314
24550
|
return source;
|
|
24315
|
-
const pluginRoot =
|
|
24316
|
-
const hookStat =
|
|
24551
|
+
const pluginRoot = dirname26(dirname26(source.path)), hooksPath = join38(pluginRoot, "hooks/hooks.json");
|
|
24552
|
+
const hookStat = lstatSync32(hooksPath, { throwIfNoEntry: false });
|
|
24317
24553
|
const text2 = read2(source.path);
|
|
24318
24554
|
if (text2 === null)
|
|
24319
24555
|
throw new Error("Reviewed Claude plugin manifest is missing");
|
|
@@ -24327,8 +24563,8 @@ function resolveAgentDiscovery(options) {
|
|
|
24327
24563
|
if (manifest.hooks !== undefined) {
|
|
24328
24564
|
if (typeof manifest.hooks !== "string")
|
|
24329
24565
|
throw new Error("Reviewed Claude plugin manifest has an unsupported hooks target");
|
|
24330
|
-
const declaredHookPath =
|
|
24331
|
-
if (!declaredHookPath.startsWith(pluginRoot +
|
|
24566
|
+
const declaredHookPath = resolve27(pluginRoot, manifest.hooks);
|
|
24567
|
+
if (!declaredHookPath.startsWith(pluginRoot + sep15))
|
|
24332
24568
|
throw new Error("Reviewed Claude plugin hooks target escapes its root");
|
|
24333
24569
|
reviewedHookPaths.add(declaredHookPath);
|
|
24334
24570
|
}
|
|
@@ -24346,39 +24582,39 @@ function resolveAgentDiscovery(options) {
|
|
|
24346
24582
|
function plugin(root2) {
|
|
24347
24583
|
root2 = canonical9(root2);
|
|
24348
24584
|
safe6(root2);
|
|
24349
|
-
const manifestPath =
|
|
24585
|
+
const manifestPath = join38(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
|
|
24350
24586
|
const raw = witness(manifestPath, undefined, undefined, agent === "claude" ? "claude-plugin-manifest-v1" : undefined);
|
|
24351
24587
|
if (raw === null)
|
|
24352
24588
|
unresolved("plugin manifest missing");
|
|
24353
24589
|
const manifest = parseConfig(raw, manifestPath);
|
|
24354
|
-
const hooks = witness(
|
|
24590
|
+
const hooks = witness(join38(root2, "hooks/hooks.json"));
|
|
24355
24591
|
if (manifest.hooks !== undefined || hooks !== null)
|
|
24356
24592
|
unresolved("plugin hooks require a separate no-skill-injection review");
|
|
24357
|
-
const commands =
|
|
24593
|
+
const commands = join38(root2, "commands");
|
|
24358
24594
|
safe6(commands);
|
|
24359
|
-
if (agent === "claude" && (manifest.commands !== undefined ||
|
|
24595
|
+
if (agent === "claude" && (manifest.commands !== undefined || lstatSync32(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(commands).length))
|
|
24360
24596
|
unresolved("legacy command discovery requires retirement or a dedicated format adapter");
|
|
24361
|
-
const rootSkill = witness(
|
|
24597
|
+
const rootSkill = witness(join38(root2, "SKILL.md"));
|
|
24362
24598
|
if (rootSkill !== null)
|
|
24363
24599
|
roots.add(root2);
|
|
24364
24600
|
const declared = manifest.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
24365
24601
|
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
24366
24602
|
unresolved("unsupported plugin skill paths");
|
|
24367
24603
|
if (agent === "claude")
|
|
24368
|
-
roots.add(
|
|
24604
|
+
roots.add(join38(root2, "skills"));
|
|
24369
24605
|
for (const value of declared) {
|
|
24370
|
-
const path =
|
|
24371
|
-
if (path !== root2 && !path.startsWith(root2 +
|
|
24606
|
+
const path = resolve27(root2, value);
|
|
24607
|
+
if (path !== root2 && !path.startsWith(root2 + sep15))
|
|
24372
24608
|
unresolved("plugin skill path escapes its root");
|
|
24373
|
-
roots.add(path.endsWith(`${
|
|
24609
|
+
roots.add(path.endsWith(`${sep15}SKILL.md`) ? dirname26(path) : path);
|
|
24374
24610
|
}
|
|
24375
24611
|
}
|
|
24376
24612
|
if (agent === "claude") {
|
|
24377
|
-
const registrations = witness(
|
|
24378
|
-
const installed = registrations === null ? {} : parseConfig(registrations,
|
|
24379
|
-
const commands = canonical9(
|
|
24613
|
+
const registrations = witness(join38(home, ".claude/plugins/installed_plugins.json"));
|
|
24614
|
+
const installed = registrations === null ? {} : parseConfig(registrations, join38(home, ".claude/plugins/installed_plugins.json")).plugins;
|
|
24615
|
+
const commands = canonical9(join38(home, ".claude/commands"));
|
|
24380
24616
|
safe6(commands);
|
|
24381
|
-
if (
|
|
24617
|
+
if (lstatSync32(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(commands).length)
|
|
24382
24618
|
unresolved("legacy home command discovery requires a dedicated format adapter");
|
|
24383
24619
|
for (const [id, enabled] of Object.entries(config.enabledPlugins ?? {})) {
|
|
24384
24620
|
if (enabled === false)
|
|
@@ -24400,16 +24636,16 @@ function resolveAgentDiscovery(options) {
|
|
|
24400
24636
|
const market = config.marketplaces?.[marketplace] ?? (marketplace === "personal" ? { source_type: "local", source: home } : undefined);
|
|
24401
24637
|
if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute28(market.source))
|
|
24402
24638
|
unresolved("enabled plugin marketplace is not an explicit local source");
|
|
24403
|
-
const catalogText = witness(
|
|
24639
|
+
const catalogText = witness(join38(market.source, ".agents/plugins/marketplace.json"));
|
|
24404
24640
|
if (catalogText === null)
|
|
24405
24641
|
unresolved("marketplace catalog missing");
|
|
24406
|
-
const catalog = parseConfig(catalogText,
|
|
24642
|
+
const catalog = parseConfig(catalogText, join38(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
|
|
24407
24643
|
if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
|
|
24408
24644
|
unresolved("plugin catalog source is missing or ambiguous");
|
|
24409
|
-
plugin(
|
|
24645
|
+
plugin(resolve27(market.source, matches[0].source.path));
|
|
24410
24646
|
}
|
|
24411
24647
|
} else if (agent === "opencode") {
|
|
24412
|
-
if (witness(
|
|
24648
|
+
if (witness(join38(home, ".config/opencode/opencode.jsonc")) !== null || witness(join38(home, ".config/opencode/config.json")) !== null)
|
|
24413
24649
|
unresolved("additional config layers require review");
|
|
24414
24650
|
if ((config.plugin?.length ?? 0) > 0 || config.skills?.urls?.length)
|
|
24415
24651
|
unresolved("external plugin or remote skill source");
|
|
@@ -24418,17 +24654,17 @@ function resolveAgentDiscovery(options) {
|
|
|
24418
24654
|
unresolved("relative or malformed added skill root");
|
|
24419
24655
|
roots.add(path);
|
|
24420
24656
|
}
|
|
24421
|
-
const localPlugins =
|
|
24657
|
+
const localPlugins = join38(home, ".config/opencode/plugins");
|
|
24422
24658
|
safe6(localPlugins);
|
|
24423
|
-
if (
|
|
24659
|
+
if (lstatSync32(localPlugins, { throwIfNoEntry: false }) && readdirSync12(localPlugins).some((name) => name !== "skills-cli.js"))
|
|
24424
24660
|
unresolved("additional local plugin hooks require review");
|
|
24425
24661
|
} else if (agent === "sumi") {
|
|
24426
|
-
if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(
|
|
24662
|
+
if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join38(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null))
|
|
24427
24663
|
unresolved("external or foreign local plugins require review");
|
|
24428
24664
|
} else if (agent === "hermes") {
|
|
24429
|
-
for (const directory2 of [
|
|
24665
|
+
for (const directory2 of [join38(home, ".hermes/plugins"), join38(home, ".hermes/hermes-agent")]) {
|
|
24430
24666
|
safe6(directory2);
|
|
24431
|
-
if (
|
|
24667
|
+
if (lstatSync32(directory2, { throwIfNoEntry: false }))
|
|
24432
24668
|
unresolved("installed runtime/plugin discovery requires reviewed source bindings");
|
|
24433
24669
|
}
|
|
24434
24670
|
if (Bun.which("hermes") || Object.keys(config.plugins ?? {}).length)
|
|
@@ -24436,30 +24672,30 @@ function resolveAgentDiscovery(options) {
|
|
|
24436
24672
|
} else if (agent === "gemini") {
|
|
24437
24673
|
if (Object.keys(config.extensions ?? {}).length || config.skills?.paths?.length)
|
|
24438
24674
|
unresolved("custom extension or skill paths");
|
|
24439
|
-
const directory2 =
|
|
24675
|
+
const directory2 = join38(home, ".gemini/extensions");
|
|
24440
24676
|
safe6(directory2);
|
|
24441
|
-
if (
|
|
24677
|
+
if (lstatSync32(directory2, { throwIfNoEntry: false }))
|
|
24442
24678
|
for (const name of readdirSync12(directory2)) {
|
|
24443
|
-
const root2 =
|
|
24679
|
+
const root2 = join38(directory2, name);
|
|
24444
24680
|
safe6(root2);
|
|
24445
|
-
if (!
|
|
24681
|
+
if (!lstatSync32(root2)?.isDirectory())
|
|
24446
24682
|
continue;
|
|
24447
|
-
const raw = witness(
|
|
24683
|
+
const raw = witness(join38(root2, "gemini-extension.json"));
|
|
24448
24684
|
if (raw === null)
|
|
24449
24685
|
unresolved("extension registration missing");
|
|
24450
|
-
const manifest = parseConfig(raw,
|
|
24451
|
-
if (manifest.hooks !== undefined || witness(
|
|
24686
|
+
const manifest = parseConfig(raw, join38(root2, "gemini-extension.json"));
|
|
24687
|
+
if (manifest.hooks !== undefined || witness(join38(root2, "hooks/hooks.json")) !== null || witness(join38(root2, ".gemini-extension-install.json")) !== null)
|
|
24452
24688
|
unresolved("extension hooks or linked registration require review");
|
|
24453
|
-
roots.add(
|
|
24689
|
+
roots.add(join38(root2, "skills"));
|
|
24454
24690
|
}
|
|
24455
24691
|
}
|
|
24456
|
-
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([
|
|
24692
|
+
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join38(home, ".hermes/plugins"), join38(home, ".hermes/hermes-agent")].map((path) => canonical9(path))) } : {} };
|
|
24457
24693
|
}
|
|
24458
24694
|
|
|
24459
24695
|
// src/lib/authoring-path.ts
|
|
24460
24696
|
var NATIVE_DISCOVERY_ROOTS = NATIVE_SKILL_ROOTS.map(([, path]) => path.split("/"));
|
|
24461
24697
|
function pathParts(path) {
|
|
24462
|
-
return
|
|
24698
|
+
return resolve28(path).split(sep16).filter(Boolean);
|
|
24463
24699
|
}
|
|
24464
24700
|
function nativeRootMatch(path) {
|
|
24465
24701
|
const parts = pathParts(path);
|
|
@@ -24473,24 +24709,24 @@ function nativeRootMatch(path) {
|
|
|
24473
24709
|
return;
|
|
24474
24710
|
}
|
|
24475
24711
|
function physicalPath(path) {
|
|
24476
|
-
let cursor =
|
|
24712
|
+
let cursor = resolve28(path);
|
|
24477
24713
|
const suffix = [];
|
|
24478
|
-
while (!
|
|
24714
|
+
while (!lstatSync33(cursor, { throwIfNoEntry: false })) {
|
|
24479
24715
|
suffix.unshift(basename16(cursor));
|
|
24480
|
-
const parent =
|
|
24716
|
+
const parent = dirname27(cursor);
|
|
24481
24717
|
if (parent === cursor)
|
|
24482
|
-
return
|
|
24718
|
+
return resolve28(path);
|
|
24483
24719
|
cursor = parent;
|
|
24484
24720
|
}
|
|
24485
|
-
return
|
|
24721
|
+
return resolve28(realpathSync14(cursor), ...suffix);
|
|
24486
24722
|
}
|
|
24487
24723
|
function contains(root2, path) {
|
|
24488
|
-
return path === root2 || path.startsWith(root2 +
|
|
24724
|
+
return path === root2 || path.startsWith(root2 + sep16);
|
|
24489
24725
|
}
|
|
24490
24726
|
function configuredRoots(home, explicitHome) {
|
|
24491
|
-
const homeData =
|
|
24727
|
+
const homeData = join39(home, ".hasna", "skills");
|
|
24492
24728
|
const homePolicy = readManagedSkillPolicySnapshot(homeData);
|
|
24493
|
-
const configuredPolicy = !explicitHome &&
|
|
24729
|
+
const configuredPolicy = !explicitHome && resolve28(getDataDirReadOnly()) !== resolve28(homeData) ? readManagedSkillPolicySnapshot(getDataDirReadOnly()) : null;
|
|
24494
24730
|
const policy = homePolicy ?? configuredPolicy;
|
|
24495
24731
|
const bridge = policy?.value.bridge;
|
|
24496
24732
|
if (bridge && (typeof bridge.home !== "string" || physicalPath(bridge.home) !== physicalPath(home))) {
|
|
@@ -24519,8 +24755,8 @@ function configuredRoots(home, explicitHome) {
|
|
|
24519
24755
|
admitBinding(binding);
|
|
24520
24756
|
continue;
|
|
24521
24757
|
}
|
|
24522
|
-
const nativeHome =
|
|
24523
|
-
if (!
|
|
24758
|
+
const nativeHome = join39(home, agent === "opencode" ? ".config/opencode" : `.${agent}`);
|
|
24759
|
+
if (!lstatSync33(nativeHome, { throwIfNoEntry: false }))
|
|
24524
24760
|
continue;
|
|
24525
24761
|
roots.push(...resolveAgentDiscovery({ home, agent, canonical: physicalPath }).roots);
|
|
24526
24762
|
}
|
|
@@ -24529,22 +24765,22 @@ function configuredRoots(home, explicitHome) {
|
|
|
24529
24765
|
function assertPortableAuthoringPath(path, options = {}) {
|
|
24530
24766
|
if (typeof path !== "string" || !path || path.includes("\x00"))
|
|
24531
24767
|
throw new Error("Invalid portable authoring path");
|
|
24532
|
-
const lexical =
|
|
24768
|
+
const lexical = resolve28(path);
|
|
24533
24769
|
const physical = physicalPath(lexical);
|
|
24534
24770
|
const candidates = [lexical, physical];
|
|
24535
24771
|
let native = candidates.map(nativeRootMatch).find(Boolean);
|
|
24536
|
-
const home =
|
|
24537
|
-
const defaults = NATIVE_DISCOVERY_ROOTS.map((parts) =>
|
|
24538
|
-
defaults.push(
|
|
24772
|
+
const home = resolve28(options.homeDir ?? homedir6());
|
|
24773
|
+
const defaults = NATIVE_DISCOVERY_ROOTS.map((parts) => join39(home, ...parts));
|
|
24774
|
+
defaults.push(join39(home, ".codex/plugins/cache"), join39(home, ".claude/plugins/cache"), join39(home, ".claude/plugins/synced"), join39(home, ".gemini/extensions"));
|
|
24539
24775
|
for (const root2 of defaults) {
|
|
24540
|
-
if (candidates.some((candidate2) => contains(
|
|
24776
|
+
if (candidates.some((candidate2) => contains(resolve28(root2), candidate2) || contains(physicalPath(root2), candidate2)))
|
|
24541
24777
|
native ??= root2;
|
|
24542
24778
|
}
|
|
24543
24779
|
if (native) {
|
|
24544
24780
|
throw new Error(`Refusing portable Skills authoring below native discovery root '${native}': ${lexical}. Set HASNA_SKILLS_DIR to a private authoring corpus outside native roots.`);
|
|
24545
24781
|
}
|
|
24546
24782
|
for (const root2 of configuredRoots(home, options.homeDir !== undefined)) {
|
|
24547
|
-
if (candidates.some((candidate2) => contains(
|
|
24783
|
+
if (candidates.some((candidate2) => contains(resolve28(root2), candidate2) || contains(physicalPath(root2), candidate2))) {
|
|
24548
24784
|
throw new Error(`Refusing portable Skills authoring below configured native discovery root: ${lexical}`);
|
|
24549
24785
|
}
|
|
24550
24786
|
}
|
|
@@ -24556,15 +24792,15 @@ function getPortableSkillsRoot(options = {}) {
|
|
|
24556
24792
|
function resolvePortableSkillsRoot(options, migrate) {
|
|
24557
24793
|
if (options.rootDir)
|
|
24558
24794
|
return options.rootDir;
|
|
24559
|
-
const appDir = options.homeDir ?
|
|
24560
|
-
const cache =
|
|
24795
|
+
const appDir = options.homeDir ? join40(options.homeDir, ".hasna", "skills") : migrate ? getDataDir() : getDataDirReadOnly();
|
|
24796
|
+
const cache = join40(appDir, SKILLS_CACHE_DIRNAME);
|
|
24561
24797
|
if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
|
|
24562
24798
|
return cache;
|
|
24563
|
-
const installed =
|
|
24799
|
+
const installed = join40(appDir, INSTALLED_SKILLS_DIRNAME);
|
|
24564
24800
|
return installed;
|
|
24565
24801
|
}
|
|
24566
24802
|
function getPortableSkillPath(name, options = {}) {
|
|
24567
|
-
return
|
|
24803
|
+
return join40(getPortableSkillsRoot(options), normalizePortableSkillName(name));
|
|
24568
24804
|
}
|
|
24569
24805
|
function findPortableSkill(name, options = {}) {
|
|
24570
24806
|
let normalized2;
|
|
@@ -24590,7 +24826,7 @@ function listPortableSkills(options = {}) {
|
|
|
24590
24826
|
for (const entry of readdirSync13(root2).sort()) {
|
|
24591
24827
|
if (entry.startsWith("."))
|
|
24592
24828
|
continue;
|
|
24593
|
-
const path =
|
|
24829
|
+
const path = join40(root2, entry);
|
|
24594
24830
|
if (!safeIsDirectory(path))
|
|
24595
24831
|
continue;
|
|
24596
24832
|
try {
|
|
@@ -24624,9 +24860,9 @@ function isOfficialSkillName(name) {
|
|
|
24624
24860
|
function scaffoldPortableSkill(name, options = {}) {
|
|
24625
24861
|
const skillName = normalizeNewPortableSkillName(name);
|
|
24626
24862
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
24627
|
-
assertPortableAuthoringPath(
|
|
24863
|
+
assertPortableAuthoringPath(join40(readOnlyRoot, skillName), options);
|
|
24628
24864
|
const root2 = getPortableSkillsRoot(options);
|
|
24629
|
-
const skillPath =
|
|
24865
|
+
const skillPath = join40(root2, skillName);
|
|
24630
24866
|
assertPortableAuthoringPath(skillPath, options);
|
|
24631
24867
|
if (existsSync14(skillPath)) {
|
|
24632
24868
|
if (!options.overwrite)
|
|
@@ -24660,7 +24896,7 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
24660
24896
|
const skipped = [];
|
|
24661
24897
|
const entries = readdirSync13(absoluteSource, { withFileTypes: true }).map((entry) => entry.name).filter((entryName) => !entryName.startsWith(".")).sort();
|
|
24662
24898
|
for (const entryName of entries) {
|
|
24663
|
-
const childPath =
|
|
24899
|
+
const childPath = join40(absoluteSource, entryName);
|
|
24664
24900
|
if (!safeIsDirectory(childPath))
|
|
24665
24901
|
continue;
|
|
24666
24902
|
if (!isSkillCandidate(childPath)) {
|
|
@@ -24689,23 +24925,23 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
24689
24925
|
};
|
|
24690
24926
|
}
|
|
24691
24927
|
function isSkillCandidate(dir) {
|
|
24692
|
-
return existsSync14(
|
|
24928
|
+
return existsSync14(join40(dir, "SKILL.md")) || existsSync14(join40(dir, "skill.json")) || existsSync14(join40(dir, "package.json"));
|
|
24693
24929
|
}
|
|
24694
24930
|
function physicalImportPath(path) {
|
|
24695
|
-
let ancestor =
|
|
24931
|
+
let ancestor = resolve29(path);
|
|
24696
24932
|
const suffix = [];
|
|
24697
24933
|
while (!existsSync14(ancestor)) {
|
|
24698
24934
|
suffix.unshift(basename17(ancestor));
|
|
24699
|
-
ancestor =
|
|
24935
|
+
ancestor = dirname28(ancestor);
|
|
24700
24936
|
}
|
|
24701
|
-
return
|
|
24937
|
+
return join40(realpathSync15(ancestor), ...suffix);
|
|
24702
24938
|
}
|
|
24703
24939
|
function assertDisjointImportPaths(source, destination) {
|
|
24704
24940
|
const sourcePath = realpathSync15(source);
|
|
24705
24941
|
const destinationPath = physicalImportPath(destination);
|
|
24706
24942
|
const contains2 = (parent, child2) => {
|
|
24707
24943
|
const path = relative11(parent, child2);
|
|
24708
|
-
return path === "" || !isAbsolute29(path) && path !== ".." && !path.startsWith(`..${
|
|
24944
|
+
return path === "" || !isAbsolute29(path) && path !== ".." && !path.startsWith(`..${sep17}`);
|
|
24709
24945
|
};
|
|
24710
24946
|
if (contains2(sourcePath, destinationPath) || contains2(destinationPath, sourcePath)) {
|
|
24711
24947
|
throw new Error("Source and destination skill directories must not overlap");
|
|
@@ -24724,11 +24960,11 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
24724
24960
|
const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename17(absoluteSource)}')` : ""} matches a bundled official skill.`;
|
|
24725
24961
|
throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
|
|
24726
24962
|
}
|
|
24727
|
-
const readOnlyDestination =
|
|
24963
|
+
const readOnlyDestination = join40(resolvePortableSkillsRoot(options, false), skillName);
|
|
24728
24964
|
assertPortableAuthoringPath(readOnlyDestination, options);
|
|
24729
24965
|
assertDisjointImportPaths(absoluteSource, readOnlyDestination);
|
|
24730
24966
|
const root2 = getPortableSkillsRoot(options);
|
|
24731
|
-
const destination =
|
|
24967
|
+
const destination = join40(root2, skillName);
|
|
24732
24968
|
assertPortableAuthoringPath(destination, options);
|
|
24733
24969
|
assertDisjointImportPaths(absoluteSource, destination);
|
|
24734
24970
|
if (existsSync14(destination)) {
|
|
@@ -24736,7 +24972,7 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
24736
24972
|
throw new Error(`Skill '${skillName}' already exists at ${destination}`);
|
|
24737
24973
|
rmSync4(destination, { recursive: true, force: true });
|
|
24738
24974
|
}
|
|
24739
|
-
mkdirSync11(
|
|
24975
|
+
mkdirSync11(dirname28(destination), { recursive: true });
|
|
24740
24976
|
copySkillDirectory(absoluteSource, destination);
|
|
24741
24977
|
const base = {
|
|
24742
24978
|
...inferred,
|
|
@@ -24773,13 +25009,13 @@ function buildCorpusManifest(input, name) {
|
|
|
24773
25009
|
function writeCorpusSkill(input, options = {}) {
|
|
24774
25010
|
const name = normalizePortableSkillName(input.name);
|
|
24775
25011
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
24776
|
-
assertPortableAuthoringPath(
|
|
25012
|
+
assertPortableAuthoringPath(join40(readOnlyRoot, name), options);
|
|
24777
25013
|
const root2 = getPortableSkillsRoot(options);
|
|
24778
|
-
const skillPath =
|
|
25014
|
+
const skillPath = join40(root2, name);
|
|
24779
25015
|
assertPortableAuthoringPath(skillPath, options);
|
|
24780
25016
|
const created = !existsSync14(skillPath);
|
|
24781
25017
|
mkdirSync11(skillPath, { recursive: true });
|
|
24782
|
-
writeFileSync9(
|
|
25018
|
+
writeFileSync9(join40(skillPath, "SKILL.md"), input.skillMd);
|
|
24783
25019
|
const manifest = buildCorpusManifest(input, name);
|
|
24784
25020
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
24785
25021
|
return { name, path: skillPath, manifest, created };
|
|
@@ -24787,22 +25023,22 @@ function writeCorpusSkill(input, options = {}) {
|
|
|
24787
25023
|
function installCorpusSkillAtomically(input, options = {}) {
|
|
24788
25024
|
const name = normalizePortableSkillName(input.name);
|
|
24789
25025
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
24790
|
-
assertPortableAuthoringPath(
|
|
25026
|
+
assertPortableAuthoringPath(join40(readOnlyRoot, name), options);
|
|
24791
25027
|
const root2 = getPortableSkillsRoot(options);
|
|
24792
|
-
const target =
|
|
25028
|
+
const target = join40(root2, name);
|
|
24793
25029
|
assertPortableAuthoringPath(target, options);
|
|
24794
25030
|
const created = !existsSync14(target);
|
|
24795
25031
|
mkdirSync11(root2, { recursive: true });
|
|
24796
|
-
const staging = mkdtempSync2(
|
|
25032
|
+
const staging = mkdtempSync2(join40(root2, `.pull-${name}-`));
|
|
24797
25033
|
let moved = false;
|
|
24798
25034
|
let backup = null;
|
|
24799
25035
|
try {
|
|
24800
|
-
writeFileSync9(
|
|
25036
|
+
writeFileSync9(join40(staging, "SKILL.md"), input.skillMd);
|
|
24801
25037
|
const manifest = buildCorpusManifest(input, name);
|
|
24802
25038
|
writeSkillJsonWithHash(staging, manifest);
|
|
24803
25039
|
if (existsSync14(target)) {
|
|
24804
|
-
backup = mkdtempSync2(
|
|
24805
|
-
renameSync6(target,
|
|
25040
|
+
backup = mkdtempSync2(join40(root2, `.pull-backup-${name}-`));
|
|
25041
|
+
renameSync6(target, join40(backup, name));
|
|
24806
25042
|
moved = true;
|
|
24807
25043
|
}
|
|
24808
25044
|
renameSync6(staging, target);
|
|
@@ -24811,9 +25047,9 @@ function installCorpusSkillAtomically(input, options = {}) {
|
|
|
24811
25047
|
return { name, path: target, manifest, created };
|
|
24812
25048
|
} catch (error) {
|
|
24813
25049
|
rmSync4(staging, { recursive: true, force: true });
|
|
24814
|
-
if (moved && backup && existsSync14(
|
|
25050
|
+
if (moved && backup && existsSync14(join40(backup, name))) {
|
|
24815
25051
|
try {
|
|
24816
|
-
renameSync6(
|
|
25052
|
+
renameSync6(join40(backup, name), target);
|
|
24817
25053
|
} catch {}
|
|
24818
25054
|
}
|
|
24819
25055
|
throw error;
|
|
@@ -24826,8 +25062,8 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
24826
25062
|
const warnings = [...base.warnings];
|
|
24827
25063
|
let manifest;
|
|
24828
25064
|
if (existsSync14(skillPath)) {
|
|
24829
|
-
const skillJsonPath =
|
|
24830
|
-
const skillMdPath =
|
|
25065
|
+
const skillJsonPath = join40(skillPath, "skill.json");
|
|
25066
|
+
const skillMdPath = join40(skillPath, "SKILL.md");
|
|
24831
25067
|
if (!existsSync14(skillJsonPath) && !existsSync14(skillMdPath)) {
|
|
24832
25068
|
add3(issues, "portable.manifest_missing", "Missing portable manifest: expected SKILL.md frontmatter and/or skill.json");
|
|
24833
25069
|
}
|
|
@@ -24847,7 +25083,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
24847
25083
|
add3(issues, "portable.version_missing", "Portable manifest missing version");
|
|
24848
25084
|
}
|
|
24849
25085
|
const contractIssues = validatePortableManifestContract(manifest, {
|
|
24850
|
-
strict: existsSync14(
|
|
25086
|
+
strict: existsSync14(join40(skillPath, "skill.json")),
|
|
24851
25087
|
skillPath
|
|
24852
25088
|
});
|
|
24853
25089
|
for (const issue of contractIssues)
|
|
@@ -24887,7 +25123,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
24887
25123
|
add3(issues, "portable.command_entry_unsafe", `Command '${command.name}' entry '${command.entry}' must stay inside the skill directory`);
|
|
24888
25124
|
continue;
|
|
24889
25125
|
}
|
|
24890
|
-
const entryPath =
|
|
25126
|
+
const entryPath = join40(skillPath, command.entry);
|
|
24891
25127
|
if (!existsSync14(entryPath))
|
|
24892
25128
|
add3(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
|
|
24893
25129
|
else if (statSync13(entryPath).isDirectory())
|
|
@@ -24898,7 +25134,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
24898
25134
|
} catch (error) {
|
|
24899
25135
|
add3(issues, "portable.manifest_invalid", error.message);
|
|
24900
25136
|
}
|
|
24901
|
-
if (manifest?.kind !== "instruction" && !existsSync14(
|
|
25137
|
+
if (manifest?.kind !== "instruction" && !existsSync14(join40(skillPath, "AGENTS.md"))) {
|
|
24902
25138
|
add3(issues, "portable.agents_missing", "Missing AGENTS.md with build-out instructions for coding agents");
|
|
24903
25139
|
}
|
|
24904
25140
|
}
|
|
@@ -24934,12 +25170,12 @@ async function runPortableSkill(name, args, options = {}) {
|
|
|
24934
25170
|
if (!isSafeRelativePath2(command.entry)) {
|
|
24935
25171
|
return { exitCode: 1, error: `Portable skill '${name}' command entry is unsafe` };
|
|
24936
25172
|
}
|
|
24937
|
-
const entryPath =
|
|
25173
|
+
const entryPath = join40(skill.path, command.entry);
|
|
24938
25174
|
if (!existsSync14(entryPath)) {
|
|
24939
25175
|
return { exitCode: 1, error: `Entry point '${command.entry}' not found in portable skill '${name}'` };
|
|
24940
25176
|
}
|
|
24941
|
-
const pkgPath =
|
|
24942
|
-
const nodeModules =
|
|
25177
|
+
const pkgPath = join40(skill.path, "package.json");
|
|
25178
|
+
const nodeModules = join40(skill.path, "node_modules");
|
|
24943
25179
|
if (existsSync14(pkgPath) && !existsSync14(nodeModules) && hasPackageDependencies(pkgPath)) {
|
|
24944
25180
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
24945
25181
|
cwd: skill.path,
|
|
@@ -25196,12 +25432,12 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
25196
25432
|
for (const entry of entries) {
|
|
25197
25433
|
if (!entry.isDirectory())
|
|
25198
25434
|
continue;
|
|
25199
|
-
const skillMdPath =
|
|
25435
|
+
const skillMdPath = join41(dir, entry.name, "SKILL.md");
|
|
25200
25436
|
if (!existsSync15(skillMdPath))
|
|
25201
25437
|
continue;
|
|
25202
25438
|
let content;
|
|
25203
25439
|
try {
|
|
25204
|
-
content =
|
|
25440
|
+
content = readFileSync27(skillMdPath, "utf-8");
|
|
25205
25441
|
} catch {
|
|
25206
25442
|
continue;
|
|
25207
25443
|
}
|
|
@@ -25216,7 +25452,7 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
25216
25452
|
category: fm.category || "Development Tools",
|
|
25217
25453
|
tags: fm.tags || [],
|
|
25218
25454
|
...fm.kind ? { kind: fm.kind } : {},
|
|
25219
|
-
...isHostedMetadataSkillDir(
|
|
25455
|
+
...isHostedMetadataSkillDir(join41(dir, entry.name)) ? { serverOwned: true } : {},
|
|
25220
25456
|
source
|
|
25221
25457
|
});
|
|
25222
25458
|
}
|
|
@@ -25232,13 +25468,13 @@ function findExtensionSkillPath(name) {
|
|
|
25232
25468
|
for (const entry of entries) {
|
|
25233
25469
|
if (!entry.isDirectory())
|
|
25234
25470
|
continue;
|
|
25235
|
-
const skillDir =
|
|
25236
|
-
const skillMdPath =
|
|
25471
|
+
const skillDir = join41(config.extensionsDir, entry.name);
|
|
25472
|
+
const skillMdPath = join41(skillDir, "SKILL.md");
|
|
25237
25473
|
if (!existsSync15(skillMdPath))
|
|
25238
25474
|
continue;
|
|
25239
25475
|
let content;
|
|
25240
25476
|
try {
|
|
25241
|
-
content =
|
|
25477
|
+
content = readFileSync27(skillMdPath, "utf-8");
|
|
25242
25478
|
} catch {
|
|
25243
25479
|
continue;
|
|
25244
25480
|
}
|
|
@@ -25303,20 +25539,20 @@ function getAllTags(catalog) {
|
|
|
25303
25539
|
}
|
|
25304
25540
|
// src/lib/run-state.ts
|
|
25305
25541
|
import { createHash as createHash31, randomBytes } from "crypto";
|
|
25306
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as
|
|
25307
|
-
import { extname, join as
|
|
25542
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as readFileSync28, readdirSync as readdirSync15, statSync as statSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
25543
|
+
import { extname, join as join42, relative as relative12 } from "path";
|
|
25308
25544
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
25309
25545
|
const now = new Date;
|
|
25310
25546
|
const id = createRunId(now);
|
|
25311
25547
|
const day = now.toISOString().slice(0, 10);
|
|
25312
25548
|
const skillName = normalizeSkillName(params.skill);
|
|
25313
25549
|
const root2 = getProjectStateDir(targetDir);
|
|
25314
|
-
const runDir =
|
|
25315
|
-
const logsDir =
|
|
25316
|
-
const exportDir =
|
|
25550
|
+
const runDir = join42(root2, "runs", day, id);
|
|
25551
|
+
const logsDir = join42(runDir, "logs");
|
|
25552
|
+
const exportDir = join42(root2, "exports", skillName, id);
|
|
25317
25553
|
mkdirSync12(logsDir, { recursive: true });
|
|
25318
25554
|
mkdirSync12(exportDir, { recursive: true });
|
|
25319
|
-
mkdirSync12(
|
|
25555
|
+
mkdirSync12(join42(root2, "tmp"), { recursive: true });
|
|
25320
25556
|
const record5 = {
|
|
25321
25557
|
id,
|
|
25322
25558
|
skill: skillName,
|
|
@@ -25368,27 +25604,27 @@ function updateSkillRun(context, patch) {
|
|
|
25368
25604
|
return context.record;
|
|
25369
25605
|
}
|
|
25370
25606
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
25371
|
-
writeFileSync10(
|
|
25372
|
-
writeFileSync10(
|
|
25607
|
+
writeFileSync10(join42(context.logsDir, "stdout.log"), stdout);
|
|
25608
|
+
writeFileSync10(join42(context.logsDir, "stderr.log"), stderr);
|
|
25373
25609
|
}
|
|
25374
25610
|
function appendRunEvent(context, event, data = {}) {
|
|
25375
25611
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
25376
25612
|
`;
|
|
25377
|
-
const path =
|
|
25378
|
-
const previous = existsSync16(path) ?
|
|
25613
|
+
const path = join42(context.runDir, "events.ndjson");
|
|
25614
|
+
const previous = existsSync16(path) ? readFileSync28(path, "utf-8") : "";
|
|
25379
25615
|
writeFileSync10(path, previous + line);
|
|
25380
25616
|
}
|
|
25381
25617
|
function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
25382
|
-
const runsRoot =
|
|
25618
|
+
const runsRoot = join42(getProjectStateDir(targetDir), "runs");
|
|
25383
25619
|
if (!existsSync16(runsRoot))
|
|
25384
25620
|
return [];
|
|
25385
25621
|
const records = [];
|
|
25386
25622
|
for (const day of readdirSync15(runsRoot).sort().reverse()) {
|
|
25387
|
-
const dayDir =
|
|
25623
|
+
const dayDir = join42(runsRoot, day);
|
|
25388
25624
|
if (!statSync14(dayDir).isDirectory())
|
|
25389
25625
|
continue;
|
|
25390
25626
|
for (const runId of readdirSync15(dayDir).sort().reverse()) {
|
|
25391
|
-
const record5 = readRunRecord(
|
|
25627
|
+
const record5 = readRunRecord(join42(dayDir, runId));
|
|
25392
25628
|
if (record5)
|
|
25393
25629
|
records.push(record5);
|
|
25394
25630
|
if (records.length >= limit)
|
|
@@ -25398,25 +25634,25 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
|
25398
25634
|
return records;
|
|
25399
25635
|
}
|
|
25400
25636
|
function findSkillRun(runId, targetDir = process.cwd()) {
|
|
25401
|
-
const runsRoot =
|
|
25637
|
+
const runsRoot = join42(getProjectStateDir(targetDir), "runs");
|
|
25402
25638
|
if (!existsSync16(runsRoot))
|
|
25403
25639
|
return null;
|
|
25404
25640
|
for (const day of readdirSync15(runsRoot)) {
|
|
25405
|
-
const record5 = readRunRecord(
|
|
25641
|
+
const record5 = readRunRecord(join42(runsRoot, day, runId));
|
|
25406
25642
|
if (record5)
|
|
25407
25643
|
return record5;
|
|
25408
25644
|
}
|
|
25409
25645
|
return null;
|
|
25410
25646
|
}
|
|
25411
25647
|
function getRunExportDir(runId, skill, targetDir = process.cwd()) {
|
|
25412
|
-
return
|
|
25648
|
+
return join42(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
|
|
25413
25649
|
}
|
|
25414
25650
|
function writeRunRecord(context) {
|
|
25415
|
-
writeFileSync10(
|
|
25651
|
+
writeFileSync10(join42(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
25416
25652
|
`);
|
|
25417
25653
|
}
|
|
25418
25654
|
function writeArtifactsManifest(context, artifacts) {
|
|
25419
|
-
writeFileSync10(
|
|
25655
|
+
writeFileSync10(join42(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
25420
25656
|
`);
|
|
25421
25657
|
}
|
|
25422
25658
|
function collectRunArtifacts(context) {
|
|
@@ -25425,7 +25661,7 @@ function collectRunArtifacts(context) {
|
|
|
25425
25661
|
const artifacts = [];
|
|
25426
25662
|
for (const path of walkFiles(context.exportDir)) {
|
|
25427
25663
|
const stat2 = statSync14(path);
|
|
25428
|
-
const bytes =
|
|
25664
|
+
const bytes = readFileSync28(path);
|
|
25429
25665
|
artifacts.push({
|
|
25430
25666
|
path: toProjectRelative(context.targetDir, path),
|
|
25431
25667
|
mime: mimeForPath(path),
|
|
@@ -25436,11 +25672,11 @@ function collectRunArtifacts(context) {
|
|
|
25436
25672
|
return artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
25437
25673
|
}
|
|
25438
25674
|
function readRunRecord(runDir) {
|
|
25439
|
-
const path =
|
|
25675
|
+
const path = join42(runDir, "run.json");
|
|
25440
25676
|
if (!existsSync16(path))
|
|
25441
25677
|
return null;
|
|
25442
25678
|
try {
|
|
25443
|
-
return JSON.parse(
|
|
25679
|
+
return JSON.parse(readFileSync28(path, "utf-8"));
|
|
25444
25680
|
} catch {
|
|
25445
25681
|
return null;
|
|
25446
25682
|
}
|
|
@@ -25448,7 +25684,7 @@ function readRunRecord(runDir) {
|
|
|
25448
25684
|
function walkFiles(dir) {
|
|
25449
25685
|
const files = [];
|
|
25450
25686
|
for (const entry of readdirSync15(dir)) {
|
|
25451
|
-
const full =
|
|
25687
|
+
const full = join42(dir, entry);
|
|
25452
25688
|
if (statSync14(full).isDirectory())
|
|
25453
25689
|
files.push(...walkFiles(full));
|
|
25454
25690
|
else
|
|
@@ -45853,10 +46089,10 @@ async function readQuoteUnavailableCode(response) {
|
|
|
45853
46089
|
let timer;
|
|
45854
46090
|
let deadlineExceeded = false;
|
|
45855
46091
|
const expired = Symbol("quote body deadline");
|
|
45856
|
-
const deadline = new Promise((
|
|
46092
|
+
const deadline = new Promise((resolve30) => {
|
|
45857
46093
|
timer = setTimeout(() => {
|
|
45858
46094
|
deadlineExceeded = true;
|
|
45859
|
-
|
|
46095
|
+
resolve30(expired);
|
|
45860
46096
|
reader.cancel().catch(() => {});
|
|
45861
46097
|
}, 1500);
|
|
45862
46098
|
});
|
|
@@ -46955,23 +47191,23 @@ async function createRemoteSkillsClient(env = process.env) {
|
|
|
46955
47191
|
return connection ? new RemoteSkillsClient(connection.apiKey, connection.apiOrigin) : null;
|
|
46956
47192
|
}
|
|
46957
47193
|
// src/lib/scheduler.ts
|
|
46958
|
-
import { existsSync as existsSync17, readFileSync as
|
|
46959
|
-
import { join as
|
|
47194
|
+
import { existsSync as existsSync17, readFileSync as readFileSync29, writeFileSync as writeFileSync11, mkdirSync as mkdirSync13 } from "fs";
|
|
47195
|
+
import { join as join43 } from "path";
|
|
46960
47196
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
46961
|
-
return
|
|
47197
|
+
return join43(targetDir, ".skills", "schedules.json");
|
|
46962
47198
|
}
|
|
46963
47199
|
function loadSchedules(targetDir = process.cwd()) {
|
|
46964
47200
|
const path = getSchedulesPath(targetDir);
|
|
46965
47201
|
if (existsSync17(path)) {
|
|
46966
47202
|
try {
|
|
46967
|
-
return JSON.parse(
|
|
47203
|
+
return JSON.parse(readFileSync29(path, "utf-8"));
|
|
46968
47204
|
} catch {}
|
|
46969
47205
|
}
|
|
46970
47206
|
return { version: 1, schedules: [] };
|
|
46971
47207
|
}
|
|
46972
47208
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
46973
47209
|
const path = getSchedulesPath(targetDir);
|
|
46974
|
-
const dir =
|
|
47210
|
+
const dir = join43(targetDir, ".skills");
|
|
46975
47211
|
if (!existsSync17(dir))
|
|
46976
47212
|
mkdirSync13(dir, { recursive: true });
|
|
46977
47213
|
writeFileSync11(path, JSON.stringify(data, null, 2));
|
|
@@ -47187,8 +47423,8 @@ function revisionIdOfRecord(record10) {
|
|
|
47187
47423
|
return revisionIdOf(record10);
|
|
47188
47424
|
}
|
|
47189
47425
|
// src/lib/pull.ts
|
|
47190
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync3, readFileSync as
|
|
47191
|
-
import { dirname as
|
|
47426
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync3, readFileSync as readFileSync30, renameSync as renameSync7, rmSync as rmSync5, writeFileSync as writeFileSync12 } from "fs";
|
|
47427
|
+
import { dirname as dirname29, join as join44 } from "path";
|
|
47192
47428
|
|
|
47193
47429
|
// src/lib/skill-bundles.ts
|
|
47194
47430
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
@@ -47282,7 +47518,7 @@ async function pullOne(client2, rawName, corpusOptions, verify) {
|
|
|
47282
47518
|
return reconcileTombstone(slug2, corpusOptions);
|
|
47283
47519
|
}
|
|
47284
47520
|
if (bundleResponse.status === 404) {
|
|
47285
|
-
const marker = readPullMarker(
|
|
47521
|
+
const marker = readPullMarker(join44(getPortableSkillsRoot(corpusOptions), slug2));
|
|
47286
47522
|
if (isPublishedInstallMarker(marker)) {
|
|
47287
47523
|
return { name: slug2, success: true, purged: true, removed: false };
|
|
47288
47524
|
}
|
|
@@ -47321,7 +47557,7 @@ async function pullOne(client2, rawName, corpusOptions, verify) {
|
|
|
47321
47557
|
return { name: slug2, success: false, error: `Skill '${slug2}' was not found on the configured Skills instance.` };
|
|
47322
47558
|
}
|
|
47323
47559
|
if (!meta3?.revisionId) {
|
|
47324
|
-
const marker = readPullMarker(
|
|
47560
|
+
const marker = readPullMarker(join44(getPortableSkillsRoot(corpusOptions), slug2));
|
|
47325
47561
|
if (isPublishedInstallMarker(marker)) {
|
|
47326
47562
|
return { name: slug2, success: true, purged: true, removed: false };
|
|
47327
47563
|
}
|
|
@@ -47378,7 +47614,7 @@ function provenRevision(meta3, slug2, bundle2) {
|
|
|
47378
47614
|
return declared;
|
|
47379
47615
|
}
|
|
47380
47616
|
function reconcileTombstone(slug2, corpusOptions) {
|
|
47381
|
-
const target =
|
|
47617
|
+
const target = join44(getPortableSkillsRoot(corpusOptions), slug2);
|
|
47382
47618
|
if (!hasSkillsOwnershipMarker(target)) {
|
|
47383
47619
|
return { name: slug2, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
47384
47620
|
}
|
|
@@ -47387,7 +47623,7 @@ function reconcileTombstone(slug2, corpusOptions) {
|
|
|
47387
47623
|
}
|
|
47388
47624
|
function readPullMarker(dir) {
|
|
47389
47625
|
try {
|
|
47390
|
-
return JSON.parse(
|
|
47626
|
+
return JSON.parse(readFileSync30(join44(dir, PULL_MARKER_FILE), "utf-8"));
|
|
47391
47627
|
} catch {
|
|
47392
47628
|
return null;
|
|
47393
47629
|
}
|
|
@@ -47522,15 +47758,15 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
|
47522
47758
|
function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
47523
47759
|
const root2 = getPortableSkillsRoot(options);
|
|
47524
47760
|
mkdirSync14(root2, { recursive: true });
|
|
47525
|
-
const target =
|
|
47761
|
+
const target = join44(root2, name);
|
|
47526
47762
|
const created = !existsSync18(target);
|
|
47527
|
-
const staging = mkdtempSync3(
|
|
47763
|
+
const staging = mkdtempSync3(join44(root2, `.pull-${name}-`));
|
|
47528
47764
|
let moved = false;
|
|
47529
47765
|
let backup = null;
|
|
47530
47766
|
try {
|
|
47531
47767
|
for (const entry of entries) {
|
|
47532
|
-
const destination =
|
|
47533
|
-
mkdirSync14(
|
|
47768
|
+
const destination = join44(staging, entry.path);
|
|
47769
|
+
mkdirSync14(dirname29(destination), { recursive: true });
|
|
47534
47770
|
writeFileSync12(destination, entry.bytes, { mode: entry.mode });
|
|
47535
47771
|
}
|
|
47536
47772
|
writePullMarker(staging, {
|
|
@@ -47542,8 +47778,8 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
47542
47778
|
...marker.revisionId ? { revisionId: marker.revisionId } : {}
|
|
47543
47779
|
});
|
|
47544
47780
|
if (existsSync18(target)) {
|
|
47545
|
-
backup = mkdtempSync3(
|
|
47546
|
-
renameSync7(target,
|
|
47781
|
+
backup = mkdtempSync3(join44(root2, `.pull-backup-${name}-`));
|
|
47782
|
+
renameSync7(target, join44(backup, name));
|
|
47547
47783
|
moved = true;
|
|
47548
47784
|
}
|
|
47549
47785
|
renameSync7(staging, target);
|
|
@@ -47551,9 +47787,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
47551
47787
|
rmSync5(backup, { recursive: true, force: true });
|
|
47552
47788
|
} catch (error61) {
|
|
47553
47789
|
rmSync5(staging, { recursive: true, force: true });
|
|
47554
|
-
if (moved && backup && existsSync18(
|
|
47790
|
+
if (moved && backup && existsSync18(join44(backup, name))) {
|
|
47555
47791
|
try {
|
|
47556
|
-
renameSync7(
|
|
47792
|
+
renameSync7(join44(backup, name), target);
|
|
47557
47793
|
} catch {}
|
|
47558
47794
|
}
|
|
47559
47795
|
throw error61;
|
|
@@ -47572,7 +47808,7 @@ function writePullMarker(dir, record10) {
|
|
|
47572
47808
|
...record10.revisionId ? { revisionId: record10.revisionId } : {},
|
|
47573
47809
|
syncedAt: new Date().toISOString()
|
|
47574
47810
|
};
|
|
47575
|
-
writeFileSync12(
|
|
47811
|
+
writeFileSync12(join44(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
47576
47812
|
`);
|
|
47577
47813
|
}
|
|
47578
47814
|
async function safeMeta(client2, slug2) {
|
|
@@ -48014,12 +48250,12 @@ class RemotePrivatePublicationsClient {
|
|
|
48014
48250
|
previous = view;
|
|
48015
48251
|
if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
|
|
48016
48252
|
return view;
|
|
48017
|
-
await new Promise((
|
|
48253
|
+
await new Promise((resolve30) => {
|
|
48018
48254
|
const timer = setTimeout(done, Math.min(1000, until - Date.now()));
|
|
48019
48255
|
function done() {
|
|
48020
48256
|
clearTimeout(timer);
|
|
48021
48257
|
options.signal?.removeEventListener("abort", done);
|
|
48022
|
-
|
|
48258
|
+
resolve30();
|
|
48023
48259
|
}
|
|
48024
48260
|
options.signal?.addEventListener("abort", done, { once: true });
|
|
48025
48261
|
});
|
|
@@ -48434,7 +48670,7 @@ function findSkillsParityForMcpTool(tool) {
|
|
|
48434
48670
|
}
|
|
48435
48671
|
// src/lib/registry-sync.ts
|
|
48436
48672
|
import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "fs";
|
|
48437
|
-
import { dirname as
|
|
48673
|
+
import { dirname as dirname30, relative as relative13 } from "path";
|
|
48438
48674
|
function createRegistrySyncArtifact(options = {}) {
|
|
48439
48675
|
const profile = options.profile ?? "all";
|
|
48440
48676
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -48491,7 +48727,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
48491
48727
|
};
|
|
48492
48728
|
}
|
|
48493
48729
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
48494
|
-
mkdirSync15(
|
|
48730
|
+
mkdirSync15(dirname30(path), { recursive: true });
|
|
48495
48731
|
writeFileSync13(path, `${JSON.stringify(artifact, null, 2)}
|
|
48496
48732
|
`);
|
|
48497
48733
|
}
|
|
@@ -49576,14 +49812,14 @@ function clone3(value) {
|
|
|
49576
49812
|
}
|
|
49577
49813
|
// src/lib/feedback.ts
|
|
49578
49814
|
import { appendFileSync, existsSync as existsSync19, mkdirSync as mkdirSync16 } from "fs";
|
|
49579
|
-
import { dirname as
|
|
49815
|
+
import { dirname as dirname31, join as join45 } from "path";
|
|
49580
49816
|
import { Database } from "bun:sqlite";
|
|
49581
49817
|
function getFeedbackDbPath() {
|
|
49582
|
-
return
|
|
49818
|
+
return join45(getDataDir(), "skills.db");
|
|
49583
49819
|
}
|
|
49584
49820
|
function getFeedbackDb() {
|
|
49585
49821
|
const dbPath = getFeedbackDbPath();
|
|
49586
|
-
const dir =
|
|
49822
|
+
const dir = dirname31(dbPath);
|
|
49587
49823
|
if (!existsSync19(dir))
|
|
49588
49824
|
mkdirSync16(dir, { recursive: true });
|
|
49589
49825
|
const db = new Database(dbPath);
|
|
@@ -49611,8 +49847,8 @@ function saveFeedback(input2) {
|
|
|
49611
49847
|
throw new Error("Feedback message is required");
|
|
49612
49848
|
const category = input2.category ?? "general";
|
|
49613
49849
|
if (isApiMode()) {
|
|
49614
|
-
const path =
|
|
49615
|
-
const dir =
|
|
49850
|
+
const path = join45(getDataDir(), "feedback.jsonl");
|
|
49851
|
+
const dir = dirname31(path);
|
|
49616
49852
|
if (!existsSync19(dir))
|
|
49617
49853
|
mkdirSync16(dir, { recursive: true });
|
|
49618
49854
|
appendFileSync(path, JSON.stringify({ message, category, email: input2.email ?? null, agent: input2.agent ?? null, version: input2.version ?? null, createdAt: new Date().toISOString() }) + `
|
|
@@ -49639,12 +49875,12 @@ import { createHash as createHash35, createHmac as createHmac2 } from "crypto";
|
|
|
49639
49875
|
import {
|
|
49640
49876
|
existsSync as existsSync20,
|
|
49641
49877
|
mkdirSync as mkdirSync17,
|
|
49642
|
-
readFileSync as
|
|
49878
|
+
readFileSync as readFileSync31,
|
|
49643
49879
|
readdirSync as readdirSync16,
|
|
49644
49880
|
statSync as statSync15,
|
|
49645
49881
|
writeFileSync as writeFileSync14
|
|
49646
49882
|
} from "fs";
|
|
49647
|
-
import { dirname as
|
|
49883
|
+
import { dirname as dirname32, join as join46, normalize as normalize5, relative as relative14, sep as sep18 } from "path";
|
|
49648
49884
|
var SKILLS_STORAGE_TABLES = [
|
|
49649
49885
|
"skills_sync_records",
|
|
49650
49886
|
"skills_sync_cursors"
|
|
@@ -49781,7 +50017,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
49781
50017
|
local: {
|
|
49782
50018
|
dataDir: getDataDir(),
|
|
49783
50019
|
projectStateDir: getProjectStateDir(targetDir),
|
|
49784
|
-
feedbackDbPath:
|
|
50020
|
+
feedbackDbPath: join46(getDataDir(), "skills.db")
|
|
49785
50021
|
},
|
|
49786
50022
|
remote: {
|
|
49787
50023
|
databaseConfigured: Boolean(config2.databaseUrl),
|
|
@@ -49806,7 +50042,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
|
49806
50042
|
const files = [];
|
|
49807
50043
|
if (existsSync20(projectStateDir)) {
|
|
49808
50044
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
49809
|
-
const bytes =
|
|
50045
|
+
const bytes = readFileSync31(filePath);
|
|
49810
50046
|
const relativePath = toPosix(relative14(targetDir, filePath));
|
|
49811
50047
|
files.push({
|
|
49812
50048
|
path: relativePath,
|
|
@@ -49840,7 +50076,7 @@ function importSkillsLocalSnapshot(snapshot2, targetDir = process.cwd(), options
|
|
|
49840
50076
|
if (hash5 !== file2.sha256) {
|
|
49841
50077
|
throw new Error(`Snapshot file checksum mismatch: ${file2.path}`);
|
|
49842
50078
|
}
|
|
49843
|
-
mkdirSync17(
|
|
50079
|
+
mkdirSync17(dirname32(absolutePath5), { recursive: true });
|
|
49844
50080
|
writeFileSync14(absolutePath5, bytes);
|
|
49845
50081
|
written += 1;
|
|
49846
50082
|
}
|
|
@@ -50138,7 +50374,7 @@ function parsePositiveInteger(value) {
|
|
|
50138
50374
|
function walkFiles2(dir) {
|
|
50139
50375
|
const files = [];
|
|
50140
50376
|
for (const entry of readdirSync16(dir)) {
|
|
50141
|
-
const full =
|
|
50377
|
+
const full = join46(dir, entry);
|
|
50142
50378
|
const stats = statSync15(full);
|
|
50143
50379
|
if (stats.isDirectory())
|
|
50144
50380
|
files.push(...walkFiles2(full));
|
|
@@ -50149,13 +50385,13 @@ function walkFiles2(dir) {
|
|
|
50149
50385
|
}
|
|
50150
50386
|
function resolveSnapshotPath(targetDir, snapshotPath) {
|
|
50151
50387
|
const normalizedPath = normalize5(snapshotPath);
|
|
50152
|
-
if (normalizedPath.startsWith("..") || normalizedPath.includes(`${
|
|
50388
|
+
if (normalizedPath.startsWith("..") || normalizedPath.includes(`${sep18}..${sep18}`) || normalizedPath.startsWith(sep18)) {
|
|
50153
50389
|
throw new Error(`Unsafe snapshot path: ${snapshotPath}`);
|
|
50154
50390
|
}
|
|
50155
50391
|
if (!toPosix(normalizedPath).startsWith(".skills/")) {
|
|
50156
50392
|
throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
|
|
50157
50393
|
}
|
|
50158
|
-
return
|
|
50394
|
+
return join46(targetDir, normalizedPath);
|
|
50159
50395
|
}
|
|
50160
50396
|
function normalizeS3Prefix(prefix) {
|
|
50161
50397
|
return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
|
|
@@ -50224,16 +50460,16 @@ import { createHash as createHash36 } from "crypto";
|
|
|
50224
50460
|
import {
|
|
50225
50461
|
copyFileSync,
|
|
50226
50462
|
mkdirSync as mkdirSync18,
|
|
50227
|
-
readFileSync as
|
|
50463
|
+
readFileSync as readFileSync32,
|
|
50228
50464
|
statSync as statSync17,
|
|
50229
50465
|
writeFileSync as writeFileSync15
|
|
50230
50466
|
} from "fs";
|
|
50231
|
-
import { dirname as
|
|
50467
|
+
import { dirname as dirname33, isAbsolute as isAbsolute31, relative as relative15, resolve as resolve30, sep as sep20 } from "path";
|
|
50232
50468
|
|
|
50233
50469
|
// src/lib/portable-snapshot-filter.ts
|
|
50234
50470
|
import { readdirSync as readdirSync17, statSync as statSync16 } from "fs";
|
|
50235
50471
|
import { homedir as homedir7 } from "os";
|
|
50236
|
-
import { join as
|
|
50472
|
+
import { join as join47, sep as sep19 } from "path";
|
|
50237
50473
|
var SYNC_HOMES = [
|
|
50238
50474
|
{ name: "skills", subClass: "skills", agent: null },
|
|
50239
50475
|
{ name: "custom", subClass: "custom", agent: null },
|
|
@@ -50326,16 +50562,16 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
50326
50562
|
function homePathFor(definition, homesRoot) {
|
|
50327
50563
|
const home = homesRoot ?? homedir7();
|
|
50328
50564
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
50329
|
-
return
|
|
50565
|
+
return join47(skillsDataRootForHome(home), definition.name);
|
|
50330
50566
|
}
|
|
50331
50567
|
if (definition.agent === "opencode") {
|
|
50332
|
-
return
|
|
50568
|
+
return join47(home, ".config", "opencode", "skills");
|
|
50333
50569
|
}
|
|
50334
|
-
return
|
|
50570
|
+
return join47(home, `.${definition.agent}`, "skills");
|
|
50335
50571
|
}
|
|
50336
50572
|
function destinationFor(definition, stationId, relativePath) {
|
|
50337
|
-
const category = definition.subClass === "agent-homes" ?
|
|
50338
|
-
return
|
|
50573
|
+
const category = definition.subClass === "agent-homes" ? join47("agent-homes", definition.agent ?? "") : definition.name;
|
|
50574
|
+
return join47("resources", stationId, "skills", category, ...relativePath.split(sep19));
|
|
50339
50575
|
}
|
|
50340
50576
|
function walkEntries(absoluteRoot) {
|
|
50341
50577
|
let entries;
|
|
@@ -50346,7 +50582,7 @@ function walkEntries(absoluteRoot) {
|
|
|
50346
50582
|
}
|
|
50347
50583
|
const output2 = [];
|
|
50348
50584
|
for (const entry of entries) {
|
|
50349
|
-
const childFull =
|
|
50585
|
+
const childFull = join47(absoluteRoot, entry.name);
|
|
50350
50586
|
if (entry.isSymbolicLink()) {
|
|
50351
50587
|
output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
50352
50588
|
continue;
|
|
@@ -50357,7 +50593,7 @@ function walkEntries(absoluteRoot) {
|
|
|
50357
50593
|
}
|
|
50358
50594
|
const nested = walkEntries(childFull);
|
|
50359
50595
|
for (const item of nested) {
|
|
50360
|
-
output2.push({ ...item, relativePath:
|
|
50596
|
+
output2.push({ ...item, relativePath: join47(entry.name, item.relativePath) });
|
|
50361
50597
|
}
|
|
50362
50598
|
continue;
|
|
50363
50599
|
}
|
|
@@ -50395,7 +50631,7 @@ function validateStationId(stationId) {
|
|
|
50395
50631
|
}
|
|
50396
50632
|
}
|
|
50397
50633
|
function sha256File(filePath) {
|
|
50398
|
-
return createHash36("sha256").update(
|
|
50634
|
+
return createHash36("sha256").update(readFileSync32(filePath)).digest("hex");
|
|
50399
50635
|
}
|
|
50400
50636
|
function scanHome(definition, homesRoot) {
|
|
50401
50637
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -50403,7 +50639,7 @@ function scanHome(definition, homesRoot) {
|
|
|
50403
50639
|
const portable = [];
|
|
50404
50640
|
const skipped = [];
|
|
50405
50641
|
for (const entry of entries) {
|
|
50406
|
-
const relativeParts = entry.relativePath.split(
|
|
50642
|
+
const relativeParts = entry.relativePath.split(sep20);
|
|
50407
50643
|
const fileName = relativeParts[relativeParts.length - 1];
|
|
50408
50644
|
if (entry.kind === "symlink") {
|
|
50409
50645
|
skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
|
|
@@ -50466,7 +50702,7 @@ function humanHomes(scanned) {
|
|
|
50466
50702
|
}));
|
|
50467
50703
|
}
|
|
50468
50704
|
function writeStationSnapshot(options) {
|
|
50469
|
-
const repoRoot =
|
|
50705
|
+
const repoRoot = resolve30(options.repoRoot ?? process.cwd());
|
|
50470
50706
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
50471
50707
|
const manifestFiles = plans.map((plan) => ({
|
|
50472
50708
|
relativePath: plan.source.relativePath,
|
|
@@ -50492,9 +50728,9 @@ function writeStationSnapshot(options) {
|
|
|
50492
50728
|
const conflicts = [];
|
|
50493
50729
|
const untouched = [];
|
|
50494
50730
|
for (const plan of plans) {
|
|
50495
|
-
const destination =
|
|
50731
|
+
const destination = resolve30(repoRoot, plan.destination);
|
|
50496
50732
|
const destinationRelative = relative15(repoRoot, destination);
|
|
50497
|
-
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(
|
|
50733
|
+
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep20) || isAbsolute31(destinationRelative)) {
|
|
50498
50734
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
50499
50735
|
}
|
|
50500
50736
|
let existingDigest = null;
|
|
@@ -50515,8 +50751,8 @@ function writeStationSnapshot(options) {
|
|
|
50515
50751
|
}
|
|
50516
50752
|
let written = 0;
|
|
50517
50753
|
for (const plan of untouched) {
|
|
50518
|
-
const destination =
|
|
50519
|
-
mkdirSync18(
|
|
50754
|
+
const destination = resolve30(repoRoot, plan.destination);
|
|
50755
|
+
mkdirSync18(dirname33(destination), { recursive: true });
|
|
50520
50756
|
copyFileSync(plan.source.fullPath, destination);
|
|
50521
50757
|
written += 1;
|
|
50522
50758
|
}
|
|
@@ -50534,8 +50770,8 @@ function writeStationSnapshot(options) {
|
|
|
50534
50770
|
},
|
|
50535
50771
|
files: manifestFiles
|
|
50536
50772
|
};
|
|
50537
|
-
const manifestPath =
|
|
50538
|
-
mkdirSync18(
|
|
50773
|
+
const manifestPath = resolve30(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
50774
|
+
mkdirSync18(dirname33(manifestPath), { recursive: true });
|
|
50539
50775
|
writeFileSync15(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
50540
50776
|
`);
|
|
50541
50777
|
return {
|
|
@@ -50551,11 +50787,11 @@ import {
|
|
|
50551
50787
|
copyFileSync as copyFileSync2,
|
|
50552
50788
|
mkdirSync as mkdirSync19,
|
|
50553
50789
|
readdirSync as readdirSync18,
|
|
50554
|
-
readFileSync as
|
|
50790
|
+
readFileSync as readFileSync33,
|
|
50555
50791
|
statSync as statSync18,
|
|
50556
50792
|
writeFileSync as writeFileSync16
|
|
50557
50793
|
} from "fs";
|
|
50558
|
-
import { dirname as
|
|
50794
|
+
import { dirname as dirname34, join as join48, resolve as resolve31, sep as sep21 } from "path";
|
|
50559
50795
|
var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
|
|
50560
50796
|
var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
|
|
50561
50797
|
var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
|
|
@@ -50563,14 +50799,14 @@ function fail2(code2, message, detail = []) {
|
|
|
50563
50799
|
throw new StationSnapshotError(code2, message, detail);
|
|
50564
50800
|
}
|
|
50565
50801
|
function snapshotRootFor(repoRoot, stationId) {
|
|
50566
|
-
return
|
|
50802
|
+
return join48(repoRoot, "resources", stationId, "skills");
|
|
50567
50803
|
}
|
|
50568
50804
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
50569
50805
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
50570
|
-
const manifestPath =
|
|
50806
|
+
const manifestPath = join48(snapshotRoot, "sync-manifest.json");
|
|
50571
50807
|
let manifest;
|
|
50572
50808
|
try {
|
|
50573
|
-
manifest = JSON.parse(
|
|
50809
|
+
manifest = JSON.parse(readFileSync33(manifestPath, "utf8"));
|
|
50574
50810
|
} catch (error61) {
|
|
50575
50811
|
fail2("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
|
|
50576
50812
|
}
|
|
@@ -50594,7 +50830,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
50594
50830
|
const hashMismatches = [];
|
|
50595
50831
|
const skippedByRule = [];
|
|
50596
50832
|
for (const agent of SYNC_AGENTS) {
|
|
50597
|
-
const agentRoot =
|
|
50833
|
+
const agentRoot = join48(snapshotRoot, "agent-homes", agent);
|
|
50598
50834
|
let identEntries;
|
|
50599
50835
|
try {
|
|
50600
50836
|
identEntries = readdirSync18(agentRoot, { withFileTypes: true });
|
|
@@ -50605,10 +50841,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
50605
50841
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
50606
50842
|
continue;
|
|
50607
50843
|
}
|
|
50608
|
-
const identRoot =
|
|
50844
|
+
const identRoot = join48(agentRoot, identEntry.name);
|
|
50609
50845
|
const entries = walkEntries(identRoot);
|
|
50610
50846
|
for (const entry of entries) {
|
|
50611
|
-
const relativeParts = [identEntry.name, ...entry.relativePath.split(
|
|
50847
|
+
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep21)];
|
|
50612
50848
|
if (entry.kind === "symlink") {
|
|
50613
50849
|
symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
|
|
50614
50850
|
continue;
|
|
@@ -50632,8 +50868,8 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
50632
50868
|
});
|
|
50633
50869
|
continue;
|
|
50634
50870
|
}
|
|
50635
|
-
const withinIdent = relativeParts.slice(1).join(
|
|
50636
|
-
const homeRelative = relativeParts.join(
|
|
50871
|
+
const withinIdent = relativeParts.slice(1).join(sep21);
|
|
50872
|
+
const homeRelative = relativeParts.join(sep21);
|
|
50637
50873
|
if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
|
|
50638
50874
|
skippedByRule.push({
|
|
50639
50875
|
ident: identEntry.name,
|
|
@@ -50706,7 +50942,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
50706
50942
|
for (const copy of copies) {
|
|
50707
50943
|
let isStub = false;
|
|
50708
50944
|
try {
|
|
50709
|
-
isStub = isPointerSkillMd(
|
|
50945
|
+
isStub = isPointerSkillMd(readFileSync33(copy.fullPath, "utf8"));
|
|
50710
50946
|
} catch {
|
|
50711
50947
|
isStub = false;
|
|
50712
50948
|
}
|
|
@@ -50756,8 +50992,8 @@ function skillSha256(skill) {
|
|
|
50756
50992
|
`)).digest("hex");
|
|
50757
50993
|
}
|
|
50758
50994
|
function writeStationHydration(options) {
|
|
50759
|
-
const repoRoot =
|
|
50760
|
-
const cacheRoot =
|
|
50995
|
+
const repoRoot = resolve31(options.repoRoot ?? process.cwd());
|
|
50996
|
+
const cacheRoot = resolve31(options.cacheRoot ?? resolveCorpusRoot());
|
|
50761
50997
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
50762
50998
|
const resultSkills = plan.winners.map((skill) => ({
|
|
50763
50999
|
ident: skill.ident,
|
|
@@ -50790,7 +51026,7 @@ function writeStationHydration(options) {
|
|
|
50790
51026
|
const toWrite = [];
|
|
50791
51027
|
for (const skill of plan.winners) {
|
|
50792
51028
|
for (const file2 of skill.files) {
|
|
50793
|
-
const destination =
|
|
51029
|
+
const destination = join48(cacheRoot, skill.ident, file2.withinIdent);
|
|
50794
51030
|
const digest4 = sha256File(file2.winner.fullPath);
|
|
50795
51031
|
let existingDigest = null;
|
|
50796
51032
|
try {
|
|
@@ -50811,7 +51047,7 @@ function writeStationHydration(options) {
|
|
|
50811
51047
|
}
|
|
50812
51048
|
let written = 0;
|
|
50813
51049
|
for (const entry of toWrite) {
|
|
50814
|
-
mkdirSync19(
|
|
51050
|
+
mkdirSync19(dirname34(entry.destination), { recursive: true });
|
|
50815
51051
|
copyFileSync2(entry.fullPath, entry.destination);
|
|
50816
51052
|
written += 1;
|
|
50817
51053
|
}
|
|
@@ -50832,8 +51068,8 @@ function writeStationHydration(options) {
|
|
|
50832
51068
|
},
|
|
50833
51069
|
skills: resultSkills
|
|
50834
51070
|
};
|
|
50835
|
-
const hydrationManifestPath =
|
|
50836
|
-
mkdirSync19(
|
|
51071
|
+
const hydrationManifestPath = join48(dirname34(cacheRoot), `hydration-${options.stationId}.json`);
|
|
51072
|
+
mkdirSync19(dirname34(hydrationManifestPath), { recursive: true });
|
|
50837
51073
|
writeFileSync16(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
50838
51074
|
`);
|
|
50839
51075
|
return {
|
|
@@ -50844,23 +51080,23 @@ function writeStationHydration(options) {
|
|
|
50844
51080
|
};
|
|
50845
51081
|
}
|
|
50846
51082
|
// src/lib/private-publication-recovery.ts
|
|
50847
|
-
import { constants as constants21, closeSync as closeSync20, fsyncSync as fsyncSync8, fstatSync as fstatSync20, lstatSync as
|
|
50848
|
-
import { dirname as
|
|
51083
|
+
import { constants as constants21, closeSync as closeSync20, fsyncSync as fsyncSync8, fstatSync as fstatSync20, lstatSync as lstatSync34, mkdirSync as mkdirSync20, openSync as openSync20, readSync as readSync15, realpathSync as realpathSync16, renameSync as renameSync8, unlinkSync as unlinkSync6, writeFileSync as writeFileSync17 } from "fs";
|
|
51084
|
+
import { dirname as dirname35, isAbsolute as isAbsolute32, join as join49, resolve as resolve32 } from "path";
|
|
50849
51085
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
50850
51086
|
var fail3 = () => {
|
|
50851
51087
|
throw new PrivatePublicationError("PUBLICATION_RECOVERY_INVALID", "The recovery directory is invalid or changed. Preserve it and inspect the existing intent; do not start a replacement automatically.");
|
|
50852
51088
|
};
|
|
50853
51089
|
function safeDirectory(directory3) {
|
|
50854
|
-
if (!isAbsolute32(directory3) || directory3 !==
|
|
51090
|
+
if (!isAbsolute32(directory3) || directory3 !== resolve32(directory3) || realpathSync16(directory3) !== directory3)
|
|
50855
51091
|
return fail3();
|
|
50856
|
-
for (let path = directory3;; path =
|
|
50857
|
-
const stat2 =
|
|
51092
|
+
for (let path = directory3;; path = dirname35(path)) {
|
|
51093
|
+
const stat2 = lstatSync34(path);
|
|
50858
51094
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
50859
51095
|
return fail3();
|
|
50860
|
-
if (path ===
|
|
51096
|
+
if (path === dirname35(path))
|
|
50861
51097
|
break;
|
|
50862
51098
|
}
|
|
50863
|
-
const own2 =
|
|
51099
|
+
const own2 = lstatSync34(directory3);
|
|
50864
51100
|
if ((own2.mode & 63) !== 0 || process.getuid && own2.uid !== process.getuid())
|
|
50865
51101
|
return fail3();
|
|
50866
51102
|
return { dev: own2.dev, ino: own2.ino };
|
|
@@ -50871,7 +51107,7 @@ function unchangedDirectory(directory3, identity5) {
|
|
|
50871
51107
|
return fail3();
|
|
50872
51108
|
}
|
|
50873
51109
|
function readOwned(directory3, name, max) {
|
|
50874
|
-
const identity5 = safeDirectory(directory3), file2 =
|
|
51110
|
+
const identity5 = safeDirectory(directory3), file2 = join49(directory3, name);
|
|
50875
51111
|
const fd = openSync20(file2, constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
50876
51112
|
try {
|
|
50877
51113
|
const stat2 = fstatSync20(fd);
|
|
@@ -50895,7 +51131,7 @@ function readOwned(directory3, name, max) {
|
|
|
50895
51131
|
}
|
|
50896
51132
|
}
|
|
50897
51133
|
function writeOwned(directory3, name, bytes) {
|
|
50898
|
-
const fd = openSync20(
|
|
51134
|
+
const fd = openSync20(join49(directory3, name), constants21.O_WRONLY | constants21.O_CREAT | constants21.O_EXCL | constants21.O_NOFOLLOW, 384);
|
|
50899
51135
|
try {
|
|
50900
51136
|
writeFileSync17(fd, bytes);
|
|
50901
51137
|
fsyncSync8(fd);
|
|
@@ -50908,7 +51144,7 @@ function save(directory3, value) {
|
|
|
50908
51144
|
writeOwned(directory3, temporary, JSON.stringify(value) + `
|
|
50909
51145
|
`);
|
|
50910
51146
|
unchangedDirectory(directory3, identity5);
|
|
50911
|
-
renameSync8(
|
|
51147
|
+
renameSync8(join49(directory3, temporary), join49(directory3, "receipt.json"));
|
|
50912
51148
|
const fd = openSync20(directory3, constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
50913
51149
|
try {
|
|
50914
51150
|
fsyncSync8(fd);
|
|
@@ -50921,7 +51157,7 @@ function bind(client2, receipt) {
|
|
|
50921
51157
|
throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
|
|
50922
51158
|
}
|
|
50923
51159
|
async function locked(directory3, action) {
|
|
50924
|
-
const identity5 = safeDirectory(directory3), file2 =
|
|
51160
|
+
const identity5 = safeDirectory(directory3), file2 = join49(directory3, "operation.lock");
|
|
50925
51161
|
let fd;
|
|
50926
51162
|
try {
|
|
50927
51163
|
fd = openSync20(file2, constants21.O_WRONLY | constants21.O_CREAT | constants21.O_EXCL | constants21.O_NOFOLLOW, 384);
|
|
@@ -50937,7 +51173,7 @@ async function locked(directory3, action) {
|
|
|
50937
51173
|
} finally {
|
|
50938
51174
|
closeSync20(fd);
|
|
50939
51175
|
unchangedDirectory(directory3, identity5);
|
|
50940
|
-
const now =
|
|
51176
|
+
const now = lstatSync34(file2);
|
|
50941
51177
|
if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
|
|
50942
51178
|
fail3();
|
|
50943
51179
|
unlinkSync6(file2);
|
|
@@ -50989,11 +51225,11 @@ async function preparePrivatePublication(client2, sourceDirectory, recoveryDirec
|
|
|
50989
51225
|
phase: "prepared",
|
|
50990
51226
|
intent: null
|
|
50991
51227
|
};
|
|
50992
|
-
if (!isAbsolute32(recoveryDirectory) ||
|
|
51228
|
+
if (!isAbsolute32(recoveryDirectory) || resolve32(recoveryDirectory) !== recoveryDirectory || realpathSync16(dirname35(recoveryDirectory)) !== dirname35(recoveryDirectory))
|
|
50993
51229
|
return fail3();
|
|
50994
51230
|
mkdirSync20(recoveryDirectory, { mode: 448 });
|
|
50995
51231
|
safeDirectory(recoveryDirectory);
|
|
50996
|
-
const parent = openSync20(
|
|
51232
|
+
const parent = openSync20(dirname35(recoveryDirectory), constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
50997
51233
|
try {
|
|
50998
51234
|
fsyncSync8(parent);
|
|
50999
51235
|
} finally {
|
|
@@ -51081,7 +51317,7 @@ async function inspectLocked(client2, directory3, cancel) {
|
|
|
51081
51317
|
}
|
|
51082
51318
|
// src/lib/skill-context.ts
|
|
51083
51319
|
import { createHash as createHash38 } from "crypto";
|
|
51084
|
-
import { resolve as
|
|
51320
|
+
import { resolve as resolve33 } from "path";
|
|
51085
51321
|
function words(value) {
|
|
51086
51322
|
return new Set(value.toLowerCase().match(/[a-z0-9]+/g) ?? []);
|
|
51087
51323
|
}
|
|
@@ -51120,7 +51356,7 @@ async function buildSkillContext(input2, options = {}) {
|
|
|
51120
51356
|
const promptWords = words(prompt);
|
|
51121
51357
|
const promptNames = new Set(prompt.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) ?? []);
|
|
51122
51358
|
const loaded = new Set(resolved.session?.loaded ?? []);
|
|
51123
|
-
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd,
|
|
51359
|
+
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd, resolve33(input2.cwd)] : []];
|
|
51124
51360
|
const candidates = profile.selections.map((selection) => {
|
|
51125
51361
|
const key2 = selectionKey(selection);
|
|
51126
51362
|
if (input2.restore && loaded.has(key2))
|
|
@@ -51315,8 +51551,8 @@ function applySessionReconciliation(snapshot2, target, planned, record12, verifi
|
|
|
51315
51551
|
}
|
|
51316
51552
|
// src/lib/claude-prospective-review.ts
|
|
51317
51553
|
import { createHash as createHash40 } from "crypto";
|
|
51318
|
-
import { lstatSync as
|
|
51319
|
-
import { basename as basename18, dirname as
|
|
51554
|
+
import { lstatSync as lstatSync35, readFileSync as readFileSync34, readdirSync as readdirSync19, realpathSync as realpathSync17 } from "fs";
|
|
51555
|
+
import { basename as basename18, dirname as dirname36, isAbsolute as isAbsolute33, join as join50, relative as relative16, resolve as resolve34, sep as sep22 } from "path";
|
|
51320
51556
|
var MAX_CANDIDATE_FILES = 16384;
|
|
51321
51557
|
var MAX_CANDIDATE_BYTES = 256 * 1024 * 1024;
|
|
51322
51558
|
var sha7 = (data) => createHash40("sha256").update(data).digest("hex");
|
|
@@ -51327,32 +51563,32 @@ var GENERATED_MARKETPLACE_TIME = Object.freeze({ $generated: "claude-utc-timesta
|
|
|
51327
51563
|
var GENERATED_FRESH_PLUGIN_INSTALLED_AT = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.fresh-row.installedAt" });
|
|
51328
51564
|
var GENERATED_PLUGIN_UPDATE_TIME = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.selected-row.lastUpdated" });
|
|
51329
51565
|
function abs(path, name) {
|
|
51330
|
-
if (typeof path !== "string" || !path || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path) || !isAbsolute33(path) ||
|
|
51566
|
+
if (typeof path !== "string" || !path || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path) || !isAbsolute33(path) || resolve34(path) !== path)
|
|
51331
51567
|
refuse6(`${name} must be a normalized absolute path`);
|
|
51332
51568
|
}
|
|
51333
51569
|
function safeFile(path, name) {
|
|
51334
51570
|
abs(path, name);
|
|
51335
|
-
for (let at =
|
|
51336
|
-
const stat3 =
|
|
51571
|
+
for (let at = dirname36(path);; at = dirname36(at)) {
|
|
51572
|
+
const stat3 = lstatSync35(at, { throwIfNoEntry: false });
|
|
51337
51573
|
if (!stat3?.isDirectory() || stat3.isSymbolicLink())
|
|
51338
51574
|
refuse6(`${name} traverses a symlink or non-directory`);
|
|
51339
|
-
if (at ===
|
|
51575
|
+
if (at === dirname36(at))
|
|
51340
51576
|
break;
|
|
51341
51577
|
}
|
|
51342
|
-
const stat2 =
|
|
51578
|
+
const stat2 = lstatSync35(path, { throwIfNoEntry: false });
|
|
51343
51579
|
if (!stat2?.isFile() || stat2.isSymbolicLink() || stat2.size > MAX_CANDIDATE_BYTES)
|
|
51344
51580
|
refuse6(`${name} is not a bounded regular file`);
|
|
51345
|
-
return
|
|
51581
|
+
return readFileSync34(path);
|
|
51346
51582
|
}
|
|
51347
51583
|
function canonicalDir(path, name) {
|
|
51348
51584
|
abs(path, name);
|
|
51349
|
-
if (realpathSync17(path) !== path || !
|
|
51585
|
+
if (realpathSync17(path) !== path || !lstatSync35(path).isDirectory())
|
|
51350
51586
|
refuse6(`${name} is not a canonical directory`);
|
|
51351
|
-
for (let at = path;; at =
|
|
51352
|
-
const stat2 =
|
|
51587
|
+
for (let at = path;; at = dirname36(at)) {
|
|
51588
|
+
const stat2 = lstatSync35(at);
|
|
51353
51589
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
51354
51590
|
refuse6(`${name} traverses a symlink`);
|
|
51355
|
-
if (at ===
|
|
51591
|
+
if (at === dirname36(at))
|
|
51356
51592
|
break;
|
|
51357
51593
|
}
|
|
51358
51594
|
}
|
|
@@ -51361,17 +51597,17 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
51361
51597
|
const entries = [];
|
|
51362
51598
|
let bytes = 0;
|
|
51363
51599
|
const walk2 = (directory3) => {
|
|
51364
|
-
const beforeDirectory =
|
|
51600
|
+
const beforeDirectory = lstatSync35(directory3);
|
|
51365
51601
|
const names = readdirSync19(directory3).sort();
|
|
51366
51602
|
if (new Set(names).size !== names.length)
|
|
51367
51603
|
refuse6("candidate contains ambiguous directory entries");
|
|
51368
51604
|
for (const name of names) {
|
|
51369
51605
|
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || /[\x00-\x1f\x7f]/.test(name))
|
|
51370
51606
|
refuse6("candidate contains an unsupported path member");
|
|
51371
|
-
const path =
|
|
51607
|
+
const path = join50(directory3, name), rel = relative16(root2, path).split(sep22).join("/");
|
|
51372
51608
|
if (entries.length >= MAX_CANDIDATE_FILES)
|
|
51373
51609
|
refuse6("candidate closure exceeds its member limit");
|
|
51374
|
-
const stat2 =
|
|
51610
|
+
const stat2 = lstatSync35(path);
|
|
51375
51611
|
if (stat2.isSymbolicLink())
|
|
51376
51612
|
refuse6("candidate closure contains a symlink");
|
|
51377
51613
|
if (stat2.isDirectory()) {
|
|
@@ -51381,14 +51617,14 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
51381
51617
|
bytes += stat2.size;
|
|
51382
51618
|
if (bytes > MAX_CANDIDATE_BYTES)
|
|
51383
51619
|
refuse6("candidate closure exceeds its byte limit");
|
|
51384
|
-
const content = safeFile(path, "candidate member"), after =
|
|
51620
|
+
const content = safeFile(path, "candidate member"), after = lstatSync35(path);
|
|
51385
51621
|
if (!after.isFile() || after.dev !== stat2.dev || after.ino !== stat2.ino || after.size !== stat2.size || after.mtimeMs !== stat2.mtimeMs || after.ctimeMs !== stat2.ctimeMs)
|
|
51386
51622
|
refuse6("candidate member changed while being read");
|
|
51387
51623
|
entries.push([rel, "file", sha7(content)]);
|
|
51388
51624
|
} else
|
|
51389
51625
|
refuse6("candidate closure contains a special file");
|
|
51390
51626
|
}
|
|
51391
|
-
const afterDirectory =
|
|
51627
|
+
const afterDirectory = lstatSync35(directory3);
|
|
51392
51628
|
if (!afterDirectory.isDirectory() || afterDirectory.dev !== beforeDirectory.dev || afterDirectory.ino !== beforeDirectory.ino || afterDirectory.mtimeMs !== beforeDirectory.mtimeMs || afterDirectory.ctimeMs !== beforeDirectory.ctimeMs)
|
|
51393
51629
|
refuse6("candidate directory changed while being read");
|
|
51394
51630
|
};
|
|
@@ -51396,6 +51632,21 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
51396
51632
|
entries.sort((a, b) => a[0].localeCompare(b[0]));
|
|
51397
51633
|
return sha7(JSON.stringify(entries));
|
|
51398
51634
|
}
|
|
51635
|
+
var EXEMPT_PROJECT_SETTINGS = new Map([
|
|
51636
|
+
["permissions", "object"],
|
|
51637
|
+
["outputStyle", "string"],
|
|
51638
|
+
["model", "string"],
|
|
51639
|
+
["theme", "string"],
|
|
51640
|
+
["effortLevel", "string"],
|
|
51641
|
+
["verbose", "boolean"],
|
|
51642
|
+
["spinnerTipsEnabled", "boolean"]
|
|
51643
|
+
]);
|
|
51644
|
+
var EXEMPT_PROJECT_PERMISSIONS = new Map([
|
|
51645
|
+
["allow", "strings"],
|
|
51646
|
+
["deny", "strings"],
|
|
51647
|
+
["ask", "strings"],
|
|
51648
|
+
["defaultMode", "string"]
|
|
51649
|
+
]);
|
|
51399
51650
|
export {
|
|
51400
51651
|
writeStationSnapshot,
|
|
51401
51652
|
writeStationHydration,
|