@gajae-code/coding-agent 0.16.0 → 0.16.1

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 (163) hide show
  1. package/CHANGELOG.md +41 -1
  2. package/dist/types/async/job-manager.d.ts +32 -1
  3. package/dist/types/browser-backend.d.ts +12 -0
  4. package/dist/types/cli/update-cli.d.ts +1 -0
  5. package/dist/types/commands/sdk.d.ts +2 -0
  6. package/dist/types/config/billing-path.d.ts +25 -0
  7. package/dist/types/config/config-schema-version.d.ts +13 -1
  8. package/dist/types/config/model-registry.d.ts +16 -0
  9. package/dist/types/config/settings-schema.d.ts +17 -7
  10. package/dist/types/extensibility/extensions/types.d.ts +8 -3
  11. package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
  12. package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
  13. package/dist/types/main.d.ts +1 -0
  14. package/dist/types/modes/acp/acp-agent.d.ts +21 -21
  15. package/dist/types/modes/components/model-selector.d.ts +4 -0
  16. package/dist/types/modes/components/session-selector.d.ts +1 -1
  17. package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
  18. package/dist/types/modes/interactive-mode.d.ts +16 -2
  19. package/dist/types/modes/theme/theme.d.ts +2 -1
  20. package/dist/types/modes/types.d.ts +2 -0
  21. package/dist/types/sdk/broker/broker.d.ts +2 -1
  22. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  23. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
  24. package/dist/types/sdk/broker/lifecycle.d.ts +14 -0
  25. package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
  26. package/dist/types/sdk/broker/state-version.d.ts +17 -0
  27. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
  28. package/dist/types/sdk/bus/index.d.ts +3 -1
  29. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
  30. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
  31. package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
  32. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  33. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  34. package/dist/types/sdk/cli/rows.d.ts +8 -2
  35. package/dist/types/sdk/cli/session-cli.d.ts +2 -1
  36. package/dist/types/sdk/host/events.d.ts +1 -0
  37. package/dist/types/sdk/host/query/cursor.d.ts +4 -2
  38. package/dist/types/sdk/host/query/handlers.d.ts +1 -0
  39. package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
  40. package/dist/types/sdk/host/session-runtime.d.ts +18 -17
  41. package/dist/types/sdk/lifecycle-session.d.ts +1 -1
  42. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
  43. package/dist/types/sdk/receipt-state.d.ts +2 -0
  44. package/dist/types/sdk/router/session-router.d.ts +4 -0
  45. package/dist/types/sdk/turn-result.d.ts +6 -0
  46. package/dist/types/session/agent-session.d.ts +47 -7
  47. package/dist/types/session/fold-coordinator.d.ts +29 -2
  48. package/dist/types/session/session-manager.d.ts +14 -2
  49. package/dist/types/tools/ask.d.ts +1 -0
  50. package/dist/types/tools/bash.d.ts +12 -0
  51. package/dist/types/tools/browser/launch.d.ts +10 -0
  52. package/dist/types/tools/browser/registry.d.ts +8 -0
  53. package/dist/types/tools/index.d.ts +6 -10
  54. package/dist/types/tools/job.d.ts +3 -1
  55. package/dist/types/utils/external-editor.d.ts +7 -0
  56. package/examples/extensions/README.md +1 -1
  57. package/examples/extensions/pirate.ts +7 -4
  58. package/package.json +7 -7
  59. package/scripts/generate-sdk-operation-inventory.ts +7 -1
  60. package/src/async/job-manager.ts +107 -9
  61. package/src/browser-backend.ts +29 -0
  62. package/src/cli/plugin-cli.ts +29 -0
  63. package/src/cli/update-cli.ts +63 -12
  64. package/src/commands/sdk.ts +22 -7
  65. package/src/config/billing-path.ts +42 -0
  66. package/src/config/config-schema-version.ts +18 -1
  67. package/src/config/model-preset-registry.ts +177 -18
  68. package/src/config/model-registry.ts +268 -42
  69. package/src/config/settings-schema.ts +19 -7
  70. package/src/config/settings.ts +46 -15
  71. package/src/coordinator-mcp/projection-scan.ts +6 -6
  72. package/src/coordinator-mcp/server.ts +21 -1
  73. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  74. package/src/extensibility/extensions/runner.ts +27 -0
  75. package/src/extensibility/extensions/types.ts +8 -3
  76. package/src/extensibility/gjc-plugins/installer.ts +51 -11
  77. package/src/extensibility/runtime-skill-discovery.ts +110 -3
  78. package/src/extensibility/skills.ts +22 -12
  79. package/src/gjc-runtime/launch-worktree.ts +285 -4
  80. package/src/gjc-runtime/session-state-lock.ts +154 -28
  81. package/src/gjc-runtime/session-state-sidecar.ts +10 -17
  82. package/src/gjc-runtime/tmux-owner-isolation.ts +16 -1
  83. package/src/internal-urls/docs-index.generated.ts +10 -10
  84. package/src/main.ts +4 -1
  85. package/src/modes/acp/acp-agent.ts +5 -5
  86. package/src/modes/components/model-selector.ts +76 -8
  87. package/src/modes/components/session-selector.ts +23 -15
  88. package/src/modes/components/sessions-dashboard.ts +8 -5
  89. package/src/modes/controllers/command-controller.ts +1 -1
  90. package/src/modes/controllers/event-controller.ts +30 -4
  91. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  92. package/src/modes/controllers/input-controller.ts +2 -2
  93. package/src/modes/controllers/selector-controller.ts +10 -3
  94. package/src/modes/interactive-mode.ts +36 -12
  95. package/src/modes/prompt-action-autocomplete.ts +30 -4
  96. package/src/modes/runtime-init.ts +12 -9
  97. package/src/modes/theme/theme.ts +5 -0
  98. package/src/modes/types.ts +2 -0
  99. package/src/modes/utils/ui-helpers.ts +22 -3
  100. package/src/prompts/agent-fragments/browser-aside.md +22 -0
  101. package/src/prompts/tools/ask.md +1 -1
  102. package/src/prompts/tools/bash.md +1 -0
  103. package/src/prompts/tools/job.md +1 -1
  104. package/src/sdk/acp/adapter.ts +5 -0
  105. package/src/sdk/broker/broker.ts +10 -1
  106. package/src/sdk/broker/ensure.ts +76 -3
  107. package/src/sdk/broker/lifecycle-ledger.ts +6 -0
  108. package/src/sdk/broker/lifecycle.ts +254 -34
  109. package/src/sdk/broker/spawn-substrate.ts +45 -2
  110. package/src/sdk/broker/startup-budget.ts +144 -2
  111. package/src/sdk/broker/state-version.ts +17 -0
  112. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  113. package/src/sdk/bus/chat-daemon-runtime.ts +111 -24
  114. package/src/sdk/bus/discord-daemon.ts +50 -10
  115. package/src/sdk/bus/index.ts +33 -3
  116. package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
  117. package/src/sdk/bus/lifecycle-commands.ts +4 -0
  118. package/src/sdk/bus/notification-service.ts +22 -5
  119. package/src/sdk/bus/prompt-reconciliation.ts +7 -2
  120. package/src/sdk/bus/reconciliation-store.ts +55 -31
  121. package/src/sdk/bus/slack-conversation.ts +1 -1
  122. package/src/sdk/bus/slack-daemon.ts +72 -38
  123. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  124. package/src/sdk/cli/rows.ts +30 -7
  125. package/src/sdk/cli/session-cli.ts +33 -21
  126. package/src/sdk/host/control/dispatch.ts +2 -2
  127. package/src/sdk/host/events.ts +1 -0
  128. package/src/sdk/host/host.ts +2 -0
  129. package/src/sdk/host/query/cursor.ts +52 -7
  130. package/src/sdk/host/query/handlers.ts +33 -12
  131. package/src/sdk/host/query/revision-store.ts +12 -0
  132. package/src/sdk/host/session-runtime.test.ts +463 -8
  133. package/src/sdk/host/session-runtime.ts +383 -54
  134. package/src/sdk/host/surface-policy.ts +3 -3
  135. package/src/sdk/lifecycle-session.ts +7 -2
  136. package/src/sdk/prompt-deadline-manager.ts +2 -1
  137. package/src/sdk/protocol/operation-inventory.generated.json +54 -3
  138. package/src/sdk/protocol/operation-registry.ts +1 -1
  139. package/src/sdk/receipt-state.ts +10 -0
  140. package/src/sdk/router/session-router.ts +152 -59
  141. package/src/sdk/session.ts +60 -41
  142. package/src/sdk/turn-result.ts +22 -0
  143. package/src/session/agent-session.ts +1033 -260
  144. package/src/session/fold-coordinator.ts +40 -3
  145. package/src/session/session-manager.ts +517 -77
  146. package/src/slash-commands/builtin-registry.ts +41 -0
  147. package/src/task/executor.ts +9 -4
  148. package/src/tools/ask-contract.test.ts +64 -0
  149. package/src/tools/ask-contract.ts +36 -1
  150. package/src/tools/ask.ts +10 -8
  151. package/src/tools/bash-interactive.ts +11 -2
  152. package/src/tools/bash.ts +156 -33
  153. package/src/tools/browser/launch.ts +37 -0
  154. package/src/tools/browser/registry.ts +99 -9
  155. package/src/tools/cron.ts +6 -4
  156. package/src/tools/descriptors.test.ts +15 -0
  157. package/src/tools/descriptors.ts +5 -1
  158. package/src/tools/index.ts +6 -6
  159. package/src/tools/job.ts +4 -1
  160. package/src/tools/monitor.ts +12 -9
  161. package/src/tools/resolve.ts +30 -10
  162. package/src/tools/tool-catalog.generated.ts +2 -2
  163. package/src/utils/external-editor.ts +72 -1
@@ -47,6 +47,7 @@ const MODEL_PRESET_REGISTRY_MAX_STATE_BYTES = 32 * 1024 * 1024;
47
47
  const MODEL_PRESET_REGISTRY_MAX_HISTORY = 4;
48
48
  const MODEL_PRESET_REGISTRY_MAX_RETENTION_ANCESTRY = 64;
49
49
  const MODEL_PRESET_REGISTRY_MAX_ERROR_BYTES = 1024;
50
+ const MODEL_PRESET_REGISTRY_IDENTITY_SKELETON_CACHE_MAX = 200_000;
50
51
  const MODEL_PRESET_REGISTRY_FETCH_TIMEOUT_MS = 8_000;
51
52
  const REVISION_PATTERN = /^[0-9]{8}$/;
52
53
  const SHA256_PATTERN = /^[a-f0-9]{64}$/;
@@ -592,10 +593,23 @@ function assertSafeRegistryDocument(value: unknown, location = "$"): void {
592
593
  }
593
594
  }
594
595
 
596
+ /**
597
+ * Confusable folding is a pure string function evaluated once per identity per
598
+ * document, and startup validates the same catalog identities repeatedly. The
599
+ * bounded memo keeps the fold cost proportional to the distinct identity count
600
+ * instead of the number of validation passes.
601
+ */
602
+ const identitySkeletonCache = new Map<string, string>();
603
+
595
604
  function identitySkeleton(value: string): string {
596
- return [...value.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase()]
605
+ const cached = identitySkeletonCache.get(value);
606
+ if (cached !== undefined) return cached;
607
+ const skeleton = [...value.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase()]
597
608
  .map(character => IDENTITY_HOMOGLYPHS.get(character) ?? character)
598
609
  .join("");
610
+ if (identitySkeletonCache.size >= MODEL_PRESET_REGISTRY_IDENTITY_SKELETON_CACHE_MAX) identitySkeletonCache.clear();
611
+ identitySkeletonCache.set(value, skeleton);
612
+ return skeleton;
599
613
  }
600
614
 
601
615
  function assertUniqueRegistryNames(values: readonly string[], label: string): void {
@@ -824,23 +838,21 @@ function assertProfilePresetReferences(
824
838
  }
825
839
  }
826
840
 
827
- function validateGeneration(
841
+ function validateGenerationUncached(
828
842
  generation: AcceptedGeneration,
829
843
  trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
830
- allowRevoked = false,
831
- ): AcceptedGeneration {
832
- const manifest = ModelPresetRegistryManifestSchema.parse(generation.manifest);
844
+ allowRevoked: boolean,
845
+ ): void {
846
+ const { manifest, snapshot, profiles, presets } = generation;
833
847
  assertSafeRegistryDocument(manifest);
834
848
  verifyManifest(manifest, trustedKeys, allowRevoked);
835
849
  assertManifestBindings(manifest);
836
- const snapshotBytes = new TextEncoder().encode(canonicalModelPresetRegistryJson(generation.snapshot));
850
+ const snapshotBytes = new TextEncoder().encode(canonicalModelPresetRegistryJson(snapshot));
837
851
  assertContentDescriptor(snapshotBytes, manifest.signed.snapshot, "Cached registry snapshot");
838
- const snapshot = ModelPresetRegistrySnapshotSchema.parse(generation.snapshot);
839
852
  assertSafeRegistryDocument(snapshot);
840
853
  assertSnapshotBindings(manifest, snapshot);
841
- const profileBytes = new TextEncoder().encode(canonicalModelPresetRegistryJson(generation.profiles));
854
+ const profileBytes = new TextEncoder().encode(canonicalModelPresetRegistryJson(profiles));
842
855
  assertContentDescriptor(profileBytes, manifest.signed.contents.profiles, "Cached registry profiles");
843
- const profiles = ModelPresetRegistryProfilesSchema.parse(generation.profiles);
844
856
  assertSafeRegistryDocument(profiles);
845
857
  assertRegistryIdentityPolicy(profiles);
846
858
  if (
@@ -848,9 +860,8 @@ function validateGeneration(
848
860
  profiles.profiles.length !== manifest.signed.contents.profiles.count
849
861
  )
850
862
  throw new Error("Cached registry profile identity is invalid.");
851
- const presetBytes = new TextEncoder().encode(canonicalModelPresetRegistryJson(generation.presets));
863
+ const presetBytes = new TextEncoder().encode(canonicalModelPresetRegistryJson(presets));
852
864
  assertContentDescriptor(presetBytes, manifest.signed.contents.presets, "Cached registry presets");
853
- const presets = ModelPresetRegistryPresetsSchema.parse(generation.presets);
854
865
  assertSafeRegistryDocument(presets);
855
866
  assertRegistryIdentityPolicy(presets);
856
867
  if (
@@ -859,24 +870,73 @@ function validateGeneration(
859
870
  )
860
871
  throw new Error("Cached registry preset identity is invalid.");
861
872
  assertProfilePresetReferences(profiles, presets);
862
- const manifestSha256 = sha256(canonicalModelPresetRegistryJson(manifest));
863
- if (manifestSha256 !== generation.manifestSha256) throw new Error("Cached registry manifest digest is invalid.");
873
+ if (sha256(canonicalModelPresetRegistryJson(manifest)) !== generation.manifestSha256)
874
+ throw new Error("Cached registry manifest digest is invalid.");
864
875
  assertSafeRegistryDocument(generation.retainedProfiles);
865
876
  assertSafeRegistryDocument(generation.retainedPresets);
866
877
  assertSafeRegistryDocument(generation.retainedDynamicProviders);
867
- const effectiveProfiles = ModelPresetRegistryProfilesSchema.parse({
878
+ const effectiveProfiles: ModelPresetRegistryProfiles = {
868
879
  ...profiles,
869
880
  dynamicProviders: [...generation.retainedDynamicProviders, ...profiles.dynamicProviders],
870
881
  profiles: [...generation.retainedProfiles, ...profiles.profiles],
871
- });
872
- const effectivePresets = ModelPresetRegistryPresetsSchema.parse({
882
+ };
883
+ const effectivePresets: ModelPresetRegistryPresets = {
873
884
  ...presets,
874
885
  presets: [...generation.retainedPresets, ...presets.presets],
875
- });
886
+ };
876
887
  assertRegistryIdentityPolicy(effectiveProfiles);
877
888
  assertRegistryIdentityPolicy(effectivePresets);
878
889
  assertProfilePresetReferences(effectiveProfiles, effectivePresets);
879
- return { ...generation, manifest, snapshot, profiles, presets, manifestSha256 };
890
+ }
891
+
892
+ /**
893
+ * Signature and binding verification is a pure function of the generation, the
894
+ * trusted key set, and the revocation allowance, and one accepted-registry read
895
+ * verifies the same generation objects from several recovery angles. The memo
896
+ * records both outcomes per (generation, keys, allowance) so a startup read
897
+ * verifies each cached generation exactly once instead of once per angle.
898
+ */
899
+ type GenerationValidationOutcome = { ok: true } | { ok: false; error: unknown };
900
+
901
+ const generationValidationCache = new WeakMap<
902
+ AcceptedGeneration,
903
+ WeakMap<ReadonlyMap<string, ModelPresetRegistryTrustedKey>, Map<boolean, GenerationValidationOutcome>>
904
+ >();
905
+
906
+ /**
907
+ * Every caller supplies a generation whose documents were already schema-parsed
908
+ * (cache reads through `parseState`, refresh through `parseCanonicalDocument`),
909
+ * so this asserts the cryptographic and structural bindings and returns the
910
+ * input rather than re-deriving an identical parse.
911
+ */
912
+ function validateGeneration(
913
+ generation: AcceptedGeneration,
914
+ trustedKeys: ReadonlyMap<string, ModelPresetRegistryTrustedKey>,
915
+ allowRevoked = false,
916
+ ): AcceptedGeneration {
917
+ let byKeys = generationValidationCache.get(generation);
918
+ if (!byKeys) {
919
+ byKeys = new WeakMap();
920
+ generationValidationCache.set(generation, byKeys);
921
+ }
922
+ let byAllowance = byKeys.get(trustedKeys);
923
+ if (!byAllowance) {
924
+ byAllowance = new Map();
925
+ byKeys.set(trustedKeys, byAllowance);
926
+ }
927
+ const memoized = byAllowance.get(allowRevoked);
928
+ if (memoized) {
929
+ if (!memoized.ok) throw memoized.error;
930
+ return generation;
931
+ }
932
+ try {
933
+ validateGenerationUncached(generation, trustedKeys, allowRevoked);
934
+ } catch (error) {
935
+ byAllowance.set(allowRevoked, { ok: false, error });
936
+ throw error;
937
+ }
938
+ byAllowance.set(allowRevoked, { ok: true });
939
+ return generation;
880
940
  }
881
941
 
882
942
  function readJsonSync(file: string): unknown | undefined {
@@ -1488,9 +1548,92 @@ function acceptedRegistryFromState(
1488
1548
  };
1489
1549
  }
1490
1550
 
1551
+ /**
1552
+ * Accepted-registry reads are pure over the on-disk state, backup, and control
1553
+ * files, and a single launch resolves the registry from several subsystems.
1554
+ * The memo keys on the content digest of those inputs, so any byte change from
1555
+ * any writer (refresh, rollback, pin, tampering) is re-verified from scratch
1556
+ * while an unchanged launch pays the verification cost once. Digesting the
1557
+ * inputs is bounded by their size cap and stays far below the signature,
1558
+ * schema, and canonical-form work it replaces.
1559
+ */
1560
+ const acceptedRegistryReadCache = new Map<string, { fingerprint: string; value: AcceptedModelPresetRegistry }>();
1561
+
1562
+ function cloneAcceptedModelPresetRegistry(value: AcceptedModelPresetRegistry): AcceptedModelPresetRegistry {
1563
+ return {
1564
+ ...value,
1565
+ profiles: new Map(
1566
+ [...value.profiles].map(([id, profile]) => [id, structuredClone(profile)] as [string, ModelProfileDefinition]),
1567
+ ),
1568
+ presets: structuredClone(value.presets),
1569
+ dynamicProviders: [...value.dynamicProviders],
1570
+ retainedProfiles: [...value.retainedProfiles],
1571
+ retainedPresets: [...value.retainedPresets],
1572
+ };
1573
+ }
1574
+
1575
+ function registryFileFingerprint(file: string): string {
1576
+ try {
1577
+ return sha256(fsSync.readFileSync(file));
1578
+ } catch {
1579
+ return "absent";
1580
+ }
1581
+ }
1582
+
1583
+ function acceptedRegistryFingerprint(
1584
+ agentDir: string,
1585
+ dependencies: Omit<ModelPresetRegistryDependencies, "agentDir">,
1586
+ ): string {
1587
+ const paths = registryPaths(agentDir);
1588
+ return [
1589
+ refreshDependencyId(effectiveTrustedKeys(dependencies, agentDir) as object),
1590
+ ...[paths.state, paths.backup, paths.control].map(registryFileFingerprint),
1591
+ ].join("|");
1592
+ }
1593
+
1594
+ async function registryFileFingerprintAsync(file: string): Promise<string> {
1595
+ try {
1596
+ return sha256(new Uint8Array(await Bun.file(file).arrayBuffer()));
1597
+ } catch {
1598
+ return "absent";
1599
+ }
1600
+ }
1601
+
1602
+ async function acceptedRegistryFingerprintAsync(
1603
+ agentDir: string,
1604
+ dependencies: Omit<ModelPresetRegistryDependencies, "agentDir">,
1605
+ ): Promise<string> {
1606
+ const paths = registryPaths(agentDir);
1607
+ const files = await Promise.all(
1608
+ [paths.state, paths.backup, paths.control].map(file => registryFileFingerprintAsync(file)),
1609
+ );
1610
+ return [refreshDependencyId(effectiveTrustedKeys(dependencies, agentDir) as object), ...files].join("|");
1611
+ }
1612
+
1613
+ /** Only the dependency-free startup read is memoized; injected dependencies always recompute. */
1614
+ function acceptedRegistryReadIsCacheable(dependencies: Omit<ModelPresetRegistryDependencies, "agentDir">): boolean {
1615
+ return Object.keys(dependencies).length === 0;
1616
+ }
1617
+
1491
1618
  export function loadAcceptedModelPresetRegistry(
1492
1619
  agentDir = getAgentDir(),
1493
1620
  dependencies: Omit<ModelPresetRegistryDependencies, "agentDir"> = {},
1621
+ ): AcceptedModelPresetRegistry {
1622
+ if (!acceptedRegistryReadIsCacheable(dependencies) || environmentDisabled())
1623
+ return loadAcceptedModelPresetRegistryUncached(agentDir, dependencies);
1624
+ const fingerprint = acceptedRegistryFingerprint(agentDir, dependencies);
1625
+ const cached = acceptedRegistryReadCache.get(agentDir);
1626
+ if (cached && cached.fingerprint === fingerprint) return cloneAcceptedModelPresetRegistry(cached.value);
1627
+ const value = loadAcceptedModelPresetRegistryUncached(agentDir, dependencies);
1628
+ const verifiedFingerprint = acceptedRegistryFingerprint(agentDir, dependencies);
1629
+ if (verifiedFingerprint !== fingerprint) return loadAcceptedModelPresetRegistryUncached(agentDir, dependencies);
1630
+ acceptedRegistryReadCache.set(agentDir, { fingerprint, value });
1631
+ return cloneAcceptedModelPresetRegistry(value);
1632
+ }
1633
+
1634
+ function loadAcceptedModelPresetRegistryUncached(
1635
+ agentDir: string,
1636
+ dependencies: Omit<ModelPresetRegistryDependencies, "agentDir">,
1494
1637
  ): AcceptedModelPresetRegistry {
1495
1638
  if (environmentDisabled())
1496
1639
  return {
@@ -1551,6 +1694,22 @@ export function loadAcceptedModelPresetRegistry(
1551
1694
  export async function loadAcceptedModelPresetRegistryAsync(
1552
1695
  agentDir = getAgentDir(),
1553
1696
  dependencies: Omit<ModelPresetRegistryDependencies, "agentDir"> = {},
1697
+ ): Promise<AcceptedModelPresetRegistry> {
1698
+ if (!acceptedRegistryReadIsCacheable(dependencies) || environmentDisabled())
1699
+ return loadAcceptedModelPresetRegistryAsyncUncached(agentDir, dependencies);
1700
+ const fingerprint = await acceptedRegistryFingerprintAsync(agentDir, dependencies);
1701
+ const cached = acceptedRegistryReadCache.get(agentDir);
1702
+ if (cached && cached.fingerprint === fingerprint) return cloneAcceptedModelPresetRegistry(cached.value);
1703
+ const value = await loadAcceptedModelPresetRegistryAsyncUncached(agentDir, dependencies);
1704
+ const verifiedFingerprint = await acceptedRegistryFingerprintAsync(agentDir, dependencies);
1705
+ if (verifiedFingerprint !== fingerprint) return loadAcceptedModelPresetRegistryAsyncUncached(agentDir, dependencies);
1706
+ acceptedRegistryReadCache.set(agentDir, { fingerprint, value });
1707
+ return cloneAcceptedModelPresetRegistry(value);
1708
+ }
1709
+
1710
+ async function loadAcceptedModelPresetRegistryAsyncUncached(
1711
+ agentDir: string,
1712
+ dependencies: Omit<ModelPresetRegistryDependencies, "agentDir">,
1554
1713
  ): Promise<AcceptedModelPresetRegistry> {
1555
1714
  if (environmentDisabled())
1556
1715
  return {