@hasna/skills 0.10.83 → 0.10.88

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.
Files changed (34) hide show
  1. package/README.md +133 -15
  2. package/bin/index.js +5690 -2868
  3. package/bin/mcp.js +1688 -810
  4. package/bin/migrate.js +1 -1
  5. package/bin/server.js +1 -1
  6. package/bin/worker.js +1 -1
  7. package/dist/cli/commands/agent-integration.d.ts +35 -0
  8. package/dist/cli/commands/context.d.ts +4 -0
  9. package/dist/cli/commands/runtime-launcher.d.ts +4 -9
  10. package/dist/index.js +3021 -1530
  11. package/dist/lib/agent-adapters.d.ts +5 -2
  12. package/dist/lib/agent-discovery.d.ts +22 -4
  13. package/dist/lib/agent-hermes.d.ts +7 -1
  14. package/dist/lib/agent-integration.d.ts +31 -2
  15. package/dist/lib/agent-opencode.d.ts +4 -0
  16. package/dist/lib/claude-hook-disable.d.ts +32 -0
  17. package/dist/lib/claude-settings-witness.d.ts +28 -0
  18. package/dist/lib/codex-settings-witness.d.ts +20 -0
  19. package/dist/lib/guarded-agent-config.d.ts +3 -0
  20. package/dist/lib/hook-diagnostics.d.ts +3 -1
  21. package/dist/lib/narrowed-witness-ledger.d.ts +24 -0
  22. package/dist/lib/native-discovery-roots.d.ts +8 -0
  23. package/dist/lib/native-drift-receipt.d.ts +55 -0
  24. package/dist/lib/opencode-hook-protocol.d.ts +5 -0
  25. package/dist/lib/profile-client.d.ts +1 -0
  26. package/dist/lib/selection-cache.d.ts +47 -2
  27. package/dist/lib/session-background-renewal.d.ts +118 -0
  28. package/dist/lib/session-reconciliation.d.ts +82 -2
  29. package/dist/lib/skill-context.d.ts +39 -1
  30. package/dist/lib/skill-relevance.d.ts +95 -0
  31. package/dist/runtime-prerequisites.js +569 -165
  32. package/dist/sdk/index.js +2626 -1396
  33. package/docs/plugin-admission.md +132 -3
  34. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ export { renderOpenCodePlugin } from "./agent-opencode.js";
1
2
  export declare const INTEGRATION_AGENTS: readonly ["claude", "codex", "gemini", "opencode", "cursor", "hermes", "sumi"];
2
3
  export type IntegrationAgent = typeof INTEGRATION_AGENTS[number];
3
4
  export declare function renderAgentHookCommand(command: string, agent: IntegrationAgent, profileId: string, event: string): string;
@@ -23,7 +24,7 @@ export declare const AGENT_ADAPTERS: {
23
24
  readonly opencode: {
24
25
  readonly root: ".config/opencode/skills";
25
26
  readonly config: ".config/opencode/opencode.json";
26
- readonly events: readonly ["chat.message"];
27
+ readonly events: readonly ["prompt", "context"];
27
28
  readonly promptContext: true;
28
29
  };
29
30
  readonly sumi: {
@@ -49,4 +50,6 @@ export declare const AGENT_ADAPTERS: {
49
50
  };
50
51
  };
51
52
  export declare function normalizeAgentHookEvent(agent: IntegrationAgent, event: string): string;
52
- export declare function renderOpenCodePlugin(command: string, profileId: string): string;
53
+ export declare function renderLegacyOpenCodePlugin(command: string, profileId: string): string;
54
+ /** Recognize exact managed bytes only, including the shipped v1 generator. */
55
+ export declare function isManagedOpenCodePlugin(text: string | null, command: string, profileId: string): boolean;
@@ -5,6 +5,7 @@ import { CLAUDE_ORPHAN_MARKER_MODE } from "./claude-plugin-cache-marker.js";
5
5
  import { type NativeDiscoveryWitnessTolerance } from "./native-discovery-witness-change.js";
6
6
  import { type ManagedPluginRegistrationWitness } from "./plugin-discovery.js";
7
7
  import { type CodexHookDiscoveryRecovery } from "./codex-hook-discovery-recovery.js";
8
+ import { type NarrowedWitnessLedger } from "./narrowed-witness-ledger.js";
8
9
  import { type CodexPluginSkillControl, type CodexPluginSourceInput } from "./codex-plugin-skill-controls.js";
9
10
  import { type RetainedReviewArtifact, type ReviewedArtifact } from "./retained-review-artifacts.js";
10
11
  import { type CodexSemanticCacheWitness, type CodexSemanticCacheAppOnlyParent, type CodexSemanticCacheReviewedHookParent } from "./codex-semantic-cache-witness.js";
@@ -13,7 +14,7 @@ export interface DiscoverySource {
13
14
  reviewArtifact?: RetainedReviewArtifact;
14
15
  path: string;
15
16
  sha256: string | null;
16
- hashMode?: "bytes" | "path-bytes" | "claude-plugin-orphaned-at-v1" | "claude-plugin-manifest-v1" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1" | "claude-settings-v2" | "claude-settings-v3" | "claude-settings-v4" | "claude-marketplace-registry-v2" | "codex-settings-v1" | "codex-settings-v2" | "codex-settings-v3" | "codex-settings-v4" | "codex-settings-v5" | "codex-settings-v6" | "executable-v1" | "sumi-settings-v1" | "claude-marketplace-entry-v1";
17
+ hashMode?: "bytes" | "path-bytes" | "claude-plugin-orphaned-at-v1" | "claude-plugin-manifest-v1" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1" | "claude-settings-v2" | "claude-settings-v3" | "claude-settings-v4" | "claude-settings-v5" | "claude-marketplace-registry-v2" | "codex-settings-v1" | "codex-settings-v2" | "codex-settings-v3" | "codex-settings-v4" | "codex-settings-v5" | "codex-settings-v6" | "codex-settings-v7" | "executable-v1" | "sumi-settings-v1" | "claude-marketplace-entry-v1";
17
18
  hookSources?: CodexReviewedHookSource[];
18
19
  managedPlugins?: ManagedPluginRegistrationWitness[];
19
20
  format?: "json" | "toml" | "yaml";
@@ -120,12 +121,15 @@ export declare function assertCodexSemanticCacheHookSafety(cacheRoot: string, pa
120
121
  /** Source-byte and directory-membership drift are NativeDiscoveryWitnessChange
121
122
  * errors. Only the live Codex prompt hook passes `witnessTolerance`; a change it
122
123
  * accepts as a vendor refresh is collected and the remaining checks still run.
124
+ * Only the live hooks pass `narrowed`: a legacy full-file settings witness that
125
+ * no longer matches is still current when the narrowed digest recorded for it
126
+ * matches (see narrowed-witness-ledger.ts), and a matching one is observed.
123
127
  * Every other change and every other failure throws as before. */
124
128
  export declare function verifyAgentDiscovery(binding: AgentDiscoveryBinding, codexRecovery?: CodexHookDiscoveryRecovery, reviewedRootAliases?: readonly {
125
129
  agent: IntegrationAgent;
126
130
  alias: string;
127
131
  target: string;
128
- }[], witnessTolerance?: NativeDiscoveryWitnessTolerance): void;
132
+ }[], witnessTolerance?: NativeDiscoveryWitnessTolerance, narrowed?: NarrowedWitnessLedger): void;
129
133
  /** One stored Claude discovery source as an installer sees it. */
130
134
  export type ClaudeDiscoverySourceVerdict = {
131
135
  status: "current";
@@ -193,11 +197,25 @@ export declare function discoveryTextKeepsWitness(binding: Pick<AgentDiscoveryBi
193
197
  /** `executable` is the agent's runtime: omitted, it is resolved through PATH;
194
198
  * a value (or null for none) is used as given, so verification never depends
195
199
  * on the caller's PATH. */
196
- export declare function resolveAgentDiscovery(options: {
200
+ type DiscoveryOptions = {
197
201
  home: string;
198
202
  agent: IntegrationAgent;
199
203
  reviewed?: ReviewedDiscoveryInputs;
200
204
  retainedReview?: AgentDiscoveryBinding;
201
205
  canonical?: (path: string) => string;
202
206
  executable?: DiscoveryExecutable | null;
203
- }): AgentDiscoveryBinding;
207
+ };
208
+ export declare function resolveAgentDiscovery(options: DiscoveryOptions): AgentDiscoveryBinding;
209
+ /** Read-only exclusion, not native admission. Retained roots remain excluded,
210
+ * while current supported controls are re-derived even when old settings or
211
+ * directory witnesses are stale. Non-enumerable executable inputs still need
212
+ * their existing review. No witness, policy or enrollment is written. */
213
+ export declare function resolveAgentDiscoveryExclusionRoots(options: Omit<DiscoveryOptions, "reviewed" | "retainedReview"> & {
214
+ retained?: AgentDiscoveryBinding;
215
+ narrowed?: NarrowedWitnessLedger;
216
+ nativeHome?: string;
217
+ }): readonly string[];
218
+ /** Metadata-only closure of aliases inside the selected native discovery
219
+ * trees. Never search the account generally or load a payload/plugin. Each
220
+ * resolved alias remains excluded while unrelated portable paths stay usable. */
221
+ export declare function nativeDiscoveryAliasTargets(roots: readonly string[], home: string): string[];
@@ -12,13 +12,19 @@ export declare function hermesHookDefinitions(supervisor: Pick<HermesSupervisorB
12
12
  fail_closed?: boolean;
13
13
  }>;
14
14
  export declare function configureHermesHooks(text: string | null, supervisor: HermesSupervisorBinding, previous?: HermesSupervisorBinding): string;
15
+ /** Every refusal here leaves the effective Hermes discovery unchecked (another
16
+ * project, home, bundled tree or project plugins), so a payload named skills-cli
17
+ * there could load through the bridge. They are bridge-integrity refusals: the
18
+ * live skill_view(skills-cli) call is refused while prompts still fail open. */
15
19
  export declare function assertHermesEnvironment(home: string): void;
16
20
  export declare function assertHermesProtection(home: string, config: Record<string, any>, command: string, profile: string, supervisor: HermesSupervisorBinding): void;
17
21
  /** skill_view also searches legacy <name>.md with rglob, including hidden and
18
22
  * dependency directories that the normal SKILL.md index skips. Check the one
19
23
  * name admitted by the native tool guard without reading any candidate bytes.
20
24
  * Only support folders directly under a real skill descriptor are excluded,
21
- * matching Hermes is_skill_support_path. Every other bounded path is checked. */
25
+ * matching Hermes is_skill_support_path. Every other bounded path is checked.
26
+ * A found or unrulable shadow is a bridge-integrity refusal: the live
27
+ * skill_view(skills-cli) call is refused, while prompts still fail open. */
22
28
  export declare function assertNoHermesLegacyShadow(root: string): void;
23
29
  export declare function normalizeHermesHookInput(input: Record<string, any>): Record<string, any>;
24
30
  export declare function assertHermesTool(input: Record<string, any>): void;
@@ -4,6 +4,7 @@ import { type ReviewedCodexSkillDenial, type CodexPluginSkillControl } from "./c
4
4
  import { type CodexNativeHookEnvelope, type ProcessInspector, type NativePolicyHelperRunner } from "./codex-native-skill-policy.js";
5
5
  import { type CodexNativeSkillCatalog } from "./codex-native-skill-catalog.js";
6
6
  import type { NativeDiscoveryWitnessKind } from "./native-discovery-witness-change.js";
7
+ import { type BigIntStats } from "node:fs";
7
8
  import { type DiscoveryExecutable, type AgentDiscoveryBinding, type ReviewedDiscoveryInputs } from "./agent-discovery.js";
8
9
  import { type IntegrationAgent } from "./agent-adapters.js";
9
10
  import type { CodexHookDiscoveryRecovery } from "./codex-hook-discovery-recovery.js";
@@ -178,6 +179,11 @@ export interface ClaudeManagedHookProjectionOptions {
178
179
  /** Supply the preceding plan's source witness when checking it before use. */
179
180
  expectedSourceSha256?: string;
180
181
  }
182
+ export declare function assertNativePathAcl(path: string, observed: BigIntStats, options?: {
183
+ allowDenyOnly?: boolean;
184
+ descriptor?: number;
185
+ creationParent?: boolean;
186
+ }): void;
181
187
  /** Read-only projection for a copied Claude configuration. This does not install
182
188
  * hooks, adopt target discovery, change policy, or confer write/admission authority.
183
189
  * The caller must preserve the full target, hold its owning writer lease, replan
@@ -207,8 +213,8 @@ export declare function planClaudeHookEventsUpdate(options: ClaudeHookUpdateOpti
207
213
  */
208
214
  export declare function planAgentSettingsWitnessUpgrade(options: {
209
215
  agent: "claude" | "codex" | "sumi";
210
- targetCodexVersion?: 2 | 3 | 4 | 5;
211
- targetClaudeVersion?: 3 | 4;
216
+ targetCodexVersion?: 2 | 3 | 4 | 5 | 7;
217
+ targetClaudeVersion?: 3 | 4 | 5;
212
218
  reviewedPreimage: string;
213
219
  home?: string;
214
220
  dataDir?: string;
@@ -404,6 +410,25 @@ interface ManagedBridgeOptions {
404
410
  * vendor copies and every other change still refuse. Install, trust, migrate
405
411
  * and sync never pass it. */
406
412
  codexPromptHook?: boolean;
413
+ /** Live lifecycle hooks only (prompt, session, subagent and tool events).
414
+ * - A legacy full-file settings witness that no longer matches stays current
415
+ * when the narrowed digest recorded for its review is unchanged, and a
416
+ * matching one is recorded (narrowed-witness-ledger.ts).
417
+ * - Anything that stops the skills-cli bridge from being verified is a
418
+ * NativeBridgeIntegrityRefusal, and any other failure to verify native
419
+ * discovery (a changed root alias, an unreadable settings file, an
420
+ * inventory error) is reported as NATIVE_SKILL_DRIFT, so the hook can fail
421
+ * open instead of blocking the prompt.
422
+ * Install, trust, migrate, sync and upgrades never pass it. */
423
+ liveHook?: boolean;
424
+ /** Live tool events that would load the skills-cli bridge (Claude
425
+ * Skill(skills-cli), Gemini activate_skill(skills-cli), Hermes
426
+ * skill_view(skills-cli)); requires liveHook. Before such a call proceeds,
427
+ * no native copy may be able to resolve under the bridge's own name, in any
428
+ * tier the agent searches, including after an earlier drift skipped the
429
+ * inventory. A found copy, or a check that cannot complete, is a
430
+ * NativeBridgeIntegrityRefusal. */
431
+ bridgeToolCall?: boolean;
407
432
  }
408
433
  /** Result of a guard call; both lists are empty unless the caller opted in.
409
434
  * `codexInertVendorCopies` holds the canonical skill directories exempted as
@@ -444,7 +469,11 @@ export declare function codexPromptDriftWarning(report: ManagedBridgeReport): st
444
469
  /** Run before any prompt context load. Missing ownership or reappearing native
445
470
  * discovery files require repair; verified cache availability is not an override. */
446
471
  export declare function assertManagedAgentBridge(agent: IntegrationAgent, options?: ManagedBridgeOptions): ManagedBridgeReport;
472
+ /** Consumption-edge check: native registry content and path must name the
473
+ * exact owned bridge, and live on-disk custody must still verify now. */
474
+ export declare function assertOpenCodeNativeBridge(skill: unknown, options?: ManagedBridgeOptions): void;
447
475
  export declare function normalizeAgentHookPrompt(agent: IntegrationAgent, event: string, prompt: string): string;
476
+ export declare function isAgentSubmittedPrompt(prompt: unknown): boolean;
448
477
  export declare function hookContextOutput(event: string, result: {
449
478
  context: string;
450
479
  receipt?: unknown;
@@ -0,0 +1,4 @@
1
+ /** OpenCode v2.0.32 Promise contract: plugin/module.ts, promise/session.ts,
2
+ * promise/tool.ts and promise/registration.ts in anomalyco/opencode.
3
+ * Prompt admission captures genuine input; context matches/restores at delivery. */
4
+ export declare function renderOpenCodePlugin(command: string, profile: string): string;
@@ -0,0 +1,32 @@
1
+ export interface ClaudeHookDisableOptions {
2
+ settings: string;
3
+ expectedSettingsSha256: string;
4
+ expectedMode: "0600" | "absent";
5
+ command: string;
6
+ expectedCommandSha256: string;
7
+ profile: string;
8
+ apply?: boolean;
9
+ /** New, exclusive, private directory. Its parent must already exist. */
10
+ preimageDir?: string;
11
+ }
12
+ export interface ClaudeHookDisableResult {
13
+ schema: "hasna.claude-hook-disable.v1";
14
+ state: "preview" | "applied" | "no-op" | "no-data" | "ambiguous";
15
+ mutationOccurred: boolean;
16
+ target: string;
17
+ beforeSha256: string | null;
18
+ plannedAfterSha256: string | null;
19
+ afterSha256: string | null;
20
+ removed: Array<{
21
+ event: string;
22
+ groupIndex: number;
23
+ hookIndex: number;
24
+ }>;
25
+ settingsWitness: "unchanged" | "not-renewed";
26
+ preimage?: string;
27
+ displaced?: string;
28
+ receipt?: string;
29
+ reason?: string;
30
+ }
31
+ /** Only removes exact rendered context registrations. Never installs, admits or rebinds. */
32
+ export declare function disableClaudeHooks(options: ClaudeHookDisableOptions): ClaudeHookDisableResult;
@@ -10,6 +10,9 @@ export interface ClaudeSettingsWitnessBudget {
10
10
  remaining: number;
11
11
  }
12
12
  export declare function readNativeSettingsWitnessFile(path: string, budget: ClaudeSettingsWitnessBudget, filename?: "settings.json" | "config.toml" | "sumi.json" | "marketplace.json"): string;
13
+ /** The same bounded, stable, strict-UTF8 read for root-exclusion inputs whose
14
+ * filenames are selected by the owning agent adapter rather than settings. */
15
+ export declare function readNativeDiscoveryWitnessFile(path: string, budget: ClaudeSettingsWitnessBudget): string;
13
16
  export type NativeJsonObject = ObjectValue;
14
17
  export type NativeJsonValue = Value;
15
18
  type ObjectValue = {
@@ -93,6 +96,31 @@ export declare function upgradeClaudeSettingsWitnessV4(previous: {
93
96
  hashMode?: "bytes" | "claude-settings-v1" | "claude-settings-v2" | "claude-settings-v3";
94
97
  sha256: string;
95
98
  }, reviewedSettingsPath: string): ReturnType<typeof captureClaudeSettingsV4>;
99
+ /** Current discovery controls, parsed by the same bounded, duplicate-key-safe
100
+ * adapter as the v5 witness. Root exclusion consumes this without changing a
101
+ * stored review or accepting that review for native loading. */
102
+ export declare function claudeDiscoveryControlKeys(text: string): string[];
103
+ /** Discovery environment names from the same owning projection, without
104
+ * returning their values or treating ordinary environment as discovery. */
105
+ export declare function claudeDiscoveryEnvironmentKeys(text: string): string[];
106
+ /** Explicit v5 review: the narrowed discovery projection above. v1-v4
107
+ * witnesses keep their meaning; nothing selects v5 implicitly. */
108
+ export declare function captureClaudeSettingsV5(path: string, budget?: ClaudeSettingsWitnessBudget): {
109
+ path: string;
110
+ hashMode: "claude-settings-v5";
111
+ sha256: string;
112
+ };
113
+ export declare function hashClaudeSettingsReplacementV5(text: string, budget: ClaudeSettingsWitnessBudget): string;
114
+ /** Explicit upgrade from any prior witness (raw bytes, v1-v4) with an exact
115
+ * preserved preimage of its review. v5 binds a subset of what each earlier mode
116
+ * binds, so only a difference in settings v5 ignores is accepted; a change to a
117
+ * hook, plugin, marketplace, skill key, Skill rule, added directory or
118
+ * discovery environment variable still refuses. */
119
+ export declare function upgradeClaudeSettingsWitnessV5(previous: {
120
+ path: string;
121
+ hashMode?: "bytes" | "claude-settings-v1" | "claude-settings-v2" | "claude-settings-v3" | "claude-settings-v4";
122
+ sha256: string;
123
+ }, reviewedSettingsPath: string): ReturnType<typeof captureClaudeSettingsV5>;
96
124
  /** Strict bounded JSON control witness; metadata exclusion must be explicit. */
97
125
  export declare function hashNativeJsonControls(text: string, excludedMetadata?: "version"): string;
98
126
  /** Sumi's versioned review uses the same strict JSON parser and bounded syntax
@@ -79,6 +79,26 @@ export declare function upgradeCodexSettingsWitnessV5(previous: {
79
79
  hashMode?: "bytes" | "codex-settings-v1" | "codex-settings-v2" | "codex-settings-v3" | "codex-settings-v4";
80
80
  sha256: string;
81
81
  }, reviewedSettingsPath: string): ReturnType<typeof captureCodexSettingsV5>;
82
+ /** Root exclusion uses the same discovery projection as V7, including every
83
+ * profile and instruction/MCP control, without creating a settings witness. */
84
+ export declare function codexDiscoveryControlKeys(config: Record<string, any>): string[];
85
+ /** Explicit V7 review: the narrowed discovery projection above. Earlier modes
86
+ * keep their meaning; nothing selects V7 implicitly. */
87
+ export declare function hashCodexSettingsReplacementV7(text: string, budget?: ClaudeSettingsWitnessBudget): string;
88
+ export declare function captureCodexSettingsV7(path: string, budget?: ClaudeSettingsWitnessBudget): {
89
+ path: string;
90
+ hashMode: "codex-settings-v7";
91
+ sha256: string;
92
+ };
93
+ /** Prove the preserved original under its exact original contract (raw bytes or
94
+ * V1-V5), then require the same V7 projection. V7 binds a subset of what each
95
+ * of those modes binds, so only a difference in settings V7 ignores passes.
96
+ * V6 is not eligible: it binds hook MCP dependencies V7 does not carry. */
97
+ export declare function upgradeCodexSettingsWitnessV7(previous: {
98
+ path: string;
99
+ hashMode?: "bytes" | "codex-settings-v1" | "codex-settings-v2" | "codex-settings-v3" | "codex-settings-v4" | "codex-settings-v5";
100
+ sha256: string;
101
+ }, reviewedSettingsPath: string): ReturnType<typeof captureCodexSettingsV7>;
82
102
  /** V6 keeps V5 display tolerances and binds routes referenced by the complete
83
103
  * independently reviewed native hook source population. No legacy migration. */
84
104
  export declare function captureCodexSettingsV6(path: string, reviewedSources: readonly CodexReviewedHookSource[], budget?: ClaudeSettingsWitnessBudget, changes?: Map<string, string>): {
@@ -0,0 +1,3 @@
1
+ export declare function guardedAgentConfigRefusal(error: unknown): string;
2
+ /** One transaction for the direct-file adapters. Failed evidence stays beside the original. */
3
+ export declare function guardedAgentConfigWrite(path: string, transform: (current: Buffer | null) => string): void;
@@ -5,7 +5,9 @@ export declare class HookDiagnosticError extends Error {
5
5
  readonly stage: "context" | "sync";
6
6
  /** Fixed marker: a refused renewal whose automatic pin migration ran out of budget. */
7
7
  readonly migration?: "timeout";
8
- constructor(code: string, stage: "context" | "sync", migration?: "timeout");
8
+ /** Fixed marker: an expired pin whose renewal continues in a background worker. */
9
+ readonly renewal?: "background";
10
+ constructor(code: string, stage: "context" | "sync", migration?: "timeout", renewal?: "background");
9
11
  }
10
12
  export declare function hookChildError(stdout: string, stage: "context" | "sync"): HookDiagnosticError;
11
13
  export declare function hookFailureReason(error: unknown, profileId?: string): string;
@@ -0,0 +1,24 @@
1
+ import type { DiscoverySource } from "./agent-discovery.js";
2
+ export type NarrowedSettingsMode = "claude-settings-v5" | "codex-settings-v7";
3
+ export declare const NARROWED_WITNESS_LEDGER_FILE = "native-witness-narrowed.json";
4
+ /** The narrowed mode that covers this legacy full-file settings witness, or
5
+ * null when it has none. */
6
+ export declare function narrowedLegacyWitnessMode(source: DiscoverySource): NarrowedSettingsMode | null;
7
+ export interface NarrowedWitnessLedger {
8
+ /** The legacy witness no longer matches: accept only when the current file's
9
+ * narrowed digest equals the one recorded for this exact witness. */
10
+ accepts(source: DiscoverySource): boolean;
11
+ /** The legacy witness verified the current file: record its narrowed digest,
12
+ * re-proving the legacy match on the same bytes first. */
13
+ observe(source: DiscoverySource): void;
14
+ /** Paths accepted through a recorded narrowed digest during this check. */
15
+ readonly accepted: string[];
16
+ }
17
+ /** Opened by the live hook only. `record` permits observations to be written
18
+ * by flush(); reads never write. Every failure is fail-closed: an unreadable or
19
+ * malformed ledger accepts nothing and is never overwritten. */
20
+ export declare function openNarrowedWitnessLedger(dataDir: string, options?: {
21
+ record: boolean;
22
+ }): NarrowedWitnessLedger & {
23
+ flush(): void;
24
+ };
@@ -1,2 +1,10 @@
1
1
  /** Shared default discovery roots for inventory and authoring exclusion. */
2
2
  export declare const NATIVE_SKILL_ROOTS: readonly [readonly ["claude", ".claude/skills"], readonly ["codex", ".codex/skills"], readonly ["codex", ".agents/skills"], readonly ["gemini", ".gemini/skills"], readonly ["sumi", ".sumi/skills"], readonly ["sumi", ".sumi/skill"], readonly ["codewith", ".codewith/skills"], readonly ["opencode", ".config/opencode/skills"], readonly ["opencode", ".opencode/skills"], readonly ["cursor", ".cursor/skills"], readonly ["hermes", ".hermes/skills"], readonly ["windsurf", ".windsurf/skills"], readonly ["pi", ".pi/agent/skills"], readonly ["amp", ".amp/skills"], readonly ["cline", ".cline/skills"], readonly ["roo", ".roo/skills"], readonly ["copilot", ".github/skills"]];
3
+ /** Supplemental Sumi project tiers are shared with inventory. The caller
4
+ * supplies Sumi's owning resolver so this root contract has no runtime or
5
+ * enrollment dependency. Authoring can still exclude the shared tiers when
6
+ * the Sumi resolver is absent. */
7
+ export declare function nativeSumiProjectSkillRoots(home: string, project: string, resolveSumiRoots: (home: string, project: string) => readonly string[]): string[];
8
+ /** Native project discovery includes every ancestor. Keep inventory and
9
+ * authoring on this same bounded context enumeration. */
10
+ export declare function nativeProjectAncestorDirectories(projects: readonly string[], canonical?: (path: string) => string): string[];
@@ -0,0 +1,55 @@
1
+ export declare const NATIVE_DRIFT_RECEIPT_SCHEMA = "hasna.skills.native-drift-receipt.v1";
2
+ export declare const NATIVE_DRIFT_RECEIPT_DIRECTORY = "native-drift";
3
+ /** A native tool-policy refusal: denying a native payload skill is the
4
+ * protection that keeps a drifted station safe, so it is never failed open. */
5
+ export declare class NativeSkillPolicyRefusal extends Error {
6
+ }
7
+ /** The skills-cli bridge itself could not be verified: it is missing or
8
+ * modified, or the managed binding that names it is missing, incompatible or
9
+ * no longer resolves. A tool call that would load the bridge is refused; a
10
+ * prompt, session or subagent event fails open with NATIVE_DRIFT_BRIDGE_NOTICE. */
11
+ export declare class NativeBridgeIntegrityRefusal extends NativeSkillPolicyRefusal {
12
+ }
13
+ export declare function isNativeSkillDrift(error: unknown): error is Error;
14
+ export declare function isNativeBridgeIntegrityRefusal(error: unknown): error is NativeBridgeIntegrityRefusal;
15
+ /** Escape controls, format characters and line separators as \uXXXX so a path
16
+ * from the file system cannot hide or reorder warning text, then bound it. */
17
+ export declare function boundedDriftReason(message: string, limit?: number): string;
18
+ export declare function nativeDriftRepair(agent: string): string;
19
+ /** One visible line: what happened, the bounded drift reason (it names the
20
+ * changed path or copies), the repair command and the receipt path. */
21
+ export declare function nativeDriftWarning(agent: string, message: string, receiptPath: string | null): string;
22
+ /** Fixed agent-facing notice for the turn whose Skills context was omitted. */
23
+ export declare const NATIVE_DRIFT_AGENT_NOTICE = "Skills context is unavailable for this turn because the station's native skill discovery changed; the change was reported to the operator. Continue the user's task. Load a selected skill explicitly with `skills load <slug>` if one is needed, and do not load native skill copies.";
24
+ /** Fixed agent-facing notice when the skills-cli bridge itself is unverified:
25
+ * the bridge tool is refused this turn, so the agent uses the CLI instead. */
26
+ export declare const NATIVE_DRIFT_BRIDGE_NOTICE = "Skills context is unavailable for this turn because the station's skills-cli bridge could not be verified; the change was reported to the operator. Continue the user's task, but do not invoke the skills-cli native skill until it is repaired. Run the `skills` command directly (for example `skills load <slug>`) if a skill is needed, and do not load native skill copies.";
27
+ export interface NativeDriftReceipt {
28
+ schema: typeof NATIVE_DRIFT_RECEIPT_SCHEMA;
29
+ agent: string;
30
+ status: "active" | "cleared";
31
+ code: "NATIVE_SKILL_DRIFT";
32
+ event: string;
33
+ promptBlocked: false;
34
+ skillsContext: "omitted";
35
+ reason: string;
36
+ reasonSha256: string;
37
+ repair: string;
38
+ firstSeenAt: string;
39
+ lastSeenAt: string;
40
+ occurrences: number;
41
+ clearedAt?: string;
42
+ }
43
+ export declare function nativeDriftReceiptPath(dataDir: string, agent: string): string;
44
+ /** Record or refresh the active receipt for this agent. Never throws: the
45
+ * warning says when the receipt could not be recorded. */
46
+ export declare function recordNativeDriftReceipt(dataDir: string, agent: string, event: string, message: string, now?: Date): string | null;
47
+ /** After a clean verification: mark an active receipt cleared, keeping its
48
+ * history. Absent or already cleared receipts are left untouched. */
49
+ export declare function clearNativeDriftReceipt(dataDir: string, agent: string, now?: Date): void;
50
+ /** Every readable receipt, for operators and monitors. */
51
+ export declare function readNativeDriftReceipts(dataDir: string): NativeDriftReceipt[];
52
+ /** The non-blocking envelope for drift, in each native adapter's own shape. */
53
+ export declare function nativeDriftFailOpen(dataDir: string, agent: string, event: string, nativeEvent: string, message: string, options?: {
54
+ bridgeUnverified?: boolean;
55
+ }): Record<string, unknown>;
@@ -0,0 +1,5 @@
1
+ export declare const OPENCODE_BRIDGE_BODY_SHA256: string;
2
+ export declare const OPENCODE_BRIDGE_DESCRIPTION_SHA256: string;
3
+ export declare function isVerifiedOpenCodeSkillDescriptor(value: unknown, expectedPath: string): boolean;
4
+ /** Explicit adapter phase, including empty attachment-only prompt admission. */
5
+ export declare function openCodeContextDelivery(input: unknown): boolean;
@@ -20,6 +20,7 @@ export declare class HttpProfileClient implements ProfileClient {
20
20
  private signal?;
21
21
  readonly authority: string;
22
22
  private origin;
23
+ private responseSignals;
23
24
  constructor(key: string, origin: string, signal?: AbortSignal | undefined);
24
25
  private requestRoute;
25
26
  private request;
@@ -55,6 +55,25 @@ export declare function validateSelection(selection: ResolvedSkillSelection): vo
55
55
  export declare function validateResolvedProfile(profile: ResolvedSkillProfile, authority?: string): void;
56
56
  export declare function selectionBundlePath(selection: ResolvedSkillSelection, options?: SelectionCacheOptions): string;
57
57
  export declare function writeSelectionJson(path: string, value: unknown): void;
58
+ /**
59
+ * Create a small private JSON file with exclusive, no-follow semantics. Returns
60
+ * false when the path already exists; never replaces anything.
61
+ */
62
+ export declare function createExclusiveSelectionJson(path: string, value: unknown, maximumBytes?: number): boolean;
63
+ /** A small private regular file's bytes and identity, or null when it is absent. */
64
+ export declare function readSelectionFileSnapshot(path: string, maximumBytes?: number): {
65
+ bytes: Uint8Array;
66
+ sha256: string;
67
+ mtimeMs: number;
68
+ dev: number;
69
+ ino: number;
70
+ } | null;
71
+ /** Remove a small private file only while it still holds exactly the inspected bytes and inode. */
72
+ export declare function removeSelectionFileIfUnchanged(path: string, expected: {
73
+ sha256: string;
74
+ dev: number;
75
+ ino: number;
76
+ }): boolean;
58
77
  export declare function readSelectionJson<T>(path: string, maximumBytes?: number): T | null;
59
78
  export declare function readCachedSelection(selection: ResolvedSkillSelection, options?: SelectionCacheOptions): Promise<SkillBundleEntry[] | null>;
60
79
  /** Verify an authenticated exact-version response without changing any cache or receipt. */
@@ -93,6 +112,24 @@ export declare function inspectSessionWriteLock(sessionId: string, options?: Sel
93
112
  hostBound: boolean;
94
113
  };
95
114
  export declare function recoverSessionWriteLock(sessionId: string, reviewDigest: string, options?: SelectionCacheOptions): string;
115
+ /**
116
+ * How long a receipt write waits for another live holder of a session write
117
+ * lock before it refuses with SESSION_WRITE_BUSY. Holders keep a lock only for
118
+ * one bounded receipt read-compare-write (or one archived replacement), so
119
+ * concurrent hooks of one session (a prompt and its subagents starting
120
+ * together) serialize instead of refusing. Waiting never replaces, recovers or
121
+ * ignores a lock: a held lock that outlives the wait still refuses.
122
+ */
123
+ export declare const SESSION_WRITE_LOCK_WAIT_MS = 2000;
124
+ /**
125
+ * A context that writes nothing never takes the session write lock, so a
126
+ * live writer cannot make it wait or refuse. It still refuses, exactly as a
127
+ * writer would, when the lock path holds something that is not a structurally
128
+ * valid writer lock (SESSION_WRITE_LOCKED: damaged, foreign or unsafe state
129
+ * that needs recovery review). A lock that changes while it is inspected (a
130
+ * writer publishing or releasing it) is inspected again a few times first.
131
+ */
132
+ export declare function assertSessionWriteLockReadable(sessionId: string, options?: SelectionCacheOptions): void;
96
133
  /**
97
134
  * Hold this session's receipt write lock for state that must change only with
98
135
  * its pin (the managed-hook migration notice). Another holder refuses with
@@ -100,9 +137,17 @@ export declare function recoverSessionWriteLock(sessionId: string, reviewDigest:
100
137
  */
101
138
  export declare function withSkillSessionWriteLock<T>(sessionId: string, options: SelectionCacheOptions, action: (assertOwned: () => void) => T): T;
102
139
  export declare function nextSkillSessionGeneration(generation: number): number;
103
- /** Receipt creation and updates are exact-snapshot CAS operations. Child creation also fences its parent. */
140
+ /**
141
+ * Receipt creation and updates are exact-snapshot CAS operations. Child
142
+ * creation also fences its parent. Under contention this blocks the calling
143
+ * thread for at most SESSION_WRITE_LOCK_WAIT_MS.
144
+ */
104
145
  export declare function writeSkillSession(receipt: SkillSessionReceipt, expected: SkillSessionWritePrecondition, options?: SelectionCacheOptions): void;
105
- /** Preserve both sides before the single atomic replacement; failures retain recovery evidence. */
146
+ /**
147
+ * Preserve both sides before the single atomic replacement; failures retain
148
+ * recovery evidence. Under contention this blocks the calling thread for at
149
+ * most SESSION_WRITE_LOCK_WAIT_MS.
150
+ */
106
151
  export declare function replaceSkillSession(expected: SkillSessionParentBinding, replacement: SkillSessionReceipt, plan: unknown, options?: SelectionCacheOptions, beforeCommit?: () => void): {
107
152
  archivePath: string;
108
153
  receiptPath: string;
@@ -0,0 +1,118 @@
1
+ import { SkillSelectionError, type SelectionCacheOptions } from "./selection-cache.js";
2
+ import { type SessionReconciliationOptions } from "./session-reconciliation.js";
3
+ /** The longest an expired-pin prompt waits for its background renewal. */
4
+ export declare const BACKGROUND_RENEWAL_WAIT_MS = 3000;
5
+ /** A lock older than this belongs to no live worker (the worker's own budget plus margin). */
6
+ export declare const BACKGROUND_RENEWAL_LOCK_TTL_MS: number;
7
+ /** A failed attempt for the same pin is reported again, not retried, within these windows. */
8
+ export declare const EXPIRED_RENEWAL_RETRY_MS = 30000;
9
+ export declare const AHEAD_RENEWAL_RETRY_MS: number;
10
+ export type BackgroundRenewalMode = "expired" | "ahead";
11
+ export type BackgroundRenewalStatus = "running" | "renewed" | "migrated" | "unchanged" | "failed";
12
+ export interface BackgroundRenewalState {
13
+ schemaVersion: 1;
14
+ kind: "session-pin-background-renewal";
15
+ sessionId: string;
16
+ profileId: string;
17
+ operationId: string;
18
+ /** verifiedAt of the exact pin this attempt renews. */
19
+ pinVerifiedAt: string;
20
+ mode: BackgroundRenewalMode;
21
+ status: BackgroundRenewalStatus;
22
+ startedAt: string;
23
+ finishedAt?: string;
24
+ workerPid?: number;
25
+ code?: string;
26
+ migrationTimeout?: true;
27
+ /** No worker could be started; such a record never delays the next attempt. */
28
+ notStarted?: true;
29
+ /**
30
+ * The exact request this attempt answers: the receipt bytes, mode,
31
+ * migration setting and prompt directory. A recorded failure is reused only
32
+ * for the same request; any change (a rewritten receipt, migration turned
33
+ * on, another directory) asks again at once.
34
+ */
35
+ requestDigest?: string;
36
+ }
37
+ export interface BackgroundRenewalRequest {
38
+ sessionId: string;
39
+ profileId: string;
40
+ pinVerifiedAt: string;
41
+ mode: BackgroundRenewalMode;
42
+ /** SHA-256 of the exact receipt bytes this request read. */
43
+ receiptSha256: string;
44
+ /** Expired pins only: the managed hook's automatic migration of a refused renewal. */
45
+ migrate?: boolean;
46
+ /** The prompt's directory, which migration requires (project locks keep the refusal). */
47
+ projectDir?: string;
48
+ }
49
+ export interface BackgroundRenewalOptions extends SelectionCacheOptions {
50
+ /**
51
+ * Tests replace the detached worker; production spawns `skills sessions
52
+ * renew-background`. `notStarted` is called if the worker fails to start
53
+ * after this returns (an asynchronous spawn error such as EAGAIN).
54
+ */
55
+ spawnWorker?: (args: string[], notStarted: () => void) => void;
56
+ }
57
+ /** The identity of one renewal request; equal digests ask the authority the same question. */
58
+ export declare function backgroundRenewalRequestDigest(request: BackgroundRenewalRequest): string;
59
+ /**
60
+ * The last recorded attempt for this session, or null when absent or not
61
+ * exact. Only validated fields are returned; anything else in the file is
62
+ * dropped, so no unvalidated value reaches a caller's output.
63
+ */
64
+ export declare function readBackgroundRenewalState(sessionId: string, options?: SelectionCacheOptions): BackgroundRenewalState | null;
65
+ export type BackgroundRenewalStart =
66
+ /** `operationId` names the attempt to wait for, when its lock is readable. */
67
+ {
68
+ status: "started" | "running";
69
+ operationId?: string;
70
+ }
71
+ /** `notStarted`: no worker could be started, so the caller may renew inline instead. */
72
+ | {
73
+ status: "failed";
74
+ state: BackgroundRenewalState;
75
+ notStarted?: true;
76
+ };
77
+ /**
78
+ * Start the session's background renewal unless one is running, or report a
79
+ * recent failure of the same pin without starting another.
80
+ */
81
+ export declare function startBackgroundRenewal(request: BackgroundRenewalRequest, options?: BackgroundRenewalOptions): BackgroundRenewalStart;
82
+ export type BackgroundRenewalWait = {
83
+ status: "running" | "changed";
84
+ } | {
85
+ status: Exclude<BackgroundRenewalStatus, "running">;
86
+ state: BackgroundRenewalState;
87
+ };
88
+ /**
89
+ * Wait up to waitMs for the attempt renewing this exact pin to finish, or for
90
+ * the session receipt to stop holding that pin. With an operation id, only
91
+ * that attempt's outcome counts.
92
+ */
93
+ export declare function awaitBackgroundRenewal(sessionId: string, pinVerifiedAt: string, waitMs: number, options?: SelectionCacheOptions, operationId?: string): Promise<BackgroundRenewalWait>;
94
+ export interface BackgroundRenewalRun {
95
+ status: BackgroundRenewalStatus | "superseded";
96
+ code?: string;
97
+ }
98
+ /**
99
+ * The worker: renew (or, for an expired pin with migration on, migrate) one
100
+ * session pin with the background budget, record the outcome, release the lock.
101
+ */
102
+ export declare function runBackgroundRenewal(sessionId: string, profileId: string, operationId: string, request: {
103
+ migrate?: boolean;
104
+ projectDir?: string;
105
+ }, options?: SessionReconciliationOptions): Promise<BackgroundRenewalRun>;
106
+ /**
107
+ * An expired pin whose background renewal is still running. It keeps the
108
+ * renewal timeout's code (fixed codes cross the hook boundary) and a fixed
109
+ * marker so the hook can say the renewal continues instead of restarting.
110
+ */
111
+ export declare class SessionRenewalInBackground extends SkillSelectionError {
112
+ readonly renewal: "background";
113
+ constructor();
114
+ }
115
+ /** The recorded failure of a background attempt, as the error the inline renewal would have raised. */
116
+ export declare function backgroundRenewalError(state: BackgroundRenewalState): SkillSelectionError;
117
+ /** The pin's age class, from its verifiedAt; null when it is neither due nor expired. */
118
+ export declare function renewalModeFor(verifiedAt: string, now: number): BackgroundRenewalMode | null;