@gajae-code/coding-agent 0.15.5 → 0.15.6

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 (155) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/dist/types/async/job-manager.d.ts +83 -0
  3. package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
  4. package/dist/types/cli/model-presets-cli.d.ts +9 -0
  5. package/dist/types/cli/update-cli.d.ts +2 -0
  6. package/dist/types/commands/auth-gateway.d.ts +3 -0
  7. package/dist/types/commands/gc.d.ts +13 -0
  8. package/dist/types/commands/model-presets.d.ts +21 -0
  9. package/dist/types/config/autorouting-tier-map.d.ts +96 -0
  10. package/dist/types/config/file-lock.d.ts +49 -2
  11. package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
  12. package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
  13. package/dist/types/config/keybindings.d.ts +3 -2
  14. package/dist/types/config/model-preset-registry.d.ts +368 -0
  15. package/dist/types/config/model-profile-activation.d.ts +39 -0
  16. package/dist/types/config/model-profiles.d.ts +5 -3
  17. package/dist/types/config/model-registry.d.ts +11 -5
  18. package/dist/types/config/settings-schema.d.ts +9 -0
  19. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
  20. package/dist/types/coordinator-mcp/projection-scan.d.ts +46 -0
  21. package/dist/types/extensibility/extensions/types.d.ts +2 -5
  22. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
  23. package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
  24. package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
  25. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +6 -0
  26. package/dist/types/index.d.ts +1 -0
  27. package/dist/types/main.d.ts +3 -1
  28. package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
  29. package/dist/types/modes/jobs-observer.d.ts +36 -0
  30. package/dist/types/sdk/broker/broker.d.ts +3 -0
  31. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
  32. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
  33. package/dist/types/sdk/client/discovery.d.ts +1 -0
  34. package/dist/types/sdk/host/model-pin.d.ts +7 -2
  35. package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
  36. package/dist/types/sdk/host/session-runtime.d.ts +15 -1
  37. package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
  38. package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
  39. package/dist/types/sdk/router/session-router.d.ts +9 -0
  40. package/dist/types/session/account-inventory.d.ts +1 -0
  41. package/dist/types/session/agent-session.d.ts +19 -7
  42. package/dist/types/session/fold-coordinator.d.ts +201 -0
  43. package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
  44. package/dist/types/session/sdk-run-capability.d.ts +2 -0
  45. package/dist/types/telemetry/control.d.ts +11 -0
  46. package/dist/types/telemetry/events.d.ts +20 -0
  47. package/dist/types/telemetry/index.d.ts +3 -0
  48. package/dist/types/telemetry/transport.d.ts +22 -0
  49. package/dist/types/tools/bash-interactive.d.ts +23 -0
  50. package/dist/types/tools/index.d.ts +6 -5
  51. package/dist/types/web/search/providers/base.d.ts +9 -4
  52. package/dist/types/web/search/providers/xai.d.ts +2 -1
  53. package/dist/types/web/search/types.d.ts +14 -0
  54. package/package.json +10 -7
  55. package/scripts/generate-hotkeys-docs.ts +4 -0
  56. package/scripts/generate-sdk-operation-inventory.ts +3 -3
  57. package/src/async/job-manager.ts +671 -39
  58. package/src/cli/auth-broker-cli.ts +40 -12
  59. package/src/cli/auth-gateway-cli.ts +273 -97
  60. package/src/cli/config-cli.ts +2 -2
  61. package/src/cli/fast-help.ts +3 -0
  62. package/src/cli/model-presets-cli.ts +100 -0
  63. package/src/cli/update-cli.ts +40 -5
  64. package/src/cli.ts +6 -2
  65. package/src/commands/auth-gateway.ts +12 -8
  66. package/src/commands/gc.ts +19 -0
  67. package/src/commands/model-presets.ts +24 -0
  68. package/src/config/autorouting-tier-map.ts +32 -0
  69. package/src/config/file-lock-gc.ts +9 -4
  70. package/src/config/file-lock.ts +775 -59
  71. package/src/config/internal/model-preset-registry-test-state.ts +28 -0
  72. package/src/config/internal/model-preset-registry-test-support.ts +11 -0
  73. package/src/config/keybindings.ts +6 -2
  74. package/src/config/model-preset-registry.ts +2799 -0
  75. package/src/config/model-profile-activation.ts +115 -28
  76. package/src/config/model-profile-contract.ts +4 -1
  77. package/src/config/model-profiles.ts +51 -9
  78. package/src/config/model-registry.ts +1021 -122
  79. package/src/config/settings-schema.ts +11 -0
  80. package/src/config/settings.ts +6 -1
  81. package/src/coordinator-mcp/model-preset.ts +32 -17
  82. package/src/coordinator-mcp/projection-scan.ts +571 -0
  83. package/src/coordinator-mcp/server.ts +19 -24
  84. package/src/coordinator-mcp/session-reaper.ts +9 -3
  85. package/src/extensibility/extensions/loader.ts +0 -1
  86. package/src/extensibility/extensions/types.ts +2 -3
  87. package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
  88. package/src/gjc-runtime/empty-delete-gc.ts +301 -0
  89. package/src/gjc-runtime/gc-render.ts +29 -0
  90. package/src/gjc-runtime/gc-runtime.ts +174 -11
  91. package/src/gjc-runtime/session-state-lock.ts +1242 -141
  92. package/src/gjc-runtime/session-state-sidecar.ts +76 -3
  93. package/src/index.ts +1 -0
  94. package/src/internal-urls/docs-index.generated.ts +9 -8
  95. package/src/lsp/config.ts +4 -3
  96. package/src/lsp/path-trust.ts +12 -6
  97. package/src/main.ts +9 -2
  98. package/src/modes/acp/acp-agent.ts +11 -5
  99. package/src/modes/components/jobs-overlay-model.ts +99 -16
  100. package/src/modes/components/model-selector.ts +158 -18
  101. package/src/modes/components/status-line/segments.ts +6 -1
  102. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  103. package/src/modes/controllers/input-controller.ts +23 -6
  104. package/src/modes/interactive-mode.ts +14 -1
  105. package/src/modes/jobs-observer.ts +149 -16
  106. package/src/modes/runtime-init.ts +1 -1
  107. package/src/modes/utils/injected-user-submission.ts +15 -1
  108. package/src/prompts/tools/bash.md +2 -2
  109. package/src/prompts/tools/fold-receipt.md +3 -0
  110. package/src/prompts/tools/job.md +1 -1
  111. package/src/sdk/acp/adapter.ts +17 -2
  112. package/src/sdk/broker/broker.ts +48 -21
  113. package/src/sdk/broker/lifecycle.ts +32 -6
  114. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  115. package/src/sdk/bus/chat-daemon-runtime.ts +11 -1
  116. package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
  117. package/src/sdk/bus/prompt-reconciliation.ts +28 -4
  118. package/src/sdk/bus/slack-conversation.ts +5 -1
  119. package/src/sdk/cli/session-cli.ts +12 -1
  120. package/src/sdk/client/discovery.ts +3 -1
  121. package/src/sdk/host/control/dispatch.ts +3 -0
  122. package/src/sdk/host/model-pin.ts +185 -25
  123. package/src/sdk/host/sdk-run-capability.ts +1 -0
  124. package/src/sdk/host/session-runtime.test.ts +371 -37
  125. package/src/sdk/host/session-runtime.ts +364 -52
  126. package/src/sdk/prompt-deadline-manager.ts +16 -1
  127. package/src/sdk/protocol/adapter-validation.ts +27 -0
  128. package/src/sdk/protocol/operation-inventory.generated.json +2 -2
  129. package/src/sdk/protocol/operation-registry.ts +4 -1
  130. package/src/sdk/router/session-router.ts +228 -96
  131. package/src/sdk/session.ts +83 -12
  132. package/src/session/account-inventory.ts +27 -11
  133. package/src/session/agent-session.ts +678 -103
  134. package/src/session/fold-coordinator.ts +433 -0
  135. package/src/session/sdk-run-capability-internal.ts +15 -0
  136. package/src/session/sdk-run-capability.ts +2 -0
  137. package/src/session/session-manager.ts +5 -4
  138. package/src/setup/provider-presets.json +1 -1
  139. package/src/slash-commands/helpers/credential-switch.ts +6 -1
  140. package/src/task/executor.ts +19 -3
  141. package/src/telemetry/control.ts +26 -0
  142. package/src/telemetry/events.ts +142 -0
  143. package/src/telemetry/index.ts +3 -0
  144. package/src/telemetry/transport.ts +87 -0
  145. package/src/tools/bash-interactive.ts +182 -85
  146. package/src/tools/bash.ts +772 -67
  147. package/src/tools/index.ts +7 -5
  148. package/src/tools/tool-catalog.generated.ts +1 -1
  149. package/src/web/search/provider.ts +120 -11
  150. package/src/web/search/providers/anthropic.ts +89 -17
  151. package/src/web/search/providers/base.ts +9 -4
  152. package/src/web/search/providers/gemini.ts +87 -15
  153. package/src/web/search/providers/openai-compatible.ts +23 -8
  154. package/src/web/search/providers/xai.ts +98 -12
  155. package/src/web/search/types.ts +16 -0
@@ -0,0 +1,2799 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fsSync from "node:fs";
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import type { Api, Model } from "@gajae-code/ai/core";
6
+ import type { NativeExactFileIdentity, NativeExactUnlinkResult } from "@gajae-code/natives";
7
+ import { getAgentDir, isEnoent } from "@gajae-code/utils";
8
+ import * as z from "zod/v4";
9
+ import { splitSelectorThinkingSuffix } from "../thinking";
10
+ import { withFileLock } from "./file-lock";
11
+ import {
12
+ getModelPresetRegistryTestTrustedKeys,
13
+ modelPresetRegistryTestUrlsAllowed,
14
+ } from "./internal/model-preset-registry-test-state";
15
+ import { type ModelProfileDefinition, type ModelProfileRole, mergeModelProfiles } from "./model-profiles";
16
+ import type { ModelsConfig } from "./models-config-schema";
17
+
18
+ type NativeExactReplacePath = (
19
+ sourcePath: string,
20
+ destinationPath: string,
21
+ expectedSource: NativeExactFileIdentity,
22
+ expectedDestination: NativeExactFileIdentity,
23
+ ) => NativeExactUnlinkResult;
24
+ let nativeExactReplacePath: NativeExactReplacePath | undefined;
25
+
26
+ function exactReplacePathNative(
27
+ sourcePath: string,
28
+ destinationPath: string,
29
+ expectedSource: NativeExactFileIdentity,
30
+ expectedDestination: NativeExactFileIdentity,
31
+ ): NativeExactUnlinkResult {
32
+ nativeExactReplacePath ??= (require("@gajae-code/natives") as { exactReplacePath: NativeExactReplacePath })
33
+ .exactReplacePath;
34
+ return nativeExactReplacePath(sourcePath, destinationPath, expectedSource, expectedDestination);
35
+ }
36
+
37
+ export const MODEL_PRESET_REGISTRY_CONTRACT_VERSION = "1.0.0";
38
+ export const DEFAULT_MODEL_PRESET_REGISTRY_URL =
39
+ "https://raw.githubusercontent.com/Yeachan-Heo/gajae-code-presets/dev/latest.json";
40
+ export const MODEL_PRESET_REGISTRY_REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;
41
+ export const MODEL_PRESET_REGISTRY_STARTUP_DELAY_MS = 30_000;
42
+ export const MODEL_PRESET_REGISTRY_MAX_MANIFEST_BYTES = 64 * 1024;
43
+ export const MODEL_PRESET_REGISTRY_MAX_SNAPSHOT_BYTES = 64 * 1024;
44
+ export const MODEL_PRESET_REGISTRY_MAX_PROFILES_BYTES = 256 * 1024;
45
+ export const MODEL_PRESET_REGISTRY_MAX_PRESETS_BYTES = 4 * 1024 * 1024;
46
+ const MODEL_PRESET_REGISTRY_MAX_STATE_BYTES = 32 * 1024 * 1024;
47
+ const MODEL_PRESET_REGISTRY_MAX_HISTORY = 4;
48
+ const MODEL_PRESET_REGISTRY_MAX_RETENTION_ANCESTRY = 64;
49
+ const MODEL_PRESET_REGISTRY_MAX_ERROR_BYTES = 1024;
50
+ const MODEL_PRESET_REGISTRY_FETCH_TIMEOUT_MS = 8_000;
51
+ const REVISION_PATTERN = /^[0-9]{8}$/;
52
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
53
+ const SEMVER_PATTERN = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
54
+ const PROFILE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
55
+ const SELECTOR_PATTERN = /^[^\s\p{Cc}\p{Cf}]+$/u;
56
+ const PRESET_IDENTIFIER_PATTERN = /^[^\s\p{Cc}\p{Cf}]+$/u;
57
+ const SAFE_TEXT_PATTERN = /^[^\p{Cc}\p{Cf}]+$/u;
58
+ const CONTEXT_PROMOTION_TARGET_PATTERN = /^[a-z0-9][a-z0-9._-]*\/[^\s\p{Cc}\p{Cf}]+$/u;
59
+ const ED25519_SIGNATURE_BASE64_PATTERN = /^[A-Za-z0-9+/]{86}==$/;
60
+
61
+ function isCanonicalEd25519SignatureBase64(value: string): boolean {
62
+ if (!ED25519_SIGNATURE_BASE64_PATTERN.test(value)) return false;
63
+ const decoded = Buffer.from(value, "base64");
64
+ return decoded.length === 64 && decoded.toString("base64") === value;
65
+ }
66
+
67
+ function decodeCanonicalEd25519Signature(value: string): Buffer {
68
+ if (!isCanonicalEd25519SignatureBase64(value)) throw new Error("Registry signature encoding is not canonical.");
69
+ return Buffer.from(value, "base64");
70
+ }
71
+ const DESCRIPTOR_PATH_PATTERN = /^revisions\/[0-9]{8}\/[a-z]+\.json$/;
72
+ const SOURCE_REPOSITORY = "https://github.com/Yeachan-Heo/gajae-code";
73
+ const REGISTRY_RAW_PATH_PREFIX = "/Yeachan-Heo/gajae-code-presets/";
74
+ const RESERVED_IDENTITY_PREFIXES = ["gjc-", "gajae-", "system-", "internal-", "__"];
75
+ const IDENTITY_HOMOGLYPHS = new Map(
76
+ Object.entries({
77
+ а: "a",
78
+ е: "e",
79
+ о: "o",
80
+ р: "p",
81
+ с: "c",
82
+ х: "x",
83
+ у: "y",
84
+ і: "i",
85
+ ј: "j",
86
+ к: "k",
87
+ м: "m",
88
+ т: "t",
89
+ в: "b",
90
+ н: "h",
91
+ Α: "a",
92
+ Β: "b",
93
+ Ε: "e",
94
+ Ζ: "z",
95
+ Η: "h",
96
+ Ι: "i",
97
+ Κ: "k",
98
+ Μ: "m",
99
+ Ν: "n",
100
+ Ο: "o",
101
+ Ρ: "p",
102
+ Τ: "t",
103
+ Υ: "y",
104
+ Χ: "x",
105
+ α: "a",
106
+ β: "b",
107
+ ε: "e",
108
+ ι: "i",
109
+ κ: "k",
110
+ ν: "v",
111
+ ο: "o",
112
+ ρ: "p",
113
+ τ: "t",
114
+ υ: "y",
115
+ χ: "x",
116
+ }),
117
+ );
118
+
119
+ export interface ModelPresetRegistryTrustedKey {
120
+ keyId: string;
121
+ publicKeyPem: string;
122
+ validFrom: string;
123
+ revokedAt?: string;
124
+ }
125
+
126
+ /** Compiled trust roots only. Runtime configuration cannot replace these keys. */
127
+ const MODEL_PRESET_REGISTRY_TRUSTED_KEYS: ReadonlyMap<string, ModelPresetRegistryTrustedKey> = new Map([
128
+ [
129
+ "registry-root-2026-01",
130
+ {
131
+ keyId: "registry-root-2026-01",
132
+ publicKeyPem:
133
+ "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAwDhA/c/hX++M+wcBddFEVSm5gB1tVSjKMZPtlMSlTSQ=\n-----END PUBLIC KEY-----\n",
134
+ validFrom: "2026-01-01T00:00:00.000Z",
135
+ },
136
+ ],
137
+ ]);
138
+
139
+ const SemverSchema = z.string().max(32).regex(SEMVER_PATTERN);
140
+ const RevisionSchema = z.string().regex(REVISION_PATTERN);
141
+ const Sha256Schema = z.string().regex(SHA256_PATTERN);
142
+ const UtcTimestampSchema = z
143
+ .string()
144
+ .datetime({ offset: false })
145
+ .refine(value => value.endsWith("Z"));
146
+ const DescriptorSchema = z
147
+ .object({
148
+ path: z.string().max(128).regex(DESCRIPTOR_PATH_PATTERN),
149
+ sha256: Sha256Schema,
150
+ bytes: z
151
+ .number()
152
+ .int()
153
+ .positive()
154
+ .max(16 * 1024 * 1024),
155
+ count: z.number().int().nonnegative().max(100_000),
156
+ })
157
+ .strict();
158
+ const ConsumerCompatibilitySchema = z.object({ minVersion: SemverSchema, maxVersion: SemverSchema }).strict();
159
+ const CompatibilitySchema = z.object({ consumerContract: ConsumerCompatibilitySchema }).strict();
160
+ const ProvenanceSchema = z
161
+ .object({
162
+ sourceRepository: z.literal(SOURCE_REPOSITORY),
163
+ sourceRevision: z.string().regex(/^[a-f0-9]{40}$/),
164
+ sourcePaths: z
165
+ .array(
166
+ z
167
+ .string()
168
+ .max(256)
169
+ .regex(/^[A-Za-z0-9._/-]+$/),
170
+ )
171
+ .min(1)
172
+ .max(8)
173
+ .refine(values => new Set(values).size === values.length, "Expected unique source paths."),
174
+ generatedBy: z
175
+ .string()
176
+ .max(128)
177
+ .regex(/^[A-Za-z0-9._/@-]+$/),
178
+ generatedAt: UtcTimestampSchema,
179
+ })
180
+ .strict();
181
+ const ContentsSchema = z.object({ presets: DescriptorSchema, profiles: DescriptorSchema }).strict();
182
+ const ManifestSignedSchema = z
183
+ .object({
184
+ registryRevision: z.number().int().positive().max(99_999_999),
185
+ revision: RevisionSchema,
186
+ publishedAt: UtcTimestampSchema,
187
+ compatibility: CompatibilitySchema,
188
+ snapshot: DescriptorSchema,
189
+ contents: ContentsSchema,
190
+ provenance: ProvenanceSchema,
191
+ })
192
+ .strict();
193
+ const SignatureSchema = z
194
+ .object({
195
+ algorithm: z.literal("Ed25519"),
196
+ keyId: z
197
+ .string()
198
+ .min(3)
199
+ .max(64)
200
+ .regex(/^[a-z0-9][a-z0-9._-]+$/),
201
+ value: z
202
+ .string()
203
+ .regex(ED25519_SIGNATURE_BASE64_PATTERN)
204
+ .refine(isCanonicalEd25519SignatureBase64, "Expected canonical Ed25519 Base64."),
205
+ })
206
+ .strict();
207
+
208
+ export const ModelPresetRegistryManifestSchema = z
209
+ .object({ schemaVersion: z.literal("1.0.0"), signed: ManifestSignedSchema, signature: SignatureSchema })
210
+ .strict();
211
+ export const ModelPresetRegistrySnapshotSchema = z
212
+ .object({
213
+ schemaVersion: z.literal("1.0.0"),
214
+ registryRevision: z.number().int().positive().max(99_999_999),
215
+ revision: RevisionSchema,
216
+ compatibility: CompatibilitySchema,
217
+ provenance: ProvenanceSchema,
218
+ contents: ContentsSchema,
219
+ })
220
+ .strict();
221
+
222
+ const ProfileSelectorSchema = z.union([
223
+ z.string().min(1).max(256).regex(SELECTOR_PATTERN),
224
+ z
225
+ .array(z.string().min(1).max(256).regex(SELECTOR_PATTERN))
226
+ .min(1)
227
+ .max(8)
228
+ .refine(values => new Set(values).size === values.length, "Expected unique selectors."),
229
+ ]);
230
+ const RoleBindingsSchema = z
231
+ .object({
232
+ default: ProfileSelectorSchema,
233
+ executor: ProfileSelectorSchema.optional(),
234
+ architect: ProfileSelectorSchema.optional(),
235
+ planner: ProfileSelectorSchema.optional(),
236
+ critic: ProfileSelectorSchema.optional(),
237
+ })
238
+ .strict();
239
+ const RegistryProfileSchema = z
240
+ .object({
241
+ id: z.string().min(1).max(128).regex(PROFILE_ID_PATTERN),
242
+ displayName: z.string().min(1).max(160).regex(SAFE_TEXT_PATTERN),
243
+ providerGroup: z.string().min(1).max(160).regex(SAFE_TEXT_PATTERN),
244
+ requiredProviders: z
245
+ .array(z.string().min(1).max(128).regex(PROFILE_ID_PATTERN))
246
+ .max(32)
247
+ .refine(values => new Set(values).size === values.length, "Expected unique providers."),
248
+ alternativeProviderGroups: z
249
+ .array(
250
+ z
251
+ .array(z.string().min(1).max(128).regex(PROFILE_ID_PATTERN))
252
+ .min(2)
253
+ .max(16)
254
+ .refine(values => new Set(values).size === values.length, "Expected unique providers."),
255
+ )
256
+ .max(16)
257
+ .optional(),
258
+ roleBindings: RoleBindingsSchema,
259
+ })
260
+ .strict();
261
+ export const ModelPresetRegistryProfilesSchema = z
262
+ .object({
263
+ schemaVersion: z.literal("1.0.0"),
264
+ revision: RevisionSchema,
265
+ dynamicProviders: z
266
+ .array(z.string().min(1).max(128).regex(PROFILE_ID_PATTERN))
267
+ .max(128)
268
+ .refine(values => new Set(values).size === values.length, "Expected unique dynamic providers."),
269
+ profiles: z.array(RegistryProfileSchema).max(4096),
270
+ })
271
+ .strict()
272
+ .superRefine((document, ctx) => {
273
+ const seen = new Set<string>();
274
+ for (const [index, profile] of document.profiles.entries()) {
275
+ if (seen.has(profile.id))
276
+ ctx.addIssue({ code: "custom", path: ["profiles", index, "id"], message: "Duplicate profile id." });
277
+ seen.add(profile.id);
278
+ }
279
+ });
280
+
281
+ const EffortSchema = z.enum(["minimal", "low", "medium", "high", "xhigh", "max"]);
282
+ const CostSchema = z
283
+ .object({
284
+ input: z.number().nonnegative().finite(),
285
+ output: z.number().nonnegative().finite(),
286
+ cacheRead: z.number().nonnegative().finite(),
287
+ cacheWrite: z.number().nonnegative().finite(),
288
+ })
289
+ .strict();
290
+ const CompatSchema = z
291
+ .object({
292
+ maxTokensField: z.literal("max_tokens").optional(),
293
+ reasoningContentField: z.literal("reasoning_content").optional(),
294
+ thinkingFormat: z.literal("zai").optional(),
295
+ reasoningEffortMap: z
296
+ .object({
297
+ minimal: EffortSchema.optional(),
298
+ low: EffortSchema.optional(),
299
+ medium: EffortSchema.optional(),
300
+ high: EffortSchema.optional(),
301
+ xhigh: EffortSchema.optional(),
302
+ max: EffortSchema.optional(),
303
+ })
304
+ .strict()
305
+ .refine(value => Object.keys(value).length > 0, "Expected at least one reasoning effort mapping.")
306
+ .optional(),
307
+ requiresAssistantContentForToolCalls: z.boolean().optional(),
308
+ requiresReasoningContentForToolCalls: z.boolean().optional(),
309
+ supportsDeveloperRole: z.boolean().optional(),
310
+ supportsMultipleSystemMessages: z.boolean().optional(),
311
+ supportsReasoningEffort: z.boolean().optional(),
312
+ supportsStore: z.boolean().optional(),
313
+ supportsToolChoice: z.boolean().optional(),
314
+ supportsUsageInStreaming: z.boolean().optional(),
315
+ })
316
+ .strict();
317
+ const ThinkingSchema = z
318
+ .object({
319
+ mode: z.enum(["anthropic-adaptive", "anthropic-budget-effort", "budget", "effort", "google-level"]),
320
+ minLevel: EffortSchema.optional(),
321
+ maxLevel: EffortSchema.optional(),
322
+ defaultLevel: EffortSchema.optional(),
323
+ levels: z
324
+ .array(EffortSchema)
325
+ .min(1)
326
+ .max(6)
327
+ .refine(values => new Set(values).size === values.length, "Expected unique thinking levels.")
328
+ .optional(),
329
+ })
330
+ .strict()
331
+ .superRefine((value, context) => {
332
+ const effortOrder = ["minimal", "low", "medium", "high", "xhigh", "max"] as const;
333
+ const rank = (level: (typeof effortOrder)[number]): number => effortOrder.indexOf(level);
334
+ if (value.minLevel === undefined || value.maxLevel === undefined) {
335
+ context.addIssue({ code: "custom", message: "Thinking metadata requires minLevel and maxLevel." });
336
+ }
337
+ if (value.minLevel !== undefined && value.maxLevel !== undefined && rank(value.minLevel) > rank(value.maxLevel)) {
338
+ context.addIssue({ code: "custom", message: "Thinking minLevel must not exceed maxLevel." });
339
+ }
340
+ if (value.levels !== undefined && value.minLevel !== undefined && !value.levels.includes(value.minLevel)) {
341
+ context.addIssue({ code: "custom", message: "Thinking levels must include minLevel." });
342
+ }
343
+ if (value.levels !== undefined && value.maxLevel !== undefined && !value.levels.includes(value.maxLevel)) {
344
+ context.addIssue({ code: "custom", message: "Thinking levels must include maxLevel." });
345
+ }
346
+ if (value.levels !== undefined && value.minLevel !== undefined && value.maxLevel !== undefined) {
347
+ for (const level of value.levels) {
348
+ if (rank(level) < rank(value.minLevel) || rank(level) > rank(value.maxLevel)) {
349
+ context.addIssue({ code: "custom", message: "Thinking levels must stay within minLevel and maxLevel." });
350
+ }
351
+ }
352
+ }
353
+ if (
354
+ value.defaultLevel !== undefined &&
355
+ (value.levels !== undefined
356
+ ? !value.levels.includes(value.defaultLevel)
357
+ : value.minLevel !== undefined &&
358
+ value.maxLevel !== undefined &&
359
+ (rank(value.defaultLevel) < rank(value.minLevel) || rank(value.defaultLevel) > rank(value.maxLevel)))
360
+ ) {
361
+ context.addIssue({ code: "custom", message: "Thinking defaultLevel must be within the supported levels." });
362
+ }
363
+ });
364
+ const LongContextPricingSchema = z.object({ threshold: z.number().int().positive(), cost: CostSchema }).strict();
365
+ const RegistryPresetSchema = z
366
+ .object({
367
+ id: z.string().min(1).max(192).regex(PRESET_IDENTIFIER_PATTERN),
368
+ provider: z.string().min(1).max(192).regex(PROFILE_ID_PATTERN),
369
+ name: z.string().min(1).max(256).regex(SAFE_TEXT_PATTERN),
370
+ api: z.enum([
371
+ "anthropic-messages",
372
+ "azure-openai-responses",
373
+ "bedrock-converse-stream",
374
+ "cursor-agent",
375
+ "google-gemini-cli",
376
+ "google-generative-ai",
377
+ "google-vertex",
378
+ "ollama-chat",
379
+ "openai-codex-responses",
380
+ "openai-completions",
381
+ "openai-responses",
382
+ ]),
383
+ reasoning: z.boolean(),
384
+ input: z
385
+ .array(z.enum(["text", "image"]))
386
+ .min(1)
387
+ .max(2)
388
+ .refine(values => new Set(values).size === values.length, "Expected unique input modalities."),
389
+ output: z
390
+ .array(z.enum(["text", "image"]))
391
+ .min(1)
392
+ .max(2)
393
+ .refine(values => new Set(values).size === values.length, "Expected unique output modalities.")
394
+ .optional(),
395
+ cost: CostSchema,
396
+ contextWindow: z.number().int().positive(),
397
+ maxTokens: z.number().int().positive(),
398
+ compat: CompatSchema.optional(),
399
+ thinking: ThinkingSchema.optional(),
400
+ longContextPricing: LongContextPricingSchema.optional(),
401
+ applyPatchToolType: z.literal("freeform").optional(),
402
+ preferWebsockets: z.boolean().optional(),
403
+ premiumMultiplier: z.number().nonnegative().finite().optional(),
404
+ priority: z.number().int().nonnegative().optional(),
405
+ contextPromotionTarget: z.string().min(3).max(256).regex(CONTEXT_PROMOTION_TARGET_PATTERN).optional(),
406
+ })
407
+ .strict();
408
+ export const ModelPresetRegistryPresetsSchema = z
409
+ .object({
410
+ schemaVersion: z.literal("1.0.0"),
411
+ revision: RevisionSchema,
412
+ presets: z.array(RegistryPresetSchema).max(100_000),
413
+ })
414
+ .strict()
415
+ .superRefine((document, ctx) => {
416
+ const seen = new Set<string>();
417
+ for (const [index, preset] of document.presets.entries()) {
418
+ const key = `${preset.provider}\u0000${preset.id}`;
419
+ if (seen.has(key))
420
+ ctx.addIssue({ code: "custom", path: ["presets", index], message: "Duplicate provider/model preset." });
421
+ seen.add(key);
422
+ }
423
+ });
424
+
425
+ export type ModelPresetRegistryManifest = z.infer<typeof ModelPresetRegistryManifestSchema>;
426
+ export type ModelPresetRegistrySnapshot = z.infer<typeof ModelPresetRegistrySnapshotSchema>;
427
+ export type ModelPresetRegistryProfiles = z.infer<typeof ModelPresetRegistryProfilesSchema>;
428
+ export type ModelPresetRegistryPresets = z.infer<typeof ModelPresetRegistryPresetsSchema>;
429
+
430
+ type AcceptedGeneration = {
431
+ manifest: ModelPresetRegistryManifest;
432
+ snapshot: ModelPresetRegistrySnapshot;
433
+ profiles: ModelPresetRegistryProfiles;
434
+ presets: ModelPresetRegistryPresets;
435
+ manifestSha256: string;
436
+ acceptedAt: string;
437
+ etag?: string;
438
+ /** Complete normalized manifest URL used to scope the validator token. */
439
+ manifestUrl?: string;
440
+ /** @deprecated Kept only to read pre-URL-scoped cache rows; never used for ETag reuse. */
441
+ manifestOrigin?: string;
442
+ retainedProfiles: ModelPresetRegistryProfiles["profiles"];
443
+ retainedPresets: ModelPresetRegistryPresets["presets"];
444
+ retainedDynamicProviders: string[];
445
+ retainedFromRevision?: number;
446
+ revoked?: boolean;
447
+ };
448
+
449
+ type RegistryState = {
450
+ version: 1;
451
+ activeRevision?: number;
452
+ highestSeenRevision?: number;
453
+ highestSeenManifestSha256?: string;
454
+ history: AcceptedGeneration[];
455
+ lastCheckedAt?: string;
456
+ lastError?: string;
457
+ };
458
+
459
+ type RegistryControl = { version: 1; disabled: boolean; pinnedRevision?: number };
460
+
461
+ export interface ModelPresetRegistryDependencies {
462
+ agentDir?: string;
463
+ manifestUrl?: string;
464
+ fetch?: typeof fetch;
465
+ now?: () => Date;
466
+ timeoutMs?: number;
467
+ maxManifestBytes?: number;
468
+ maxSnapshotBytes?: number;
469
+ maxProfilesBytes?: number;
470
+ maxPresetsBytes?: number;
471
+ maxStateBytes?: number;
472
+ automaticRefresh?: boolean;
473
+ startupDelayMs?: number;
474
+ refreshIntervalMs?: number;
475
+ knownManifestSha256?: string;
476
+ }
477
+
478
+ export interface AcceptedModelPresetRegistry {
479
+ profiles: Map<string, ModelProfileDefinition>;
480
+ presets: Model<Api>[];
481
+ dynamicProviders: string[];
482
+ revision?: number;
483
+ revisionId?: string;
484
+ manifestSha256?: string;
485
+ keyId?: string;
486
+ sourceRevision?: string;
487
+ retainedProfiles: string[];
488
+ retainedPresets: string[];
489
+ error?: string;
490
+ disabled: boolean;
491
+ pinnedRevision?: number;
492
+ }
493
+
494
+ export type ModelPresetRegistryRefreshResult =
495
+ | {
496
+ status: "updated";
497
+ revision: number;
498
+ revisionId: string;
499
+ manifestSha256: string;
500
+ retainedProfiles: string[];
501
+ retainedPresets: string[];
502
+ }
503
+ | { status: "not_modified"; revision?: number }
504
+ | { status: "disabled"; revision?: number };
505
+
506
+ export interface ModelPresetRegistryStatus {
507
+ contractVersion: string;
508
+ source: "embedded" | "registry";
509
+ cacheHealth: "empty" | "valid" | "corrupt";
510
+ activeRevision?: number;
511
+ activeRevisionId?: string;
512
+ highestSeenRevision?: number;
513
+ manifestSha256?: string;
514
+ snapshotSha256?: string;
515
+ profilesSha256?: string;
516
+ presetsSha256?: string;
517
+ keyId?: string;
518
+ sourceRevision?: string;
519
+ acceptedAt?: string;
520
+ publishedAt?: string;
521
+ lastCheckedAt?: string;
522
+ lastError?: string;
523
+ disabled: boolean;
524
+ pinnedRevision?: number;
525
+ retainedProfiles: string[];
526
+ retainedPresets: string[];
527
+ historyRevisions: number[];
528
+ profileCount: number;
529
+ presetCount: number;
530
+ }
531
+
532
+ function registryPaths(agentDir: string) {
533
+ const root = path.join(agentDir, "model-presets");
534
+ return {
535
+ root,
536
+ state: path.join(root, "state.json"),
537
+ backup: path.join(root, "state.backup.json"),
538
+ failure: path.join(root, "failure.json"),
539
+ control: path.join(root, "control.json"),
540
+ transaction: path.join(root, "transaction"),
541
+ };
542
+ }
543
+
544
+ type RegistryStatePaths = { state: string; backup: string };
545
+
546
+ async function writeRegistryState(
547
+ paths: RegistryStatePaths,
548
+ state: RegistryState,
549
+ canWrite: () => boolean = () => true,
550
+ ): Promise<void> {
551
+ // Publish the backup before replacing the primary. If the process stops in
552
+ // the replacement window, recovery can still combine the old primary's
553
+ // active intent with the newer backup's anti-rollback floor.
554
+ if (!canWrite()) return;
555
+ await writeAtomicJson(paths.backup, state);
556
+ if (!canWrite()) return;
557
+ await writeAtomicJson(paths.state, state);
558
+ }
559
+
560
+ function safeError(error: unknown): string {
561
+ const raw = error instanceof Error ? error.message : String(error);
562
+ if (!/^(?:Registry|Cached registry|Cannot pin|Cannot rollback|The preset registry)/.test(raw))
563
+ return "Registry refresh failed.";
564
+ const redacted = raw
565
+ .replace(/https?:\/\/[^\s]+/gi, "<registry-url>")
566
+ .replace(/(api[_-]?key|token|secret|authorization|cookie)\s*[:=]\s*[^\s,;]+/gi, "$1=<redacted>")
567
+ .replace(/[\p{Cc}\p{Cf}]+/gu, " ")
568
+ .trim();
569
+ if (Buffer.byteLength(redacted) <= MODEL_PRESET_REGISTRY_MAX_ERROR_BYTES) return redacted;
570
+ let end = redacted.length;
571
+ while (end > 0 && Buffer.byteLength(redacted.slice(0, end)) > MODEL_PRESET_REGISTRY_MAX_ERROR_BYTES) end--;
572
+ return redacted.slice(0, end);
573
+ }
574
+
575
+ function assertSafeRegistryDocument(value: unknown, location = "$"): void {
576
+ if (typeof value === "string") {
577
+ if (/https?:\/\//i.test(value) && value !== SOURCE_REPOSITORY)
578
+ throw new Error(`Registry document contains an unsafe URL at ${location}.`);
579
+ if (/(?:^|[^a-z])(sk-[A-Za-z0-9_-]{12,}|gh[opusr]_[A-Za-z0-9]{12,}|AKIA[0-9A-Z]{16}|Bearer\s+\S+)/i.test(value))
580
+ throw new Error(`Registry document contains possible secret material at ${location}.`);
581
+ return;
582
+ }
583
+ if (!value || typeof value !== "object") return;
584
+ if (Array.isArray(value)) {
585
+ for (const [index, entry] of value.entries()) assertSafeRegistryDocument(entry, `${location}[${index}]`);
586
+ return;
587
+ }
588
+ for (const [key, entry] of Object.entries(value)) {
589
+ if (/^(?:headers?|api[_-]?key|base[_-]?url|secret|password|command|script|extraBody)$/i.test(key))
590
+ throw new Error(`Registry document contains unsafe field ${location}.${key}.`);
591
+ assertSafeRegistryDocument(entry, `${location}.${key}`);
592
+ }
593
+ }
594
+
595
+ function identitySkeleton(value: string): string {
596
+ return [...value.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase()]
597
+ .map(character => IDENTITY_HOMOGLYPHS.get(character) ?? character)
598
+ .join("");
599
+ }
600
+
601
+ function assertUniqueRegistryNames(values: readonly string[], label: string): void {
602
+ const exact = new Set<string>();
603
+ const folded = new Map<string, string>();
604
+ for (const value of values) {
605
+ if (exact.has(value)) throw new Error(`Registry document contains duplicate ${label}.`);
606
+ exact.add(value);
607
+ const skeleton = identitySkeleton(value);
608
+ const previous = folded.get(skeleton);
609
+ if (previous && previous !== value) throw new Error(`Registry document contains confusable ${label}.`);
610
+ folded.set(skeleton, value);
611
+ if (RESERVED_IDENTITY_PREFIXES.some(prefix => value.toLowerCase().startsWith(prefix)))
612
+ throw new Error(`Registry document uses a reserved ${label} namespace.`);
613
+ }
614
+ }
615
+
616
+ function assertRegistryIdentityPolicy(value: unknown): void {
617
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
618
+ const document = value as Record<string, unknown>;
619
+ if (Array.isArray(document.profiles)) {
620
+ assertUniqueRegistryNames(
621
+ document.profiles.map(profile => String((profile as { id?: unknown }).id ?? "")),
622
+ "profile id",
623
+ );
624
+ }
625
+ if (Array.isArray(document.dynamicProviders))
626
+ assertUniqueRegistryNames(document.dynamicProviders.map(String), "dynamic provider id");
627
+ if (Array.isArray(document.presets)) {
628
+ assertUniqueRegistryNames(
629
+ document.presets.map(preset => {
630
+ const record = preset as { provider?: unknown; id?: unknown };
631
+ return `${String(record.provider ?? "")}/${String(record.id ?? "")}`;
632
+ }),
633
+ "preset selector",
634
+ );
635
+ }
636
+ }
637
+
638
+ function assertCanonicalUnicode(value: string): void {
639
+ for (let index = 0; index < value.length; index++) {
640
+ const code = value.charCodeAt(index);
641
+ if (code >= 0xd800 && code <= 0xdbff) {
642
+ const next = value.charCodeAt(index + 1);
643
+ if (!Number.isInteger(next) || next < 0xdc00 || next > 0xdfff)
644
+ throw new TypeError("Canonical JSON rejects lone high surrogates.");
645
+ index++;
646
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
647
+ throw new TypeError("Canonical JSON rejects lone low surrogates.");
648
+ }
649
+ }
650
+ }
651
+
652
+ function serializeCanonicalJson(value: unknown, stack: Set<object>): string {
653
+ if (value === null) return "null";
654
+ if (typeof value === "boolean") return value ? "true" : "false";
655
+ if (typeof value === "number") {
656
+ if (!Number.isFinite(value)) throw new TypeError("Canonical JSON rejects non-finite numbers.");
657
+ return JSON.stringify(Object.is(value, -0) ? 0 : value);
658
+ }
659
+ if (typeof value === "string") {
660
+ assertCanonicalUnicode(value);
661
+ return JSON.stringify(value);
662
+ }
663
+ if (typeof value !== "object") throw new TypeError(`Canonical JSON rejects ${typeof value}.`);
664
+ if (stack.has(value)) throw new TypeError("Canonical JSON rejects cycles.");
665
+ stack.add(value);
666
+ try {
667
+ if (Array.isArray(value)) {
668
+ const entries: string[] = [];
669
+ for (let index = 0; index < value.length; index++) {
670
+ if (!Object.hasOwn(value, index)) throw new TypeError("Canonical JSON rejects sparse arrays.");
671
+ entries.push(serializeCanonicalJson(value[index], stack));
672
+ }
673
+ return `[${entries.join(",")}]`;
674
+ }
675
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
676
+ throw new TypeError("Canonical JSON accepts plain objects only.");
677
+ const record = value as Record<string, unknown>;
678
+ const entries: string[] = [];
679
+ for (const key of Object.keys(record).sort()) {
680
+ assertCanonicalUnicode(key);
681
+ entries.push(`${JSON.stringify(key)}:${serializeCanonicalJson(record[key], stack)}`);
682
+ }
683
+ return `{${entries.join(",")}}`;
684
+ } finally {
685
+ stack.delete(value);
686
+ }
687
+ }
688
+
689
+ export function canonicalModelPresetRegistryJson(value: unknown): string {
690
+ return serializeCanonicalJson(value, new Set());
691
+ }
692
+
693
+ function sha256(bytes: Uint8Array | string): string {
694
+ return crypto.createHash("sha256").update(bytes).digest("hex");
695
+ }
696
+
697
+ function serializedJsonByteLength(value: unknown): number {
698
+ return Buffer.byteLength(`${JSON.stringify(value)}\n`, "utf8");
699
+ }
700
+
701
+ function parseCanonicalDocument<T>(bytes: Uint8Array, description: string, schema: z.ZodType<T>): T {
702
+ let text: string;
703
+ let value: unknown;
704
+ try {
705
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
706
+ value = JSON.parse(text);
707
+ } catch {
708
+ throw new Error(`${description} is not valid UTF-8 JSON.`);
709
+ }
710
+ const checked = schema.safeParse(value);
711
+ if (!checked.success)
712
+ throw new Error(`${description} schema rejected: ${checked.error.issues[0]?.message ?? "invalid"}.`);
713
+ assertSafeRegistryDocument(checked.data);
714
+ assertRegistryIdentityPolicy(checked.data);
715
+ if (text !== canonicalModelPresetRegistryJson(checked.data))
716
+ throw new Error(`${description} is not canonical JSON.`);
717
+ return checked.data;
718
+ }
719
+
720
+ function compareSemver(left: string, right: string): number {
721
+ const a = left.split(".").map(Number);
722
+ const b = right.split(".").map(Number);
723
+ for (let index = 0; index < 3; index++) {
724
+ if (a[index] !== b[index]) return (a[index] ?? 0) - (b[index] ?? 0);
725
+ }
726
+ return 0;
727
+ }
728
+
729
+ function assertCompatible(compatibility: z.infer<typeof CompatibilitySchema>): void {
730
+ const { minVersion, maxVersion } = compatibility.consumerContract;
731
+ if (compareSemver(minVersion, maxVersion) > 0) throw new Error("Registry compatibility bounds are inverted.");
732
+ if (
733
+ compareSemver(MODEL_PRESET_REGISTRY_CONTRACT_VERSION, minVersion) < 0 ||
734
+ compareSemver(MODEL_PRESET_REGISTRY_CONTRACT_VERSION, maxVersion) > 0
735
+ )
736
+ throw new Error("Registry manifest is incompatible with this GJC preset contract.");
737
+ }
738
+
739
+ function verifyManifest(
740
+ manifest: ModelPresetRegistryManifest,
741
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
742
+ allowRevoked = false,
743
+ ): void {
744
+ assertCompatible(manifest.signed.compatibility);
745
+ if (manifest.signed.revision !== String(manifest.signed.registryRevision).padStart(8, "0"))
746
+ throw new Error("Registry revision identity is inconsistent.");
747
+ const key = trustedKeys.get(manifest.signature.keyId);
748
+ if (!key || key.keyId !== manifest.signature.keyId)
749
+ throw new Error(`Registry signature key id is not trusted: ${manifest.signature.keyId}.`);
750
+ const publishedAt = Date.parse(manifest.signed.publishedAt);
751
+ if (!Number.isFinite(publishedAt) || publishedAt < Date.parse(key.validFrom))
752
+ throw new Error("Registry manifest predates its signing key validity.");
753
+ if (key.revokedAt && !allowRevoked) throw new Error("Registry manifest signing key is revoked.");
754
+ const signature = decodeCanonicalEd25519Signature(manifest.signature.value);
755
+ if (
756
+ !crypto.verify(null, Buffer.from(canonicalModelPresetRegistryJson(manifest.signed)), key.publicKeyPem, signature)
757
+ )
758
+ throw new Error("Registry manifest signature verification failed.");
759
+ }
760
+
761
+ function assertDescriptorRevision(descriptor: z.infer<typeof DescriptorSchema>, revision: string): void {
762
+ if (!descriptor.path.startsWith(`revisions/${revision}/`))
763
+ throw new Error("Registry descriptor path does not match its revision.");
764
+ }
765
+
766
+ function assertManifestBindings(manifest: ModelPresetRegistryManifest): void {
767
+ for (const descriptor of [
768
+ manifest.signed.snapshot,
769
+ manifest.signed.contents.profiles,
770
+ manifest.signed.contents.presets,
771
+ ])
772
+ assertDescriptorRevision(descriptor, manifest.signed.revision);
773
+ }
774
+
775
+ function assertSnapshotBindings(manifest: ModelPresetRegistryManifest, snapshot: ModelPresetRegistrySnapshot): void {
776
+ if (
777
+ snapshot.registryRevision !== manifest.signed.registryRevision ||
778
+ snapshot.revision !== manifest.signed.revision ||
779
+ canonicalModelPresetRegistryJson(snapshot.compatibility) !==
780
+ canonicalModelPresetRegistryJson(manifest.signed.compatibility) ||
781
+ canonicalModelPresetRegistryJson(snapshot.provenance) !==
782
+ canonicalModelPresetRegistryJson(manifest.signed.provenance) ||
783
+ canonicalModelPresetRegistryJson(snapshot.contents) !== canonicalModelPresetRegistryJson(manifest.signed.contents)
784
+ )
785
+ throw new Error("Registry snapshot does not match the signed manifest.");
786
+ }
787
+
788
+ function assertContentDescriptor(
789
+ bytes: Uint8Array,
790
+ descriptor: z.infer<typeof DescriptorSchema>,
791
+ description: string,
792
+ ): void {
793
+ if (bytes.byteLength !== descriptor.bytes) throw new Error(`${description} size mismatch.`);
794
+ if (sha256(bytes) !== descriptor.sha256) throw new Error(`${description} digest mismatch.`);
795
+ }
796
+
797
+ function selectorIdentityCandidates(selector: string): string[] {
798
+ const suffix = splitSelectorThinkingSuffix(selector);
799
+ return suffix.thinkingLevel === undefined ? [selector] : [selector, suffix.selector];
800
+ }
801
+
802
+ function assertProfilePresetReferences(
803
+ profiles: ModelPresetRegistryProfiles,
804
+ presets: ModelPresetRegistryPresets,
805
+ ): void {
806
+ const exact = new Set(presets.presets.map(preset => `${preset.provider}/${preset.id}`));
807
+ const bare = new Set(presets.presets.map(preset => preset.id));
808
+ const dynamic = new Set(profiles.dynamicProviders);
809
+ for (const profile of profiles.profiles) {
810
+ for (const binding of Object.values(profile.roleBindings)) {
811
+ for (const selector of Array.isArray(binding) ? binding : [binding]) {
812
+ const identities = selectorIdentityCandidates(selector);
813
+ const exactMatch = identities.find(identity => exact.has(identity));
814
+ if (exactMatch) continue;
815
+ const dynamicMatch = identities.find(identity => {
816
+ const slash = identity.indexOf("/");
817
+ return slash >= 0 && dynamic.has(identity.slice(0, slash));
818
+ });
819
+ if (dynamicMatch) continue;
820
+ if (!identities.some(identity => bare.has(identity)))
821
+ throw new Error(`Registry profile ${profile.id} references an unknown model alias.`);
822
+ }
823
+ }
824
+ }
825
+ }
826
+
827
+ function validateGeneration(
828
+ generation: AcceptedGeneration,
829
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
830
+ allowRevoked = false,
831
+ ): AcceptedGeneration {
832
+ const manifest = ModelPresetRegistryManifestSchema.parse(generation.manifest);
833
+ assertSafeRegistryDocument(manifest);
834
+ verifyManifest(manifest, trustedKeys, allowRevoked);
835
+ assertManifestBindings(manifest);
836
+ const snapshotBytes = new TextEncoder().encode(canonicalModelPresetRegistryJson(generation.snapshot));
837
+ assertContentDescriptor(snapshotBytes, manifest.signed.snapshot, "Cached registry snapshot");
838
+ const snapshot = ModelPresetRegistrySnapshotSchema.parse(generation.snapshot);
839
+ assertSafeRegistryDocument(snapshot);
840
+ assertSnapshotBindings(manifest, snapshot);
841
+ const profileBytes = new TextEncoder().encode(canonicalModelPresetRegistryJson(generation.profiles));
842
+ assertContentDescriptor(profileBytes, manifest.signed.contents.profiles, "Cached registry profiles");
843
+ const profiles = ModelPresetRegistryProfilesSchema.parse(generation.profiles);
844
+ assertSafeRegistryDocument(profiles);
845
+ assertRegistryIdentityPolicy(profiles);
846
+ if (
847
+ profiles.revision !== manifest.signed.revision ||
848
+ profiles.profiles.length !== manifest.signed.contents.profiles.count
849
+ )
850
+ throw new Error("Cached registry profile identity is invalid.");
851
+ const presetBytes = new TextEncoder().encode(canonicalModelPresetRegistryJson(generation.presets));
852
+ assertContentDescriptor(presetBytes, manifest.signed.contents.presets, "Cached registry presets");
853
+ const presets = ModelPresetRegistryPresetsSchema.parse(generation.presets);
854
+ assertSafeRegistryDocument(presets);
855
+ assertRegistryIdentityPolicy(presets);
856
+ if (
857
+ presets.revision !== manifest.signed.revision ||
858
+ presets.presets.length !== manifest.signed.contents.presets.count
859
+ )
860
+ throw new Error("Cached registry preset identity is invalid.");
861
+ assertProfilePresetReferences(profiles, presets);
862
+ const manifestSha256 = sha256(canonicalModelPresetRegistryJson(manifest));
863
+ if (manifestSha256 !== generation.manifestSha256) throw new Error("Cached registry manifest digest is invalid.");
864
+ assertSafeRegistryDocument(generation.retainedProfiles);
865
+ assertSafeRegistryDocument(generation.retainedPresets);
866
+ assertSafeRegistryDocument(generation.retainedDynamicProviders);
867
+ const effectiveProfiles = ModelPresetRegistryProfilesSchema.parse({
868
+ ...profiles,
869
+ dynamicProviders: [...generation.retainedDynamicProviders, ...profiles.dynamicProviders],
870
+ profiles: [...generation.retainedProfiles, ...profiles.profiles],
871
+ });
872
+ const effectivePresets = ModelPresetRegistryPresetsSchema.parse({
873
+ ...presets,
874
+ presets: [...generation.retainedPresets, ...presets.presets],
875
+ });
876
+ assertRegistryIdentityPolicy(effectiveProfiles);
877
+ assertRegistryIdentityPolicy(effectivePresets);
878
+ assertProfilePresetReferences(effectiveProfiles, effectivePresets);
879
+ return { ...generation, manifest, snapshot, profiles, presets, manifestSha256 };
880
+ }
881
+
882
+ function readJsonSync(file: string): unknown | undefined {
883
+ try {
884
+ const stat = fsSync.lstatSync(file);
885
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("Registry cache path is not a regular file.");
886
+ if (stat.size > MODEL_PRESET_REGISTRY_MAX_STATE_BYTES) throw new Error("Registry cache is oversized.");
887
+ return JSON.parse(fsSync.readFileSync(file, "utf8"));
888
+ } catch (error) {
889
+ if (isEnoent(error)) return undefined;
890
+ throw error;
891
+ }
892
+ }
893
+
894
+ /**
895
+ * Async cache reads for callers that already have an async lifecycle boundary.
896
+ * Keep the synchronous reader above for legacy startup paths until their
897
+ * constructor-wide migration can be completed without changing public APIs.
898
+ */
899
+ async function readJsonBun(file: string): Promise<unknown | undefined> {
900
+ try {
901
+ const stat = await fs.lstat(file, { bigint: true });
902
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("Registry cache path is not a regular file.");
903
+ if (stat.size > BigInt(MODEL_PRESET_REGISTRY_MAX_STATE_BYTES)) throw new Error("Registry cache is oversized.");
904
+ return JSON.parse(await Bun.file(file).text());
905
+ } catch (error) {
906
+ if (isEnoent(error)) return undefined;
907
+ throw error;
908
+ }
909
+ }
910
+
911
+ function parseState(value: unknown): RegistryState {
912
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Registry cache state is invalid.");
913
+ const state = value as Partial<RegistryState>;
914
+ if (state.version !== 1 || !Array.isArray(state.history))
915
+ throw new Error("Registry cache state version is invalid.");
916
+ for (const field of ["activeRevision", "highestSeenRevision"] as const) {
917
+ const entry = state[field];
918
+ if (entry !== undefined && (!Number.isSafeInteger(entry) || entry <= 0))
919
+ throw new Error(`Registry cache ${field} is invalid.`);
920
+ }
921
+ if (state.highestSeenManifestSha256 !== undefined && !SHA256_PATTERN.test(state.highestSeenManifestSha256))
922
+ throw new Error("Registry cache highest manifest digest is invalid.");
923
+ if (state.lastCheckedAt !== undefined && !Number.isFinite(Date.parse(state.lastCheckedAt)))
924
+ throw new Error("Registry cache check timestamp is invalid.");
925
+ if (state.lastError !== undefined && typeof state.lastError !== "string")
926
+ throw new Error("Registry cache error state is invalid.");
927
+ const lastError = state.lastError === undefined ? undefined : safeError(state.lastError);
928
+ const history = state.history.map((entry, index): AcceptedGeneration => {
929
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
930
+ throw new Error(`Registry cache generation ${index} is invalid.`);
931
+ const generation = entry as Partial<AcceptedGeneration>;
932
+ const manifest = ModelPresetRegistryManifestSchema.safeParse(generation.manifest);
933
+ const snapshot = ModelPresetRegistrySnapshotSchema.safeParse(generation.snapshot);
934
+ const profiles = ModelPresetRegistryProfilesSchema.safeParse(generation.profiles);
935
+ const presets = ModelPresetRegistryPresetsSchema.safeParse(generation.presets);
936
+ const retainedProfiles = z.array(RegistryProfileSchema).max(4096).safeParse(generation.retainedProfiles);
937
+ const retainedPresets = z.array(RegistryPresetSchema).max(100_000).safeParse(generation.retainedPresets);
938
+ const retainedDynamicProviders = z
939
+ .array(z.string().min(1).max(128).regex(PROFILE_ID_PATTERN))
940
+ .max(128)
941
+ .refine(values => new Set(values).size === values.length)
942
+ .safeParse(generation.retainedDynamicProviders);
943
+ const retainedFromRevision = generation.retainedFromRevision;
944
+ let manifestUrl: string | undefined;
945
+ if (generation.manifestUrl !== undefined) {
946
+ if (typeof generation.manifestUrl !== "string")
947
+ throw new Error(`Registry cache generation ${index} is invalid.`);
948
+ try {
949
+ const parsed = new URL(generation.manifestUrl);
950
+ if (
951
+ parsed.protocol !== "https:" ||
952
+ parsed.username ||
953
+ parsed.password ||
954
+ parsed.search ||
955
+ parsed.hash ||
956
+ parsed.href !== generation.manifestUrl
957
+ )
958
+ throw new Error();
959
+ manifestUrl = parsed.href;
960
+ } catch {
961
+ throw new Error(`Registry cache generation ${index} is invalid.`);
962
+ }
963
+ }
964
+ if (
965
+ !manifest.success ||
966
+ !snapshot.success ||
967
+ !profiles.success ||
968
+ !presets.success ||
969
+ !retainedProfiles.success ||
970
+ !retainedPresets.success ||
971
+ !retainedDynamicProviders.success ||
972
+ (retainedFromRevision !== undefined &&
973
+ (!Number.isSafeInteger(retainedFromRevision) || retainedFromRevision <= 0)) ||
974
+ typeof generation.manifestSha256 !== "string" ||
975
+ !SHA256_PATTERN.test(generation.manifestSha256) ||
976
+ typeof generation.acceptedAt !== "string" ||
977
+ !Number.isFinite(Date.parse(generation.acceptedAt)) ||
978
+ (generation.etag !== undefined && typeof generation.etag !== "string") ||
979
+ (generation.manifestOrigin !== undefined &&
980
+ (typeof generation.manifestOrigin !== "string" ||
981
+ !generation.manifestOrigin.startsWith("https://") ||
982
+ new URL(generation.manifestOrigin).origin !== generation.manifestOrigin)) ||
983
+ (generation.revoked !== undefined && typeof generation.revoked !== "boolean")
984
+ )
985
+ throw new Error(`Registry cache generation ${index} is invalid.`);
986
+ return {
987
+ manifest: manifest.data,
988
+ snapshot: snapshot.data,
989
+ profiles: profiles.data,
990
+ presets: presets.data,
991
+ manifestSha256: generation.manifestSha256,
992
+ acceptedAt: generation.acceptedAt,
993
+ etag: generation.etag,
994
+ manifestUrl,
995
+ manifestOrigin: generation.manifestOrigin,
996
+ retainedProfiles: retainedProfiles.data,
997
+ retainedPresets: retainedPresets.data,
998
+ retainedDynamicProviders: retainedDynamicProviders.data,
999
+ retainedFromRevision,
1000
+ revoked: generation.revoked === true,
1001
+ };
1002
+ });
1003
+ return { ...state, version: 1, history, lastError };
1004
+ }
1005
+
1006
+ type ParsedRegistryState = {
1007
+ present: boolean;
1008
+ state?: RegistryState;
1009
+ error?: unknown;
1010
+ };
1011
+
1012
+ type RegistryStateEvidence = {
1013
+ state: RegistryState;
1014
+ fullyVerified: boolean;
1015
+ verifiedGenerations: AcceptedGeneration[];
1016
+ allGenerationsVerified: boolean;
1017
+ duplicateRevision: boolean;
1018
+ maxUnverifiedRevision?: number;
1019
+ activeRevision?: number;
1020
+ floor?: { revision: number; manifestSha256: string };
1021
+ unverifiedCheckpointRevision?: number;
1022
+ resetShaped: boolean;
1023
+ };
1024
+
1025
+ type RegistryStateRecovery = {
1026
+ state: RegistryState;
1027
+ stateIsVerified: boolean;
1028
+ highestSeenRevision?: number;
1029
+ highestSeenManifestSha256?: string;
1030
+ hadInvalidCopy: boolean;
1031
+ firstRun: boolean;
1032
+ };
1033
+
1034
+ function readParsedStateSync(file: string): ParsedRegistryState {
1035
+ try {
1036
+ const value = readJsonSync(file);
1037
+ if (value === undefined) return { present: false };
1038
+ return { present: true, state: parseState(value) };
1039
+ } catch (error) {
1040
+ return { present: true, error };
1041
+ }
1042
+ }
1043
+
1044
+ function isResetShapedState(state: RegistryState): boolean {
1045
+ return state.history.length === 0 && state.activeRevision === undefined && state.highestSeenRevision === undefined;
1046
+ }
1047
+
1048
+ function verifyGenerationForRecovery(
1049
+ generation: AcceptedGeneration,
1050
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
1051
+ ): AcceptedGeneration | undefined {
1052
+ try {
1053
+ return validateGeneration(generation, trustedKeys);
1054
+ } catch {
1055
+ try {
1056
+ const verified = validateGeneration(generation, trustedKeys, true);
1057
+ return { ...verified, revoked: true };
1058
+ } catch {
1059
+ return undefined;
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ function verifyManifestCheckpointForRecovery(
1065
+ generation: AcceptedGeneration,
1066
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
1067
+ ): boolean {
1068
+ try {
1069
+ const manifest = ModelPresetRegistryManifestSchema.parse(generation.manifest);
1070
+ assertSafeRegistryDocument(manifest);
1071
+ verifyManifest(manifest, trustedKeys, true);
1072
+ assertManifestBindings(manifest);
1073
+ return sha256(canonicalModelPresetRegistryJson(manifest)) === generation.manifestSha256;
1074
+ } catch {
1075
+ return false;
1076
+ }
1077
+ }
1078
+
1079
+ function inspectStateEvidence(
1080
+ parsed: ParsedRegistryState,
1081
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
1082
+ ): RegistryStateEvidence | undefined {
1083
+ if (!parsed.state) return undefined;
1084
+ const state = parsed.state;
1085
+ let fullyVerified = true;
1086
+ try {
1087
+ validateStateGenerations(state, trustedKeys);
1088
+ } catch {
1089
+ fullyVerified = false;
1090
+ }
1091
+ const verifiedGenerations = state.history
1092
+ .map(generation => verifyGenerationForRecovery(generation, trustedKeys))
1093
+ .filter((generation): generation is AcceptedGeneration => generation !== undefined);
1094
+ const verifiedRevisionSet = new Set<number>();
1095
+ let duplicateRevision = false;
1096
+ for (const generation of state.history) {
1097
+ const revision = generation.manifest.signed.registryRevision;
1098
+ if (verifiedRevisionSet.has(revision)) duplicateRevision = true;
1099
+ verifiedRevisionSet.add(revision);
1100
+ }
1101
+ const verifiedGenerationRevisions = new Set(
1102
+ verifiedGenerations.map(generation => generation.manifest.signed.registryRevision),
1103
+ );
1104
+ const maxUnverifiedRevision = state.history
1105
+ .map(generation => generation.manifest.signed.registryRevision)
1106
+ .filter(revision => !verifiedGenerationRevisions.has(revision))
1107
+ .sort((left, right) => right - left)[0];
1108
+ const verifiedByRevision = new Map(
1109
+ verifiedGenerations.map(generation => [generation.manifest.signed.registryRevision, generation]),
1110
+ );
1111
+ const activeGeneration =
1112
+ state.activeRevision === undefined ? undefined : verifiedByRevision.get(state.activeRevision);
1113
+ const activeRevision = activeGeneration && !activeGeneration.revoked ? state.activeRevision : undefined;
1114
+ let floor: RegistryStateEvidence["floor"];
1115
+ let unverifiedCheckpointRevision: number | undefined;
1116
+ if (state.highestSeenRevision !== undefined && state.highestSeenManifestSha256 !== undefined) {
1117
+ const checkpoint = verifiedByRevision.get(state.highestSeenRevision);
1118
+ if (checkpoint?.manifestSha256 === state.highestSeenManifestSha256)
1119
+ floor = { revision: state.highestSeenRevision, manifestSha256: state.highestSeenManifestSha256 };
1120
+ else {
1121
+ const checkpointCandidate = state.history.find(
1122
+ generation => generation.manifest.signed.registryRevision === state.highestSeenRevision,
1123
+ );
1124
+ if (
1125
+ checkpointCandidate?.manifestSha256 === state.highestSeenManifestSha256 &&
1126
+ verifyManifestCheckpointForRecovery(checkpointCandidate, trustedKeys)
1127
+ )
1128
+ floor = { revision: state.highestSeenRevision, manifestSha256: state.highestSeenManifestSha256 };
1129
+ else if (checkpointCandidate) unverifiedCheckpointRevision = state.highestSeenRevision;
1130
+ }
1131
+ }
1132
+ for (const generation of verifiedGenerations) {
1133
+ const revision = generation.manifest.signed.registryRevision;
1134
+ if (!floor || revision > floor.revision) floor = { revision, manifestSha256: generation.manifestSha256 };
1135
+ }
1136
+ return {
1137
+ state,
1138
+ fullyVerified,
1139
+ verifiedGenerations,
1140
+ allGenerationsVerified: verifiedGenerations.length === state.history.length,
1141
+ duplicateRevision,
1142
+ maxUnverifiedRevision,
1143
+ activeRevision,
1144
+ floor,
1145
+ unverifiedCheckpointRevision,
1146
+ resetShaped: isResetShapedState(state),
1147
+ };
1148
+ }
1149
+
1150
+ function recoverStateCopiesSync(
1151
+ agentDir: string,
1152
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
1153
+ options: { allowInvalidPrimaryFallback?: boolean; allowUnreadablePrimaryFallback?: boolean } = {},
1154
+ ): RegistryStateRecovery {
1155
+ const paths = registryPaths(agentDir);
1156
+ const primary = readParsedStateSync(paths.state);
1157
+ const backup = readParsedStateSync(paths.backup);
1158
+ if (!primary.present && !backup.present)
1159
+ return { state: { version: 1, history: [] }, stateIsVerified: true, hadInvalidCopy: false, firstRun: true };
1160
+ const primaryEvidence = inspectStateEvidence(primary, trustedKeys);
1161
+ const backupEvidence = inspectStateEvidence(backup, trustedKeys);
1162
+ if (primary.error !== undefined && !options.allowUnreadablePrimaryFallback)
1163
+ throw new Error("Registry primary cache state is unreadable.");
1164
+ if (
1165
+ primaryEvidence &&
1166
+ !primaryEvidence.resetShaped &&
1167
+ (!primaryEvidence.allGenerationsVerified || primaryEvidence.duplicateRevision)
1168
+ )
1169
+ if (!options.allowInvalidPrimaryFallback || !backupEvidence?.fullyVerified) {
1170
+ try {
1171
+ validateStateGenerations(primaryEvidence.state, trustedKeys);
1172
+ } catch (error) {
1173
+ throw error;
1174
+ }
1175
+ }
1176
+ if (
1177
+ backupEvidence &&
1178
+ (!backupEvidence.allGenerationsVerified || backupEvidence.duplicateRevision) &&
1179
+ (!primaryEvidence?.fullyVerified || primaryEvidence.resetShaped)
1180
+ )
1181
+ throw new Error("Registry backup cache contains an unverifiable accepted generation.");
1182
+ const evidences = [primaryEvidence, backupEvidence].filter(
1183
+ (evidence): evidence is RegistryStateEvidence => evidence !== undefined,
1184
+ );
1185
+ const byRevision = new Map<number, AcceptedGeneration>();
1186
+ for (const evidence of evidences) {
1187
+ for (const generation of evidence.verifiedGenerations) {
1188
+ const revision = generation.manifest.signed.registryRevision;
1189
+ const existing = byRevision.get(revision);
1190
+ if (existing && existing.manifestSha256 !== generation.manifestSha256)
1191
+ throw new Error(`Registry cache contains conflicting verified revision ${revision}.`);
1192
+ if (!existing || (generation.revoked && !existing.revoked)) byRevision.set(revision, generation);
1193
+ }
1194
+ }
1195
+ const floors = evidences.flatMap(evidence => (evidence.floor ? [evidence.floor] : []));
1196
+ const floor = floors.sort((left, right) => right.revision - left.revision)[0];
1197
+ if (
1198
+ primaryEvidence?.unverifiedCheckpointRevision !== undefined &&
1199
+ (primaryEvidence.unverifiedCheckpointRevision > (floor?.revision ?? 0) || floor === undefined)
1200
+ )
1201
+ throw new Error("Registry primary cache anti-rollback checkpoint is unverifiable.");
1202
+ if (
1203
+ primaryEvidence?.maxUnverifiedRevision !== undefined &&
1204
+ (primaryEvidence.maxUnverifiedRevision > (floor?.revision ?? 0) || floor === undefined)
1205
+ )
1206
+ throw new Error("Registry primary cache contains a revision without a verified checkpoint.");
1207
+ const primaryUsable =
1208
+ primaryEvidence !== undefined &&
1209
+ (!primaryEvidence.resetShaped
1210
+ ? backupEvidence === undefined || primaryEvidence.fullyVerified
1211
+ : backupEvidence === undefined);
1212
+ const preferred = primaryUsable
1213
+ ? primaryEvidence
1214
+ : options.allowInvalidPrimaryFallback && backupEvidence?.fullyVerified
1215
+ ? backupEvidence
1216
+ : primaryEvidence && primary.error === undefined
1217
+ ? primaryEvidence
1218
+ : (backupEvidence ?? primaryEvidence);
1219
+ const activeRevision =
1220
+ preferred?.activeRevision !== undefined && byRevision.has(preferred.activeRevision)
1221
+ ? preferred.activeRevision
1222
+ : undefined;
1223
+ const history = [...byRevision.values()].sort(
1224
+ (left, right) => right.manifest.signed.registryRevision - left.manifest.signed.registryRevision,
1225
+ );
1226
+ const hadInvalidCopy =
1227
+ (primary.present && (primary.error !== undefined || primaryEvidence?.fullyVerified !== true)) ||
1228
+ (backup.present && (backup.error !== undefined || backupEvidence?.fullyVerified !== true));
1229
+ const firstRun =
1230
+ !hadInvalidCopy &&
1231
+ evidences.length > 0 &&
1232
+ evidences.every(evidence => evidence.resetShaped) &&
1233
+ floor === undefined;
1234
+ if (floor === undefined && !firstRun)
1235
+ throw new Error("Registry cache anti-rollback checkpoint cannot be reconstructed.");
1236
+ const recovered: RegistryState = {
1237
+ ...(preferred?.state ?? {}),
1238
+ version: 1,
1239
+ activeRevision,
1240
+ highestSeenRevision: floor?.revision,
1241
+ highestSeenManifestSha256: floor?.manifestSha256,
1242
+ history,
1243
+ lastCheckedAt: preferred?.state.lastCheckedAt,
1244
+ lastError: preferred?.state.lastError,
1245
+ };
1246
+ try {
1247
+ validateStateGenerations(recovered, trustedKeys);
1248
+ } catch (error) {
1249
+ if (!preferred?.fullyVerified) throw error;
1250
+ if (
1251
+ preferred.state.highestSeenRevision !== undefined &&
1252
+ floor !== undefined &&
1253
+ preferred.state.highestSeenRevision >= floor.revision
1254
+ )
1255
+ return {
1256
+ state: preferred.state,
1257
+ stateIsVerified: true,
1258
+ highestSeenRevision: preferred.state.highestSeenRevision,
1259
+ highestSeenManifestSha256: preferred.state.highestSeenManifestSha256,
1260
+ hadInvalidCopy,
1261
+ firstRun: false,
1262
+ };
1263
+ throw error;
1264
+ }
1265
+ return {
1266
+ state: recovered,
1267
+ stateIsVerified: true,
1268
+ highestSeenRevision: floor?.revision,
1269
+ highestSeenManifestSha256: floor?.manifestSha256,
1270
+ hadInvalidCopy,
1271
+ firstRun,
1272
+ };
1273
+ }
1274
+
1275
+ function loadControlSync(agentDir: string): RegistryControl {
1276
+ const value = readJsonSync(registryPaths(agentDir).control);
1277
+ if (value === undefined) return { version: 1, disabled: false };
1278
+ if (!value || typeof value !== "object" || Array.isArray(value))
1279
+ throw new Error("Registry control state is invalid.");
1280
+ const control = value as Partial<RegistryControl>;
1281
+ if (control.version !== 1 || typeof control.disabled !== "boolean")
1282
+ throw new Error("Registry control version is invalid.");
1283
+ if (
1284
+ control.pinnedRevision !== undefined &&
1285
+ (!Number.isSafeInteger(control.pinnedRevision) || control.pinnedRevision <= 0)
1286
+ )
1287
+ throw new Error("Registry pin is invalid.");
1288
+ return { version: 1, disabled: control.disabled, pinnedRevision: control.pinnedRevision };
1289
+ }
1290
+
1291
+ async function loadControlBun(agentDir: string): Promise<RegistryControl> {
1292
+ const value = await readJsonBun(registryPaths(agentDir).control);
1293
+ if (value === undefined) return { version: 1, disabled: false };
1294
+ if (!value || typeof value !== "object" || Array.isArray(value))
1295
+ throw new Error("Registry control state is invalid.");
1296
+ const control = value as Partial<RegistryControl>;
1297
+ if (control.version !== 1 || typeof control.disabled !== "boolean")
1298
+ throw new Error("Registry control version is invalid.");
1299
+ if (
1300
+ control.pinnedRevision !== undefined &&
1301
+ (!Number.isSafeInteger(control.pinnedRevision) || control.pinnedRevision <= 0)
1302
+ )
1303
+ throw new Error("Registry pin is invalid.");
1304
+ return { version: 1, disabled: control.disabled, pinnedRevision: control.pinnedRevision };
1305
+ }
1306
+
1307
+ async function syncDirectory(directory: string): Promise<void> {
1308
+ // Windows does not expose a directory fsync barrier through Bun/Node. The
1309
+ // temporary file is still synced before atomic rename; only persistence of
1310
+ // the renamed directory entry across sudden power loss is weaker there.
1311
+ if (process.platform === "win32") return;
1312
+ const handle = await fs.open(directory, "r");
1313
+ try {
1314
+ await handle.sync();
1315
+ } finally {
1316
+ await handle.close();
1317
+ }
1318
+ }
1319
+
1320
+ async function exactFileIdentity(file: string): Promise<NativeExactFileIdentity> {
1321
+ const [bytes, stat, parent] = await Promise.all([
1322
+ fs.readFile(file),
1323
+ fs.lstat(file, { bigint: true }),
1324
+ fs.lstat(path.dirname(file), { bigint: true }),
1325
+ ]);
1326
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n)
1327
+ throw new Error("Registry cache replacement requires a stable single-link regular file.");
1328
+ return {
1329
+ dev: stat.dev,
1330
+ ino: stat.ino,
1331
+ nlink: stat.nlink,
1332
+ parentDev: parent.dev,
1333
+ parentIno: parent.ino,
1334
+ size: stat.size,
1335
+ mtimeNs: stat.mtimeNs,
1336
+ sha256: sha256(bytes),
1337
+ };
1338
+ }
1339
+
1340
+ async function writeAtomicJson(file: string, value: unknown): Promise<void> {
1341
+ await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
1342
+ const serialized = `${JSON.stringify(value)}\n`;
1343
+ if (path.basename(file) === "state.json" && serializedJsonByteLength(value) > MODEL_PRESET_REGISTRY_MAX_STATE_BYTES)
1344
+ throw new Error("Registry cache state exceeds its durable size limit.");
1345
+ const temporary = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
1346
+ const handle = await fs.open(temporary, "wx", 0o600);
1347
+ try {
1348
+ await handle.writeFile(serialized, "utf8");
1349
+ await handle.sync();
1350
+ } finally {
1351
+ await handle.close();
1352
+ }
1353
+ try {
1354
+ if (process.platform === "win32") {
1355
+ let destinationExists = true;
1356
+ try {
1357
+ await fs.lstat(file);
1358
+ } catch (error) {
1359
+ if (!isEnoent(error)) throw error;
1360
+ destinationExists = false;
1361
+ }
1362
+ if (destinationExists) {
1363
+ const [sourceIdentity, destinationIdentity] = await Promise.all([
1364
+ exactFileIdentity(temporary),
1365
+ exactFileIdentity(file),
1366
+ ]);
1367
+ const replaced = exactReplacePathNative(temporary, file, sourceIdentity, destinationIdentity);
1368
+ if (!replaced.ok)
1369
+ throw new Error(`Native Windows registry cache replacement failed: ${replaced.code ?? "unknown"}.`);
1370
+ } else {
1371
+ await fs.rename(temporary, file);
1372
+ }
1373
+ } else await fs.rename(temporary, file);
1374
+ await syncDirectory(path.dirname(file));
1375
+ } catch (error) {
1376
+ await fs.rm(temporary, { force: true }).catch(() => undefined);
1377
+ throw error;
1378
+ }
1379
+ }
1380
+
1381
+ function effectiveAgentDir(dependencies: ModelPresetRegistryDependencies): string {
1382
+ return dependencies.agentDir ?? getAgentDir();
1383
+ }
1384
+ function effectiveTrustedKeys(
1385
+ dependencies: ModelPresetRegistryDependencies,
1386
+ agentDir = effectiveAgentDir(dependencies),
1387
+ ) {
1388
+ return getModelPresetRegistryTestTrustedKeys(agentDir) ?? MODEL_PRESET_REGISTRY_TRUSTED_KEYS;
1389
+ }
1390
+ function effectiveManifestUrl(dependencies: ModelPresetRegistryDependencies): string {
1391
+ return dependencies.manifestUrl ?? process.env.GJC_MODEL_PRESET_REGISTRY_URL ?? DEFAULT_MODEL_PRESET_REGISTRY_URL;
1392
+ }
1393
+ function environmentDisabled(): boolean {
1394
+ return /^(?:1|true|yes|on)$/i.test(process.env.GJC_MODEL_PRESET_REGISTRY_DISABLED ?? "");
1395
+ }
1396
+ function assertHttpsUrl(raw: string, description: string): URL {
1397
+ let url: URL;
1398
+ try {
1399
+ url = new URL(raw);
1400
+ } catch {
1401
+ throw new Error(`${description} is invalid.`);
1402
+ }
1403
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
1404
+ throw new Error(`${description} must use credential-free HTTPS.`);
1405
+ return url;
1406
+ }
1407
+ function assertRegistryUrl(url: URL, manifestUrl: URL, allowTestUrls: boolean): void {
1408
+ if (allowTestUrls || manifestUrl.href !== DEFAULT_MODEL_PRESET_REGISTRY_URL) {
1409
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
1410
+ throw new Error("Registry content URL must use credential-free HTTPS.");
1411
+ if (url.origin !== manifestUrl.origin) throw new Error("Registry content URL changed origin.");
1412
+ return;
1413
+ }
1414
+ if (
1415
+ url.hostname !== "raw.githubusercontent.com" ||
1416
+ url.port ||
1417
+ url.search ||
1418
+ url.hash ||
1419
+ !url.pathname.startsWith(REGISTRY_RAW_PATH_PREFIX) ||
1420
+ url.origin !== manifestUrl.origin
1421
+ )
1422
+ throw new Error("Registry content URL is outside the approved immutable registry namespace.");
1423
+ }
1424
+ function descriptorUrl(descriptor: z.infer<typeof DescriptorSchema>, manifestUrl: URL, allowTestUrls: boolean): URL {
1425
+ const url = new URL(descriptor.path, manifestUrl);
1426
+ assertRegistryUrl(url, manifestUrl, allowTestUrls);
1427
+ return url;
1428
+ }
1429
+
1430
+ function generationProfiles(generation: AcceptedGeneration): Map<string, ModelProfileDefinition> {
1431
+ const definitions = [...generation.retainedProfiles, ...generation.profiles.profiles];
1432
+ return new Map(
1433
+ definitions.map(profile => [
1434
+ profile.id,
1435
+ {
1436
+ name: profile.id,
1437
+ displayName: profile.displayName,
1438
+ providerGroup: profile.providerGroup,
1439
+ requiredProviders: [...profile.requiredProviders],
1440
+ alternativeProviderGroups: profile.alternativeProviderGroups?.map(group => [...group]),
1441
+ modelMapping: { ...profile.roleBindings } as Partial<Record<ModelProfileRole, string | string[]>>,
1442
+ source: "registry" as const,
1443
+ },
1444
+ ]),
1445
+ );
1446
+ }
1447
+ function generationPresets(generation: AcceptedGeneration): Model<Api>[] {
1448
+ return [...generation.retainedPresets, ...generation.presets.presets].map(preset => ({ ...preset }) as Model<Api>);
1449
+ }
1450
+
1451
+ function acceptedRegistryFromState(
1452
+ agentDir: string,
1453
+ dependencies: Omit<ModelPresetRegistryDependencies, "agentDir">,
1454
+ control: RegistryControl,
1455
+ state: RegistryState,
1456
+ ): AcceptedModelPresetRegistry {
1457
+ state = recoverStateForRead(state, effectiveTrustedKeys(dependencies, agentDir));
1458
+ const pinnedGeneration =
1459
+ control.pinnedRevision === undefined
1460
+ ? undefined
1461
+ : state.history.find(item => item.manifest.signed.registryRevision === control.pinnedRevision);
1462
+ const staleControlPin =
1463
+ control.pinnedRevision !== undefined && (pinnedGeneration === undefined || pinnedGeneration.revoked === true);
1464
+ const revision = staleControlPin ? state.activeRevision : (control.pinnedRevision ?? state.activeRevision);
1465
+ const generation = state.history.find(item => item.manifest.signed.registryRevision === revision);
1466
+ if (revision !== undefined && !generation)
1467
+ throw new Error(`Registry selected revision ${revision} is missing from accepted history.`);
1468
+ if (!generation)
1469
+ return {
1470
+ profiles: new Map(),
1471
+ presets: [],
1472
+ dynamicProviders: [],
1473
+ retainedProfiles: [],
1474
+ retainedPresets: [],
1475
+ disabled: false,
1476
+ pinnedRevision: staleControlPin ? undefined : control.pinnedRevision,
1477
+ };
1478
+ const valid = validateGeneration(generation, effectiveTrustedKeys(dependencies, agentDir));
1479
+ return {
1480
+ profiles: generationProfiles(valid),
1481
+ presets: generationPresets(valid),
1482
+ dynamicProviders: [...new Set([...valid.retainedDynamicProviders, ...valid.profiles.dynamicProviders])],
1483
+ revision: valid.manifest.signed.registryRevision,
1484
+ revisionId: valid.manifest.signed.revision,
1485
+ manifestSha256: valid.manifestSha256,
1486
+ keyId: valid.manifest.signature.keyId,
1487
+ sourceRevision: valid.manifest.signed.provenance.sourceRevision,
1488
+ retainedProfiles: valid.retainedProfiles.map(profile => profile.id),
1489
+ retainedPresets: valid.retainedPresets.map(preset => `${preset.provider}/${preset.id}`),
1490
+ disabled: false,
1491
+ pinnedRevision: staleControlPin ? undefined : control.pinnedRevision,
1492
+ };
1493
+ }
1494
+
1495
+ export function loadAcceptedModelPresetRegistry(
1496
+ agentDir = getAgentDir(),
1497
+ dependencies: Omit<ModelPresetRegistryDependencies, "agentDir"> = {},
1498
+ ): AcceptedModelPresetRegistry {
1499
+ if (environmentDisabled())
1500
+ return {
1501
+ profiles: new Map(),
1502
+ presets: [],
1503
+ dynamicProviders: [],
1504
+ retainedProfiles: [],
1505
+ retainedPresets: [],
1506
+ disabled: true,
1507
+ };
1508
+ let control: RegistryControl;
1509
+ try {
1510
+ control = loadControlSync(agentDir);
1511
+ } catch (error) {
1512
+ return {
1513
+ profiles: new Map(),
1514
+ presets: [],
1515
+ dynamicProviders: [],
1516
+ retainedProfiles: [],
1517
+ retainedPresets: [],
1518
+ error: safeError(error),
1519
+ disabled: environmentDisabled(),
1520
+ };
1521
+ }
1522
+ const disabled = control.disabled || environmentDisabled();
1523
+ if (disabled)
1524
+ return {
1525
+ profiles: new Map(),
1526
+ presets: [],
1527
+ dynamicProviders: [],
1528
+ retainedProfiles: [],
1529
+ retainedPresets: [],
1530
+ disabled: true,
1531
+ pinnedRevision: control.pinnedRevision,
1532
+ };
1533
+ try {
1534
+ const recovery = recoverStateCopiesSync(agentDir, effectiveTrustedKeys(dependencies, agentDir));
1535
+ return acceptedRegistryFromState(agentDir, dependencies, control, recovery.state);
1536
+ } catch (error) {
1537
+ return {
1538
+ profiles: new Map(),
1539
+ presets: [],
1540
+ dynamicProviders: [],
1541
+ retainedProfiles: [],
1542
+ retainedPresets: [],
1543
+ error: safeError(error),
1544
+ disabled: false,
1545
+ pinnedRevision: control.pinnedRevision,
1546
+ };
1547
+ }
1548
+ }
1549
+
1550
+ /**
1551
+ * Async accepted-registry loader for validation paths that can await disk I/O.
1552
+ * It mirrors the synchronous loader's fail-closed result contract while using
1553
+ * Bun file reads for state/control payloads.
1554
+ */
1555
+ export async function loadAcceptedModelPresetRegistryAsync(
1556
+ agentDir = getAgentDir(),
1557
+ dependencies: Omit<ModelPresetRegistryDependencies, "agentDir"> = {},
1558
+ ): Promise<AcceptedModelPresetRegistry> {
1559
+ if (environmentDisabled())
1560
+ return {
1561
+ profiles: new Map(),
1562
+ presets: [],
1563
+ dynamicProviders: [],
1564
+ retainedProfiles: [],
1565
+ retainedPresets: [],
1566
+ disabled: true,
1567
+ };
1568
+ let control: RegistryControl;
1569
+ try {
1570
+ control = await loadControlBun(agentDir);
1571
+ } catch (error) {
1572
+ return {
1573
+ profiles: new Map(),
1574
+ presets: [],
1575
+ dynamicProviders: [],
1576
+ retainedProfiles: [],
1577
+ retainedPresets: [],
1578
+ error: safeError(error),
1579
+ disabled: environmentDisabled(),
1580
+ };
1581
+ }
1582
+ const disabled = control.disabled || environmentDisabled();
1583
+ if (disabled)
1584
+ return {
1585
+ profiles: new Map(),
1586
+ presets: [],
1587
+ dynamicProviders: [],
1588
+ retainedProfiles: [],
1589
+ retainedPresets: [],
1590
+ disabled: true,
1591
+ pinnedRevision: control.pinnedRevision,
1592
+ };
1593
+ try {
1594
+ const recovery = recoverStateCopiesSync(agentDir, effectiveTrustedKeys(dependencies, agentDir));
1595
+ return acceptedRegistryFromState(agentDir, dependencies, control, recovery.state);
1596
+ } catch (error) {
1597
+ return {
1598
+ profiles: new Map(),
1599
+ presets: [],
1600
+ dynamicProviders: [],
1601
+ retainedProfiles: [],
1602
+ retainedPresets: [],
1603
+ error: safeError(error),
1604
+ disabled: false,
1605
+ pinnedRevision: control.pinnedRevision,
1606
+ };
1607
+ }
1608
+ }
1609
+
1610
+ export function loadAcceptedModelPresetProfiles(
1611
+ agentDir = getAgentDir(),
1612
+ dependencies: Omit<ModelPresetRegistryDependencies, "agentDir"> = {},
1613
+ ): AcceptedModelPresetRegistry {
1614
+ return loadAcceptedModelPresetRegistry(agentDir, dependencies);
1615
+ }
1616
+
1617
+ export function loadEffectiveModelProfiles(
1618
+ userProfiles: ModelsConfig["profiles"] | undefined,
1619
+ agentDir = getAgentDir(),
1620
+ dependencies: Omit<ModelPresetRegistryDependencies, "agentDir"> = {},
1621
+ ): Map<string, ModelProfileDefinition> {
1622
+ const accepted = loadAcceptedModelPresetRegistry(agentDir, dependencies);
1623
+ return mergeModelProfiles(userProfiles, accepted.profiles);
1624
+ }
1625
+
1626
+ async function readBoundedResponse(response: Response, maxBytes: number, description: string): Promise<Uint8Array> {
1627
+ const declaredLength = response.headers.get("content-length");
1628
+ if (declaredLength !== null && Number(declaredLength) > maxBytes)
1629
+ throw new Error(`${description} exceeds the byte limit.`);
1630
+ if (!response.body) return new Uint8Array();
1631
+ const reader = response.body.getReader();
1632
+ const chunks: Uint8Array[] = [];
1633
+ let total = 0;
1634
+ for (;;) {
1635
+ const { done, value } = await reader.read();
1636
+ if (done) break;
1637
+ total += value.byteLength;
1638
+ if (total > maxBytes) {
1639
+ await reader.cancel();
1640
+ throw new Error(`${description} exceeds the byte limit.`);
1641
+ }
1642
+ chunks.push(value);
1643
+ }
1644
+ const result = new Uint8Array(total);
1645
+ let offset = 0;
1646
+ for (const chunk of chunks) {
1647
+ result.set(chunk, offset);
1648
+ offset += chunk.byteLength;
1649
+ }
1650
+ return result;
1651
+ }
1652
+
1653
+ async function boundedFetch(
1654
+ url: URL,
1655
+ maxBytes: number,
1656
+ dependencies: ModelPresetRegistryDependencies,
1657
+ headers: Record<string, string> = {},
1658
+ ): Promise<{ response: Response; bytes?: Uint8Array }> {
1659
+ assertHttpsUrl(url.href, "Registry URL");
1660
+ const controller = new AbortController();
1661
+ const timeout = setTimeout(
1662
+ () => controller.abort(new Error("Registry request timed out.")),
1663
+ dependencies.timeoutMs ?? MODEL_PRESET_REGISTRY_FETCH_TIMEOUT_MS,
1664
+ );
1665
+ try {
1666
+ const response = await (dependencies.fetch ?? fetch)(url.href, {
1667
+ method: "GET",
1668
+ headers,
1669
+ redirect: "error",
1670
+ credentials: "omit",
1671
+ signal: controller.signal,
1672
+ });
1673
+ if (response.url && assertHttpsUrl(response.url, "Registry response URL").href !== url.href)
1674
+ throw new Error("Registry response URL changed unexpectedly.");
1675
+ if (response.status === 304) return { response };
1676
+ if (!response.ok) throw new Error(`Registry request failed with HTTP ${response.status}.`);
1677
+ return { response, bytes: await readBoundedResponse(response, maxBytes, descriptionForUrl(url)) };
1678
+ } finally {
1679
+ clearTimeout(timeout);
1680
+ }
1681
+ }
1682
+ function descriptionForUrl(url: URL): string {
1683
+ return `Registry ${path.posix.basename(url.pathname) || "response"}`;
1684
+ }
1685
+
1686
+ interface RetainedRegistryEntries {
1687
+ retainedProfiles: ModelPresetRegistryProfiles["profiles"];
1688
+ retainedPresets: ModelPresetRegistryPresets["presets"];
1689
+ retainedDynamicProviders: string[];
1690
+ }
1691
+
1692
+ interface RegistryStateCandidate {
1693
+ retained: RetainedRegistryEntries;
1694
+ nextState: RegistryState;
1695
+ }
1696
+
1697
+ function retainRemoved(
1698
+ previous: AcceptedGeneration | undefined,
1699
+ profiles: ModelPresetRegistryProfiles,
1700
+ presets: ModelPresetRegistryPresets,
1701
+ ): RetainedRegistryEntries {
1702
+ if (!previous) return { retainedProfiles: [], retainedPresets: [], retainedDynamicProviders: [] };
1703
+ const nextProfiles = new Set(profiles.profiles.map(profile => profile.id));
1704
+ const previousProfilesById = new Map(
1705
+ [...previous.retainedProfiles, ...previous.profiles.profiles].map(profile => [profile.id, profile]),
1706
+ );
1707
+ const nextProfilesById = new Map(profiles.profiles.map(profile => [profile.id, profile]));
1708
+ const retainedProfiles = [...previousProfilesById.values()].filter(profile => !nextProfiles.has(profile.id));
1709
+ const previousPresetSelectors = new Set(
1710
+ [...previous.retainedPresets, ...previous.presets.presets].flatMap(preset => [
1711
+ `${preset.provider}/${preset.id}`,
1712
+ preset.id,
1713
+ ]),
1714
+ );
1715
+ const retainedSelectors = new Set<string>();
1716
+ const retainedSelectorProviders = new Set<string>();
1717
+ const profilesWhosePreviousSelectorsMustRemain = [
1718
+ ...retainedProfiles,
1719
+ ...previous.profiles.profiles.filter(profile => {
1720
+ const replacement = nextProfilesById.get(profile.id);
1721
+ return (
1722
+ replacement !== undefined &&
1723
+ canonicalModelPresetRegistryJson(replacement.roleBindings) !==
1724
+ canonicalModelPresetRegistryJson(profile.roleBindings)
1725
+ );
1726
+ }),
1727
+ ];
1728
+ for (const profile of profilesWhosePreviousSelectorsMustRemain) {
1729
+ for (const binding of Object.values(profile.roleBindings)) {
1730
+ for (const selector of Array.isArray(binding) ? binding : [binding]) {
1731
+ const identities = selectorIdentityCandidates(selector);
1732
+ const retentionIdentities =
1733
+ identities.length === 2 && previousPresetSelectors.has(identities[0]!)
1734
+ ? [identities[0]!]
1735
+ : [identities[identities.length - 1]!];
1736
+ for (const identity of retentionIdentities) {
1737
+ retainedSelectors.add(identity);
1738
+ const slash = identity.indexOf("/");
1739
+ if (slash >= 0) retainedSelectorProviders.add(identity.slice(0, slash));
1740
+ }
1741
+ }
1742
+ }
1743
+ }
1744
+ for (const preset of previous.retainedPresets) retainedSelectors.add(`${preset.provider}/${preset.id}`);
1745
+ const nextPresets = new Set(presets.presets.map(preset => `${preset.provider}\u0000${preset.id}`));
1746
+ const previousPresetsByKey = new Map(
1747
+ [...previous.retainedPresets, ...previous.presets.presets].map(preset => [
1748
+ `${preset.provider}\u0000${preset.id}`,
1749
+ preset,
1750
+ ]),
1751
+ );
1752
+ return {
1753
+ retainedProfiles,
1754
+ retainedDynamicProviders: [
1755
+ ...new Set([...previous.retainedDynamicProviders, ...previous.profiles.dynamicProviders]),
1756
+ ].filter(provider => !profiles.dynamicProviders.includes(provider) && retainedSelectorProviders.has(provider)),
1757
+ retainedPresets: [...previousPresetsByKey.entries()]
1758
+ .filter(([key, preset]) => {
1759
+ if (nextPresets.has(key)) return false;
1760
+ return retainedSelectors.has(`${preset.provider}/${preset.id}`) || retainedSelectors.has(preset.id);
1761
+ })
1762
+ .map(([, preset]) => preset),
1763
+ };
1764
+ }
1765
+
1766
+ function validateStateGenerations(
1767
+ state: RegistryState,
1768
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
1769
+ ): void {
1770
+ const byRevision = new Map<number, AcceptedGeneration>();
1771
+ for (const generation of state.history) {
1772
+ const revision = generation.manifest.signed.registryRevision;
1773
+ if (byRevision.has(revision)) throw new Error(`Registry history contains duplicate revision ${revision}.`);
1774
+ byRevision.set(revision, generation);
1775
+ }
1776
+ const highestHistoryRevision = [...byRevision.keys()].reduce((highest, revision) => Math.max(highest, revision), 0);
1777
+ if (state.activeRevision !== undefined && !byRevision.has(state.activeRevision))
1778
+ throw new Error(`Registry selected revision ${state.activeRevision} is missing from accepted history.`);
1779
+ if (state.activeRevision !== undefined && byRevision.get(state.activeRevision)?.revoked)
1780
+ throw new Error(`Registry selected revision ${state.activeRevision} is revoked.`);
1781
+ if (state.highestSeenRevision === undefined) {
1782
+ if (highestHistoryRevision > 0 || state.highestSeenManifestSha256 !== undefined)
1783
+ throw new Error("Registry highest-seen manifest checkpoint is incomplete.");
1784
+ } else {
1785
+ if (state.highestSeenManifestSha256 === undefined)
1786
+ throw new Error("Registry highest-seen manifest checkpoint is incomplete.");
1787
+ if (state.highestSeenRevision < highestHistoryRevision)
1788
+ throw new Error("Registry highest-seen revision is older than accepted history.");
1789
+ const checkpoint = byRevision.get(state.highestSeenRevision);
1790
+ if (!checkpoint || checkpoint.manifestSha256 !== state.highestSeenManifestSha256)
1791
+ throw new Error("Registry highest-seen manifest checkpoint is not bound to accepted history.");
1792
+ }
1793
+ const validated = new Set<number>();
1794
+ const visiting = new Set<number>();
1795
+ const visit = (generation: AcceptedGeneration, ancestryDepth = 0): void => {
1796
+ if (ancestryDepth > MODEL_PRESET_REGISTRY_MAX_RETENTION_ANCESTRY)
1797
+ throw new Error("Registry retained provenance ancestry exceeds its bound.");
1798
+ const revision = generation.manifest.signed.registryRevision;
1799
+ if (validated.has(revision)) return;
1800
+ if (visiting.has(revision)) throw new Error("Registry retained provenance contains a cycle.");
1801
+ visiting.add(revision);
1802
+ validateGeneration(generation, trustedKeys, generation.revoked === true);
1803
+ if (generation.revoked && !trustedKeys.get(generation.manifest.signature.keyId)?.revokedAt)
1804
+ throw new Error(`Registry revoked generation key is not revoked: ${generation.manifest.signature.keyId}.`);
1805
+ const hasRetained =
1806
+ generation.retainedProfiles.length > 0 ||
1807
+ generation.retainedPresets.length > 0 ||
1808
+ generation.retainedDynamicProviders.length > 0;
1809
+ if (hasRetained !== (generation.retainedFromRevision !== undefined))
1810
+ throw new Error("Registry retained provenance binding is invalid.");
1811
+ if (generation.retainedFromRevision !== undefined) {
1812
+ const source = byRevision.get(generation.retainedFromRevision);
1813
+ if (!source) throw new Error("Registry retained provenance source is missing.");
1814
+ if (source.manifest.signed.registryRevision >= revision)
1815
+ throw new Error("Registry retained provenance source is not older than its consumer.");
1816
+ visit(source, ancestryDepth + 1);
1817
+ const expected = retainRemoved(source, generation.profiles, generation.presets);
1818
+ if (
1819
+ canonicalModelPresetRegistryJson(expected.retainedProfiles) !==
1820
+ canonicalModelPresetRegistryJson(generation.retainedProfiles) ||
1821
+ canonicalModelPresetRegistryJson(expected.retainedPresets) !==
1822
+ canonicalModelPresetRegistryJson(generation.retainedPresets) ||
1823
+ canonicalModelPresetRegistryJson(expected.retainedDynamicProviders) !==
1824
+ canonicalModelPresetRegistryJson(generation.retainedDynamicProviders)
1825
+ )
1826
+ throw new Error("Registry retained provenance content is invalid.");
1827
+ }
1828
+ visiting.delete(revision);
1829
+ validated.add(revision);
1830
+ };
1831
+ for (const generation of state.history) visit(generation);
1832
+ }
1833
+
1834
+ function retainedAncestryDepth(generation: AcceptedGeneration | undefined, state: RegistryState): number {
1835
+ if (!generation) return 0;
1836
+ const byRevision = new Map(state.history.map(candidate => [candidate.manifest.signed.registryRevision, candidate]));
1837
+ let depth = 0;
1838
+ let current: AcceptedGeneration | undefined = generation;
1839
+ const seen = new Set<number>();
1840
+ while (current?.retainedFromRevision !== undefined) {
1841
+ if (seen.has(current.retainedFromRevision)) return MODEL_PRESET_REGISTRY_MAX_RETENTION_ANCESTRY + 1;
1842
+ seen.add(current.retainedFromRevision);
1843
+ depth++;
1844
+ current = byRevision.get(current.retainedFromRevision);
1845
+ }
1846
+ return depth;
1847
+ }
1848
+
1849
+ function withoutRetainedEntries(generation: AcceptedGeneration): AcceptedGeneration {
1850
+ return {
1851
+ ...generation,
1852
+ retainedProfiles: [],
1853
+ retainedPresets: [],
1854
+ retainedDynamicProviders: [],
1855
+ retainedFromRevision: undefined,
1856
+ };
1857
+ }
1858
+
1859
+ function recoverLatestVerifiedGeneration(
1860
+ state: RegistryState,
1861
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
1862
+ ): AcceptedGeneration | undefined {
1863
+ let latest: AcceptedGeneration | undefined;
1864
+ for (const generation of state.history) {
1865
+ try {
1866
+ const verified = validateGeneration(generation, trustedKeys);
1867
+ if (!latest || verified.manifest.signed.registryRevision > latest.manifest.signed.registryRevision)
1868
+ latest = verified;
1869
+ } catch {
1870
+ // A recovery checkpoint must come from a complete, independently verified generation.
1871
+ }
1872
+ }
1873
+ return latest;
1874
+ }
1875
+
1876
+ function recoverRevokedGenerations(
1877
+ state: RegistryState,
1878
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
1879
+ ): AcceptedGeneration[] {
1880
+ const generations = new Map<number, AcceptedGeneration>();
1881
+ for (const generation of state.history) {
1882
+ const key = trustedKeys.get(generation.manifest.signature.keyId);
1883
+ if (!key?.revokedAt) continue;
1884
+ try {
1885
+ const verified = validateGeneration(generation, trustedKeys, true);
1886
+ generations.set(verified.manifest.signed.registryRevision, { ...verified, revoked: true });
1887
+ } catch {
1888
+ // A revoked generation can be retained only when its signed content and bindings remain intact.
1889
+ }
1890
+ }
1891
+ return [...generations.values()].sort(
1892
+ (left, right) => right.manifest.signed.registryRevision - left.manifest.signed.registryRevision,
1893
+ );
1894
+ }
1895
+
1896
+ function recoveryStateFromGeneration(
1897
+ generation: AcceptedGeneration | undefined,
1898
+ revokedHistory: readonly AcceptedGeneration[] = [],
1899
+ priorState?: RegistryState,
1900
+ ): RegistryState {
1901
+ const history = [...(generation ? [generation] : []), ...revokedHistory].sort(
1902
+ (left, right) => right.manifest.signed.registryRevision - left.manifest.signed.registryRevision,
1903
+ );
1904
+ const activeRevision = generation?.manifest.signed.registryRevision;
1905
+ const priorFloor = priorState?.highestSeenRevision;
1906
+ const priorFloorGeneration =
1907
+ priorFloor === undefined
1908
+ ? undefined
1909
+ : history.find(
1910
+ item =>
1911
+ item.manifest.signed.registryRevision === priorFloor &&
1912
+ item.manifestSha256 === priorState?.highestSeenManifestSha256 &&
1913
+ item.revoked === true,
1914
+ );
1915
+ const floor =
1916
+ priorFloorGeneration && (priorFloor ?? 0) > (activeRevision ?? 0)
1917
+ ? priorFloorGeneration
1918
+ : generation
1919
+ ? { manifest: generation.manifest, manifestSha256: generation.manifestSha256 }
1920
+ : priorFloorGeneration;
1921
+ const boundedHistory = history.slice(0, MODEL_PRESET_REGISTRY_MAX_HISTORY);
1922
+ if (priorFloor !== undefined && !priorFloorGeneration && priorFloor > (activeRevision ?? 0)) {
1923
+ return {
1924
+ version: 1,
1925
+ activeRevision,
1926
+ highestSeenRevision: priorFloor,
1927
+ highestSeenManifestSha256: priorState?.highestSeenManifestSha256,
1928
+ history: boundedHistory,
1929
+ };
1930
+ }
1931
+ if (
1932
+ floor &&
1933
+ !boundedHistory.some(item => item.manifest.signed.registryRevision === floor.manifest.signed.registryRevision)
1934
+ )
1935
+ boundedHistory.push(
1936
+ history.find(item => item.manifest.signed.registryRevision === floor.manifest.signed.registryRevision)!,
1937
+ );
1938
+ if (!floor) return { version: 1, history: boundedHistory };
1939
+ return {
1940
+ version: 1,
1941
+ activeRevision,
1942
+ highestSeenRevision: floor.manifest.signed.registryRevision,
1943
+ highestSeenManifestSha256: floor.manifestSha256,
1944
+ history: boundedHistory,
1945
+ };
1946
+ }
1947
+
1948
+ function recoverStateForRead(
1949
+ state: RegistryState,
1950
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
1951
+ ): RegistryState {
1952
+ try {
1953
+ validateStateGenerations(state, trustedKeys);
1954
+ return state;
1955
+ } catch (error) {
1956
+ const hasRevokedGeneration = state.history.some(
1957
+ generation => trustedKeys.get(generation.manifest.signature.keyId)?.revokedAt !== undefined,
1958
+ );
1959
+ if (!hasRevokedGeneration) throw error;
1960
+ const recovered = recoveryStateFromGeneration(
1961
+ recoverLatestVerifiedGeneration(state, trustedKeys),
1962
+ recoverRevokedGenerations(state, trustedKeys),
1963
+ state,
1964
+ );
1965
+ validateStateGenerations(recovered, trustedKeys);
1966
+ return recovered;
1967
+ }
1968
+ }
1969
+
1970
+ function preserveRawStateFloor(state: RegistryState, copies: readonly ParsedRegistryState[]): RegistryState {
1971
+ const rawFloors = copies
1972
+ .map(copy => copy.state)
1973
+ .filter(
1974
+ (candidate): candidate is RegistryState =>
1975
+ candidate !== undefined && candidate.highestSeenRevision !== undefined,
1976
+ )
1977
+ .sort((left, right) => (right.highestSeenRevision ?? 0) - (left.highestSeenRevision ?? 0));
1978
+ const rawFloor = rawFloors[0];
1979
+ if (
1980
+ rawFloor?.highestSeenRevision !== undefined &&
1981
+ (rawFloor.highestSeenRevision > (state.highestSeenRevision ?? 0) || state.highestSeenRevision === undefined)
1982
+ )
1983
+ return {
1984
+ ...state,
1985
+ highestSeenRevision: rawFloor.highestSeenRevision,
1986
+ highestSeenManifestSha256: rawFloor.highestSeenManifestSha256,
1987
+ };
1988
+ return state;
1989
+ }
1990
+
1991
+ async function writeFailureDiagnostic(
1992
+ paths: { failure: string },
1993
+ error: unknown,
1994
+ now: Date,
1995
+ canWrite: () => boolean = () => true,
1996
+ ): Promise<void> {
1997
+ if (!canWrite()) return;
1998
+ await writeAtomicJson(paths.failure, {
1999
+ version: 1,
2000
+ lastCheckedAt: now.toISOString(),
2001
+ lastError: safeError(error),
2002
+ });
2003
+ }
2004
+
2005
+ function readFailureDiagnostic(agentDir: string): string | undefined {
2006
+ try {
2007
+ const value = readJsonSync(registryPaths(agentDir).failure);
2008
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
2009
+ const diagnostic = value as { version?: unknown; lastError?: unknown };
2010
+ if (diagnostic.version !== 1 || typeof diagnostic.lastError !== "string") return undefined;
2011
+ return safeError(diagnostic.lastError);
2012
+ } catch {
2013
+ return undefined;
2014
+ }
2015
+ }
2016
+
2017
+ async function recordFailure(
2018
+ agentDir: string,
2019
+ error: unknown,
2020
+ now: Date,
2021
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
2022
+ canWrite: () => boolean = () => true,
2023
+ ): Promise<void> {
2024
+ if (!canWrite()) return;
2025
+ const paths = registryPaths(agentDir);
2026
+ const writeFailure = async (): Promise<void> => {
2027
+ await writeFailureDiagnostic(paths, error, now, canWrite);
2028
+ };
2029
+ await withFileLock(
2030
+ paths.transaction,
2031
+ async () => {
2032
+ if (!canWrite()) return;
2033
+ let control: RegistryControl = { version: 1, disabled: false };
2034
+ let controlIsValid = true;
2035
+ try {
2036
+ control = loadControlSync(agentDir);
2037
+ } catch {
2038
+ controlIsValid = false;
2039
+ }
2040
+ const copies = [readParsedStateSync(paths.state), readParsedStateSync(paths.backup)];
2041
+ if (!copies.some(copy => copy.present)) {
2042
+ await writeFailure();
2043
+ return;
2044
+ }
2045
+ let recovery: RegistryStateRecovery | undefined;
2046
+ try {
2047
+ recovery = recoverStateCopiesSync(agentDir, trustedKeys, {
2048
+ allowInvalidPrimaryFallback: true,
2049
+ allowUnreadablePrimaryFallback: true,
2050
+ });
2051
+ } catch {
2052
+ // Never replace a copy that could not be independently read. Keep the
2053
+ // failure diagnostic separate so a later refresh remains fail-closed.
2054
+ if (copies.some(copy => copy.error !== undefined)) {
2055
+ await writeFailure();
2056
+ return;
2057
+ }
2058
+ const backupEvidence = inspectStateEvidence(copies[1], trustedKeys);
2059
+ if (backupEvidence?.fullyVerified) {
2060
+ recovery = {
2061
+ state: backupEvidence.state,
2062
+ stateIsVerified: true,
2063
+ highestSeenRevision: backupEvidence.floor?.revision,
2064
+ highestSeenManifestSha256: backupEvidence.floor?.manifestSha256,
2065
+ hadInvalidCopy: true,
2066
+ firstRun: false,
2067
+ };
2068
+ }
2069
+ }
2070
+ if (!recovery) {
2071
+ const primaryEvidence = inspectStateEvidence(copies[0], trustedKeys);
2072
+ if (primaryEvidence && (!primaryEvidence.allGenerationsVerified || primaryEvidence.duplicateRevision)) {
2073
+ await writeFailure();
2074
+ return;
2075
+ }
2076
+ }
2077
+ const backupEvidence = inspectStateEvidence(copies[1], trustedKeys);
2078
+ if (recovery?.hadInvalidCopy && backupEvidence?.fullyVerified) {
2079
+ recovery = {
2080
+ ...recovery,
2081
+ state: backupEvidence.state,
2082
+ highestSeenRevision: backupEvidence.floor?.revision,
2083
+ highestSeenManifestSha256: backupEvidence.floor?.manifestSha256,
2084
+ };
2085
+ }
2086
+ let state = recovery?.state ?? copies.find(copy => copy.state)?.state;
2087
+ if (!state) {
2088
+ await writeFailure();
2089
+ return;
2090
+ }
2091
+ if (recovery?.firstRun) {
2092
+ await writeFailure();
2093
+ return;
2094
+ }
2095
+ state = preserveRawStateFloor(state, copies);
2096
+ if (recovery?.hadInvalidCopy || !controlIsValid || !recovery) {
2097
+ // An unreadable control file cannot safely preserve a stale pin or disablement.
2098
+ if (canWrite())
2099
+ await writeAtomicJson(
2100
+ paths.control,
2101
+ controlIsValid ? { ...control, pinnedRevision: undefined } : { version: 1, disabled: false },
2102
+ );
2103
+ }
2104
+ const failedState = {
2105
+ ...state,
2106
+ lastCheckedAt: now.toISOString(),
2107
+ lastError: safeError(error),
2108
+ };
2109
+ await writeRegistryState(paths, failedState, canWrite);
2110
+ },
2111
+ { retries: 20, retryDelayMs: 50, staleMs: 30_000 },
2112
+ );
2113
+ }
2114
+
2115
+ class RefreshCancelledError extends Error {
2116
+ constructor() {
2117
+ super("Model preset registry refresh was cancelled.");
2118
+ this.name = "RefreshCancelledError";
2119
+ }
2120
+ }
2121
+
2122
+ function isRefreshCancelled(error: unknown): boolean {
2123
+ return error instanceof RefreshCancelledError;
2124
+ }
2125
+
2126
+ interface RefreshFlightConsumer {
2127
+ cancelled: boolean;
2128
+ }
2129
+
2130
+ interface RefreshFlight {
2131
+ promise: Promise<ModelPresetRegistryRefreshResult>;
2132
+ consumers: Set<RefreshFlightConsumer>;
2133
+ }
2134
+
2135
+ const refreshSingleFlight = new Map<string, RefreshFlight>();
2136
+ const registryChangeListeners = new Map<string, Set<() => void>>();
2137
+ const refreshDependencyIds = new WeakMap<object, number>();
2138
+ let nextRefreshDependencyId = 1;
2139
+
2140
+ function subscribeRegistryChanges(agentDir: string, listener: () => void): () => void {
2141
+ let listeners = registryChangeListeners.get(agentDir);
2142
+ if (!listeners) {
2143
+ listeners = new Set();
2144
+ registryChangeListeners.set(agentDir, listeners);
2145
+ }
2146
+ listeners.add(listener);
2147
+ return () => {
2148
+ listeners?.delete(listener);
2149
+ if (listeners?.size === 0) registryChangeListeners.delete(agentDir);
2150
+ };
2151
+ }
2152
+
2153
+ function notifyRegistryChanges(agentDir: string): void {
2154
+ for (const listener of registryChangeListeners.get(agentDir) ?? []) listener();
2155
+ }
2156
+
2157
+ function refreshDependencyId(value: object): number {
2158
+ const existing = refreshDependencyIds.get(value);
2159
+ if (existing !== undefined) return existing;
2160
+ const id = nextRefreshDependencyId++;
2161
+ refreshDependencyIds.set(value, id);
2162
+ return id;
2163
+ }
2164
+
2165
+ function refreshSingleFlightKey(dependencies: ModelPresetRegistryDependencies, agentDir: string): string {
2166
+ const trustedKeys = getModelPresetRegistryTestTrustedKeys(agentDir) ?? MODEL_PRESET_REGISTRY_TRUSTED_KEYS;
2167
+ return [
2168
+ agentDir,
2169
+ effectiveManifestUrl(dependencies),
2170
+ refreshDependencyId((dependencies.fetch ?? fetch) as object),
2171
+ refreshDependencyId(trustedKeys as object),
2172
+ dependencies.now ? refreshDependencyId(dependencies.now) : 0,
2173
+ modelPresetRegistryTestUrlsAllowed(agentDir) ? 1 : 0,
2174
+ dependencies.timeoutMs ?? "",
2175
+ dependencies.maxManifestBytes ?? "",
2176
+ dependencies.maxSnapshotBytes ?? "",
2177
+ dependencies.maxProfilesBytes ?? "",
2178
+ dependencies.maxPresetsBytes ?? "",
2179
+ dependencies.maxStateBytes ?? "",
2180
+ dependencies.knownManifestSha256 ?? "",
2181
+ ].join("\u0000");
2182
+ }
2183
+
2184
+ export async function refreshModelPresetRegistry(
2185
+ dependencies: ModelPresetRegistryDependencies = {},
2186
+ ): Promise<ModelPresetRegistryRefreshResult> {
2187
+ return getRefreshFlight(dependencies).promise;
2188
+ }
2189
+
2190
+ function refreshCanWrite(flight: RefreshFlight): boolean {
2191
+ for (const consumer of flight.consumers) {
2192
+ if (!consumer.cancelled) return true;
2193
+ }
2194
+ return false;
2195
+ }
2196
+
2197
+ function attachRefreshFlightConsumer(flight: RefreshFlight, consumer: RefreshFlightConsumer): void {
2198
+ flight.consumers.add(consumer);
2199
+ void flight.promise.then(
2200
+ () => flight.consumers.delete(consumer),
2201
+ () => flight.consumers.delete(consumer),
2202
+ );
2203
+ }
2204
+
2205
+ function getRefreshFlight(
2206
+ dependencies: ModelPresetRegistryDependencies,
2207
+ consumer?: RefreshFlightConsumer,
2208
+ ): RefreshFlight {
2209
+ const agentDir = effectiveAgentDir(dependencies);
2210
+ const flightKey = refreshSingleFlightKey(dependencies, agentDir);
2211
+ const existing = refreshSingleFlight.get(flightKey);
2212
+ if (existing) {
2213
+ if (consumer) attachRefreshFlightConsumer(existing, consumer);
2214
+ return existing;
2215
+ }
2216
+ const flight: RefreshFlight = {
2217
+ promise: undefined as unknown as Promise<ModelPresetRegistryRefreshResult>,
2218
+ consumers: new Set<RefreshFlightConsumer>(),
2219
+ };
2220
+ if (consumer) flight.consumers.add(consumer);
2221
+ else flight.consumers.add({ cancelled: false });
2222
+ const promise = refreshModelPresetRegistryInner({ ...dependencies, agentDir }, () =>
2223
+ refreshCanWrite(flight),
2224
+ ).finally(() => {
2225
+ if (refreshSingleFlight.get(flightKey) === flight) refreshSingleFlight.delete(flightKey);
2226
+ flight.consumers.clear();
2227
+ });
2228
+ flight.promise = promise;
2229
+ refreshSingleFlight.set(flightKey, flight);
2230
+ return flight;
2231
+ }
2232
+
2233
+ async function refreshModelPresetRegistryInner(
2234
+ dependencies: ModelPresetRegistryDependencies & { agentDir: string },
2235
+ canWrite: () => boolean = () => true,
2236
+ ): Promise<ModelPresetRegistryRefreshResult> {
2237
+ const { agentDir } = dependencies;
2238
+ const paths = registryPaths(agentDir);
2239
+ const now = (dependencies.now ?? (() => new Date()))();
2240
+ const ensureWritable = (): void => {
2241
+ if (!canWrite()) throw new RefreshCancelledError();
2242
+ };
2243
+ try {
2244
+ return await withFileLock(
2245
+ paths.transaction,
2246
+ async () => {
2247
+ if (environmentDisabled()) return { status: "disabled", revision: undefined } as const;
2248
+ const allowTestUrls = modelPresetRegistryTestUrlsAllowed(agentDir);
2249
+ let control = loadControlSync(agentDir);
2250
+ if (control.disabled || environmentDisabled())
2251
+ return { status: "disabled", revision: control.pinnedRevision };
2252
+ const trustedKeys = effectiveTrustedKeys(dependencies);
2253
+ const stateRecovery = recoverStateCopiesSync(agentDir, trustedKeys, {
2254
+ allowInvalidPrimaryFallback: true,
2255
+ allowUnreadablePrimaryFallback: true,
2256
+ });
2257
+ const state = stateRecovery.state;
2258
+ const stateIsVerified = stateRecovery.stateIsVerified;
2259
+ const usableState: RegistryState = state;
2260
+ const controlPinnedGeneration =
2261
+ control.pinnedRevision === undefined
2262
+ ? undefined
2263
+ : usableState.history.find(item => item.manifest.signed.registryRevision === control.pinnedRevision);
2264
+ const staleControlPin =
2265
+ control.pinnedRevision !== undefined &&
2266
+ (controlPinnedGeneration === undefined || controlPinnedGeneration.revoked === true);
2267
+ if (staleControlPin) {
2268
+ control = { ...control, pinnedRevision: undefined };
2269
+ ensureWritable();
2270
+ await writeAtomicJson(paths.control, control);
2271
+ }
2272
+ const effectivePinnedRevision =
2273
+ stateIsVerified && !stateRecovery.hadInvalidCopy ? control.pinnedRevision : undefined;
2274
+ const trustedHighestSeenRevision = stateRecovery.highestSeenRevision;
2275
+ const trustedHighestSeenManifestSha256 = stateRecovery.highestSeenManifestSha256;
2276
+ const latest = usableState.history.reduce<AcceptedGeneration | undefined>(
2277
+ (current, item) =>
2278
+ item.revoked ||
2279
+ (current && item.manifest.signed.registryRevision <= current.manifest.signed.registryRevision)
2280
+ ? current
2281
+ : item,
2282
+ undefined,
2283
+ );
2284
+ const manifestUrl = assertHttpsUrl(effectiveManifestUrl(dependencies), "Registry manifest URL");
2285
+ assertRegistryUrl(manifestUrl, manifestUrl, allowTestUrls);
2286
+ const manifestResponse = await boundedFetch(
2287
+ manifestUrl,
2288
+ dependencies.maxManifestBytes ?? MODEL_PRESET_REGISTRY_MAX_MANIFEST_BYTES,
2289
+ dependencies,
2290
+ latest?.etag && latest.manifestUrl === manifestUrl.href ? { "If-None-Match": latest.etag } : {},
2291
+ );
2292
+ if (manifestResponse.response.status === 304) {
2293
+ const currentState = recoverStateCopiesSync(agentDir, effectiveTrustedKeys(dependencies, agentDir), {
2294
+ allowInvalidPrimaryFallback: true,
2295
+ allowUnreadablePrimaryFallback: true,
2296
+ }).state;
2297
+ const currentLatest = currentState.history.reduce<AcceptedGeneration | undefined>(
2298
+ (current, item) =>
2299
+ item.revoked ||
2300
+ (current && item.manifest.signed.registryRevision <= current.manifest.signed.registryRevision)
2301
+ ? current
2302
+ : item,
2303
+ undefined,
2304
+ );
2305
+ if (!currentLatest) throw new Error("Registry returned 304 without a verified cached generation.");
2306
+ ensureWritable();
2307
+ await writeRegistryState(paths, {
2308
+ ...currentState,
2309
+ lastCheckedAt: now.toISOString(),
2310
+ lastError: undefined,
2311
+ });
2312
+ if (
2313
+ !latest ||
2314
+ currentLatest.manifest.signed.registryRevision !== latest.manifest.signed.registryRevision ||
2315
+ currentLatest.manifestSha256 !== latest.manifestSha256 ||
2316
+ (dependencies.knownManifestSha256 !== undefined &&
2317
+ currentLatest.manifestSha256 !== dependencies.knownManifestSha256)
2318
+ )
2319
+ return {
2320
+ status: "updated",
2321
+ revision: currentLatest.manifest.signed.registryRevision,
2322
+ revisionId: currentLatest.manifest.signed.revision,
2323
+ manifestSha256: currentLatest.manifestSha256,
2324
+ retainedProfiles: currentLatest.retainedProfiles.map(profile => profile.id),
2325
+ retainedPresets: currentLatest.retainedPresets.map(preset => `${preset.provider}/${preset.id}`),
2326
+ };
2327
+ return { status: "not_modified", revision: currentLatest.manifest.signed.registryRevision };
2328
+ }
2329
+ const manifestBytes = manifestResponse.bytes ?? new Uint8Array();
2330
+ const manifest = parseCanonicalDocument(
2331
+ manifestBytes,
2332
+ "Registry manifest",
2333
+ ModelPresetRegistryManifestSchema,
2334
+ );
2335
+ verifyManifest(manifest, effectiveTrustedKeys(dependencies));
2336
+ assertManifestBindings(manifest);
2337
+ const manifestSha256 = sha256(manifestBytes);
2338
+ if (trustedHighestSeenRevision !== undefined) {
2339
+ if (manifest.signed.registryRevision < trustedHighestSeenRevision)
2340
+ throw new Error("Registry revision downgrade rejected.");
2341
+ if (
2342
+ manifest.signed.registryRevision === trustedHighestSeenRevision &&
2343
+ trustedHighestSeenManifestSha256 !== undefined &&
2344
+ manifestSha256 !== trustedHighestSeenManifestSha256
2345
+ )
2346
+ throw new Error("Registry revision equivocation rejected.");
2347
+ }
2348
+ const snapshotResponse = await boundedFetch(
2349
+ descriptorUrl(manifest.signed.snapshot, manifestUrl, allowTestUrls),
2350
+ dependencies.maxSnapshotBytes ?? MODEL_PRESET_REGISTRY_MAX_SNAPSHOT_BYTES,
2351
+ dependencies,
2352
+ );
2353
+ const snapshotBytes = snapshotResponse.bytes ?? new Uint8Array();
2354
+ assertContentDescriptor(snapshotBytes, manifest.signed.snapshot, "Registry snapshot");
2355
+ const snapshot = parseCanonicalDocument(
2356
+ snapshotBytes,
2357
+ "Registry snapshot",
2358
+ ModelPresetRegistrySnapshotSchema,
2359
+ );
2360
+ assertSnapshotBindings(manifest, snapshot);
2361
+ const profilesResponse = await boundedFetch(
2362
+ descriptorUrl(manifest.signed.contents.profiles, manifestUrl, allowTestUrls),
2363
+ dependencies.maxProfilesBytes ?? MODEL_PRESET_REGISTRY_MAX_PROFILES_BYTES,
2364
+ dependencies,
2365
+ );
2366
+ const profileBytes = profilesResponse.bytes ?? new Uint8Array();
2367
+ assertContentDescriptor(profileBytes, manifest.signed.contents.profiles, "Registry profiles");
2368
+ const profiles = parseCanonicalDocument(
2369
+ profileBytes,
2370
+ "Registry profiles",
2371
+ ModelPresetRegistryProfilesSchema,
2372
+ );
2373
+ if (
2374
+ profiles.revision !== manifest.signed.revision ||
2375
+ profiles.profiles.length !== manifest.signed.contents.profiles.count
2376
+ )
2377
+ throw new Error("Registry profile identity is invalid.");
2378
+ const presetsResponse = await boundedFetch(
2379
+ descriptorUrl(manifest.signed.contents.presets, manifestUrl, allowTestUrls),
2380
+ dependencies.maxPresetsBytes ?? MODEL_PRESET_REGISTRY_MAX_PRESETS_BYTES,
2381
+ dependencies,
2382
+ );
2383
+ const presetBytes = presetsResponse.bytes ?? new Uint8Array();
2384
+ assertContentDescriptor(presetBytes, manifest.signed.contents.presets, "Registry presets");
2385
+ const presets = parseCanonicalDocument(presetBytes, "Registry presets", ModelPresetRegistryPresetsSchema);
2386
+ if (
2387
+ presets.revision !== manifest.signed.revision ||
2388
+ presets.presets.length !== manifest.signed.contents.presets.count
2389
+ )
2390
+ throw new Error("Registry preset identity is invalid.");
2391
+ assertProfilePresetReferences(profiles, presets);
2392
+ let compactRetention =
2393
+ latest !== undefined &&
2394
+ manifest.signed.registryRevision > latest.manifest.signed.registryRevision &&
2395
+ retainedAncestryDepth(latest, usableState) >= MODEL_PRESET_REGISTRY_MAX_RETENTION_ANCESTRY;
2396
+ const latestRevision = latest?.manifest.signed.registryRevision;
2397
+ const buildCandidate = (compact: boolean): RegistryStateCandidate => {
2398
+ const latestIsSelected = latest !== undefined && effectivePinnedRevision === latestRevision;
2399
+ const retentionSource = compact && latest && !latestIsSelected ? withoutRetainedEntries(latest) : latest;
2400
+ const retentionSourceWasStripped = retentionSource !== latest;
2401
+ const retained = retainRemoved(retentionSource, profiles, presets);
2402
+ const hasRetained =
2403
+ retained.retainedProfiles.length > 0 ||
2404
+ retained.retainedPresets.length > 0 ||
2405
+ retained.retainedDynamicProviders.length > 0;
2406
+ const generation: AcceptedGeneration = {
2407
+ manifest,
2408
+ snapshot,
2409
+ profiles,
2410
+ presets,
2411
+ manifestSha256,
2412
+ acceptedAt: now.toISOString(),
2413
+ etag: manifestResponse.response.headers.get("etag") ?? undefined,
2414
+ manifestUrl: manifestUrl.href,
2415
+ manifestOrigin: manifestUrl.origin,
2416
+ ...retained,
2417
+ retainedFromRevision: hasRetained
2418
+ ? latestRevision === manifest.signed.registryRevision
2419
+ ? latest?.retainedFromRevision
2420
+ : latestRevision
2421
+ : undefined,
2422
+ };
2423
+ const protectedRevisions = new Set(
2424
+ [effectivePinnedRevision, usableState.activeRevision, generation.retainedFromRevision].filter(
2425
+ (revision): revision is number => revision !== undefined,
2426
+ ),
2427
+ );
2428
+ const historyByRevision = new Map(
2429
+ usableState.history.map(item => [item.manifest.signed.registryRevision, item]),
2430
+ );
2431
+ const selectedHistory: AcceptedGeneration[] = [];
2432
+ const pendingSelectedRevisions = [...protectedRevisions];
2433
+ const selectedRevisionSet = new Set<number>();
2434
+ for (let index = 0; index < pendingSelectedRevisions.length; index++) {
2435
+ const selectedRevision = pendingSelectedRevisions[index]!;
2436
+ if (selectedRevisionSet.has(selectedRevision)) continue;
2437
+ selectedRevisionSet.add(selectedRevision);
2438
+ const selectedGeneration = historyByRevision.get(selectedRevision);
2439
+ if (!selectedGeneration) continue;
2440
+ const sameAsRetentionSource =
2441
+ selectedGeneration.manifest.signed.registryRevision ===
2442
+ retentionSource?.manifest.signed.registryRevision;
2443
+ if (sameAsRetentionSource && retentionSourceWasStripped) continue;
2444
+ if (!sameAsRetentionSource) selectedHistory.push(selectedGeneration);
2445
+ if (selectedGeneration.retainedFromRevision !== undefined)
2446
+ pendingSelectedRevisions.push(selectedGeneration.retainedFromRevision);
2447
+ }
2448
+ const priorHistory = compact ? [retentionSource!, ...selectedHistory] : usableState.history;
2449
+ const historyCandidates = [
2450
+ generation,
2451
+ ...priorHistory.filter(
2452
+ item => item.manifest.signed.registryRevision !== manifest.signed.registryRevision,
2453
+ ),
2454
+ ].sort((left, right) => right.manifest.signed.registryRevision - left.manifest.signed.registryRevision);
2455
+ const history = historyCandidates.slice(0, MODEL_PRESET_REGISTRY_MAX_HISTORY);
2456
+ const pendingProtectedRevisions = [...protectedRevisions];
2457
+ for (let index = 0; index < pendingProtectedRevisions.length; index++) {
2458
+ const protectedRevision = pendingProtectedRevisions[index]!;
2459
+ const protectedGeneration = historyCandidates.find(
2460
+ item => item.manifest.signed.registryRevision === protectedRevision,
2461
+ );
2462
+ if (
2463
+ protectedGeneration &&
2464
+ !history.some(item => item.manifest.signed.registryRevision === protectedRevision)
2465
+ )
2466
+ history.push(protectedGeneration);
2467
+ if (
2468
+ protectedGeneration?.retainedFromRevision !== undefined &&
2469
+ !protectedRevisions.has(protectedGeneration.retainedFromRevision)
2470
+ ) {
2471
+ protectedRevisions.add(protectedGeneration.retainedFromRevision);
2472
+ pendingProtectedRevisions.push(protectedGeneration.retainedFromRevision);
2473
+ }
2474
+ }
2475
+ return {
2476
+ retained,
2477
+ nextState: {
2478
+ version: 1,
2479
+ activeRevision:
2480
+ effectivePinnedRevision ??
2481
+ (usableState.activeRevision !== undefined &&
2482
+ trustedHighestSeenRevision !== undefined &&
2483
+ usableState.activeRevision < trustedHighestSeenRevision
2484
+ ? usableState.activeRevision
2485
+ : manifest.signed.registryRevision),
2486
+ highestSeenRevision: Math.max(trustedHighestSeenRevision ?? 0, manifest.signed.registryRevision),
2487
+ highestSeenManifestSha256: manifestSha256,
2488
+ history,
2489
+ lastCheckedAt: now.toISOString(),
2490
+ lastError: undefined,
2491
+ },
2492
+ };
2493
+ };
2494
+ const maxStateBytes = dependencies.maxStateBytes ?? MODEL_PRESET_REGISTRY_MAX_STATE_BYTES;
2495
+ let candidate = buildCandidate(compactRetention);
2496
+ validateStateGenerations(candidate.nextState, effectiveTrustedKeys(dependencies));
2497
+ if (
2498
+ serializedJsonByteLength(candidate.nextState) > maxStateBytes &&
2499
+ !compactRetention &&
2500
+ latest !== undefined &&
2501
+ (usableState.activeRevision === undefined || usableState.activeRevision === latestRevision)
2502
+ ) {
2503
+ compactRetention = true;
2504
+ candidate = buildCandidate(true);
2505
+ validateStateGenerations(candidate.nextState, effectiveTrustedKeys(dependencies));
2506
+ }
2507
+ if (serializedJsonByteLength(candidate.nextState) > maxStateBytes)
2508
+ throw new Error("Registry cache state exceeds its durable size limit.");
2509
+ const { nextState, retained } = candidate;
2510
+ if (stateRecovery.hadInvalidCopy && control.pinnedRevision !== undefined) {
2511
+ ensureWritable();
2512
+ await writeAtomicJson(paths.control, { ...control, pinnedRevision: undefined });
2513
+ }
2514
+ ensureWritable();
2515
+ await writeRegistryState(paths, nextState, canWrite);
2516
+ return {
2517
+ status: "updated",
2518
+ revision: manifest.signed.registryRevision,
2519
+ revisionId: manifest.signed.revision,
2520
+ manifestSha256,
2521
+ retainedProfiles: retained.retainedProfiles.map(profile => profile.id),
2522
+ retainedPresets: retained.retainedPresets.map(preset => `${preset.provider}/${preset.id}`),
2523
+ };
2524
+ },
2525
+ { retries: 20, retryDelayMs: 50, staleMs: 30_000 },
2526
+ );
2527
+ } catch (error) {
2528
+ if (isRefreshCancelled(error)) throw error;
2529
+ const redacted = new Error(safeError(error));
2530
+ if (canWrite())
2531
+ await recordFailure(agentDir, redacted, now, effectiveTrustedKeys(dependencies), canWrite).catch(
2532
+ () => undefined,
2533
+ );
2534
+ throw redacted;
2535
+ }
2536
+ }
2537
+
2538
+ export async function setModelPresetRegistryDisabled(
2539
+ options: ModelPresetRegistryDependencies & { disabled: boolean },
2540
+ ): Promise<void> {
2541
+ const { disabled } = options;
2542
+ const agentDir = effectiveAgentDir(options);
2543
+ const paths = registryPaths(agentDir);
2544
+ await withFileLock(paths.transaction, async () => {
2545
+ const current = loadControlSync(agentDir);
2546
+ await writeAtomicJson(paths.control, { ...current, disabled });
2547
+ });
2548
+ notifyRegistryChanges(agentDir);
2549
+ }
2550
+
2551
+ export async function setModelPresetRegistryPin(
2552
+ options: ModelPresetRegistryDependencies & { revision?: number },
2553
+ ): Promise<void> {
2554
+ const { revision } = options;
2555
+ const agentDir = effectiveAgentDir(options);
2556
+ const paths = registryPaths(agentDir);
2557
+ await withFileLock(paths.transaction, async () => {
2558
+ let state = recoverStateCopiesSync(agentDir, effectiveTrustedKeys(options)).state;
2559
+ if (revision === undefined) {
2560
+ const highest = state.history.reduce(
2561
+ (value, item) => (item.revoked ? value : Math.max(value, item.manifest.signed.registryRevision)),
2562
+ 0,
2563
+ );
2564
+ state = { ...state, activeRevision: highest || undefined };
2565
+ }
2566
+ if (revision !== undefined) {
2567
+ const generation = state.history.find(item => item.manifest.signed.registryRevision === revision);
2568
+ if (!generation) throw new Error(`Cannot pin unaccepted registry revision ${revision}.`);
2569
+ if (generation.revoked) throw new Error(`Cannot pin revoked registry revision ${revision}.`);
2570
+ }
2571
+ const current = loadControlSync(agentDir);
2572
+ await writeRegistryState(paths, state);
2573
+ await writeAtomicJson(paths.control, { ...current, pinnedRevision: revision });
2574
+ });
2575
+ notifyRegistryChanges(agentDir);
2576
+ }
2577
+
2578
+ export async function rollbackModelPresetRegistry(
2579
+ options: ModelPresetRegistryDependencies & { revision?: number },
2580
+ ): Promise<void> {
2581
+ const agentDir = effectiveAgentDir(options);
2582
+ const paths = registryPaths(agentDir);
2583
+ await withFileLock(paths.transaction, async () => {
2584
+ const state = recoverStateCopiesSync(agentDir, effectiveTrustedKeys(options)).state;
2585
+ const control = loadControlSync(agentDir);
2586
+ const pinnedGeneration =
2587
+ control.pinnedRevision === undefined
2588
+ ? undefined
2589
+ : state.history.find(item => item.manifest.signed.registryRevision === control.pinnedRevision);
2590
+ const activeRevision = pinnedGeneration?.revoked
2591
+ ? state.activeRevision
2592
+ : (control.pinnedRevision ?? state.activeRevision);
2593
+ const revision =
2594
+ options.revision ??
2595
+ state.history
2596
+ .filter(item => !item.revoked)
2597
+ .map(item => item.manifest.signed.registryRevision)
2598
+ .filter(candidate => activeRevision === undefined || candidate < activeRevision)
2599
+ .sort((left, right) => right - left)[0];
2600
+ if (revision === undefined) throw new Error("Registry accepted history has no previous revision.");
2601
+ const generation = state.history.find(item => item.manifest.signed.registryRevision === revision);
2602
+ if (!generation) throw new Error(`Registry revision ${revision} is not in accepted history.`);
2603
+ validateGeneration(generation, effectiveTrustedKeys(options));
2604
+ await writeRegistryState(paths, { ...state, activeRevision: revision, lastError: undefined });
2605
+ await writeAtomicJson(paths.control, { ...control, pinnedRevision: undefined });
2606
+ });
2607
+ notifyRegistryChanges(agentDir);
2608
+ }
2609
+
2610
+ export function getModelPresetRegistryStatus(
2611
+ dependencies: ModelPresetRegistryDependencies = {},
2612
+ ): ModelPresetRegistryStatus {
2613
+ const agentDir = effectiveAgentDir(dependencies);
2614
+ if (environmentDisabled())
2615
+ return {
2616
+ contractVersion: MODEL_PRESET_REGISTRY_CONTRACT_VERSION,
2617
+ source: "embedded",
2618
+ cacheHealth: "empty",
2619
+ disabled: true,
2620
+ retainedProfiles: [],
2621
+ retainedPresets: [],
2622
+ historyRevisions: [],
2623
+ profileCount: 0,
2624
+ presetCount: 0,
2625
+ };
2626
+ let control: RegistryControl = { version: 1, disabled: false };
2627
+ let state: RegistryState = { version: 1, history: [] };
2628
+ let cacheHealth: ModelPresetRegistryStatus["cacheHealth"] = "empty";
2629
+ let loadError: string | undefined;
2630
+ let failureDiagnostic: string | undefined;
2631
+ try {
2632
+ control = loadControlSync(agentDir);
2633
+ state = recoverStateCopiesSync(agentDir, effectiveTrustedKeys(dependencies, agentDir)).state;
2634
+ cacheHealth = state.history.length > 0 ? "valid" : "empty";
2635
+ if (state.history.length === 0 && state.lastError === undefined)
2636
+ failureDiagnostic = readFailureDiagnostic(agentDir);
2637
+ } catch (error) {
2638
+ cacheHealth = "corrupt";
2639
+ state = { version: 1, history: [] };
2640
+ loadError = safeError(error);
2641
+ }
2642
+ const disabled = control.disabled || environmentDisabled();
2643
+ const pinnedGeneration =
2644
+ control.pinnedRevision === undefined
2645
+ ? undefined
2646
+ : state.history.find(item => item.manifest.signed.registryRevision === control.pinnedRevision);
2647
+ const staleControlPin =
2648
+ control.pinnedRevision !== undefined && (pinnedGeneration === undefined || pinnedGeneration.revoked === true);
2649
+ const revision = staleControlPin ? state.activeRevision : (control.pinnedRevision ?? state.activeRevision);
2650
+ const generation = state.history.find(item => item.manifest.signed.registryRevision === revision);
2651
+ let valid: AcceptedGeneration | undefined;
2652
+ if (revision !== undefined && !generation) {
2653
+ cacheHealth = "corrupt";
2654
+ loadError = `Registry selected revision ${revision} is missing from accepted history.`;
2655
+ }
2656
+ if (generation && !disabled) {
2657
+ try {
2658
+ valid = validateGeneration(generation, effectiveTrustedKeys(dependencies, agentDir));
2659
+ } catch (error) {
2660
+ cacheHealth = "corrupt";
2661
+ loadError = safeError(error);
2662
+ }
2663
+ }
2664
+ return {
2665
+ contractVersion: MODEL_PRESET_REGISTRY_CONTRACT_VERSION,
2666
+ source: valid ? "registry" : "embedded",
2667
+ cacheHealth,
2668
+ activeRevision: valid?.manifest.signed.registryRevision,
2669
+ activeRevisionId: valid?.manifest.signed.revision,
2670
+ highestSeenRevision: state.highestSeenRevision,
2671
+ manifestSha256: valid?.manifestSha256,
2672
+ snapshotSha256: valid?.manifest.signed.snapshot.sha256,
2673
+ profilesSha256: valid?.manifest.signed.contents.profiles.sha256,
2674
+ presetsSha256: valid?.manifest.signed.contents.presets.sha256,
2675
+ keyId: valid?.manifest.signature.keyId,
2676
+ sourceRevision: valid?.manifest.signed.provenance.sourceRevision,
2677
+ acceptedAt: valid?.acceptedAt,
2678
+ publishedAt: valid?.manifest.signed.publishedAt,
2679
+ lastCheckedAt: state.lastCheckedAt,
2680
+ lastError: loadError ?? state.lastError ?? failureDiagnostic,
2681
+ disabled,
2682
+ pinnedRevision: staleControlPin ? undefined : control.pinnedRevision,
2683
+ retainedProfiles: valid?.retainedProfiles.map(profile => profile.id) ?? [],
2684
+ retainedPresets: valid?.retainedPresets.map(preset => `${preset.provider}/${preset.id}`) ?? [],
2685
+ historyRevisions: state.history
2686
+ .map(item => item.manifest.signed.registryRevision)
2687
+ .sort((left, right) => right - left),
2688
+ profileCount: valid ? valid.profiles.profiles.length + valid.retainedProfiles.length : 0,
2689
+ presetCount: valid ? valid.presets.presets.length + valid.retainedPresets.length : 0,
2690
+ };
2691
+ }
2692
+
2693
+ export function refreshModelPresetRegistryInBackground(
2694
+ dependencies: ModelPresetRegistryDependencies = {},
2695
+ onAccepted?: () => void,
2696
+ ): () => Promise<void> {
2697
+ if (dependencies.automaticRefresh === false) return async () => {};
2698
+ const agentDir = effectiveAgentDir(dependencies);
2699
+ let status: ModelPresetRegistryStatus;
2700
+ try {
2701
+ status = getModelPresetRegistryStatus({ ...dependencies, agentDir });
2702
+ } catch {
2703
+ status = {
2704
+ contractVersion: MODEL_PRESET_REGISTRY_CONTRACT_VERSION,
2705
+ source: "embedded",
2706
+ cacheHealth: "corrupt",
2707
+ disabled: false,
2708
+ retainedProfiles: [],
2709
+ retainedPresets: [],
2710
+ historyRevisions: [],
2711
+ profileCount: 0,
2712
+ presetCount: 0,
2713
+ };
2714
+ }
2715
+ const lastChecked = status.lastCheckedAt ? Date.parse(status.lastCheckedAt) : 0;
2716
+ const now = (dependencies.now ?? (() => new Date()))().getTime();
2717
+ const refreshIntervalMs = dependencies.refreshIntervalMs ?? MODEL_PRESET_REGISTRY_REFRESH_INTERVAL_MS;
2718
+ const recentAge = Number.isFinite(lastChecked) ? Math.max(0, now - lastChecked) : Number.POSITIVE_INFINITY;
2719
+ const initialDelay = status.disabled
2720
+ ? refreshIntervalMs
2721
+ : recentAge < refreshIntervalMs
2722
+ ? refreshIntervalMs - recentAge
2723
+ : (dependencies.startupDelayMs ?? MODEL_PRESET_REGISTRY_STARTUP_DELAY_MS);
2724
+ let cancelled = false;
2725
+ let disposal: Promise<void> | undefined;
2726
+ const consumer: RefreshFlightConsumer = { cancelled: false };
2727
+ const pendingFlights = new Set<Promise<ModelPresetRegistryRefreshResult>>();
2728
+ let knownManifestSha256 = dependencies.knownManifestSha256 ?? status.manifestSha256;
2729
+ const publicationFingerprint = (current: ModelPresetRegistryStatus): string =>
2730
+ JSON.stringify({
2731
+ disabled: current.disabled,
2732
+ pinnedRevision: current.pinnedRevision,
2733
+ activeRevision: current.activeRevision,
2734
+ manifestSha256: current.manifestSha256,
2735
+ cacheHealth: current.cacheHealth,
2736
+ });
2737
+ let publishedFingerprint = publicationFingerprint(status);
2738
+ const publishCurrentStatus = (): void => {
2739
+ if (cancelled) return;
2740
+ try {
2741
+ const currentStatus = getModelPresetRegistryStatus({ ...dependencies, agentDir });
2742
+ const currentFingerprint = publicationFingerprint(currentStatus);
2743
+ if (currentFingerprint !== publishedFingerprint) {
2744
+ publishedFingerprint = currentFingerprint;
2745
+ try {
2746
+ onAccepted?.();
2747
+ } catch {
2748
+ // Consumer publication must not make a durable local control mutation fail.
2749
+ }
2750
+ }
2751
+ } catch {
2752
+ // The origin refresh below records bounded diagnostics; local publication remains best-effort.
2753
+ }
2754
+ };
2755
+ const unsubscribe = subscribeRegistryChanges(agentDir, () => queueMicrotask(publishCurrentStatus));
2756
+ let timer: Timer | undefined;
2757
+ const schedule = (delayMs: number): void => {
2758
+ if (cancelled) return;
2759
+ timer = setTimeout(() => {
2760
+ if (cancelled) return;
2761
+ publishCurrentStatus();
2762
+ const flight = getRefreshFlight({ ...dependencies, agentDir, knownManifestSha256 }, consumer);
2763
+ pendingFlights.add(flight.promise);
2764
+ void flight.promise
2765
+ .then(result => {
2766
+ if (result.status === "updated") knownManifestSha256 = result.manifestSha256;
2767
+ if (!cancelled) {
2768
+ let shouldPublish = false;
2769
+ try {
2770
+ const nextFingerprint = publicationFingerprint(
2771
+ getModelPresetRegistryStatus({ ...dependencies, agentDir }),
2772
+ );
2773
+ shouldPublish = nextFingerprint !== publishedFingerprint;
2774
+ publishedFingerprint = nextFingerprint;
2775
+ } catch {
2776
+ shouldPublish = result.status === "updated";
2777
+ }
2778
+ if (shouldPublish) onAccepted?.();
2779
+ }
2780
+ })
2781
+ .catch(() => undefined)
2782
+ .finally(() => {
2783
+ pendingFlights.delete(flight.promise);
2784
+ schedule(refreshIntervalMs);
2785
+ });
2786
+ }, delayMs);
2787
+ timer.unref?.();
2788
+ };
2789
+ schedule(initialDelay);
2790
+ return () => {
2791
+ if (disposal) return disposal;
2792
+ cancelled = true;
2793
+ consumer.cancelled = true;
2794
+ unsubscribe();
2795
+ if (timer) clearTimeout(timer);
2796
+ disposal = Promise.allSettled([...pendingFlights]).then(() => undefined);
2797
+ return disposal;
2798
+ };
2799
+ }