@hasna/skills 0.10.70 → 0.10.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -6
- package/bin/index.js +6023 -5233
- package/bin/mcp.js +1972 -1794
- package/bin/migrate.js +2 -2
- package/bin/server.js +2 -2
- package/bin/worker.js +2 -2
- package/dist/cli/commands/context.d.ts +1 -0
- package/dist/index.js +4485 -3970
- package/dist/lib/agent-discovery-directories.d.ts +8 -3
- package/dist/lib/agent-discovery.d.ts +20 -4
- package/dist/lib/agent-integration.d.ts +39 -1
- package/dist/lib/claude-plugin-cache-marker.d.ts +10 -0
- package/dist/lib/codex-plugin-skill-controls.d.ts +9 -0
- package/dist/lib/codex-vendor-refresh.d.ts +54 -0
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/hook-diagnostics.d.ts +3 -1
- package/dist/lib/native-discovery-witness-change.d.ts +25 -0
- package/dist/lib/selection-cache.d.ts +22 -0
- package/dist/lib/selection-resolver.d.ts +3 -1
- package/dist/lib/session-reconciliation.d.ts +57 -1
- package/dist/runtime-prerequisites.js +188 -102
- package/dist/sdk/index.js +2348 -1837
- package/dist/storage.js +8 -4
- package/docs/plugin-admission.md +18 -3
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type NativeDiscoveryWitnessTolerance } from "./native-discovery-witness-change.js";
|
|
1
2
|
/** Recursive path/type membership only; source-file hashes bind reviewed bytes. */
|
|
2
3
|
export interface DiscoveryDirectory {
|
|
3
4
|
path: string;
|
|
@@ -10,8 +11,12 @@ export interface DiscoveryDirectoryProjection extends DiscoveryDirectory {
|
|
|
10
11
|
* reviewed inactive roots. It never replaces a historical full witness. */
|
|
11
12
|
export declare function captureDiscoveryDirectoryProjection(paths: string[], omittedRoots: readonly string[]): DiscoveryDirectoryProjection[];
|
|
12
13
|
/** Compare a planner-captured inventory after removing only exact inert graphs.
|
|
13
|
-
* The stored rows must reproduce their original digest before projection.
|
|
14
|
-
|
|
14
|
+
* The stored rows must reproduce their original digest before projection. A
|
|
15
|
+
* membership change is a NativeDiscoveryWitnessChange: thrown, unless the live
|
|
16
|
+
* Codex prompt hook's tolerance accepts it. Invalid inventories always throw. */
|
|
17
|
+
export declare function verifyDiscoveryDirectoryProjection(directories: DiscoveryDirectoryProjection[], omittedRoots: readonly string[], tolerance?: NativeDiscoveryWitnessTolerance): void;
|
|
15
18
|
/** Read-only capture; callers must review both membership and source bytes. */
|
|
16
19
|
export declare function captureDiscoveryDirectories(paths: string[], excludedRoots?: readonly string[]): DiscoveryDirectory[];
|
|
17
|
-
|
|
20
|
+
/** A membership change is a NativeDiscoveryWitnessChange: thrown, unless the
|
|
21
|
+
* live Codex prompt hook's tolerance accepts it. Invalid collections always throw. */
|
|
22
|
+
export declare function verifyDiscoveryDirectories(directories: DiscoveryDirectory[], excludedRoots?: readonly string[], tolerance?: NativeDiscoveryWitnessTolerance): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IntegrationAgent } from "./agent-adapters.js";
|
|
2
2
|
import { type DiscoveryDirectory } from "./agent-discovery-directories.js";
|
|
3
3
|
import { type CodexReviewedHookSource } from "./codex-hook-mcp-dependencies.js";
|
|
4
|
+
import { type NativeDiscoveryWitnessTolerance } from "./native-discovery-witness-change.js";
|
|
4
5
|
import { type ManagedPluginRegistrationWitness } from "./plugin-discovery.js";
|
|
5
6
|
import { type CodexHookDiscoveryRecovery } from "./codex-hook-discovery-recovery.js";
|
|
6
7
|
import { type CodexPluginSkillControl, type CodexPluginSourceInput } from "./codex-plugin-skill-controls.js";
|
|
@@ -11,7 +12,7 @@ export interface DiscoverySource {
|
|
|
11
12
|
reviewArtifact?: RetainedReviewArtifact;
|
|
12
13
|
path: string;
|
|
13
14
|
sha256: string | null;
|
|
14
|
-
hashMode?: "bytes" | "path-bytes" | "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";
|
|
15
|
+
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";
|
|
15
16
|
hookSources?: CodexReviewedHookSource[];
|
|
16
17
|
managedPlugins?: ManagedPluginRegistrationWitness[];
|
|
17
18
|
format?: "json" | "toml" | "yaml";
|
|
@@ -99,15 +100,19 @@ export declare function captureRetiredCodexDiscovery(binding: AgentDiscoveryBind
|
|
|
99
100
|
export declare function codexSemanticCacheSourceRole(source: DiscoverySource, cacheRoot: string, parents: readonly string[], hookParents?: readonly string[]): boolean;
|
|
100
101
|
export declare function codexSemanticCacheDirectoryRole(path: string, cacheRoot: string): boolean;
|
|
101
102
|
export declare function assertCodexSemanticCacheHookSafety(cacheRoot: string, parents: readonly string[], configPath: string, hookRootAlias?: CodexSemanticCacheDiscovery["hookRootAlias"]): void;
|
|
103
|
+
/** Source-byte and directory-membership drift are NativeDiscoveryWitnessChange
|
|
104
|
+
* errors. Only the live Codex prompt hook passes `witnessTolerance`; a change it
|
|
105
|
+
* accepts as a vendor refresh is collected and the remaining checks still run.
|
|
106
|
+
* Every other change and every other failure throws as before. */
|
|
102
107
|
export declare function verifyAgentDiscovery(binding: AgentDiscoveryBinding, codexRecovery?: CodexHookDiscoveryRecovery, reviewedRootAliases?: readonly {
|
|
103
108
|
agent: IntegrationAgent;
|
|
104
109
|
alias: string;
|
|
105
110
|
target: string;
|
|
106
|
-
}[]): void;
|
|
111
|
+
}[], witnessTolerance?: NativeDiscoveryWitnessTolerance): void;
|
|
107
112
|
export declare function rebindAgentDiscovery(binding: AgentDiscoveryBinding, changes: Map<string, string>): AgentDiscoveryBinding;
|
|
108
113
|
/** Explicit streaming integrity witness. Never read an executable as text or execute it. */
|
|
109
114
|
export declare function captureDiscoveryExecutableSources(paths: string[]): DiscoverySource[];
|
|
110
|
-
/** Capture
|
|
115
|
+
/** Capture exact source bytes, or typed Claude cache marker presence; never execute them. */
|
|
111
116
|
export declare function captureDiscoveryByteSources(paths: string[]): DiscoverySource[];
|
|
112
117
|
/** Capture executable/source path identity, including links and absent targets. */
|
|
113
118
|
export declare function captureDiscoveryPathSources(paths: string[]): DiscoverySource[];
|
|
@@ -120,13 +125,24 @@ export declare function assertProjectDiscovery(agent: IntegrationAgent, director
|
|
|
120
125
|
export declare function agentDiscoveryConfigPath(home: string, agent: IntegrationAgent): string;
|
|
121
126
|
/** Re-derive an automatic binding and compare it with the stored one. A
|
|
122
127
|
* mismatch is drift, except when the only difference is a runtime this process
|
|
123
|
-
* cannot resolve: then it is DISCOVERY_ROOT_UNRESOLVED, which stays blocking.
|
|
128
|
+
* cannot resolve: then it is DISCOVERY_ROOT_UNRESOLVED, which stays blocking.
|
|
129
|
+
* Only the live Codex prompt hook passes `witnessTolerance`: when roots, method
|
|
130
|
+
* and every source and directory path are unchanged, only witness digests differ
|
|
131
|
+
* and the tolerance accepts every one of them, they are collected instead of
|
|
132
|
+
* refusing. */
|
|
124
133
|
export declare function verifyAutomaticDiscoveryClosure(binding: AgentDiscoveryBinding, options: {
|
|
125
134
|
home: string;
|
|
126
135
|
canonical: (path: string) => string;
|
|
127
136
|
change: string;
|
|
128
137
|
executable?: DiscoveryExecutable;
|
|
138
|
+
witnessTolerance?: NativeDiscoveryWitnessTolerance;
|
|
129
139
|
}): void;
|
|
140
|
+
/** True only when the changed Codex config.toml differs from its reviewed
|
|
141
|
+
* witness by appended vendor disable rows and/or vendor plugin disables:
|
|
142
|
+
* removing those entries or restoring a formerly enabled vendor flag reproduces
|
|
143
|
+
* the stored digest. Witness modes
|
|
144
|
+
* without a replacement digest (V6, artifacts, managed registries) stay strict. */
|
|
145
|
+
export declare function codexVendorConfigChangeOnly(source: DiscoverySource, cache: string): boolean;
|
|
130
146
|
/** `executable` is the agent's runtime: omitted, it is resolved through PATH;
|
|
131
147
|
* a value (or null for none) is used as given, so verification never depends
|
|
132
148
|
* on the caller's PATH. */
|
|
@@ -2,6 +2,7 @@ import { type CodexCorpusWriteOptions } from "./codex-corpus-write.js";
|
|
|
2
2
|
import { type ReviewedCodexSkillDenial } from "./codex-plugin-skill-controls.js";
|
|
3
3
|
import { type CodexNativeHookEnvelope, type ProcessInspector, type NativePolicyHelperRunner } from "./codex-native-skill-policy.js";
|
|
4
4
|
import { type CodexNativeSkillCatalog } from "./codex-native-skill-catalog.js";
|
|
5
|
+
import type { NativeDiscoveryWitnessKind } from "./native-discovery-witness-change.js";
|
|
5
6
|
import { type DiscoveryExecutable, type AgentDiscoveryBinding, type ReviewedDiscoveryInputs } from "./agent-discovery.js";
|
|
6
7
|
import { type IntegrationAgent } from "./agent-adapters.js";
|
|
7
8
|
import type { CodexHookDiscoveryRecovery } from "./codex-hook-discovery-recovery.js";
|
|
@@ -279,15 +280,52 @@ interface ManagedBridgeOptions {
|
|
|
279
280
|
profileId?: string;
|
|
280
281
|
codexDiscoveryRecovery?: CodexHookDiscoveryRecovery;
|
|
281
282
|
codexNativePolicy?: CodexNativePolicyGuardInput;
|
|
283
|
+
/** Live Codex prompt hook only. Exempt vendor Codex plugin-cache copies that
|
|
284
|
+
* Codex configuration provably disables, and tolerate, with a warning, the
|
|
285
|
+
* discovery-witness changes a vendor plugin refresh provably makes. Enabled
|
|
286
|
+
* vendor copies and every other change still refuse. Install, trust, migrate
|
|
287
|
+
* and sync never pass it. */
|
|
288
|
+
codexPromptHook?: boolean;
|
|
289
|
+
}
|
|
290
|
+
/** Result of a guard call; both lists are empty unless the caller opted in.
|
|
291
|
+
* `codexInertVendorCopies` holds the canonical skill directories exempted as
|
|
292
|
+
* disabled vendor copies; `codexDiscoveryChanges` the witnesses tolerated as a
|
|
293
|
+
* vendor plugin refresh. */
|
|
294
|
+
export interface ManagedBridgeReport {
|
|
295
|
+
codexInertVendorCopies: string[];
|
|
296
|
+
codexDiscoveryChanges: Array<{
|
|
297
|
+
kind: NativeDiscoveryWitnessKind;
|
|
298
|
+
path: string | null;
|
|
299
|
+
}>;
|
|
282
300
|
}
|
|
283
301
|
/** Package-classified installed-plugin content below the Codex plugin cache that
|
|
284
302
|
* a verified restricted policy cannot load. Classification is the existing plugin
|
|
285
303
|
* manifest identity and real-path binding, never a path prefix; user, repo and
|
|
286
304
|
* project copies, unclassified cache files and any allowed host path refuse. */
|
|
287
305
|
export declare function isInertCodexPluginCacheCopy(entry: NativeSkillEntry, cache: string, allowedHostPaths: readonly string[], read: (path: string) => string): boolean;
|
|
306
|
+
/** A vendor plugin-cache copy that Codex configuration provably disables, so its
|
|
307
|
+
* payload never loads natively. Vendor means: classified installed-plugin content
|
|
308
|
+
* in an allowlisted OpenAI marketplace (exact names), with the native remote
|
|
309
|
+
* install receipt for remote marketplaces, no Skills-managed or Hasna marker,
|
|
310
|
+
* and a plugin whose every cached version passes the fail-closed refresh check
|
|
311
|
+
* (codexVendorPluginRefreshSafe: one Codex manifest with only known keys, no
|
|
312
|
+
* hooks, MCP, command, agent or extension declaration, no other manifest
|
|
313
|
+
* location, no link and no skill hidden from the inventory or nested in
|
|
314
|
+
* another). Classification reads only the Codex manifest, so without that check
|
|
315
|
+
* a version could load payloads no rule here covers. Disabled means: a `[[skills.config]]`
|
|
316
|
+
* rule names its exact SKILL.md path or its plugin-qualified name with
|
|
317
|
+
* `enabled = false` (the native ordered evaluation agrees), and no matching rule
|
|
318
|
+
* enables it. A whole-plugin `[plugins."<id>"] enabled = false` table is never
|
|
319
|
+
* enough: remote state can enable a locally disabled remote plugin, and a
|
|
320
|
+
* cache copy carries no proof that its source is configuration-controlled. */
|
|
321
|
+
export declare function isInertCodexVendorPluginCopy(entry: NativeSkillEntry, cache: string, rules: unknown, read: (path: string) => string): boolean;
|
|
322
|
+
/** Visible, non-blocking warning for the discovery-witness changes the live Codex
|
|
323
|
+
* prompt hook tolerated as a vendor plugin refresh, or undefined when there were
|
|
324
|
+
* none. One bounded line; paths only, never payloads or file contents. */
|
|
325
|
+
export declare function codexPromptDriftWarning(report: ManagedBridgeReport): string | undefined;
|
|
288
326
|
/** Run before any prompt context load. Missing ownership or reappearing native
|
|
289
327
|
* discovery files require repair; verified cache availability is not an override. */
|
|
290
|
-
export declare function assertManagedAgentBridge(agent: IntegrationAgent, options?: ManagedBridgeOptions):
|
|
328
|
+
export declare function assertManagedAgentBridge(agent: IntegrationAgent, options?: ManagedBridgeOptions): ManagedBridgeReport;
|
|
291
329
|
export declare function normalizeAgentHookPrompt(agent: IntegrationAgent, event: string, prompt: string): string;
|
|
292
330
|
export declare function hookContextOutput(event: string, result: {
|
|
293
331
|
context: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DiscoveryByteBudget } from "./agent-discovery-bytes.js";
|
|
2
|
+
export declare const CLAUDE_ORPHAN_MARKER_MODE: "claude-plugin-orphaned-at-v1";
|
|
3
|
+
/** True only for `<...>/.claude/plugins/cache/<marketplace>/<plugin>/<version>/.orphaned_at`
|
|
4
|
+
* with exactly one cache segment in the path. Every other path, including a
|
|
5
|
+
* marker at another depth, keeps its ordinary exact witness. */
|
|
6
|
+
export declare function isClaudePluginCacheOrphanMarker(path: string): boolean;
|
|
7
|
+
/** Validate on every capture and check; only presence enters the digest.
|
|
8
|
+
* Claude rewrites the timestamp (and may replace the inode) between checks,
|
|
9
|
+
* so neither is bound, but the file may not change during one read. */
|
|
10
|
+
export declare function hashClaudePluginCacheOrphanMarker(path: string, budget?: DiscoveryByteBudget, changes?: Map<string, string>): string | null;
|
|
@@ -32,6 +32,15 @@ export interface CodexPluginSourceInput {
|
|
|
32
32
|
sourceSha256: string;
|
|
33
33
|
}
|
|
34
34
|
type Read = (path: string) => string;
|
|
35
|
+
/** The remote plugin identifier from the exact native install receipt Codex
|
|
36
|
+
* writes beside a remote plugin's versions, or null when it is absent or invalid. */
|
|
37
|
+
export declare function codexRemoteInstallReceipt(parent: string, read: Read): string | null;
|
|
38
|
+
/** Whether a vendor refresh's `.app.json` connector declaration is valid under
|
|
39
|
+
* the same appControls validation rootControls applies (duplicate keys, the
|
|
40
|
+
* single `apps` object, app names, `id`/`category`/`required` only, types and
|
|
41
|
+
* caps), read as a regular, unlinked file of at most 1 MiB in strict UTF-8.
|
|
42
|
+
* Every failure is false. */
|
|
43
|
+
export declare function codexAppDeclarationValid(path: string): boolean;
|
|
35
44
|
export interface CodexPluginRootControls {
|
|
36
45
|
namespace: string;
|
|
37
46
|
manifestSha256: string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** OpenAI's own Codex plugin marketplaces, by their exact native names in Codex
|
|
2
|
+
* 0.162.1. Only these hold vendor plugin copies; every other marketplace,
|
|
3
|
+
* including Hasna's and look-alike names, is never treated as vendor content. */
|
|
4
|
+
export declare const CODEX_VENDOR_MARKETPLACES: readonly string[];
|
|
5
|
+
/** Vendor marketplaces whose plugins Codex installs remotely and records with a
|
|
6
|
+
* `.codex-remote-plugin-install.json` receipt beside their versions. */
|
|
7
|
+
export declare const CODEX_REMOTE_VENDOR_MARKETPLACES: readonly string[];
|
|
8
|
+
/** The allowlisted vendor marketplace a canonical path lies in, at or below
|
|
9
|
+
* `<cache>/<marketplace>`, or null for the cache itself, other marketplaces and
|
|
10
|
+
* any non-canonical path. */
|
|
11
|
+
export declare function codexVendorMarketplace(cache: string, path: string): string | null;
|
|
12
|
+
/** A plugin version root a vendor refresh can add without review: its only
|
|
13
|
+
* manifest is a regular `.codex-plugin/plugin.json` (alone in its directory,
|
|
14
|
+
* no duplicate keys) whose keys are all in VENDOR_MANIFEST_KEYS, with the
|
|
15
|
+
* default skills root and, when present, exactly the `./.app.json` connector
|
|
16
|
+
* declaration backed by an `.app.json` that passes the strict connector
|
|
17
|
+
* validation rootControls applies (codexAppDeclarationValid); there is no hooks, MCP, command
|
|
18
|
+
* or agent location and no root, Claude or Cursor manifest; every skill in it
|
|
19
|
+
* is visible to the native inventory. Errors and bounds refuse. */
|
|
20
|
+
export declare function codexVendorPluginRootRefreshSafe(root: string, budget?: {
|
|
21
|
+
remaining: number;
|
|
22
|
+
}): boolean;
|
|
23
|
+
/** True only when every cached version of one vendor plugin
|
|
24
|
+
* (`<cache>/<marketplace>/<plugin>`) passes codexVendorPluginRootRefreshSafe,
|
|
25
|
+
* the plugin directory carries no manifest, hooks, MCP, command or agent
|
|
26
|
+
* location of its own, and no version is a link. Errors and bounds refuse. */
|
|
27
|
+
export declare function codexVendorPluginRefreshSafe(parent: string, budget?: {
|
|
28
|
+
remaining: number;
|
|
29
|
+
}): boolean;
|
|
30
|
+
/** True only when every plugin in a vendor marketplace subtree could have been
|
|
31
|
+
* added by a routine vendor refresh: each plugin passes
|
|
32
|
+
* codexVendorPluginRefreshSafe and the marketplace holds no link, special file
|
|
33
|
+
* or hidden directory. Plugin hooks and other skill roots need their own
|
|
34
|
+
* review, so a membership change that adds them is never a routine vendor
|
|
35
|
+
* refresh. The whole marketplace is checked, not only new versions, so anything
|
|
36
|
+
* unexpected anywhere refuses. Bounded; links are never followed; errors refuse. */
|
|
37
|
+
export declare function codexVendorSubtreeHookFree(cache: string, path: string): boolean;
|
|
38
|
+
/** Candidate earlier versions of a Codex config.toml that differ from `text`
|
|
39
|
+
* only by appended vendor disable rows and/or vendor plugin disables,
|
|
40
|
+
* the entries an operator or Codex adds when a vendor plugin refreshes. Each
|
|
41
|
+
* candidate is checked to parse to exactly `text` without those entries, so a
|
|
42
|
+
* candidate whose digest equals the reviewed witness proves the change is
|
|
43
|
+
* vendor-only. Anything the text model cannot remove exactly yields no
|
|
44
|
+
* candidate, which keeps the witness strict.
|
|
45
|
+
*
|
|
46
|
+
* Bounds: at most 8 vendor plugin tables, 16 trailing rows and 128 candidates,
|
|
47
|
+
* tried smallest edit first (fewest changed tables, then fewest removed rows).
|
|
48
|
+
* `rowsAffectDigest: false` (a typed V2-V5 witness, which ignores disable-only
|
|
49
|
+
* rows) skips row removal; with it, every change to at most two of eight
|
|
50
|
+
* tables fits the 128 candidates. Otherwise appended rows alone always fit.
|
|
51
|
+
* A bound only ever omits candidates, so it can only cause a refusal. */
|
|
52
|
+
export declare function codexVendorConfigCandidates(text: string, cache: string, options?: {
|
|
53
|
+
rowsAffectDigest?: boolean;
|
|
54
|
+
}): string[];
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface SkillsConfig {
|
|
|
39
39
|
defaultScope?: "global" | "project";
|
|
40
40
|
format?: "compact" | "json" | "csv";
|
|
41
41
|
extensionsDir?: string;
|
|
42
|
+
/** Managed hooks, global scope only: "auto" (default) migrates a refused expired session pin; "off" keeps the refusal. */
|
|
43
|
+
sessionPinMigration?: "auto" | "off";
|
|
42
44
|
}
|
|
43
45
|
export type ConfigScope = "global" | "project";
|
|
44
46
|
/**
|
|
@@ -97,6 +99,24 @@ export declare function loadConfigReadOnly(): SkillsConfig;
|
|
|
97
99
|
* Get the config file path for a given scope
|
|
98
100
|
*/
|
|
99
101
|
export declare function getConfigPath(scope: ConfigScope): string;
|
|
102
|
+
/**
|
|
103
|
+
* Managed hooks migrate a session pin whose renewal was refused only because
|
|
104
|
+
* its profile moved on, unless the station turns that off with
|
|
105
|
+
* `skills config set sessionPinMigration off --global`. Only the global
|
|
106
|
+
* configuration is read: the prompt hook never reads a project-controlled file
|
|
107
|
+
* for this.
|
|
108
|
+
*
|
|
109
|
+
* This reads the file itself instead of readConfigFile, which returns {} for
|
|
110
|
+
* an unreadable or malformed file. An opt-out must survive a damaged file, so
|
|
111
|
+
* "auto" needs one of two confirmed states. Either lstat finds no entry at the
|
|
112
|
+
* path and its directory exists, or the entry is a regular file (not a
|
|
113
|
+
* symlink), opened without following links, holding a JSON object without the
|
|
114
|
+
* key or with exactly "auto". Everything else keeps the refusal: a dangling or
|
|
115
|
+
* other symlink, a directory or other non-regular entry, a missing or dangling
|
|
116
|
+
* data directory, any read error, a parse error (including a truncated write),
|
|
117
|
+
* a non-object document, a retired key, or a value other than "auto" or "off".
|
|
118
|
+
*/
|
|
119
|
+
export declare function sessionPinMigrationEnabled(): boolean;
|
|
100
120
|
/**
|
|
101
121
|
* Load merged config: project-local overrides global
|
|
102
122
|
*/
|
|
@@ -3,7 +3,9 @@ export declare const HOOK_REFUSAL_REASON_CODES: readonly string[];
|
|
|
3
3
|
export declare class HookDiagnosticError extends Error {
|
|
4
4
|
readonly code: string;
|
|
5
5
|
readonly stage: "context" | "sync";
|
|
6
|
-
|
|
6
|
+
/** Fixed marker: a refused renewal whose automatic pin migration ran out of budget. */
|
|
7
|
+
readonly migration?: "timeout";
|
|
8
|
+
constructor(code: string, stage: "context" | "sync", migration?: "timeout");
|
|
7
9
|
}
|
|
8
10
|
export declare function hookChildError(stdout: string, stage: "context" | "sync"): HookDiagnosticError;
|
|
9
11
|
export declare function hookFailureReason(error: unknown, profileId?: string): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DiscoverySource } from "./agent-discovery.js";
|
|
2
|
+
/** What changed under a reviewed native discovery witness: a bound source
|
|
3
|
+
* file's projected bytes, or a bound directory's membership. */
|
|
4
|
+
export type NativeDiscoveryWitnessKind = "input" | "membership";
|
|
5
|
+
/** A reviewed discovery witness no longer matches the current file system. It is
|
|
6
|
+
* drift of something the review bound, never an invalid, missing or unresolvable
|
|
7
|
+
* binding, which keep their own plain errors. The message is unchanged from the
|
|
8
|
+
* plain error each site threw before, so every caller that does not tolerate
|
|
9
|
+
* these sees exactly the same refusal. */
|
|
10
|
+
export declare class NativeDiscoveryWitnessChange extends Error {
|
|
11
|
+
readonly kind: NativeDiscoveryWitnessKind;
|
|
12
|
+
/** Canonical path the witness bound, or null when the site names none. */
|
|
13
|
+
readonly path: string | null;
|
|
14
|
+
constructor(kind: NativeDiscoveryWitnessKind, path: string | null, message: string);
|
|
15
|
+
}
|
|
16
|
+
/** Live Codex prompt hook only. Decides, per changed witness, whether the change
|
|
17
|
+
* is provably one a Codex vendor plugin refresh makes; accepted changes are
|
|
18
|
+
* collected instead of refusing. Every other change still throws. */
|
|
19
|
+
export interface NativeDiscoveryWitnessTolerance {
|
|
20
|
+
acceptsInput(source: DiscoverySource): boolean;
|
|
21
|
+
acceptsMembership(path: string): boolean;
|
|
22
|
+
readonly changes: NativeDiscoveryWitnessChange[];
|
|
23
|
+
}
|
|
24
|
+
/** Throw the change unless an opted-in tolerance accepted it. */
|
|
25
|
+
export declare function witnessChanged(change: NativeDiscoveryWitnessChange, tolerance: NativeDiscoveryWitnessTolerance | undefined, accepted: boolean): void;
|
|
@@ -18,6 +18,11 @@ export interface SkillSessionParentBinding {
|
|
|
18
18
|
generation: number;
|
|
19
19
|
receiptSha256: string;
|
|
20
20
|
}
|
|
21
|
+
/** The project lock a session's pin was inherited from: its absolute path and the SHA-256 of its exact bytes. */
|
|
22
|
+
export interface SkillSessionProjectLock {
|
|
23
|
+
path: string;
|
|
24
|
+
sha256: string;
|
|
25
|
+
}
|
|
21
26
|
export interface SkillSessionReceipt extends CachedSelectionProfile {
|
|
22
27
|
sessionId: string;
|
|
23
28
|
loaded: string[];
|
|
@@ -25,6 +30,12 @@ export interface SkillSessionReceipt extends CachedSelectionProfile {
|
|
|
25
30
|
generation?: number;
|
|
26
31
|
/** Private local metadata binding a child receipt to the exact parent snapshot it inherited. */
|
|
27
32
|
parent?: SkillSessionParentBinding;
|
|
33
|
+
/**
|
|
34
|
+
* Set when the session was created from a project lock (and inherited by its
|
|
35
|
+
* child sessions). Such a pin follows that intentional lock, so the managed
|
|
36
|
+
* hook never migrates it automatically, whatever the prompt's directory.
|
|
37
|
+
*/
|
|
38
|
+
projectLock?: SkillSessionProjectLock;
|
|
28
39
|
}
|
|
29
40
|
export interface SkillSessionSnapshot extends SkillSessionParentBinding {
|
|
30
41
|
path: string;
|
|
@@ -60,6 +71,11 @@ export declare function assertFreshCachedProfile(receipt: CachedSelectionProfile
|
|
|
60
71
|
}): void;
|
|
61
72
|
export declare function projectSelectionLockPath(projectDir: string): string;
|
|
62
73
|
export declare function readProjectSelection(projectDir: string): CachedSelectionProfile | null;
|
|
74
|
+
/** The project lock with the provenance a session created from it records. */
|
|
75
|
+
export declare function readProjectSelectionSnapshot(projectDir: string): {
|
|
76
|
+
receipt: CachedSelectionProfile;
|
|
77
|
+
lock: SkillSessionProjectLock;
|
|
78
|
+
} | null;
|
|
63
79
|
export declare function sessionReceiptPath(sessionId: string, options?: SelectionCacheOptions): string;
|
|
64
80
|
export declare function readSkillSession(sessionId: string, options?: SelectionCacheOptions): SkillSessionReceipt | null;
|
|
65
81
|
export declare function readSkillSessionSnapshotIfExists(sessionId: string, options?: SelectionCacheOptions): SkillSessionSnapshot | null;
|
|
@@ -77,6 +93,12 @@ export declare function inspectSessionWriteLock(sessionId: string, options?: Sel
|
|
|
77
93
|
hostBound: boolean;
|
|
78
94
|
};
|
|
79
95
|
export declare function recoverSessionWriteLock(sessionId: string, reviewDigest: string, options?: SelectionCacheOptions): string;
|
|
96
|
+
/**
|
|
97
|
+
* Hold this session's receipt write lock for state that must change only with
|
|
98
|
+
* its pin (the managed-hook migration notice). Another holder refuses with
|
|
99
|
+
* SESSION_WRITE_BUSY; nothing waits.
|
|
100
|
+
*/
|
|
101
|
+
export declare function withSkillSessionWriteLock<T>(sessionId: string, options: SelectionCacheOptions, action: (assertOwned: () => void) => T): T;
|
|
80
102
|
export declare function nextSkillSessionGeneration(generation: number): number;
|
|
81
103
|
/** Receipt creation and updates are exact-snapshot CAS operations. Child creation also fences its parent. */
|
|
82
104
|
export declare function writeSkillSession(receipt: SkillSessionReceipt, expected: SkillSessionWritePrecondition, options?: SelectionCacheOptions): void;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { type ProfileClient } from "./profile-client.js";
|
|
3
3
|
import type { ResolvedSkillProfile, ResolvedSkillSelection } from "../types/skill-selection.js";
|
|
4
4
|
import type { SkillBundleEntry } from "./skill-bundle.js";
|
|
5
|
-
import { type CachedSelectionProfile, type SelectionCacheOptions, type SkillSessionParentBinding, type SkillSessionReceipt } from "./selection-cache.js";
|
|
5
|
+
import { type CachedSelectionProfile, type SelectionCacheOptions, type SkillSessionParentBinding, type SkillSessionProjectLock, type SkillSessionReceipt } from "./selection-cache.js";
|
|
6
6
|
export interface SelectionResolverOptions extends SelectionCacheOptions {
|
|
7
7
|
client?: ProfileClient;
|
|
8
8
|
cached?: boolean;
|
|
@@ -36,6 +36,8 @@ export interface ResolvedSelectionContext {
|
|
|
36
36
|
parentSnapshot?: SkillSessionParentBinding;
|
|
37
37
|
inheritedLoaded?: string[];
|
|
38
38
|
client?: ProfileClient;
|
|
39
|
+
/** Set when a new session's pin comes from the project lock; the session receipt records it. */
|
|
40
|
+
projectLock?: SkillSessionProjectLock;
|
|
39
41
|
}
|
|
40
42
|
export declare function resolveSelectionContext(profileId: string, options?: SelectionResolverOptions): Promise<ResolvedSelectionContext>;
|
|
41
43
|
export declare function readSelectedEntries(selection: ResolvedSkillSelection, context: ResolvedSelectionContext, options?: SelectionResolverOptions): Promise<SkillBundleEntry[]>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ProfileClient } from "./profile-client.js";
|
|
2
|
-
import { type SelectionCacheOptions } from "./selection-cache.js";
|
|
2
|
+
import { SkillSelectionError, type SelectionCacheOptions } from "./selection-cache.js";
|
|
3
3
|
export interface SessionReconciliationInput {
|
|
4
4
|
sessionId: string;
|
|
5
5
|
fromProfile: string;
|
|
@@ -58,8 +58,21 @@ export declare const SESSION_RENEWAL_TIMEOUT_MS = 4000;
|
|
|
58
58
|
* It binds the exact receipt bytes, profile, authority, workspace and the
|
|
59
59
|
* locally synced profile revision, and lapses after a fixed window that the
|
|
60
60
|
* file cannot extend. Anything unexpected is ignored and the authority is asked.
|
|
61
|
+
*
|
|
62
|
+
* Two kinds exist. A renewal refusal says only that this pin cannot be renewed
|
|
63
|
+
* as it is, so it is repeated only while migration is off. A refusal of the
|
|
64
|
+
* managed hook's migration itself (a project lock, a rotated epoch, a planner
|
|
65
|
+
* refusal or anything else that is not transient) also implies the renewal
|
|
66
|
+
* refusal, so it is repeated in either mode. Timeouts, an unavailable
|
|
67
|
+
* authority, write-lock contention and other transient failures are never
|
|
68
|
+
* remembered.
|
|
61
69
|
*/
|
|
62
70
|
export declare const SESSION_RENEWAL_REFUSAL_TTL_MS: number;
|
|
71
|
+
/**
|
|
72
|
+
* Return the pending migration notice for this session once its pin holds the
|
|
73
|
+
* migrated profile, then mark it delivered. Anything unexpected returns null.
|
|
74
|
+
*/
|
|
75
|
+
export declare function takeSessionPinMigrationNotice(sessionId: string, options?: SelectionCacheOptions): string | null;
|
|
63
76
|
/**
|
|
64
77
|
* Renew authorization for the complete immutable pin; never manufacture a
|
|
65
78
|
* profile combining historical loaded versions with current unloaded versions.
|
|
@@ -67,6 +80,49 @@ export declare const SESSION_RENEWAL_REFUSAL_TTL_MS: number;
|
|
|
67
80
|
* versions additionally require the authenticated exact-version bundle read.
|
|
68
81
|
*/
|
|
69
82
|
export declare function reconcileSkillSessionIfSafe(sessionId: string, profileId: string, options?: SessionReconciliationOptions): Promise<boolean>;
|
|
83
|
+
/** Metadata only: what an automatic migration retired, never payloads. */
|
|
84
|
+
export interface SessionPinMigration {
|
|
85
|
+
profileId: string;
|
|
86
|
+
fromRevision: string;
|
|
87
|
+
toRevision: string;
|
|
88
|
+
retainedLoadedCount: number;
|
|
89
|
+
retiredLoadedCount: number;
|
|
90
|
+
retired: SessionSelectionDelta[];
|
|
91
|
+
archivePath: string;
|
|
92
|
+
receiptPath: string;
|
|
93
|
+
}
|
|
94
|
+
export type SessionPinRenewalOutcome = {
|
|
95
|
+
outcome: "unchanged";
|
|
96
|
+
} | {
|
|
97
|
+
outcome: "renewed";
|
|
98
|
+
} | {
|
|
99
|
+
outcome: "migrated";
|
|
100
|
+
migration: SessionPinMigration;
|
|
101
|
+
};
|
|
102
|
+
export interface SessionPinRenewalOptions extends SessionReconciliationOptions {
|
|
103
|
+
/**
|
|
104
|
+
* Managed hooks only: when renewal is refused because the same profile moved
|
|
105
|
+
* on, apply the explicit reconciliation plan to the authenticated current
|
|
106
|
+
* revision instead. Every other failure keeps its existing refusal.
|
|
107
|
+
*/
|
|
108
|
+
migrate?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Required for `migrate`: the prompt's directory (the hook input's cwd).
|
|
111
|
+
* Without it, or while it or an ancestor holds a project lock, migration
|
|
112
|
+
* does not run.
|
|
113
|
+
*/
|
|
114
|
+
projectDir?: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A refused renewal whose automatic migration could not finish within the
|
|
118
|
+
* renewal deadline. It keeps the refusal code; the fixed `migration` marker
|
|
119
|
+
* lets the hook say why without carrying free text across its boundary.
|
|
120
|
+
*/
|
|
121
|
+
export declare class SessionPinMigrationTimeout extends SkillSelectionError {
|
|
122
|
+
readonly migration: "timeout";
|
|
123
|
+
constructor();
|
|
124
|
+
}
|
|
125
|
+
export declare function renewSkillSessionPin(sessionId: string, profileId: string, options?: SessionPinRenewalOptions): Promise<SessionPinRenewalOutcome>;
|
|
70
126
|
/** Explicit review for changed loaded selections or intentional profile migration. */
|
|
71
127
|
export declare function reconcileSkillSession(input: SessionReconciliationInput, options?: SessionReconciliationOptions): Promise<{
|
|
72
128
|
applied: false;
|