@hasna/skills 0.10.64 → 0.10.66

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.
@@ -4,7 +4,7 @@ import { type ManagedPluginRegistrationWitness } from "./plugin-discovery.js";
4
4
  import { type CodexHookDiscoveryRecovery } from "./codex-hook-discovery-recovery.js";
5
5
  import { type CodexPluginSkillControl, type CodexPluginSourceInput } from "./codex-plugin-skill-controls.js";
6
6
  import { type RetainedReviewArtifact, type ReviewedArtifact } from "./retained-review-artifacts.js";
7
- import { type CodexSemanticCacheWitness, type CodexSemanticCacheAppOnlyParent } from "./codex-semantic-cache-witness.js";
7
+ import { type CodexSemanticCacheWitness, type CodexSemanticCacheAppOnlyParent, type CodexSemanticCacheReviewedHookParent } from "./codex-semantic-cache-witness.js";
8
8
  export { captureDiscoveryDirectories, type DiscoveryDirectory } from "./agent-discovery-directories.js";
9
9
  export interface DiscoverySource {
10
10
  reviewArtifact?: RetainedReviewArtifact;
@@ -26,6 +26,7 @@ export interface CodexSemanticCacheDiscovery {
26
26
  } | null;
27
27
  witness: CodexSemanticCacheWitness;
28
28
  appOnlyParents: CodexSemanticCacheAppOnlyParent[];
29
+ reviewedHookParents?: CodexSemanticCacheReviewedHookParent[];
29
30
  previousPolicySha256: string;
30
31
  supersededSources: DiscoverySource[];
31
32
  supersededDirectories: DiscoveryDirectory[];
@@ -93,7 +94,7 @@ export declare function projectNativeDiscoveryFields(text: string, format: "json
93
94
  export declare function captureRetiredCodexDiscovery(binding: AgentDiscoveryBinding, controls: CodexPluginSkillControl[], rules: unknown): AgentDiscoveryBinding;
94
95
  /** Only native capability locations covered by the complete typed cache census
95
96
  * qualify. Historical sources remain stored; arbitrary missing files do not. */
96
- export declare function codexSemanticCacheSourceRole(source: DiscoverySource, cacheRoot: string, parents: readonly string[]): boolean;
97
+ export declare function codexSemanticCacheSourceRole(source: DiscoverySource, cacheRoot: string, parents: readonly string[], hookParents?: readonly string[]): boolean;
97
98
  export declare function codexSemanticCacheDirectoryRole(path: string, cacheRoot: string): boolean;
98
99
  export declare function assertCodexSemanticCacheHookSafety(cacheRoot: string, parents: readonly string[], configPath: string, hookRootAlias?: CodexSemanticCacheDiscovery["hookRootAlias"]): void;
99
100
  export declare function verifyAgentDiscovery(binding: AgentDiscoveryBinding, codexRecovery?: CodexHookDiscoveryRecovery, reviewedRootAliases?: readonly {
@@ -215,6 +215,8 @@ export declare function planAgentSettingsWitnessUpgrade(options: {
215
215
  export interface CodexSemanticCacheUpgrade {
216
216
  agent: "codex";
217
217
  appOnlyParents: string[];
218
+ /** Explicit cache parents whose complete native mcp_tool hook contract was already enrolled. */
219
+ reviewedHookParents?: string[];
218
220
  expectedPolicySha256: string;
219
221
  expectedSettingsSha256: string;
220
222
  expectedCacheWitnessSha256?: string;
@@ -32,11 +32,31 @@ export interface CodexSemanticCacheAppOnlyParentWitness extends CodexSemanticCac
32
32
  appSha256: string;
33
33
  mcpSha256?: string;
34
34
  }
35
+ /** Explicitly reviewed native MCP-hook parent. Its cache materializations are
36
+ * admitted only when the complete manifest and hooks declaration were already
37
+ * enrolled as full-byte native discovery sources. */
38
+ export interface CodexSemanticCacheReviewedHookParent {
39
+ role: "reviewed-hook";
40
+ pluginId: string;
41
+ namespace: string;
42
+ pluginParent: string;
43
+ installation: "remote-receipt" | "local-marketplace";
44
+ remotePluginId?: string;
45
+ marketplaceSourcePath?: string;
46
+ marketplaceSha256?: string;
47
+ }
48
+ export interface CodexSemanticCacheReviewedHookParentWitness extends CodexSemanticCacheReviewedHookParent {
49
+ manifestSha256: string;
50
+ hooksSha256: string;
51
+ receiptSha256?: string;
52
+ }
35
53
  export interface CodexSemanticCacheWitness {
36
54
  schema: typeof CODEX_SEMANTIC_CACHE_WITNESS_SCHEMA;
37
55
  cacheRoot: string;
38
56
  controls: CodexSemanticCacheControl[];
39
57
  appOnlyParents: CodexSemanticCacheAppOnlyParentWitness[];
58
+ /** Optional only for compatibility with already persisted v1 witnesses. */
59
+ hookParents?: CodexSemanticCacheReviewedHookParentWitness[];
40
60
  skills: CodexSemanticCacheSkill[];
41
61
  sha256: string;
42
62
  }
@@ -45,10 +65,13 @@ export interface CaptureCodexSemanticCacheWitnessOptions {
45
65
  controls: CodexPluginSkillControl[];
46
66
  /** Explicit package-reviewed parents; absence never infers this role. */
47
67
  appOnlyParents?: CodexSemanticCacheAppOnlyParent[];
68
+ /** Explicit roles only; cache presence never creates a hook role. */
69
+ reviewedHookParents?: CodexSemanticCacheReviewedHookParent[];
48
70
  rules: unknown;
49
71
  /** Package-owned, bounded read used by the existing semantic control review. */
50
72
  read?: (path: string) => string;
51
73
  }
74
+ export declare function codexLocalMarketplaceMapsPlugin(text: string, marketplace: string, plugin: string): boolean;
52
75
  /** Capture the current semantic identity without changing the stored baseline.
53
76
  * Existing package-owned parsers validate documents and capability controls. */
54
77
  export declare function captureCodexSemanticCacheWitness(options: CaptureCodexSemanticCacheWitnessOptions): CodexSemanticCacheWitness;
@@ -1,3 +1,5 @@
1
+ /** Fixed codes only; generated native adapters must never copy refusal prose. */
2
+ export declare const HOOK_REFUSAL_REASON_CODES: readonly string[];
1
3
  export declare class HookDiagnosticError extends Error {
2
4
  readonly code: string;
3
5
  readonly stage: "context" | "sync";
@@ -49,7 +49,7 @@ var __require = import.meta.require;
49
49
  // src/runtime-prerequisites.ts
50
50
  import { isAbsolute as isAbsolute5 } from "path";
51
51
  // package.json
52
- var version = "0.10.64";
52
+ var version = "0.10.66";
53
53
  var package_default = {
54
54
  name: "@hasna/skills",
55
55
  version,
@@ -2643,8 +2643,8 @@ function assertAgentPolicyCollections(policy) {
2643
2643
  }
2644
2644
  }
2645
2645
  if (value.codexSemanticCache !== undefined) {
2646
- const proof = record(value.codexSemanticCache, 8), witness = record(proof.witness, 6);
2647
- requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && Object.keys(proof).length === 8 && proof.schema === "hasna.codex-semantic-cache-discovery.v1" && typeof proof.previousPolicySha256 === "string" && /^[a-f0-9]{64}$/.test(proof.previousPolicySha256) && Object.keys(witness).length === 6 && witness.schema === "hasna.codex-semantic-cache-witness.v1" && typeof witness.sha256 === "string" && /^[a-f0-9]{64}$/.test(witness.sha256));
2646
+ const proof = record(value.codexSemanticCache, 9), witness = record(proof.witness, 7);
2647
+ requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && [8, 9].includes(Object.keys(proof).length) && Object.keys(proof).every((key) => ["schema", "configPath", "hookRootAlias", "witness", "appOnlyParents", "previousPolicySha256", "supersededSources", "supersededDirectories", "reviewedHookParents"].includes(key)) && proof.schema === "hasna.codex-semantic-cache-discovery.v1" && typeof proof.previousPolicySha256 === "string" && /^[a-f0-9]{64}$/.test(proof.previousPolicySha256) && [6, 7].includes(Object.keys(witness).length) && Object.keys(witness).every((key) => ["schema", "cacheRoot", "controls", "appOnlyParents", "skills", "sha256", "hookParents"].includes(key)) && witness.schema === "hasna.codex-semantic-cache-witness.v1" && typeof witness.sha256 === "string" && /^[a-f0-9]{64}$/.test(witness.sha256));
2648
2648
  text(proof.configPath);
2649
2649
  requireBound(isAbsolute3(proof.configPath) && resolve5(proof.configPath) === proof.configPath && basename3(proof.configPath) === "config.toml");
2650
2650
  text(witness.cacheRoot);
@@ -2663,6 +2663,20 @@ function assertAgentPolicyCollections(policy) {
2663
2663
  text(parent[key]);
2664
2664
  requireBound(isAbsolute3(parent.pluginParent) && resolve5(parent.pluginParent) === parent.pluginParent);
2665
2665
  }
2666
+ if (proof.reviewedHookParents !== undefined)
2667
+ for (const parent of array(proof.reviewedHookParents, 4096)) {
2668
+ requireBound(object(parent) && parent.role === "reviewed-hook" && (parent.installation === "remote-receipt" && Object.keys(parent).length === 6 || parent.installation === "local-marketplace" && Object.keys(parent).length === 7));
2669
+ requireBound(parent.installation === "remote-receipt" ? Object.keys(parent).sort().join(",") === "installation,namespace,pluginId,pluginParent,remotePluginId,role" : Object.keys(parent).sort().join(",") === "installation,marketplaceSha256,marketplaceSourcePath,namespace,pluginId,pluginParent,role");
2670
+ for (const key of ["pluginId", "namespace", "pluginParent", "installation"])
2671
+ text(parent[key]);
2672
+ if (parent.installation === "remote-receipt")
2673
+ text(parent.remotePluginId);
2674
+ else {
2675
+ text(parent.marketplaceSourcePath);
2676
+ requireBound(typeof parent.marketplaceSha256 === "string" && /^[a-f0-9]{64}$/.test(parent.marketplaceSha256) && isAbsolute3(parent.marketplaceSourcePath) && resolve5(parent.marketplaceSourcePath) === parent.marketplaceSourcePath && basename3(parent.marketplaceSourcePath) === "marketplace.json");
2677
+ }
2678
+ requireBound(isAbsolute3(parent.pluginParent) && resolve5(parent.pluginParent) === parent.pluginParent);
2679
+ }
2666
2680
  for (const row of [...array(witness.controls, 4096), ...array(witness.appOnlyParents, 4096)]) {
2667
2681
  requireBound(object(row));
2668
2682
  for (const key of ["pluginId", "namespace", "pluginParent"])
@@ -2671,6 +2685,26 @@ function assertAgentPolicyCollections(policy) {
2671
2685
  if (row[key] !== undefined)
2672
2686
  requireBound(typeof row[key] === "string" && /^[a-f0-9]{64}$/.test(row[key]));
2673
2687
  }
2688
+ if (witness.hookParents !== undefined)
2689
+ for (const row of array(witness.hookParents, 4096)) {
2690
+ requireBound(object(row) && row.role === "reviewed-hook" && (row.installation === "remote-receipt" && Object.keys(row).length === 9 || row.installation === "local-marketplace" && Object.keys(row).length === 9));
2691
+ requireBound(row.installation === "remote-receipt" ? Object.keys(row).sort().join(",") === "hooksSha256,installation,manifestSha256,namespace,pluginId,pluginParent,receiptSha256,remotePluginId,role" : Object.keys(row).sort().join(",") === "hooksSha256,installation,manifestSha256,marketplaceSha256,marketplaceSourcePath,namespace,pluginId,pluginParent,role");
2692
+ for (const key of ["pluginId", "namespace", "pluginParent", "installation"])
2693
+ text(row[key]);
2694
+ if (row.installation === "remote-receipt")
2695
+ text(row.remotePluginId);
2696
+ else {
2697
+ text(row.marketplaceSourcePath);
2698
+ requireBound(typeof row.marketplaceSha256 === "string" && /^[a-f0-9]{64}$/.test(row.marketplaceSha256) && isAbsolute3(row.marketplaceSourcePath) && resolve5(row.marketplaceSourcePath) === row.marketplaceSourcePath && basename3(row.marketplaceSourcePath) === "marketplace.json");
2699
+ }
2700
+ requireBound(isAbsolute3(row.pluginParent) && resolve5(row.pluginParent) === row.pluginParent);
2701
+ for (const key of ["manifestSha256", "hooksSha256"])
2702
+ requireBound(typeof row[key] === "string" && /^[a-f0-9]{64}$/.test(row[key]));
2703
+ if (row.installation === "remote-receipt")
2704
+ requireBound(typeof row.receiptSha256 === "string" && /^[a-f0-9]{64}$/.test(row.receiptSha256));
2705
+ else
2706
+ requireBound(row.receiptSha256 === undefined);
2707
+ }
2674
2708
  for (const skill of array(witness.skills, 4096)) {
2675
2709
  requireBound(object(skill));
2676
2710
  for (const key of ["name", "pluginId", "namespace", "pluginParent"])
@@ -2864,6 +2898,78 @@ function detectedIntegrationAgents(options = {}) {
2864
2898
  import { spawnSync as spawnSync2 } from "child_process";
2865
2899
  import { accessSync, constants as constants4, existsSync as existsSync4, lstatSync as lstatSync5, realpathSync, statSync as statSync2 } from "fs";
2866
2900
  import { delimiter, isAbsolute as isAbsolute4, join as join8, resolve as resolve7 } from "path";
2901
+
2902
+ // src/lib/hook-diagnostics.ts
2903
+ var CONTEXT_ERROR_CODES = new Set([
2904
+ "SKILLS_API_CREDENTIAL_UNAVAILABLE",
2905
+ "SKILLS_API_UNAVAILABLE",
2906
+ "SKILLS_API_UNAUTHORIZED",
2907
+ "SKILLS_API_FORBIDDEN",
2908
+ "SKILLS_API_RESOURCE_UNAVAILABLE",
2909
+ "SESSION_WRITE_BUSY",
2910
+ "SKILLS_CONTEXT_FAILED",
2911
+ "SKILLS_STORAGE_FULL",
2912
+ "PROFILE_LOCK_MISMATCH",
2913
+ "PROFILE_IDENTITY_MISMATCH",
2914
+ "PROFILE_APPLIED_REPORT_FAILED",
2915
+ "CACHED_AUTHORITY_REQUIRED",
2916
+ "CACHED_PROFILE_EXPIRED",
2917
+ "CACHED_PROFILE_MISSING",
2918
+ "CACHED_BUNDLE_MISSING",
2919
+ "INVALID_CONTEXT_INPUT",
2920
+ "CONTEXT_INPUT_TOO_LARGE",
2921
+ "INVALID_CONTEXT_BUDGET",
2922
+ "INVALID_SELECTION",
2923
+ "INVALID_SELECTION_ALIASES",
2924
+ "INVALID_RECEIPT",
2925
+ "INVALID_CACHE_FILE",
2926
+ "UNSAFE_CACHE_PATH",
2927
+ "RECEIPT_TOO_LARGE",
2928
+ "BUNDLE_DIGEST_MISMATCH",
2929
+ "BUNDLE_IDENTITY_MISMATCH",
2930
+ "BUNDLE_UNAVAILABLE",
2931
+ "BUNDLE_TOO_LARGE",
2932
+ "SKILL_NOT_SELECTED",
2933
+ "INVALID_SESSION",
2934
+ "SESSION_PARENT_NOT_FOUND",
2935
+ "SESSION_PARENT_MISMATCH",
2936
+ "SESSION_NOT_FOUND",
2937
+ "SESSION_GENERATION_CHANGED",
2938
+ "SESSION_GENERATION_EXHAUSTED",
2939
+ "SESSION_PARENT_CHANGED",
2940
+ "SESSION_RECEIPT_CHANGED",
2941
+ "SESSION_RECONCILIATION_REQUIRED",
2942
+ "SESSION_RENEWAL_TIMEOUT",
2943
+ "SESSION_CONTEXT_CONFLICT",
2944
+ "SESSION_RECONCILIATION_INCOMPLETE",
2945
+ "SESSION_WRITE_LOCKED",
2946
+ "SESSION_WRITE_LOCK_CHANGED",
2947
+ "SESSION_LOCK_RECOVERY_INCOMPLETE"
2948
+ ]);
2949
+ var HOOK_REFUSAL_REASON_CODES = Object.freeze([
2950
+ ...CONTEXT_ERROR_CODES,
2951
+ "SKILLS_HOOK_FAILED",
2952
+ "SKILLS_HOOK_TIMEOUT",
2953
+ "SKILLS_HOOK_INVALID_RESPONSE"
2954
+ ]);
2955
+ var OPTIONAL_CONTEXT_FAILURES = new Set([
2956
+ "SKILLS_API_CREDENTIAL_UNAVAILABLE",
2957
+ "SKILLS_API_UNAVAILABLE",
2958
+ "SKILLS_API_UNAUTHORIZED",
2959
+ "SKILLS_API_FORBIDDEN",
2960
+ "SKILLS_API_RESOURCE_UNAVAILABLE",
2961
+ "CACHED_PROFILE_EXPIRED",
2962
+ "CACHED_PROFILE_MISSING",
2963
+ "CACHED_BUNDLE_MISSING",
2964
+ "SESSION_RECONCILIATION_REQUIRED",
2965
+ "SESSION_RENEWAL_TIMEOUT",
2966
+ "SESSION_WRITE_BUSY",
2967
+ "SESSION_PARENT_CHANGED",
2968
+ "SESSION_CONTEXT_CONFLICT",
2969
+ "SKILLS_HOOK_TIMEOUT"
2970
+ ]);
2971
+
2972
+ // src/lib/agent-sumi.ts
2867
2973
  var SUMI_PATH_SELECTORS = ["SUMI_HOME", "SUMI_CONFIG_DIR", "XDG_DATA_HOME", "XDG_CACHE_HOME", "XDG_CONFIG_HOME", "XDG_STATE_HOME"];
2868
2974
  var SUMI_PATH_KEYS = ["data", "cache", "config", "state"];
2869
2975
  var SUMI_PATH_OUTPUT_LIMIT = 128 * 1024;
@@ -3005,24 +3111,33 @@ function sumiConfigPath(home, cwd = process.cwd(), env = process.env) {
3005
3111
  return join8(sumiConfigDirectory(home, env, cwd), "sumi.json");
3006
3112
  }
3007
3113
  function renderSumiPlugin(command, profile) {
3008
- return renderSumiPluginVersion(command, profile, false);
3114
+ return renderSumiPluginVersion(command, profile, 3);
3009
3115
  }
3010
3116
  function isManagedSumiPlugin(text2, command, profile) {
3011
- return text2 === renderSumiPlugin(command, profile) || text2 === renderSumiPluginVersion(command, profile, true);
3117
+ return text2 === renderSumiPlugin(command, profile) || text2 === renderSumiPluginVersion(command, profile, 2) || text2 === renderSumiPluginVersion(command, profile, 1);
3012
3118
  }
3013
- function renderSumiPluginVersion(command, profile, legacy) {
3119
+ function renderSumiPluginVersion(command, profile, version2) {
3120
+ const legacy = version2 === 1;
3014
3121
  const failure = (message) => legacy ? `new Error(${JSON.stringify(message)})` : "refusal()";
3015
3122
  return `// Managed by @hasna/skills. Regenerate with skills hook install --agent sumi.
3016
3123
  import { spawn } from "node:child_process";
3017
3124
  const command = ${JSON.stringify(command)};
3018
3125
  const profile = ${JSON.stringify(profile)};
3019
- ${legacy ? "" : `function refusal() {
3020
- const error = new Error("Skills verification blocked this request. Review the Sumi Skills hook configuration, then retry.");
3126
+ ${legacy ? "" : `function refusal(${version2 === 3 ? "reasonCode" : ""}) {
3127
+ const error = new Error("Skills verification blocked this request. Review the Sumi Skills hook configuration, then retry."${version2 === 3 ? ' + (reasonCode ? " [" + reasonCode + "]" : "")' : ""});
3021
3128
  error.name = "SkillsHookRefusal";
3022
- error.skillsHookRefusal = { version: 1, code: "SKILLS_HOOK_REFUSED" };
3129
+ error.skillsHookRefusal = { version: 1, code: "SKILLS_HOOK_REFUSED"${version2 === 3 ? ", ...(reasonCode ? { reasonCode } : {})" : ""} };
3023
3130
  return error;
3024
3131
  }
3025
- `}function context(input) {
3132
+ `}${version2 === 3 ? `const reasonCodes = new Set(${JSON.stringify(HOOK_REFUSAL_REASON_CODES)});
3133
+ function refusedResult(result) {
3134
+ const reason = result.reason ?? result.stopReason ?? result.systemMessage;
3135
+ if (typeof reason !== "string" || reason.length > 16384) return refusal();
3136
+ if (reason.startsWith("NATIVE_SKILL_DRIFT:")) return refusal("NATIVE_SKILL_DRIFT");
3137
+ const code = /^Skills context is unavailable \\[([A-Z_]+)\\]/.exec(reason)?.[1];
3138
+ return refusal(reasonCodes.has(code) ? code : undefined);
3139
+ }
3140
+ ` : ""}function context(input) {
3026
3141
  return new Promise((resolve, reject) => {
3027
3142
  const child = spawn(command, ["hook", "user-prompt", "--agent", "sumi", "--selection-profile", profile], { cwd: input.cwd, stdio: ["pipe", "pipe", "pipe"] });
3028
3143
  const chunks = []; let bytes = 0, failure;
@@ -3037,12 +3152,12 @@ ${legacy ? "" : `function refusal() {
3037
3152
  try {
3038
3153
  const result = JSON.parse(Buffer.concat(chunks).toString("utf8"));
3039
3154
  if (!result || typeof result !== "object" || Array.isArray(result)) throw ${failure("Invalid Skills hook response")};
3040
- if (result.decision === "block" || result.continue === false) throw ${failure("Skills prompt hook refused")};
3155
+ if (result.decision === "block" || result.continue === false) throw ${version2 === 3 ? "refusedResult(result)" : failure("Skills prompt hook refused")};
3041
3156
  if (Object.keys(result).length && (!result.hookSpecificOutput || result.hookSpecificOutput.hookEventName !== input.hook_event_name)) throw ${failure("Invalid Skills hook response")};
3042
3157
  const text = result.hookSpecificOutput?.additionalContext ?? "";
3043
3158
  if (typeof text !== "string") throw ${failure("Invalid Skills prompt context")};
3044
3159
  resolve(text);
3045
- } catch (error) { reject(${legacy ? "error" : "refusal()"}); }
3160
+ } catch (error) { reject(${legacy ? "error" : version2 === 3 ? 'error instanceof Error && error.name === "SkillsHookRefusal" ? error : refusal()' : "refusal()"}); }
3046
3161
  });
3047
3162
  child.stdin.end(JSON.stringify(input));
3048
3163
  });
@@ -3075,9 +3190,13 @@ export default {
3075
3190
  sessions.add(event.sessionID);
3076
3191
  }));
3077
3192
  registrations.push(await ctx.session.hook("context", async event => {
3078
- const last = event.messages.findLast(message => message.role === "user");
3193
+ ${version2 === 3 ? ` // Model messages also contain checkpoints, attachments and synthetic
3194
+ // user-role history, with no owner-prompt provenance. Prompt admission
3195
+ // already matched and persisted Skills; restore that verified state only.
3196
+ const prompt = "";
3197
+ ` : ` const last = event.messages.findLast(message => message.role === "user");
3079
3198
  const prompt = (last?.content ?? []).filter(part => part.type === "text").map(part => part.text).join("\\n");
3080
- // Recheck on every primary request, including resumed and synthetic
3199
+ `} // Recheck on every primary request, including resumed and synthetic
3081
3200
  // sessions. The CLI owns optional failures versus explicit refusals.
3082
3201
  const text = await context(await input(event.sessionID, prompt, !sessions.has(event.sessionID)));
3083
3202
  if (text) event.system.push({ type: "text", text });