@hasna/skills 0.9.12 → 0.9.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +97 -22
- package/bin/maintenance.js +73 -0
- package/bin/mcp.js +63 -18
- package/bin/migrate.js +36 -1
- package/bin/server.js +96 -25
- package/bin/worker.js +74 -8
- package/dist/index.d.ts +4 -3
- package/dist/index.js +142 -40
- package/dist/lib/agent-discovery.d.ts +1 -1
- package/dist/lib/agent-integration.d.ts +9 -1
- package/dist/lib/claude-marketplace-registry.d.ts +6 -0
- package/dist/lib/claude-settings-witness.d.ts +12 -1
- package/dist/sdk/index.js +213 -45
- package/dist/server/rows.d.ts +2 -1
- package/dist/server/sqlite-store.d.ts +2 -1
- package/dist/server/store.d.ts +3 -1
- package/dist/server/types.d.ts +18 -0
- package/docs/plugin-admission.md +60 -0
- package/package.json +1 -1
package/dist/sdk/index.js
CHANGED
|
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
|
|
|
32373
32373
|
// package.json
|
|
32374
32374
|
var package_default = {
|
|
32375
32375
|
name: "@hasna/skills",
|
|
32376
|
-
version: "0.9.
|
|
32376
|
+
version: "0.9.14",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -37545,10 +37545,10 @@ function assertAgentPolicyCollections(policy) {
|
|
|
37545
37545
|
text(source.path);
|
|
37546
37546
|
requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
|
|
37547
37547
|
if (source.hashMode !== undefined)
|
|
37548
|
-
requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
37549
|
-
if (source.hashMode === "claude-settings-v1")
|
|
37548
|
+
requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
37549
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
|
|
37550
37550
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve3(source.path) === source.path && basename2(source.path) === "settings.json");
|
|
37551
|
-
if (source.hashMode === "claude-marketplace-registry")
|
|
37551
|
+
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
|
|
37552
37552
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve3(source.path) === source.path && basename2(source.path) === "known_marketplaces.json");
|
|
37553
37553
|
if (source.hashMode === "claude-plugin-registry") {
|
|
37554
37554
|
requireBound(agent === "claude");
|
|
@@ -39895,9 +39895,13 @@ function textField(value) {
|
|
|
39895
39895
|
need2(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
39896
39896
|
return value.value;
|
|
39897
39897
|
}
|
|
39898
|
-
function normalizeRow(row) {
|
|
39899
|
-
if (!exactKeys2(row, ["source", "installLocation", "lastUpdated"]))
|
|
39900
|
-
|
|
39898
|
+
function normalizeRow(row, version = 1) {
|
|
39899
|
+
if (!exactKeys2(row, ["source", "installLocation", "lastUpdated"])) {
|
|
39900
|
+
if (version !== 2 || !exactKeys2(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
|
|
39901
|
+
return;
|
|
39902
|
+
const autoUpdate = field(row, "autoUpdate");
|
|
39903
|
+
need2(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
|
|
39904
|
+
}
|
|
39901
39905
|
const source = field(row, "source");
|
|
39902
39906
|
if (source?.kind !== "object")
|
|
39903
39907
|
return;
|
|
@@ -39924,6 +39928,13 @@ function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024
|
|
|
39924
39928
|
const sha2562 = createHash11("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
39925
39929
|
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
39926
39930
|
}
|
|
39931
|
+
function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
39932
|
+
const value = parse5(readRegistry2(path, budget));
|
|
39933
|
+
for (const [, row] of value.entries)
|
|
39934
|
+
normalizeRow(row, 2);
|
|
39935
|
+
const sha2562 = createHash11("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
|
|
39936
|
+
return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
|
|
39937
|
+
}
|
|
39927
39938
|
|
|
39928
39939
|
// src/lib/claude-settings-witness.ts
|
|
39929
39940
|
import { createHash as createHash12 } from "crypto";
|
|
@@ -40115,30 +40126,59 @@ function canonical3(value) {
|
|
|
40115
40126
|
return { kind: "array", items: value.items.map(canonical3) };
|
|
40116
40127
|
return value;
|
|
40117
40128
|
}
|
|
40118
|
-
|
|
40129
|
+
var CLAUDE_COORDINATED_HOOK_EVENTS = ["PreToolUse", "PostToolUse", "Stop", "Notification", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart"];
|
|
40130
|
+
function assertClaudeHookEventsReplacement(before, replacement, events) {
|
|
40131
|
+
need3(Array.isArray(events) && events.length > 0 && events.length <= CLAUDE_COORDINATED_HOOK_EVENTS.length && new Set(events).size === events.length && events.every((event) => CLAUDE_COORDINATED_HOOK_EVENTS.includes(event)), "requires explicit supported hook events");
|
|
40132
|
+
const label = events.join(",");
|
|
40119
40133
|
function bounded(text2) {
|
|
40120
|
-
need3(typeof text2 === "string" && Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes,
|
|
40134
|
+
need3(typeof text2 === "string" && Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, `${label} update exceeds its byte limit`);
|
|
40121
40135
|
return parse6(text2);
|
|
40122
40136
|
}
|
|
40123
40137
|
const original = bounded(before ?? "{}"), next = bounded(replacement);
|
|
40124
40138
|
const hadHooks = original.entries.some(([key]) => key === "hooks");
|
|
40125
|
-
function
|
|
40139
|
+
function withoutOwnedEvents(value) {
|
|
40126
40140
|
const hooks = value.entries.find(([key]) => key === "hooks")?.[1];
|
|
40127
40141
|
if (hooks !== undefined) {
|
|
40128
|
-
need3(hooks.kind === "object",
|
|
40129
|
-
const
|
|
40130
|
-
|
|
40131
|
-
|
|
40142
|
+
need3(hooks.kind === "object", `${label} update requires an object of hooks`);
|
|
40143
|
+
for (const event of events) {
|
|
40144
|
+
const selected = hooks.entries.find(([key]) => key === event)?.[1];
|
|
40145
|
+
need3(selected === undefined || selected.kind === "array", `${event} update requires a ${event} array`);
|
|
40146
|
+
}
|
|
40147
|
+
hooks.entries = hooks.entries.filter(([key]) => !events.includes(key));
|
|
40132
40148
|
if (!hadHooks && hooks.entries.length === 0)
|
|
40133
40149
|
value.entries = value.entries.filter(([key]) => key !== "hooks");
|
|
40134
40150
|
}
|
|
40135
40151
|
return canonical3(value);
|
|
40136
40152
|
}
|
|
40137
|
-
need3(JSON.stringify(
|
|
40153
|
+
need3(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
|
|
40154
|
+
}
|
|
40155
|
+
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
40156
|
+
function inferencePreference(key, child) {
|
|
40157
|
+
if (key === "skipDangerousModePermissionPrompt") {
|
|
40158
|
+
need3(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
40159
|
+
return false;
|
|
40160
|
+
}
|
|
40161
|
+
if (key === "effortLevel") {
|
|
40162
|
+
need3(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
|
|
40163
|
+
return true;
|
|
40164
|
+
}
|
|
40165
|
+
if (key !== "modelSettings")
|
|
40166
|
+
return false;
|
|
40167
|
+
need3(child.kind === "object", "requires an object of model preferences");
|
|
40168
|
+
child.entries = child.entries.filter(([model, preferences]) => {
|
|
40169
|
+
if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
|
|
40170
|
+
return true;
|
|
40171
|
+
const effort = preferences.entries[0][1];
|
|
40172
|
+
need3(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
40173
|
+
return false;
|
|
40174
|
+
});
|
|
40175
|
+
return child.entries.length === 0;
|
|
40138
40176
|
}
|
|
40139
|
-
function settingsDigest(text2) {
|
|
40177
|
+
function settingsDigest(text2, version = 1) {
|
|
40140
40178
|
const value = parse6(text2);
|
|
40141
40179
|
value.entries = value.entries.filter(([key, child]) => {
|
|
40180
|
+
if (version === 2 && inferencePreference(key, child))
|
|
40181
|
+
return false;
|
|
40142
40182
|
if (key === "model") {
|
|
40143
40183
|
need3(child.kind === "string", "contains an invalid model selection");
|
|
40144
40184
|
return !BUILTIN_MODEL_SELECTIONS.has(child.value);
|
|
@@ -40153,7 +40193,7 @@ function settingsDigest(text2) {
|
|
|
40153
40193
|
}
|
|
40154
40194
|
return true;
|
|
40155
40195
|
});
|
|
40156
|
-
return createHash12("sha256").update(
|
|
40196
|
+
return createHash12("sha256").update(`hasna.skills.claude-settings.v${version}\x00`).update(JSON.stringify(canonical3(value))).digest("hex");
|
|
40157
40197
|
}
|
|
40158
40198
|
function hashClaudeSettingsReplacement(text2, budget) {
|
|
40159
40199
|
need3(typeof text2 === "string", "requires settings text");
|
|
@@ -40165,6 +40205,16 @@ function hashClaudeSettingsReplacement(text2, budget) {
|
|
|
40165
40205
|
function captureClaudeSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
40166
40206
|
return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readSettings(path, budget)) };
|
|
40167
40207
|
}
|
|
40208
|
+
function captureClaudeSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
40209
|
+
return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(readSettings(path, budget), 2) };
|
|
40210
|
+
}
|
|
40211
|
+
function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
40212
|
+
need3(typeof text2 === "string", "requires settings text");
|
|
40213
|
+
const bytes = Buffer.byteLength(text2);
|
|
40214
|
+
need3(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
40215
|
+
budget.remaining -= bytes;
|
|
40216
|
+
return settingsDigest(text2, 2);
|
|
40217
|
+
}
|
|
40168
40218
|
|
|
40169
40219
|
// src/lib/codex-hook-discovery-recovery.ts
|
|
40170
40220
|
import { createHash as createHash14 } from "crypto";
|
|
@@ -40268,18 +40318,18 @@ function read2(path, changes) {
|
|
|
40268
40318
|
return readFileSync12(path, "utf8");
|
|
40269
40319
|
}
|
|
40270
40320
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
40271
|
-
if (source.hashMode === "claude-settings-v1") {
|
|
40321
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
|
|
40272
40322
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
40273
40323
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
40274
|
-
const current = captureClaudeSettings(source.path, budget).sha256;
|
|
40324
|
+
const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
40275
40325
|
if (current !== source.sha256)
|
|
40276
40326
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
40277
|
-
return changes?.has(source.path) ? hashClaudeSettingsReplacement(changes.get(source.path), budget) : current;
|
|
40327
|
+
return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
|
|
40278
40328
|
}
|
|
40279
|
-
if (source.hashMode === "claude-marketplace-registry") {
|
|
40329
|
+
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
40280
40330
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256) || changes?.has(source.path))
|
|
40281
40331
|
throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
|
|
40282
|
-
const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
|
|
40332
|
+
const sha2562 = (source.hashMode === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : captureClaudeMarketplaceRegistry)(source.path, budget).sha256;
|
|
40283
40333
|
if (sha2562 !== source.sha256)
|
|
40284
40334
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
40285
40335
|
return sha2562;
|
|
@@ -40336,9 +40386,9 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
40336
40386
|
safe4(root2);
|
|
40337
40387
|
}
|
|
40338
40388
|
function assertMarketplaceBinding(binding, source) {
|
|
40339
|
-
if (source.hashMode === "claude-settings-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || basename6(source.path) !== "settings.json"))
|
|
40389
|
+
if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename6(source.path) !== "settings.json"))
|
|
40340
40390
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
40341
|
-
if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
40391
|
+
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
40342
40392
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
40343
40393
|
}
|
|
40344
40394
|
function rebindAgentDiscovery(binding, changes) {
|
|
@@ -40479,7 +40529,7 @@ function resolveAgentDiscovery(options) {
|
|
|
40479
40529
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
40480
40530
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
40481
40531
|
verifyAgentDiscovery(supplied);
|
|
40482
|
-
if (review.sources.some((source) => source.hashMode === "claude-settings-v1" && source.path !== canonical4(configPath)))
|
|
40532
|
+
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
|
|
40483
40533
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
40484
40534
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
40485
40535
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -41256,6 +41306,34 @@ function planAgentIntegration(options) {
|
|
|
41256
41306
|
return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
41257
41307
|
}
|
|
41258
41308
|
function planClaudeStopHookUpdate(options) {
|
|
41309
|
+
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
41310
|
+
}
|
|
41311
|
+
function planClaudePreToolUseHookUpdate(options) {
|
|
41312
|
+
return planClaudeHookEventsUpdate({ ...options, events: ["PreToolUse"] });
|
|
41313
|
+
}
|
|
41314
|
+
function claudeSettingsConsumers(home, path, discoveries, aliases) {
|
|
41315
|
+
const canonical4 = (value) => canonicalAgentPath(value, aliases);
|
|
41316
|
+
const containsSettings = (root2) => {
|
|
41317
|
+
const distance = relative3(canonical4(root2), path);
|
|
41318
|
+
return distance === "" || distance !== ".." && !distance.startsWith(`..${sep4}`) && !isAbsolute15(distance);
|
|
41319
|
+
};
|
|
41320
|
+
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical4(join21(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical4(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
|
|
41321
|
+
}
|
|
41322
|
+
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
41323
|
+
try {
|
|
41324
|
+
if (binding.method !== "automatic" && binding.method !== "reviewed")
|
|
41325
|
+
throw new Error("Invalid managed discovery method");
|
|
41326
|
+
verifyAgentDiscovery(binding);
|
|
41327
|
+
if (binding.method === "automatic") {
|
|
41328
|
+
const current = resolveAgentDiscovery({ home, agent: binding.agent, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
41329
|
+
if (JSON.stringify(current) !== JSON.stringify(binding))
|
|
41330
|
+
throw new Error("Configured native discovery roots changed during Claude update");
|
|
41331
|
+
}
|
|
41332
|
+
} catch (error) {
|
|
41333
|
+
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
41334
|
+
}
|
|
41335
|
+
}
|
|
41336
|
+
function planClaudeHookEventsUpdate(options) {
|
|
41259
41337
|
const home = resolve14(options.home ?? homedir3()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
41260
41338
|
const policyPath = join21(dataDir2, "agent-policy.json");
|
|
41261
41339
|
assertSafePath(policyPath);
|
|
@@ -41273,31 +41351,42 @@ function planClaudeStopHookUpdate(options) {
|
|
|
41273
41351
|
const before = lstatSync18(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
41274
41352
|
if ((before === null ? "absent" : sha2(before)) !== options.expectedSettingsSha256)
|
|
41275
41353
|
throw new Error("Claude settings preimage changed");
|
|
41276
|
-
|
|
41354
|
+
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
41277
41355
|
const agents = bridge.agents;
|
|
41278
41356
|
const discoveries = Object.entries(bridge.discovery).map(([agent, binding]) => {
|
|
41279
|
-
if (!agents.includes(agent) || binding?.agent !== agent)
|
|
41280
|
-
throw new Error("Invalid managed discovery owner");
|
|
41357
|
+
if (!agents.includes(agent) || binding?.agent !== agent || typeof bridge.commands?.[agent] !== "string" || typeof bridge.profiles?.[agent] !== "string")
|
|
41358
|
+
throw new Error("Invalid managed discovery owner or command/profile binding");
|
|
41281
41359
|
return binding;
|
|
41282
41360
|
});
|
|
41283
|
-
if (discoveries.length !== agents.length)
|
|
41284
|
-
throw new Error("Missing managed discovery coverage");
|
|
41285
|
-
for (const
|
|
41361
|
+
if (discoveries.length !== agents.length || !agents.includes("claude"))
|
|
41362
|
+
throw new Error("Missing managed Claude discovery coverage");
|
|
41363
|
+
for (const binding of discoveries)
|
|
41364
|
+
verifyCoordinatedDiscovery(binding, home, aliases);
|
|
41365
|
+
const consumers = claudeSettingsConsumers(home, path, discoveries, aliases);
|
|
41366
|
+
for (const agent of consumers)
|
|
41286
41367
|
assertManagedAgentBridge(agent, { home, dataDir: dataDir2, projectDir: home });
|
|
41287
41368
|
const replacements = new Map(before === options.replacement ? [] : [[path, options.replacement]]);
|
|
41288
|
-
const discoveryAfter = discoveries.map((binding) =>
|
|
41369
|
+
const discoveryAfter = discoveries.map((binding) => {
|
|
41370
|
+
const next = rebindAgentDiscovery(binding, replacements);
|
|
41371
|
+
for (const [index, source] of binding.sources.entries()) {
|
|
41372
|
+
if (source.path !== path && JSON.stringify(next.sources[index]) !== JSON.stringify(source)) {
|
|
41373
|
+
throw new Error(`Unrelated native discovery input changed during Claude update: ${source.path}`);
|
|
41374
|
+
}
|
|
41375
|
+
}
|
|
41376
|
+
return JSON.stringify(next) === JSON.stringify(binding) ? binding : next;
|
|
41377
|
+
});
|
|
41289
41378
|
const nextPolicy = { ...policy, bridge: { ...bridge, discovery: Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) } };
|
|
41290
41379
|
const changes = before === options.replacement ? [] : [{ path, before, after: options.replacement }];
|
|
41291
41380
|
if (JSON.stringify(nextPolicy) !== JSON.stringify(policy))
|
|
41292
41381
|
changes.push({ path: policyPath, before: snapshot2.text, after: serializeManagedSkillPolicy(nextPolicy) });
|
|
41293
41382
|
recheckRootAliases(aliases);
|
|
41294
41383
|
const nativeSkills = inventoryNativeSkills(home, {
|
|
41295
|
-
agents,
|
|
41384
|
+
agents: consumers,
|
|
41296
41385
|
projectDir: home,
|
|
41297
41386
|
includeVendor: true,
|
|
41298
|
-
guardHermes:
|
|
41387
|
+
guardHermes: consumers.includes("hermes"),
|
|
41299
41388
|
allowRootAliases: aliases.length > 0,
|
|
41300
|
-
agentRoots: discoveries.flatMap((binding) => binding.roots.map((path2) => ({ agent: binding.agent, path: path2 })))
|
|
41389
|
+
agentRoots: discoveries.filter((binding) => consumers.includes(binding.agent)).flatMap((binding) => binding.roots.map((path2) => ({ agent: binding.agent, path: path2 })))
|
|
41301
41390
|
});
|
|
41302
41391
|
return {
|
|
41303
41392
|
dataDir: dataDir2,
|
|
@@ -41308,7 +41397,7 @@ function planClaudeStopHookUpdate(options) {
|
|
|
41308
41397
|
discoveryBefore: discoveries,
|
|
41309
41398
|
discoveryAfter,
|
|
41310
41399
|
rootAliases: aliases,
|
|
41311
|
-
managedAgentChecks: { home, agents:
|
|
41400
|
+
managedAgentChecks: { home, agents: consumers }
|
|
41312
41401
|
};
|
|
41313
41402
|
}
|
|
41314
41403
|
function atomicWrite2(path, content) {
|
|
@@ -41335,8 +41424,12 @@ function applyAgentIntegration(plan) {
|
|
|
41335
41424
|
recheckRootAliases(aliases);
|
|
41336
41425
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
41337
41426
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
41338
|
-
for (const binding of plan.discoveryBefore ?? [])
|
|
41339
|
-
|
|
41427
|
+
for (const binding of plan.discoveryBefore ?? []) {
|
|
41428
|
+
if (plan.managedAgentChecks)
|
|
41429
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
41430
|
+
else
|
|
41431
|
+
verifyAgentDiscovery(binding);
|
|
41432
|
+
}
|
|
41340
41433
|
if (plan.observedPolicy && currentText(plan.observedPolicy.path) !== plan.observedPolicy.before)
|
|
41341
41434
|
throw new Error("Agent policy changed after planning");
|
|
41342
41435
|
for (const change of plan.changes)
|
|
@@ -41368,8 +41461,12 @@ function applyAgentIntegration(plan) {
|
|
|
41368
41461
|
written.push({ path: change.path, before: change.before, after });
|
|
41369
41462
|
}
|
|
41370
41463
|
recheckRootAliases(aliases);
|
|
41371
|
-
for (const binding of plan.discoveryAfter ?? [])
|
|
41372
|
-
|
|
41464
|
+
for (const binding of plan.discoveryAfter ?? []) {
|
|
41465
|
+
if (plan.managedAgentChecks)
|
|
41466
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
41467
|
+
else
|
|
41468
|
+
verifyAgentDiscovery(binding);
|
|
41469
|
+
}
|
|
41373
41470
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
41374
41471
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
41375
41472
|
atomicWrite2(join21(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha2(change.before), afterHash: sha2(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
|
@@ -55561,6 +55658,18 @@ function rowToSkill(row) {
|
|
|
55561
55658
|
...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
|
|
55562
55659
|
};
|
|
55563
55660
|
}
|
|
55661
|
+
function rowToPublishedSelectionState(row) {
|
|
55662
|
+
return {
|
|
55663
|
+
slug: String(row.slug),
|
|
55664
|
+
version: String(row.version),
|
|
55665
|
+
current: row.skill_exists ? {
|
|
55666
|
+
lifecycle: row.lifecycle === "archived" ? "archived" : "active",
|
|
55667
|
+
...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
|
|
55668
|
+
} : null,
|
|
55669
|
+
versionBundleSha256: typeof row.bundle_sha256 === "string" ? row.bundle_sha256 : null,
|
|
55670
|
+
bundleAvailable: Boolean(row.bundle_available)
|
|
55671
|
+
};
|
|
55672
|
+
}
|
|
55564
55673
|
function rowToPin(row) {
|
|
55565
55674
|
return {
|
|
55566
55675
|
orgId: String(row.org_id),
|
|
@@ -56335,6 +56444,22 @@ class SqliteSkillsStore {
|
|
|
56335
56444
|
const row = this.get("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [principal.orgId, slug, version2]);
|
|
56336
56445
|
return row ? rowToSkillVersion(row) : null;
|
|
56337
56446
|
}
|
|
56447
|
+
async getPublishedSelectionStates(principal, selections2) {
|
|
56448
|
+
if (selections2.length > MAX_PROFILE_SELECTIONS)
|
|
56449
|
+
throw new RangeError("Published selection lookup exceeds the profile selection limit");
|
|
56450
|
+
if (!selections2.length)
|
|
56451
|
+
return [];
|
|
56452
|
+
const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
|
|
56453
|
+
return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
|
|
56454
|
+
r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
|
|
56455
|
+
v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
|
|
56456
|
+
FROM json_each(?) q
|
|
56457
|
+
LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
|
|
56458
|
+
LEFT JOIN skills_versions v ON v.org_id = ?
|
|
56459
|
+
AND v.slug = json_extract(q.value, '$.slug') AND v.version = json_extract(q.value, '$.version')
|
|
56460
|
+
LEFT JOIN skills_bundles b ON b.org_id = ? AND b.sha256 = v.bundle_sha256
|
|
56461
|
+
ORDER BY CAST(q.key AS INTEGER)`, [requested, principal.orgId, principal.orgId, principal.orgId]).map(rowToPublishedSelectionState);
|
|
56462
|
+
}
|
|
56338
56463
|
async pinSkill(principal, slug, metadata = {}) {
|
|
56339
56464
|
const row = this.get(`INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
|
|
56340
56465
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -57463,16 +57588,21 @@ function selected(value) {
|
|
|
57463
57588
|
return selections2;
|
|
57464
57589
|
}
|
|
57465
57590
|
async function validatePublished(store, principal, selections2) {
|
|
57466
|
-
|
|
57467
|
-
|
|
57591
|
+
const states = await store.getPublishedSelectionStates(principal, selections2);
|
|
57592
|
+
if (states.length !== selections2.length)
|
|
57593
|
+
throw new SkillRequestError(503, "SELECTION_UNAVAILABLE", "Selected publication state is unavailable");
|
|
57594
|
+
for (const [index, selection2] of selections2.entries()) {
|
|
57595
|
+
const state2 = states[index];
|
|
57596
|
+
if (!state2 || state2.slug !== selection2.slug || state2.version !== selection2.version)
|
|
57597
|
+
throw new SkillRequestError(503, "SELECTION_UNAVAILABLE", "Selected publication state is unavailable");
|
|
57598
|
+
const current = state2.current;
|
|
57468
57599
|
if (!current || current.tombstonedAt)
|
|
57469
57600
|
throw new SkillRequestError(current?.tombstonedAt ? 410 : 404, "SELECTION_UNAVAILABLE", `Selected skill '${selection2.slug}' is unavailable`);
|
|
57470
57601
|
if (current.lifecycle === "archived")
|
|
57471
57602
|
throw new SkillRequestError(409, "SELECTION_ARCHIVED", `Selected skill '${selection2.slug}' is archived`);
|
|
57472
|
-
|
|
57473
|
-
if (!version2 || `sha256:${version2.bundleSha256}` !== selection2.bundleDigest)
|
|
57603
|
+
if (!state2.versionBundleSha256 || `sha256:${state2.versionBundleSha256}` !== selection2.bundleDigest)
|
|
57474
57604
|
throw new SkillRequestError(409, "SELECTION_VERSION_MISMATCH", `Selected version of '${selection2.slug}' does not match its published digest`);
|
|
57475
|
-
if (!
|
|
57605
|
+
if (!state2.bundleAvailable)
|
|
57476
57606
|
throw new SkillRequestError(503, "SELECTION_BUNDLE_UNAVAILABLE", "Selected bundle is unavailable");
|
|
57477
57607
|
}
|
|
57478
57608
|
}
|
|
@@ -63361,6 +63491,25 @@ class MemorySkillsStore {
|
|
|
63361
63491
|
const found = this.versions.get(versionKey(principal.orgId, slug, version2));
|
|
63362
63492
|
return found && found.orgId === principal.orgId ? found : null;
|
|
63363
63493
|
}
|
|
63494
|
+
async getPublishedSelectionStates(principal, selections2) {
|
|
63495
|
+
if (selections2.length > MAX_PROFILE_SELECTIONS)
|
|
63496
|
+
throw new RangeError("Published selection lookup exceeds the profile selection limit");
|
|
63497
|
+
return selections2.map(({ slug, version: version2 }) => {
|
|
63498
|
+
const current = this.skills.get(skillKey(principal.orgId, slug));
|
|
63499
|
+
const selectedVersion = this.versions.get(versionKey(principal.orgId, slug, version2));
|
|
63500
|
+
const bundle2 = selectedVersion && this.bundles.get(skillKey(principal.orgId, selectedVersion.bundleSha256));
|
|
63501
|
+
return {
|
|
63502
|
+
slug,
|
|
63503
|
+
version: version2,
|
|
63504
|
+
current: current?.orgId === principal.orgId ? {
|
|
63505
|
+
lifecycle: current.lifecycle,
|
|
63506
|
+
...current.tombstonedAt ? { tombstonedAt: current.tombstonedAt } : {}
|
|
63507
|
+
} : null,
|
|
63508
|
+
versionBundleSha256: selectedVersion?.orgId === principal.orgId ? selectedVersion.bundleSha256 : null,
|
|
63509
|
+
bundleAvailable: bundle2?.orgId === principal.orgId
|
|
63510
|
+
};
|
|
63511
|
+
});
|
|
63512
|
+
}
|
|
63364
63513
|
async listSkills(principal) {
|
|
63365
63514
|
return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt).sort((a4, b4) => a4.slug.localeCompare(b4.slug));
|
|
63366
63515
|
}
|
|
@@ -64169,6 +64318,25 @@ class PostgresSkillsStore {
|
|
|
64169
64318
|
`;
|
|
64170
64319
|
return rows[0] ? rowToSkillVersion(rows[0]) : null;
|
|
64171
64320
|
}
|
|
64321
|
+
async getPublishedSelectionStates(principal, selections2) {
|
|
64322
|
+
if (selections2.length > MAX_PROFILE_SELECTIONS)
|
|
64323
|
+
throw new RangeError("Published selection lookup exceeds the profile selection limit");
|
|
64324
|
+
if (!selections2.length)
|
|
64325
|
+
return [];
|
|
64326
|
+
const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
|
|
64327
|
+
const rows = await this.sql`
|
|
64328
|
+
SELECT q.value->>'slug' AS slug, q.value->>'version' AS version,
|
|
64329
|
+
r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
|
|
64330
|
+
v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
|
|
64331
|
+
FROM jsonb_array_elements(${requested}::text::jsonb) WITH ORDINALITY AS q(value, ordinal)
|
|
64332
|
+
LEFT JOIN skills_registry r ON r.org_id = ${principal.orgId} AND r.slug = q.value->>'slug'
|
|
64333
|
+
LEFT JOIN skills_versions v ON v.org_id = ${principal.orgId}
|
|
64334
|
+
AND v.slug = q.value->>'slug' AND v.version = q.value->>'version'
|
|
64335
|
+
LEFT JOIN skills_bundles b ON b.org_id = ${principal.orgId} AND b.sha256 = v.bundle_sha256
|
|
64336
|
+
ORDER BY q.ordinal
|
|
64337
|
+
`;
|
|
64338
|
+
return rows.map(rowToPublishedSelectionState);
|
|
64339
|
+
}
|
|
64172
64340
|
async pinSkill(principal, slug, metadata = {}) {
|
|
64173
64341
|
const rows = await this.sql`
|
|
64174
64342
|
INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
|
package/dist/server/rows.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ServerSkillVersion, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord } from "./types.js";
|
|
1
|
+
import type { ServerSkillVersion, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, PublishedSkillSelectionState } from "./types.js";
|
|
2
2
|
export declare function nowIso(): string;
|
|
3
3
|
/**
|
|
4
4
|
* Coerce a caller-supplied row limit to a non-negative integer.
|
|
@@ -17,6 +17,7 @@ export declare function rowToRun(row: Record<string, unknown>): ServerRunRecord;
|
|
|
17
17
|
export declare function rowToLog(row: Record<string, unknown>): ServerRunLog;
|
|
18
18
|
export declare function rowToArtifact(row: Record<string, unknown>): ServerArtifact;
|
|
19
19
|
export declare function rowToSkill(row: Record<string, unknown>): ServerSkillRecord;
|
|
20
|
+
export declare function rowToPublishedSelectionState(row: Record<string, unknown>): PublishedSkillSelectionState;
|
|
20
21
|
export declare function rowToPin(row: Record<string, unknown>): ServerPin;
|
|
21
22
|
export declare function rowToSkillBundle(row: Record<string, unknown>): ServerSkillBundle;
|
|
22
23
|
export declare function parseJsonObject(value: unknown): Record<string, unknown>;
|
|
@@ -15,7 +15,7 @@ import { SqliteExecutionGrantStore } from "./execution-grant-store.js";
|
|
|
15
15
|
*/
|
|
16
16
|
import { Database } from "bun:sqlite";
|
|
17
17
|
import { SqliteSkillSelectionStore } from "./selection-store.js";
|
|
18
|
-
import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillVersion, ApiKeyScopeUpdateResult, OperatorScopeEnrollmentInput, OperatorScopeEnrollmentResult, OperatorScopeTargetSnapshot, ServerSkillRecord, SkillLifecyclePatch, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
|
|
18
|
+
import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, PublishedSkillSelection, PublishedSkillSelectionState, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillVersion, ApiKeyScopeUpdateResult, OperatorScopeEnrollmentInput, OperatorScopeEnrollmentResult, OperatorScopeTargetSnapshot, ServerSkillRecord, SkillLifecyclePatch, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
|
|
19
19
|
export interface SqliteStoreOptions {
|
|
20
20
|
/** Apply pending migrations on open. Default true - it is what makes zero-config work. */
|
|
21
21
|
migrate?: boolean;
|
|
@@ -113,6 +113,7 @@ export declare class SqliteSkillsStore implements SkillsProductStore {
|
|
|
113
113
|
getSkillBundle(principal: ApiPrincipal, sha256: string): Promise<ServerSkillBundle | null>;
|
|
114
114
|
listSkillVersions(principal: ApiPrincipal, slug: string): Promise<ServerSkillVersion[]>;
|
|
115
115
|
getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
|
|
116
|
+
getPublishedSelectionStates(principal: ApiPrincipal, selections: readonly PublishedSkillSelection[]): Promise<PublishedSkillSelectionState[]>;
|
|
116
117
|
pinSkill(principal: ApiPrincipal, slug: string, metadata?: Record<string, unknown>): Promise<ServerPin>;
|
|
117
118
|
unpinSkill(principal: ApiPrincipal, slug: string): Promise<boolean>;
|
|
118
119
|
listPins(principal: ApiPrincipal): Promise<ServerPin[]>;
|
package/dist/server/store.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MemoryExecutionGrantStore, PostgresExecutionGrantStore } from "./execution-grant-store.js";
|
|
2
2
|
import { MemorySkillSelectionStore, PostgresSkillSelectionStore } from "./selection-store.js";
|
|
3
|
-
import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, ServerSkillVersion, ApiKeyScopeUpdateResult, OperatorScopeEnrollmentInput, OperatorScopeEnrollmentResult, OperatorScopeTargetSnapshot, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
|
|
3
|
+
import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, PublishedSkillSelection, PublishedSkillSelectionState, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, ServerSkillVersion, ApiKeyScopeUpdateResult, OperatorScopeEnrollmentInput, OperatorScopeEnrollmentResult, OperatorScopeTargetSnapshot, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
|
|
4
4
|
import { type SkillLifecyclePatch } from "./types.js";
|
|
5
5
|
import { type SqliteStoreOptions } from "./sqlite-store.js";
|
|
6
6
|
export declare function createArtifactId(): string;
|
|
@@ -79,6 +79,7 @@ export declare class MemorySkillsStore implements SkillsProductStore {
|
|
|
79
79
|
publishSkill(input: PublishSkillInput): Promise<ServerSkillRecord>;
|
|
80
80
|
listSkillVersions(principal: ApiPrincipal, slug: string): Promise<ServerSkillVersion[]>;
|
|
81
81
|
getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
|
|
82
|
+
getPublishedSelectionStates(principal: ApiPrincipal, selections: readonly PublishedSkillSelection[]): Promise<PublishedSkillSelectionState[]>;
|
|
82
83
|
listSkills(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
|
|
83
84
|
getSkill(principal: ApiPrincipal, slug: string): Promise<ServerSkillRecord | null>;
|
|
84
85
|
setSkillLifecycle(principal: ApiPrincipal, slug: string, patch: SkillLifecyclePatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
|
|
@@ -180,6 +181,7 @@ export declare class PostgresSkillsStore implements SkillsProductStore {
|
|
|
180
181
|
getSkillBundle(principal: ApiPrincipal, sha256: string): Promise<ServerSkillBundle | null>;
|
|
181
182
|
listSkillVersions(principal: ApiPrincipal, slug: string): Promise<ServerSkillVersion[]>;
|
|
182
183
|
getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
|
|
184
|
+
getPublishedSelectionStates(principal: ApiPrincipal, selections: readonly PublishedSkillSelection[]): Promise<PublishedSkillSelectionState[]>;
|
|
183
185
|
pinSkill(principal: ApiPrincipal, slug: string, metadata?: Record<string, unknown>): Promise<ServerPin>;
|
|
184
186
|
unpinSkill(principal: ApiPrincipal, slug: string): Promise<boolean>;
|
|
185
187
|
listPins(principal: ApiPrincipal): Promise<ServerPin[]>;
|
package/dist/server/types.d.ts
CHANGED
|
@@ -279,6 +279,17 @@ export interface ServerSkillVersion {
|
|
|
279
279
|
publishedByUserId?: string;
|
|
280
280
|
createdAt: string;
|
|
281
281
|
}
|
|
282
|
+
/** Exact version lookup, independent of the registry's mutable current version. */
|
|
283
|
+
export interface PublishedSkillSelection {
|
|
284
|
+
slug: string;
|
|
285
|
+
version: string;
|
|
286
|
+
}
|
|
287
|
+
/** Metadata only: bundle availability means its tenant-owned storage row exists. */
|
|
288
|
+
export interface PublishedSkillSelectionState extends PublishedSkillSelection {
|
|
289
|
+
current: Pick<ServerSkillRecord, "lifecycle" | "tombstonedAt"> | null;
|
|
290
|
+
versionBundleSha256: string | null;
|
|
291
|
+
bundleAvailable: boolean;
|
|
292
|
+
}
|
|
282
293
|
export interface PublishSkillInput {
|
|
283
294
|
/** Internal boot-seed precondition: never revive or replace non-bundled records. */
|
|
284
295
|
seedBundledOnly?: true;
|
|
@@ -441,6 +452,13 @@ export interface SkillsProductStore {
|
|
|
441
452
|
/** Every published version of a slug, newest first (hasna/apps#1630). */
|
|
442
453
|
listSkillVersions(principal: ApiPrincipal, slug: string): Promise<ServerSkillVersion[]>;
|
|
443
454
|
getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
|
|
455
|
+
/**
|
|
456
|
+
* Read at most MAX_PROFILE_SELECTIONS exact selections in one database snapshot,
|
|
457
|
+
* scoped to the principal's org at every join. Returns one row per input in input
|
|
458
|
+
* order, including missing selections and duplicates. Never reads bundle bytes or
|
|
459
|
+
* substitutes the latest version; callers retain lifecycle/digest error policy.
|
|
460
|
+
*/
|
|
461
|
+
getPublishedSelectionStates(principal: ApiPrincipal, selections: readonly PublishedSkillSelection[]): Promise<PublishedSkillSelectionState[]>;
|
|
444
462
|
pinSkill(principal: ApiPrincipal, slug: string, metadata?: Record<string, unknown>): Promise<ServerPin>;
|
|
445
463
|
/** False when this principal has no pin by that slug. */
|
|
446
464
|
unpinSkill(principal: ApiPrincipal, slug: string): Promise<boolean>;
|
package/docs/plugin-admission.md
CHANGED
|
@@ -286,3 +286,63 @@ cache history is bounded to 128 entries and an aggregate 256 MiB per witness.
|
|
|
286
286
|
Primary runtime references: [command sources](https://code.claude.com/docs/en/plugin-marketplaces#command-sources),
|
|
287
287
|
[plugin components](https://code.claude.com/docs/en/plugins-reference), and
|
|
288
288
|
[exact command acceptance](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#21271).
|
|
289
|
+
|
|
290
|
+
### Explicit v2 preference reviews
|
|
291
|
+
|
|
292
|
+
`captureClaudeSettingsV2(path)` creates a `claude-settings-v2` witness. In
|
|
293
|
+
addition to the v1 display/model selections, it tolerates the documented
|
|
294
|
+
persisted effort values `low`, `medium`, `high`, and `xhigh`, both at the top
|
|
295
|
+
level and in known built-in model entries whose only property is `effortLevel`.
|
|
296
|
+
Custom model entries and entries with any other property remain fully bound.
|
|
297
|
+
The boolean `skipDangerousModePermissionPrompt` remains bound: Claude also uses
|
|
298
|
+
this saved acceptance in bypass-mode and pending project MCP approval decisions.
|
|
299
|
+
Permission settings, plugins, hooks, environment, ultracode and unknown
|
|
300
|
+
configuration remain bound. Version 0.9.14 corrects the 0.9.13 normalization of
|
|
301
|
+
this flag; an existing v2 review containing it must be explicitly reviewed again.
|
|
302
|
+
Invalid recognized preference types are refused.
|
|
303
|
+
|
|
304
|
+
`captureClaudeMarketplaceRegistryV2(path)` creates a
|
|
305
|
+
`claude-marketplace-registry-v2` witness. It also recognizes a registry row with
|
|
306
|
+
an optional boolean `autoUpdate`. That boolean remains bound; only a strictly
|
|
307
|
+
validated `lastUpdated` timestamp may vary. A new source or installation path,
|
|
308
|
+
a changed automatic-update policy, or an unknown field still requires review.
|
|
309
|
+
|
|
310
|
+
Capture either witness through the CLI:
|
|
311
|
+
|
|
312
|
+
```sh
|
|
313
|
+
skills hook witness --kind claude-settings-v2 --path /canonical/home/.claude/settings.json --json
|
|
314
|
+
skills hook witness --kind claude-marketplace-registry-v2 --path /canonical/home/.claude/plugins/known_marketplaces.json --json
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Capture does not install or authorize a review. Compare the current complete
|
|
318
|
+
configuration with its previous review, put the chosen witness in the reviewed
|
|
319
|
+
discovery inputs, and preview `skills hook install --discovery-inputs <file>`
|
|
320
|
+
before applying. Existing byte and v1 witnesses are never silently converted.
|
|
321
|
+
Runtime support for the selected witness version must be installed before
|
|
322
|
+
policy activation. A real hook installation is a configuration change, not a
|
|
323
|
+
preference: its owning installer must coordinate a guarded settings/policy
|
|
324
|
+
update, or it needs a fresh explicit review.
|
|
325
|
+
|
|
326
|
+
The effort schema follows [Claude's model configuration documentation](https://code.claude.com/docs/en/model-config#adjust-effort-level).
|
|
327
|
+
|
|
328
|
+
Cooperating installers can use `planClaudeHookEventsUpdate` with an explicit
|
|
329
|
+
selection of supported Claude hook events. `planClaudePreToolUseHookUpdate` and
|
|
330
|
+
`planClaudeStopHookUpdate` are single-event convenience wrappers. Supported events
|
|
331
|
+
are `PreToolUse`, `PostToolUse`, `Stop`, `Notification`, `SessionStart`, `SessionEnd`
|
|
332
|
+
`UserPromptSubmit` and `SubagentStart`. The transaction requires the exact settings SHA-256
|
|
333
|
+
preimage and preserves every field outside the selected events, including other
|
|
334
|
+
hooks. Apply the returned plan through `applyAgentIntegration`; this validates
|
|
335
|
+
existing managed discovery and native guards, writes settings and policy with
|
|
336
|
+
precondition checks, and retains recovery backups. An invalid policy or existing
|
|
337
|
+
drift is a refusal, never permission to use an uncoordinated writer. A null plan
|
|
338
|
+
means no managed Skills policy exists. These APIs do not authorize the hook's
|
|
339
|
+
behavior or turn a configuration-changing hook into a preference.
|
|
340
|
+
|
|
341
|
+
The transaction verifies every agent's discovery sources, directories and
|
|
342
|
+
automatic discovery closure. Native runtime and trust checks cover Claude and
|
|
343
|
+
the declared consumers of the settings path, including containing roots or
|
|
344
|
+
directories and OpenCode's Claude compatibility surface. Unrelated agents keep
|
|
345
|
+
their exact witnesses and trust state; they must still pass their own consumer
|
|
346
|
+
checks before use. The plan's `managedAgentChecks.agents` and `nativeSkills`
|
|
347
|
+
describe this scoped runtime check, not a fleet health result. A path-identity
|
|
348
|
+
witness intersecting the write still refuses instead of adopting a new inode.
|