@ai-matrx/agents 0.59.257 → 0.59.258

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## 0.59.257
3
+ ## 0.59.258
4
4
 
5
5
  UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
6
6
  Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/matrx-agents/src`.
7
7
 
8
+ ## 0.59.257
9
+
10
+ Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 76b41cb76b 0a2b8c9356 -- apps/shared/matrx-agents/src`
11
+ has no shipped-source changes outside tests, fixtures and docs). No consumer action required.
12
+
8
13
  ## 0.59.256
9
14
 
10
15
  Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff be3d056be5 76b41cb76b -- apps/shared/matrx-agents/src`
@@ -42,6 +42,7 @@ __export(models_exports, {
42
42
  TOP_TIER_COST_RATING: () => TOP_TIER_COST_RATING,
43
43
  TOP_TIER_LOCKED_TITLE: () => TOP_TIER_LOCKED_TITLE,
44
44
  acceptsTextInstructions: () => acceptsTextInstructions,
45
+ announceModelCatalogChanged: () => announceModelCatalogChanged,
45
46
  blendedPrice: () => blendedPrice,
46
47
  buildModelClassIndex: () => buildModelClassIndex,
47
48
  buildSuccessionIndex: () => buildSuccessionIndex,
@@ -65,6 +66,7 @@ __export(models_exports, {
65
66
  fitsMemory: () => fitsMemory,
66
67
  fitsNeed: () => fitsNeed,
67
68
  fitsThisComputer: () => fitsThisComputer,
69
+ followModelCatalogChanges: () => followModelCatalogChanges,
68
70
  formatMemoryBytes: () => formatMemoryBytes,
69
71
  fromDiscovery: () => fromDiscovery,
70
72
  ggufEngine: () => ggufEngine,
@@ -82,21 +84,25 @@ __export(models_exports, {
82
84
  isListedVariant: () => isListedVariant,
83
85
  isLocalModelCatalog: () => isLocalModelCatalog,
84
86
  isManagedAgentModelCapability: () => isManagedAgentModelCapability,
87
+ isModelCatalogChange: () => isModelCatalogChange,
85
88
  isProjectorName: () => isProjectorName,
86
89
  isSupersedable: () => isSupersedable,
87
90
  isTopTierRating: () => isTopTierRating,
88
91
  judgeHeaderFacts: () => judgeHeaderFacts,
89
92
  mergeAuditRecordIntoCapabilities: () => mergeAuditRecordIntoCapabilities,
90
93
  missingShards: () => missingShards,
94
+ modelCatalogRevision: () => modelCatalogRevision,
91
95
  modelClassKnobFor: () => modelClassKnobFor,
92
96
  modelClassName: () => modelClassName,
93
97
  modelClassPinOf: () => modelClassPinOf,
98
+ modelDisplayLabel: () => modelDisplayLabel,
94
99
  modelsForSelectionPurpose: () => modelsForSelectionPurpose,
95
100
  normalizeArch: () => normalizeArch,
96
101
  normalizeModel: () => normalizeModel,
97
102
  normalizeModels: () => normalizeModels,
98
103
  normalizeOs: () => normalizeOs,
99
104
  normalizePromptSettings: () => normalizePromptSettings,
105
+ onModelCatalogChanged: () => onModelCatalogChanged,
100
106
  orderCatalogGroups: () => orderCatalogGroups,
101
107
  orderCatalogModels: () => orderCatalogModels,
102
108
  outputFormatControlKey: () => outputFormatControlKey,
@@ -108,6 +114,8 @@ __export(models_exports, {
108
114
  readCuratedRows: () => readCuratedRows,
109
115
  readGgufHeader: () => readGgufHeader,
110
116
  readModelCatalog: () => readModelCatalog,
117
+ readModelIdentities: () => readModelIdentities,
118
+ readPublicModel: () => readPublicModel,
111
119
  reconcileModelFavoriteIds: () => reconcileModelFavoriteIds,
112
120
  reportModelDiagnostic: () => reportModelDiagnostic,
113
121
  requireCanonicalCapabilities: () => requireCanonicalCapabilities,
@@ -918,6 +926,48 @@ var OUTPUT_MODALITIES = [
918
926
  "decision"
919
927
  ];
920
928
 
929
+ // models/catalog/changes.ts
930
+ var KEY2 = /* @__PURE__ */ Symbol.for("ai-matrx.agents.model-catalog-changes");
931
+ function hub() {
932
+ const g = globalThis;
933
+ return g[KEY2] ??= { listeners: /* @__PURE__ */ new Set(), revision: 0 };
934
+ }
935
+ function isModelCatalogChange(payload) {
936
+ const catalog = payload?.["catalog"];
937
+ return catalog === void 0 || catalog === null || catalog === "models";
938
+ }
939
+ function onModelCatalogChanged(listener) {
940
+ const h = hub();
941
+ h.listeners.add(listener);
942
+ return () => {
943
+ h.listeners.delete(listener);
944
+ };
945
+ }
946
+ function modelCatalogRevision() {
947
+ return hub().revision;
948
+ }
949
+ function announceModelCatalogChanged(payload = {}) {
950
+ if (!isModelCatalogChange(payload)) return;
951
+ const h = hub();
952
+ h.revision += 1;
953
+ for (const l of [...h.listeners]) {
954
+ try {
955
+ l(payload);
956
+ } catch (err) {
957
+ console.error("[@ai-matrx/agents/models] a catalog_changed listener threw", err);
958
+ }
959
+ }
960
+ }
961
+ function followModelCatalogChanges(subscribe) {
962
+ return subscribe("catalog_changed", (payload) => announceModelCatalogChanged(payload ?? {}));
963
+ }
964
+
965
+ // models/label.ts
966
+ function modelDisplayLabel(row) {
967
+ if (!row) return void 0;
968
+ return row.common_name || row.name || void 0;
969
+ }
970
+
921
971
  // models/catalog/records.ts
922
972
  var classConfigKey = (modelId, offeringId) => `${modelId}:${offeringId}`;
923
973
  var EMPTY_MODEL_RECORDS = Object.freeze({
@@ -986,7 +1036,7 @@ function withIdLists(state2) {
986
1036
  const active = [];
987
1037
  const deprecated = [];
988
1038
  for (const [id, record] of Object.entries(state2.entities)) (record.is_deprecated === true ? deprecated : active).push(id);
989
- const label = (id) => state2.entities[id]?.common_name ?? state2.entities[id]?.name ?? "";
1039
+ const label = (id) => modelDisplayLabel(state2.entities[id]) ?? "";
990
1040
  const sort = (ids) => ids.sort((a, b) => label(a).localeCompare(label(b)));
991
1041
  return { ...state2, activeIds: sort(active), deprecatedIds: sort(deprecated) };
992
1042
  }
@@ -1407,13 +1457,13 @@ function selectDeprecatedModels(s3) {
1407
1457
  var allOf = memo((entities) => Object.values(entities));
1408
1458
  var selectAllModels = (s3) => allOf(s3.entities);
1409
1459
  var optionsOf = memo(
1410
- (models) => models.map((m) => ({ value: m.id, label: m.common_name || m.name || m.id, maker: m.maker, costRating: m.cost_rating, speedRating: m.speed_rating }))
1460
+ (models) => models.map((m) => ({ value: m.id, label: modelDisplayLabel(m) || m.id, maker: m.maker, costRating: m.cost_rating, speedRating: m.speed_rating }))
1411
1461
  );
1412
1462
  var selectModelOptions = (s3) => optionsOf(selectActiveModels(s3));
1413
1463
  var allOptionsOf = memo(
1414
1464
  (entities) => Object.values(entities).map((m) => ({
1415
1465
  value: m.id,
1416
- label: m.common_name || m.name || m.id,
1466
+ label: modelDisplayLabel(m) || m.id,
1417
1467
  maker: m.maker,
1418
1468
  isDeprecated: m.is_deprecated ?? false,
1419
1469
  isRetired: m.retired_at != null,
@@ -1426,7 +1476,7 @@ function selectModelLabelById(s3, modelId) {
1426
1476
  if (!modelId) return void 0;
1427
1477
  const m = s3.entities[modelId];
1428
1478
  const identity = s3.identityById[modelId];
1429
- return m?.common_name || m?.name || identity?.common_name || identity?.name || void 0;
1479
+ return modelDisplayLabel(m) || modelDisplayLabel(identity);
1430
1480
  }
1431
1481
  var selectModelNameById = (s3, modelId) => modelId ? s3.entities[modelId]?.name : void 0;
1432
1482
  var selectModelClassName = (s3, modelId, offeringId) => modelClassName(s3.modelClassIndex, modelId, offeringId);
@@ -1444,6 +1494,18 @@ var selectDeprecatedModelsReady = (s3) => (s3.fetchScope === "deprecated" || s3.
1444
1494
  var selectAllModelsReady = (s3) => s3.fetchScope === "all";
1445
1495
  var selectModelIdentityById = (s3, modelId) => s3.identityById[modelId];
1446
1496
  var selectModelIdentityLookupStatus = (s3, modelId) => s3.identityStatusById[modelId] ?? "idle";
1497
+ async function readModelIdentities(client, modelIds) {
1498
+ const ids = [...new Set(modelIds.filter(Boolean))];
1499
+ if (ids.length === 0) return [];
1500
+ const { data, error } = await client.schema("ai").from("model_definition").select("id, name, common_name, is_deprecated").in("id", ids);
1501
+ if (error) throw new Error(error.message);
1502
+ return Array.isArray(data) ? data : [];
1503
+ }
1504
+ async function readPublicModel(client, key) {
1505
+ const { data, error } = await client.schema("ai").from("model_public").select("id, name").eq(catalogKeyColumn(key), key.trim()).maybeSingle();
1506
+ if (error) return { row: null, error: error.message };
1507
+ return { row: data ?? null, error: null };
1508
+ }
1447
1509
 
1448
1510
  // models/catalog/store.ts
1449
1511
  var IDLE = { status: "idle", models: [], error: null, loadedAt: null };
@@ -1713,8 +1775,16 @@ function createModelCatalog(config) {
1713
1775
  errorSink,
1714
1776
  ...config.isSignedIn ? { isSignedIn: config.isSignedIn } : {}
1715
1777
  });
1778
+ const refresh = async () => {
1779
+ const variants = Object.keys(state2).filter((v) => state2[v].status !== "idle");
1780
+ await Promise.all([records.refresh(), ...variants.map((v) => run(v))]);
1781
+ };
1782
+ const stopFollowing = config.followChanges === false ? () => {
1783
+ } : onModelCatalogChanged(() => void refresh());
1716
1784
  return {
1717
1785
  records,
1786
+ refresh,
1787
+ dispose: stopFollowing,
1718
1788
  getState: () => state2,
1719
1789
  subscribe(listener) {
1720
1790
  listeners.add(listener);
@@ -750,6 +750,41 @@ declare const selectDeprecatedModelsReady: (s: ModelRecordsState) => boolean;
750
750
  declare const selectAllModelsReady: (s: ModelRecordsState) => boolean;
751
751
  declare const selectModelIdentityById: (s: ModelRecordsState, modelId: string) => ModelIdentityRow | undefined;
752
752
  declare const selectModelIdentityLookupStatus: (s: ModelRecordsState, modelId: string) => "loading" | "succeeded" | "failed" | "idle";
753
+ /**
754
+ * The durable identities (id, name, common_name, is_deprecated) of these models, read from
755
+ * `ai.model_definition` — the same read `records.loadIdentity` makes. For a process with no records
756
+ * store that stamps a name ONCE (a sync snapshot); a screen names models through the records hooks.
757
+ * Rows the database does not return are absent. Throws the database's error.
758
+ */
759
+ declare function readModelIdentities(client: ModelRecordsClient, modelIds: readonly string[]): Promise<ModelIdentityRow[]>;
760
+ /** The structural read `readPublicModel` needs (a supabase-js client fits it as is). */
761
+ interface PublicModelClient {
762
+ schema(name: "ai"): {
763
+ from(table: "model_public"): {
764
+ select(columns: string): {
765
+ eq(column: string, value: string): {
766
+ maybeSingle(): PromiseLike<{
767
+ data: unknown;
768
+ error: {
769
+ message: string;
770
+ } | null;
771
+ }>;
772
+ };
773
+ };
774
+ };
775
+ };
776
+ }
777
+ /**
778
+ * One `ai.model_public` row (id + canonical name) by id or name, read fresh — for a value sent on the wire at
779
+ * the moment it is used (a voice session's transcription model), never for a label on screen.
780
+ */
781
+ declare function readPublicModel(client: PublicModelClient, key: string): Promise<{
782
+ row: {
783
+ id?: unknown;
784
+ name?: unknown;
785
+ } | null;
786
+ error: string | null;
787
+ }>;
753
788
 
754
789
  /**
755
790
  * `@ai-matrx/agents/models` — `createModelCatalog`: the headless model catalog store.
@@ -802,6 +837,8 @@ type ModelCatalogErrorSink = (event: {
802
837
  context?: object;
803
838
  }) => void;
804
839
  interface ModelCatalogConfig {
840
+ /** Re-read on every model catalog change notice (catalog/changes.ts). Default true. */
841
+ followChanges?: boolean;
805
842
  client: ModelCatalogClient;
806
843
  /** OPTIONAL — DEFAULT: a tagged `console.error` (the in-picker error banner always renders too). */
807
844
  errorSink?: ModelCatalogErrorSink;
@@ -828,6 +865,14 @@ interface ModelCatalog {
828
865
  load(variant: ModelCatalogVariant): Promise<void>;
829
866
  /** Re-read the variant now (rows stay visible while it runs). Never throws. */
830
867
  reload(variant: ModelCatalogVariant): Promise<void>;
868
+ /**
869
+ * The catalog changed upstream (`catalog_changed`): re-read every picker variant already read and every
870
+ * record already read (`records.refresh()`). Called by itself on every `announceModelCatalogChanged`
871
+ * (catalog/changes.ts) unless `followChanges: false`. Never throws.
872
+ */
873
+ refresh(): Promise<void>;
874
+ /** Stop following `catalog_changed` (a test, or a catalog a host discards). */
875
+ dispose(): void;
831
876
  /**
832
877
  * The RECORDS part (agent core B3): options list, full records with resolved controls, class
833
878
  * configs, the class index and identities — what chat's retired `modelRegistry` slice held.
@@ -1818,4 +1863,49 @@ catalog?: SuccessionClient): Promise<NewAgentModel>;
1818
1863
  */
1819
1864
  declare function resolveModelMapKnob(value: unknown, catalog: SuccessionClient): Promise<Record<string, string>>;
1820
1865
 
1821
- export { type AdminOffering, type AdminPricingBand, type ArtifactUse, type AuditCapabilitiesRecord, CHAT_DEFAULT_MODEL_KNOB, CONTENT_TYPES, type CapabilitiesParseContext, type CatalogKeyColumn, type CatalogKindInput, type CatalogModel, type CatalogTier, type CheapestAdequateOptions, type CompatStatus, type ContentType, type CuratedCatalogClient, type CuratedModel, type CuratedPresentation, type CuratedRows, type CuratedVariant, DEFAULT_CAPABILITIES, type DecidedBy, type DeviceGraphics, type DeviceHardware, type DiscoveryAmbiguity, type DiscoveryArtifact, type DiscoveryMedia, type DiscoveryResult, type DiscoveryRuntime, type DiscoverySource, type DiscoveryVariant, EMPTY_MODEL_RECORDS, EXCLUDED_REVIEW_STATES, FEATURE_BUCKETS, FEATURE_BUCKET_ORDER, FEATURE_KEYS, FILE_TYPE_QUANT, type FeatureBucket, type FeatureBucketMeta, type FeatureKey, type FitCapabilities, GgufError, type GgufHeader, type GgufRead, type GgufTensor, type GroupedFeatures, type HardwareClass, type HardwareClasses, type HeaderFlag, type HeaderJudgement, type HeaderRefusal, INPUT_MODALITIES, INTERACTION_MODES, type Interaction, type InteractionMode, type LocalArtifact, type LocalCatalogClient, type LocalCatalogQuery, type LocalCompatRow, type LocalModel, type LocalModelCatalog, type LocalPresentation, type LocalRuntime, type LocalRuntimeBuild, type LocalVariant, MAX_SUCCESSION_HOPS, MODEL_CATALOG_KINDS, MODEL_FAVORITE_ENTITY_TYPE, type Modality, type ModelCapabilities, type ModelCatalog, type ModelCatalogClient, type ModelCatalogConfig, type ModelCatalogErrorSink, type ModelCatalogPgError, type ModelCatalogPgResult, type ModelCatalogQuery, type ModelCatalogState, type ModelCatalogVariant, type ModelCatalogVariantState, type ModelClassConfig, type ModelClassEntry, type ModelClassIndex, type ModelDiagnosticEntry, type ModelDiagnosticsSink, type ModelFavorites, type ModelFavoritesCache, type ModelFavoritesClient, type ModelFavoritesConfig, type ModelFavoritesErrorSink, type ModelFavoritesState, type ModelFetchScope, type ModelFileHandle, type ModelFit, type ModelIdentityRow, type ModelJson, type ModelNeed, type ModelOfferingRow, type ModelOption, type ModelOptionWithLifecycle, type ModelRecord, type ModelRecordFetchType, type ModelRecords, type ModelRecordsClient, type ModelRecordsConfig, type ModelRecordsState, type ModelSelectionPurpose, type ModelSetEnv, type ModelSetFacts, type ModelSetValidation, NEW_AGENT_DEFAULT_MODEL_KNOB, type NewAgentModel, type NoSelection, OUTPUT_MODALITIES, type OrderedLike, type PreferredModelChoice, type PreferredModelChoicePorts, type PriceTier, type PricedCatalogModel, type RankOptions, type RankedVariant, type ReadModelCatalogOptions, SERVICE_LABEL, SERVICE_LABEL_PLURAL, type SelectDevice, type Selection, type ShardName, type SuccessionClient, type SuccessionIndex, type SuccessionNode, TOP_TIER_COST_RATING, TOP_TIER_LOCKED_TITLE, type VariantDiscovery, type VariantVerdict, type Want, acceptsTextInstructions, blendedPrice, buildModelClassIndex, buildSuccessionIndex, catalogFingerprint, catalogForApp, catalogKeyColumn, catalogKindOf, cheapestAdequateModel, classConfigKey, collapseTiersByClass, costRatingTier, costRatingWord, createModelCatalog, createModelFavorites, createModelRecords, curatedList, displayOrderOf, favoriteIdsEqual, fetchCuratedModels, findNonCanonicalCapabilityValues, fitsMemory, fitsNeed, fitsThisComputer, formatMemoryBytes, fromDiscovery, ggufEngine, ggufFacts, groupFeatures, hardwareClassesOf, hasCompatibleDecisionInteraction, isChatModelCapability, isContentType, isConversationalModelCapability, isDecisionModelCapability, isFeatureKey, isHelperGguf, isInteractionMode, isListedVariant, isLocalModelCatalog, isManagedAgentModelCapability, isProjectorName, isSupersedable, isTopTierRating, judgeHeaderFacts, mergeAuditRecordIntoCapabilities, missingShards, modelClassKnobFor, modelClassName, modelClassPinOf, modelsForSelectionPurpose, normalizeArch, normalizeModel, normalizeModels, normalizeOs, normalizePromptSettings, orderCatalogGroups, orderCatalogModels, outputFormatControlKey, parseCapabilities, pickProjector, priceTierRating, quantFromName, rankVariants, readCuratedRows, readGgufHeader, readModelCatalog, reconcileModelFavoriteIds, reportModelDiagnostic, requireCanonicalCapabilities, requiredArtifacts, resetSuccessionLogForTests, resolveLiveModelId, resolveModelMapKnob, resolveNewAgentModelId, resolvePreferredModelChoice, resolveSuccessor, runtimesOnEngine, seesImages, selectActiveModels, selectActiveModelsReady, selectAllModelOptions, selectAllModels, selectAllModelsReady, selectDeprecatedModels, selectDeprecatedModelsReady, selectModelById, selectModelClassConfig, selectModelClassName, selectModelControls, selectModelDetailError, selectModelFetchType, selectModelFullyLoaded, selectModelIdentityById, selectModelIdentityLookupStatus, selectModelLabelById, selectModelLabelWithClass, selectModelNameById, selectModelOptions, selectVariant, serviceCountLabel, setModelDiagnosticsSink, shapeCuratedModels, shardFileName, shardName, speedRatingLabel, speedRatingWord, splitSetProblems, takesInput, toAuditRecord, validateModelSet, variantVerdict, withModelClass };
1866
+ /**
1867
+ * `@ai-matrx/agents/models` — THE model label formula. Every surface that names a model computes the
1868
+ * name here: the person-facing `common_name`, else the canonical `name`. An empty `common_name` counts
1869
+ * as absent. Pure; the row it is handed must come from the subscribed catalog (records / readModelCatalog),
1870
+ * so a `catalog_changed` notice re-renders it. Guard: `scripts/check-model-catalog-reads.mjs`.
1871
+ */
1872
+ /** The two columns a model label is made from (cloud records, identities, local catalog rows). */
1873
+ interface ModelLabelSource {
1874
+ common_name?: string | null;
1875
+ name?: string | null;
1876
+ }
1877
+ /** A row that always has a canonical name always has a label. */
1878
+ declare function modelDisplayLabel(row: {
1879
+ common_name?: string | null;
1880
+ name: string;
1881
+ }): string;
1882
+ declare function modelDisplayLabel(row: ModelLabelSource | null | undefined): string | undefined;
1883
+
1884
+ /**
1885
+ * `@ai-matrx/agents/models` — the ONE subscription to `catalog_changed`.
1886
+ *
1887
+ * The server publishes `catalog_changed` on the platform bus for every admin edit and direct SQL write to
1888
+ * the model catalog. A host binds its bus client ONCE with `followModelCatalogChanges(subscribe)` (the web's
1889
+ * directive registry, Matrx 2's IPC forward of the main-process bus); every model catalog
1890
+ * (`createModelCatalog`: picker rows + records) re-reads itself on the notice, and any other reader
1891
+ * subscribes here (`onModelCatalogChanged`, or `useModelCatalogRevision` / `useOnModelCatalogChanged` in
1892
+ * `@ai-matrx/agents/models/react`). No surface keeps a model cache that misses the event.
1893
+ *
1894
+ * The hub lives on `globalThis` so two bundled copies of this module share one set of listeners.
1895
+ */
1896
+ /** A `catalog_changed` payload: `{ catalog?: "models" | ..., change?, model_id? }`. */
1897
+ type ModelCatalogChangePayload = Record<string, unknown>;
1898
+ /** The bus client's subscribe call: `(action, handler) => unsubscribe` (the web's `registerDirectiveHandler`). */
1899
+ type ModelCatalogDirectiveSubscribe = (action: "catalog_changed", handler: (payload: ModelCatalogChangePayload) => void) => () => void;
1900
+ /** Is this notice about the MODEL catalog? (`catalog` absent or `"models"`; the tool catalog shares the action.) */
1901
+ declare function isModelCatalogChange(payload: ModelCatalogChangePayload | null | undefined): boolean;
1902
+ /** Listen for model catalog changes. Returns the unsubscribe. */
1903
+ declare function onModelCatalogChanged(listener: (payload: ModelCatalogChangePayload) => void): () => void;
1904
+ /** How many model catalog changes this page has seen (a React key / effect dependency). */
1905
+ declare function modelCatalogRevision(): number;
1906
+ /** Announce a model catalog change to every listener (a notice that is not about models is ignored). */
1907
+ declare function announceModelCatalogChanged(payload?: ModelCatalogChangePayload): void;
1908
+ /** Bind the host's bus client: every `catalog_changed` it delivers reaches every model reader. Returns the unbind. */
1909
+ declare function followModelCatalogChanges(subscribe: ModelCatalogDirectiveSubscribe): () => void;
1910
+
1911
+ export { type AdminOffering, type AdminPricingBand, type ArtifactUse, type AuditCapabilitiesRecord, CHAT_DEFAULT_MODEL_KNOB, CONTENT_TYPES, type CapabilitiesParseContext, type CatalogKeyColumn, type CatalogKindInput, type CatalogModel, type CatalogTier, type CheapestAdequateOptions, type CompatStatus, type ContentType, type CuratedCatalogClient, type CuratedModel, type CuratedPresentation, type CuratedRows, type CuratedVariant, DEFAULT_CAPABILITIES, type DecidedBy, type DeviceGraphics, type DeviceHardware, type DiscoveryAmbiguity, type DiscoveryArtifact, type DiscoveryMedia, type DiscoveryResult, type DiscoveryRuntime, type DiscoverySource, type DiscoveryVariant, EMPTY_MODEL_RECORDS, EXCLUDED_REVIEW_STATES, FEATURE_BUCKETS, FEATURE_BUCKET_ORDER, FEATURE_KEYS, FILE_TYPE_QUANT, type FeatureBucket, type FeatureBucketMeta, type FeatureKey, type FitCapabilities, GgufError, type GgufHeader, type GgufRead, type GgufTensor, type GroupedFeatures, type HardwareClass, type HardwareClasses, type HeaderFlag, type HeaderJudgement, type HeaderRefusal, INPUT_MODALITIES, INTERACTION_MODES, type Interaction, type InteractionMode, type LocalArtifact, type LocalCatalogClient, type LocalCatalogQuery, type LocalCompatRow, type LocalModel, type LocalModelCatalog, type LocalPresentation, type LocalRuntime, type LocalRuntimeBuild, type LocalVariant, MAX_SUCCESSION_HOPS, MODEL_CATALOG_KINDS, MODEL_FAVORITE_ENTITY_TYPE, type Modality, type ModelCapabilities, type ModelCatalog, type ModelCatalogChangePayload, type ModelCatalogClient, type ModelCatalogConfig, type ModelCatalogDirectiveSubscribe, type ModelCatalogErrorSink, type ModelCatalogPgError, type ModelCatalogPgResult, type ModelCatalogQuery, type ModelCatalogState, type ModelCatalogVariant, type ModelCatalogVariantState, type ModelClassConfig, type ModelClassEntry, type ModelClassIndex, type ModelDiagnosticEntry, type ModelDiagnosticsSink, type ModelFavorites, type ModelFavoritesCache, type ModelFavoritesClient, type ModelFavoritesConfig, type ModelFavoritesErrorSink, type ModelFavoritesState, type ModelFetchScope, type ModelFileHandle, type ModelFit, type ModelIdentityRow, type ModelJson, type ModelLabelSource, type ModelNeed, type ModelOfferingRow, type ModelOption, type ModelOptionWithLifecycle, type ModelRecord, type ModelRecordFetchType, type ModelRecords, type ModelRecordsClient, type ModelRecordsConfig, type ModelRecordsState, type ModelSelectionPurpose, type ModelSetEnv, type ModelSetFacts, type ModelSetValidation, NEW_AGENT_DEFAULT_MODEL_KNOB, type NewAgentModel, type NoSelection, OUTPUT_MODALITIES, type OrderedLike, type PreferredModelChoice, type PreferredModelChoicePorts, type PriceTier, type PricedCatalogModel, type PublicModelClient, type RankOptions, type RankedVariant, type ReadModelCatalogOptions, SERVICE_LABEL, SERVICE_LABEL_PLURAL, type SelectDevice, type Selection, type ShardName, type SuccessionClient, type SuccessionIndex, type SuccessionNode, TOP_TIER_COST_RATING, TOP_TIER_LOCKED_TITLE, type VariantDiscovery, type VariantVerdict, type Want, acceptsTextInstructions, announceModelCatalogChanged, blendedPrice, buildModelClassIndex, buildSuccessionIndex, catalogFingerprint, catalogForApp, catalogKeyColumn, catalogKindOf, cheapestAdequateModel, classConfigKey, collapseTiersByClass, costRatingTier, costRatingWord, createModelCatalog, createModelFavorites, createModelRecords, curatedList, displayOrderOf, favoriteIdsEqual, fetchCuratedModels, findNonCanonicalCapabilityValues, fitsMemory, fitsNeed, fitsThisComputer, followModelCatalogChanges, formatMemoryBytes, fromDiscovery, ggufEngine, ggufFacts, groupFeatures, hardwareClassesOf, hasCompatibleDecisionInteraction, isChatModelCapability, isContentType, isConversationalModelCapability, isDecisionModelCapability, isFeatureKey, isHelperGguf, isInteractionMode, isListedVariant, isLocalModelCatalog, isManagedAgentModelCapability, isModelCatalogChange, isProjectorName, isSupersedable, isTopTierRating, judgeHeaderFacts, mergeAuditRecordIntoCapabilities, missingShards, modelCatalogRevision, modelClassKnobFor, modelClassName, modelClassPinOf, modelDisplayLabel, modelsForSelectionPurpose, normalizeArch, normalizeModel, normalizeModels, normalizeOs, normalizePromptSettings, onModelCatalogChanged, orderCatalogGroups, orderCatalogModels, outputFormatControlKey, parseCapabilities, pickProjector, priceTierRating, quantFromName, rankVariants, readCuratedRows, readGgufHeader, readModelCatalog, readModelIdentities, readPublicModel, reconcileModelFavoriteIds, reportModelDiagnostic, requireCanonicalCapabilities, requiredArtifacts, resetSuccessionLogForTests, resolveLiveModelId, resolveModelMapKnob, resolveNewAgentModelId, resolvePreferredModelChoice, resolveSuccessor, runtimesOnEngine, seesImages, selectActiveModels, selectActiveModelsReady, selectAllModelOptions, selectAllModels, selectAllModelsReady, selectDeprecatedModels, selectDeprecatedModelsReady, selectModelById, selectModelClassConfig, selectModelClassName, selectModelControls, selectModelDetailError, selectModelFetchType, selectModelFullyLoaded, selectModelIdentityById, selectModelIdentityLookupStatus, selectModelLabelById, selectModelLabelWithClass, selectModelNameById, selectModelOptions, selectVariant, serviceCountLabel, setModelDiagnosticsSink, shapeCuratedModels, shardFileName, shardName, speedRatingLabel, speedRatingWord, splitSetProblems, takesInput, toAuditRecord, validateModelSet, variantVerdict, withModelClass };
@@ -750,6 +750,41 @@ declare const selectDeprecatedModelsReady: (s: ModelRecordsState) => boolean;
750
750
  declare const selectAllModelsReady: (s: ModelRecordsState) => boolean;
751
751
  declare const selectModelIdentityById: (s: ModelRecordsState, modelId: string) => ModelIdentityRow | undefined;
752
752
  declare const selectModelIdentityLookupStatus: (s: ModelRecordsState, modelId: string) => "loading" | "succeeded" | "failed" | "idle";
753
+ /**
754
+ * The durable identities (id, name, common_name, is_deprecated) of these models, read from
755
+ * `ai.model_definition` — the same read `records.loadIdentity` makes. For a process with no records
756
+ * store that stamps a name ONCE (a sync snapshot); a screen names models through the records hooks.
757
+ * Rows the database does not return are absent. Throws the database's error.
758
+ */
759
+ declare function readModelIdentities(client: ModelRecordsClient, modelIds: readonly string[]): Promise<ModelIdentityRow[]>;
760
+ /** The structural read `readPublicModel` needs (a supabase-js client fits it as is). */
761
+ interface PublicModelClient {
762
+ schema(name: "ai"): {
763
+ from(table: "model_public"): {
764
+ select(columns: string): {
765
+ eq(column: string, value: string): {
766
+ maybeSingle(): PromiseLike<{
767
+ data: unknown;
768
+ error: {
769
+ message: string;
770
+ } | null;
771
+ }>;
772
+ };
773
+ };
774
+ };
775
+ };
776
+ }
777
+ /**
778
+ * One `ai.model_public` row (id + canonical name) by id or name, read fresh — for a value sent on the wire at
779
+ * the moment it is used (a voice session's transcription model), never for a label on screen.
780
+ */
781
+ declare function readPublicModel(client: PublicModelClient, key: string): Promise<{
782
+ row: {
783
+ id?: unknown;
784
+ name?: unknown;
785
+ } | null;
786
+ error: string | null;
787
+ }>;
753
788
 
754
789
  /**
755
790
  * `@ai-matrx/agents/models` — `createModelCatalog`: the headless model catalog store.
@@ -802,6 +837,8 @@ type ModelCatalogErrorSink = (event: {
802
837
  context?: object;
803
838
  }) => void;
804
839
  interface ModelCatalogConfig {
840
+ /** Re-read on every model catalog change notice (catalog/changes.ts). Default true. */
841
+ followChanges?: boolean;
805
842
  client: ModelCatalogClient;
806
843
  /** OPTIONAL — DEFAULT: a tagged `console.error` (the in-picker error banner always renders too). */
807
844
  errorSink?: ModelCatalogErrorSink;
@@ -828,6 +865,14 @@ interface ModelCatalog {
828
865
  load(variant: ModelCatalogVariant): Promise<void>;
829
866
  /** Re-read the variant now (rows stay visible while it runs). Never throws. */
830
867
  reload(variant: ModelCatalogVariant): Promise<void>;
868
+ /**
869
+ * The catalog changed upstream (`catalog_changed`): re-read every picker variant already read and every
870
+ * record already read (`records.refresh()`). Called by itself on every `announceModelCatalogChanged`
871
+ * (catalog/changes.ts) unless `followChanges: false`. Never throws.
872
+ */
873
+ refresh(): Promise<void>;
874
+ /** Stop following `catalog_changed` (a test, or a catalog a host discards). */
875
+ dispose(): void;
831
876
  /**
832
877
  * The RECORDS part (agent core B3): options list, full records with resolved controls, class
833
878
  * configs, the class index and identities — what chat's retired `modelRegistry` slice held.
@@ -1818,4 +1863,49 @@ catalog?: SuccessionClient): Promise<NewAgentModel>;
1818
1863
  */
1819
1864
  declare function resolveModelMapKnob(value: unknown, catalog: SuccessionClient): Promise<Record<string, string>>;
1820
1865
 
1821
- export { type AdminOffering, type AdminPricingBand, type ArtifactUse, type AuditCapabilitiesRecord, CHAT_DEFAULT_MODEL_KNOB, CONTENT_TYPES, type CapabilitiesParseContext, type CatalogKeyColumn, type CatalogKindInput, type CatalogModel, type CatalogTier, type CheapestAdequateOptions, type CompatStatus, type ContentType, type CuratedCatalogClient, type CuratedModel, type CuratedPresentation, type CuratedRows, type CuratedVariant, DEFAULT_CAPABILITIES, type DecidedBy, type DeviceGraphics, type DeviceHardware, type DiscoveryAmbiguity, type DiscoveryArtifact, type DiscoveryMedia, type DiscoveryResult, type DiscoveryRuntime, type DiscoverySource, type DiscoveryVariant, EMPTY_MODEL_RECORDS, EXCLUDED_REVIEW_STATES, FEATURE_BUCKETS, FEATURE_BUCKET_ORDER, FEATURE_KEYS, FILE_TYPE_QUANT, type FeatureBucket, type FeatureBucketMeta, type FeatureKey, type FitCapabilities, GgufError, type GgufHeader, type GgufRead, type GgufTensor, type GroupedFeatures, type HardwareClass, type HardwareClasses, type HeaderFlag, type HeaderJudgement, type HeaderRefusal, INPUT_MODALITIES, INTERACTION_MODES, type Interaction, type InteractionMode, type LocalArtifact, type LocalCatalogClient, type LocalCatalogQuery, type LocalCompatRow, type LocalModel, type LocalModelCatalog, type LocalPresentation, type LocalRuntime, type LocalRuntimeBuild, type LocalVariant, MAX_SUCCESSION_HOPS, MODEL_CATALOG_KINDS, MODEL_FAVORITE_ENTITY_TYPE, type Modality, type ModelCapabilities, type ModelCatalog, type ModelCatalogClient, type ModelCatalogConfig, type ModelCatalogErrorSink, type ModelCatalogPgError, type ModelCatalogPgResult, type ModelCatalogQuery, type ModelCatalogState, type ModelCatalogVariant, type ModelCatalogVariantState, type ModelClassConfig, type ModelClassEntry, type ModelClassIndex, type ModelDiagnosticEntry, type ModelDiagnosticsSink, type ModelFavorites, type ModelFavoritesCache, type ModelFavoritesClient, type ModelFavoritesConfig, type ModelFavoritesErrorSink, type ModelFavoritesState, type ModelFetchScope, type ModelFileHandle, type ModelFit, type ModelIdentityRow, type ModelJson, type ModelNeed, type ModelOfferingRow, type ModelOption, type ModelOptionWithLifecycle, type ModelRecord, type ModelRecordFetchType, type ModelRecords, type ModelRecordsClient, type ModelRecordsConfig, type ModelRecordsState, type ModelSelectionPurpose, type ModelSetEnv, type ModelSetFacts, type ModelSetValidation, NEW_AGENT_DEFAULT_MODEL_KNOB, type NewAgentModel, type NoSelection, OUTPUT_MODALITIES, type OrderedLike, type PreferredModelChoice, type PreferredModelChoicePorts, type PriceTier, type PricedCatalogModel, type RankOptions, type RankedVariant, type ReadModelCatalogOptions, SERVICE_LABEL, SERVICE_LABEL_PLURAL, type SelectDevice, type Selection, type ShardName, type SuccessionClient, type SuccessionIndex, type SuccessionNode, TOP_TIER_COST_RATING, TOP_TIER_LOCKED_TITLE, type VariantDiscovery, type VariantVerdict, type Want, acceptsTextInstructions, blendedPrice, buildModelClassIndex, buildSuccessionIndex, catalogFingerprint, catalogForApp, catalogKeyColumn, catalogKindOf, cheapestAdequateModel, classConfigKey, collapseTiersByClass, costRatingTier, costRatingWord, createModelCatalog, createModelFavorites, createModelRecords, curatedList, displayOrderOf, favoriteIdsEqual, fetchCuratedModels, findNonCanonicalCapabilityValues, fitsMemory, fitsNeed, fitsThisComputer, formatMemoryBytes, fromDiscovery, ggufEngine, ggufFacts, groupFeatures, hardwareClassesOf, hasCompatibleDecisionInteraction, isChatModelCapability, isContentType, isConversationalModelCapability, isDecisionModelCapability, isFeatureKey, isHelperGguf, isInteractionMode, isListedVariant, isLocalModelCatalog, isManagedAgentModelCapability, isProjectorName, isSupersedable, isTopTierRating, judgeHeaderFacts, mergeAuditRecordIntoCapabilities, missingShards, modelClassKnobFor, modelClassName, modelClassPinOf, modelsForSelectionPurpose, normalizeArch, normalizeModel, normalizeModels, normalizeOs, normalizePromptSettings, orderCatalogGroups, orderCatalogModels, outputFormatControlKey, parseCapabilities, pickProjector, priceTierRating, quantFromName, rankVariants, readCuratedRows, readGgufHeader, readModelCatalog, reconcileModelFavoriteIds, reportModelDiagnostic, requireCanonicalCapabilities, requiredArtifacts, resetSuccessionLogForTests, resolveLiveModelId, resolveModelMapKnob, resolveNewAgentModelId, resolvePreferredModelChoice, resolveSuccessor, runtimesOnEngine, seesImages, selectActiveModels, selectActiveModelsReady, selectAllModelOptions, selectAllModels, selectAllModelsReady, selectDeprecatedModels, selectDeprecatedModelsReady, selectModelById, selectModelClassConfig, selectModelClassName, selectModelControls, selectModelDetailError, selectModelFetchType, selectModelFullyLoaded, selectModelIdentityById, selectModelIdentityLookupStatus, selectModelLabelById, selectModelLabelWithClass, selectModelNameById, selectModelOptions, selectVariant, serviceCountLabel, setModelDiagnosticsSink, shapeCuratedModels, shardFileName, shardName, speedRatingLabel, speedRatingWord, splitSetProblems, takesInput, toAuditRecord, validateModelSet, variantVerdict, withModelClass };
1866
+ /**
1867
+ * `@ai-matrx/agents/models` — THE model label formula. Every surface that names a model computes the
1868
+ * name here: the person-facing `common_name`, else the canonical `name`. An empty `common_name` counts
1869
+ * as absent. Pure; the row it is handed must come from the subscribed catalog (records / readModelCatalog),
1870
+ * so a `catalog_changed` notice re-renders it. Guard: `scripts/check-model-catalog-reads.mjs`.
1871
+ */
1872
+ /** The two columns a model label is made from (cloud records, identities, local catalog rows). */
1873
+ interface ModelLabelSource {
1874
+ common_name?: string | null;
1875
+ name?: string | null;
1876
+ }
1877
+ /** A row that always has a canonical name always has a label. */
1878
+ declare function modelDisplayLabel(row: {
1879
+ common_name?: string | null;
1880
+ name: string;
1881
+ }): string;
1882
+ declare function modelDisplayLabel(row: ModelLabelSource | null | undefined): string | undefined;
1883
+
1884
+ /**
1885
+ * `@ai-matrx/agents/models` — the ONE subscription to `catalog_changed`.
1886
+ *
1887
+ * The server publishes `catalog_changed` on the platform bus for every admin edit and direct SQL write to
1888
+ * the model catalog. A host binds its bus client ONCE with `followModelCatalogChanges(subscribe)` (the web's
1889
+ * directive registry, Matrx 2's IPC forward of the main-process bus); every model catalog
1890
+ * (`createModelCatalog`: picker rows + records) re-reads itself on the notice, and any other reader
1891
+ * subscribes here (`onModelCatalogChanged`, or `useModelCatalogRevision` / `useOnModelCatalogChanged` in
1892
+ * `@ai-matrx/agents/models/react`). No surface keeps a model cache that misses the event.
1893
+ *
1894
+ * The hub lives on `globalThis` so two bundled copies of this module share one set of listeners.
1895
+ */
1896
+ /** A `catalog_changed` payload: `{ catalog?: "models" | ..., change?, model_id? }`. */
1897
+ type ModelCatalogChangePayload = Record<string, unknown>;
1898
+ /** The bus client's subscribe call: `(action, handler) => unsubscribe` (the web's `registerDirectiveHandler`). */
1899
+ type ModelCatalogDirectiveSubscribe = (action: "catalog_changed", handler: (payload: ModelCatalogChangePayload) => void) => () => void;
1900
+ /** Is this notice about the MODEL catalog? (`catalog` absent or `"models"`; the tool catalog shares the action.) */
1901
+ declare function isModelCatalogChange(payload: ModelCatalogChangePayload | null | undefined): boolean;
1902
+ /** Listen for model catalog changes. Returns the unsubscribe. */
1903
+ declare function onModelCatalogChanged(listener: (payload: ModelCatalogChangePayload) => void): () => void;
1904
+ /** How many model catalog changes this page has seen (a React key / effect dependency). */
1905
+ declare function modelCatalogRevision(): number;
1906
+ /** Announce a model catalog change to every listener (a notice that is not about models is ignored). */
1907
+ declare function announceModelCatalogChanged(payload?: ModelCatalogChangePayload): void;
1908
+ /** Bind the host's bus client: every `catalog_changed` it delivers reaches every model reader. Returns the unbind. */
1909
+ declare function followModelCatalogChanges(subscribe: ModelCatalogDirectiveSubscribe): () => void;
1910
+
1911
+ export { type AdminOffering, type AdminPricingBand, type ArtifactUse, type AuditCapabilitiesRecord, CHAT_DEFAULT_MODEL_KNOB, CONTENT_TYPES, type CapabilitiesParseContext, type CatalogKeyColumn, type CatalogKindInput, type CatalogModel, type CatalogTier, type CheapestAdequateOptions, type CompatStatus, type ContentType, type CuratedCatalogClient, type CuratedModel, type CuratedPresentation, type CuratedRows, type CuratedVariant, DEFAULT_CAPABILITIES, type DecidedBy, type DeviceGraphics, type DeviceHardware, type DiscoveryAmbiguity, type DiscoveryArtifact, type DiscoveryMedia, type DiscoveryResult, type DiscoveryRuntime, type DiscoverySource, type DiscoveryVariant, EMPTY_MODEL_RECORDS, EXCLUDED_REVIEW_STATES, FEATURE_BUCKETS, FEATURE_BUCKET_ORDER, FEATURE_KEYS, FILE_TYPE_QUANT, type FeatureBucket, type FeatureBucketMeta, type FeatureKey, type FitCapabilities, GgufError, type GgufHeader, type GgufRead, type GgufTensor, type GroupedFeatures, type HardwareClass, type HardwareClasses, type HeaderFlag, type HeaderJudgement, type HeaderRefusal, INPUT_MODALITIES, INTERACTION_MODES, type Interaction, type InteractionMode, type LocalArtifact, type LocalCatalogClient, type LocalCatalogQuery, type LocalCompatRow, type LocalModel, type LocalModelCatalog, type LocalPresentation, type LocalRuntime, type LocalRuntimeBuild, type LocalVariant, MAX_SUCCESSION_HOPS, MODEL_CATALOG_KINDS, MODEL_FAVORITE_ENTITY_TYPE, type Modality, type ModelCapabilities, type ModelCatalog, type ModelCatalogChangePayload, type ModelCatalogClient, type ModelCatalogConfig, type ModelCatalogDirectiveSubscribe, type ModelCatalogErrorSink, type ModelCatalogPgError, type ModelCatalogPgResult, type ModelCatalogQuery, type ModelCatalogState, type ModelCatalogVariant, type ModelCatalogVariantState, type ModelClassConfig, type ModelClassEntry, type ModelClassIndex, type ModelDiagnosticEntry, type ModelDiagnosticsSink, type ModelFavorites, type ModelFavoritesCache, type ModelFavoritesClient, type ModelFavoritesConfig, type ModelFavoritesErrorSink, type ModelFavoritesState, type ModelFetchScope, type ModelFileHandle, type ModelFit, type ModelIdentityRow, type ModelJson, type ModelLabelSource, type ModelNeed, type ModelOfferingRow, type ModelOption, type ModelOptionWithLifecycle, type ModelRecord, type ModelRecordFetchType, type ModelRecords, type ModelRecordsClient, type ModelRecordsConfig, type ModelRecordsState, type ModelSelectionPurpose, type ModelSetEnv, type ModelSetFacts, type ModelSetValidation, NEW_AGENT_DEFAULT_MODEL_KNOB, type NewAgentModel, type NoSelection, OUTPUT_MODALITIES, type OrderedLike, type PreferredModelChoice, type PreferredModelChoicePorts, type PriceTier, type PricedCatalogModel, type PublicModelClient, type RankOptions, type RankedVariant, type ReadModelCatalogOptions, SERVICE_LABEL, SERVICE_LABEL_PLURAL, type SelectDevice, type Selection, type ShardName, type SuccessionClient, type SuccessionIndex, type SuccessionNode, TOP_TIER_COST_RATING, TOP_TIER_LOCKED_TITLE, type VariantDiscovery, type VariantVerdict, type Want, acceptsTextInstructions, announceModelCatalogChanged, blendedPrice, buildModelClassIndex, buildSuccessionIndex, catalogFingerprint, catalogForApp, catalogKeyColumn, catalogKindOf, cheapestAdequateModel, classConfigKey, collapseTiersByClass, costRatingTier, costRatingWord, createModelCatalog, createModelFavorites, createModelRecords, curatedList, displayOrderOf, favoriteIdsEqual, fetchCuratedModels, findNonCanonicalCapabilityValues, fitsMemory, fitsNeed, fitsThisComputer, followModelCatalogChanges, formatMemoryBytes, fromDiscovery, ggufEngine, ggufFacts, groupFeatures, hardwareClassesOf, hasCompatibleDecisionInteraction, isChatModelCapability, isContentType, isConversationalModelCapability, isDecisionModelCapability, isFeatureKey, isHelperGguf, isInteractionMode, isListedVariant, isLocalModelCatalog, isManagedAgentModelCapability, isModelCatalogChange, isProjectorName, isSupersedable, isTopTierRating, judgeHeaderFacts, mergeAuditRecordIntoCapabilities, missingShards, modelCatalogRevision, modelClassKnobFor, modelClassName, modelClassPinOf, modelDisplayLabel, modelsForSelectionPurpose, normalizeArch, normalizeModel, normalizeModels, normalizeOs, normalizePromptSettings, onModelCatalogChanged, orderCatalogGroups, orderCatalogModels, outputFormatControlKey, parseCapabilities, pickProjector, priceTierRating, quantFromName, rankVariants, readCuratedRows, readGgufHeader, readModelCatalog, readModelIdentities, readPublicModel, reconcileModelFavoriteIds, reportModelDiagnostic, requireCanonicalCapabilities, requiredArtifacts, resetSuccessionLogForTests, resolveLiveModelId, resolveModelMapKnob, resolveNewAgentModelId, resolvePreferredModelChoice, resolveSuccessor, runtimesOnEngine, seesImages, selectActiveModels, selectActiveModelsReady, selectAllModelOptions, selectAllModels, selectAllModelsReady, selectDeprecatedModels, selectDeprecatedModelsReady, selectModelById, selectModelClassConfig, selectModelClassName, selectModelControls, selectModelDetailError, selectModelFetchType, selectModelFullyLoaded, selectModelIdentityById, selectModelIdentityLookupStatus, selectModelLabelById, selectModelLabelWithClass, selectModelNameById, selectModelOptions, selectVariant, serviceCountLabel, setModelDiagnosticsSink, shapeCuratedModels, shardFileName, shardName, speedRatingLabel, speedRatingWord, splitSetProblems, takesInput, toAuditRecord, validateModelSet, variantVerdict, withModelClass };
@@ -759,6 +759,48 @@ var OUTPUT_MODALITIES = [
759
759
  "decision"
760
760
  ];
761
761
 
762
+ // models/catalog/changes.ts
763
+ var KEY2 = /* @__PURE__ */ Symbol.for("ai-matrx.agents.model-catalog-changes");
764
+ function hub() {
765
+ const g = globalThis;
766
+ return g[KEY2] ??= { listeners: /* @__PURE__ */ new Set(), revision: 0 };
767
+ }
768
+ function isModelCatalogChange(payload) {
769
+ const catalog = payload?.["catalog"];
770
+ return catalog === void 0 || catalog === null || catalog === "models";
771
+ }
772
+ function onModelCatalogChanged(listener) {
773
+ const h = hub();
774
+ h.listeners.add(listener);
775
+ return () => {
776
+ h.listeners.delete(listener);
777
+ };
778
+ }
779
+ function modelCatalogRevision() {
780
+ return hub().revision;
781
+ }
782
+ function announceModelCatalogChanged(payload = {}) {
783
+ if (!isModelCatalogChange(payload)) return;
784
+ const h = hub();
785
+ h.revision += 1;
786
+ for (const l of [...h.listeners]) {
787
+ try {
788
+ l(payload);
789
+ } catch (err) {
790
+ console.error("[@ai-matrx/agents/models] a catalog_changed listener threw", err);
791
+ }
792
+ }
793
+ }
794
+ function followModelCatalogChanges(subscribe) {
795
+ return subscribe("catalog_changed", (payload) => announceModelCatalogChanged(payload ?? {}));
796
+ }
797
+
798
+ // models/label.ts
799
+ function modelDisplayLabel(row) {
800
+ if (!row) return void 0;
801
+ return row.common_name || row.name || void 0;
802
+ }
803
+
762
804
  // models/catalog/records.ts
763
805
  var classConfigKey = (modelId, offeringId) => `${modelId}:${offeringId}`;
764
806
  var EMPTY_MODEL_RECORDS = Object.freeze({
@@ -827,7 +869,7 @@ function withIdLists(state2) {
827
869
  const active = [];
828
870
  const deprecated = [];
829
871
  for (const [id, record] of Object.entries(state2.entities)) (record.is_deprecated === true ? deprecated : active).push(id);
830
- const label = (id) => state2.entities[id]?.common_name ?? state2.entities[id]?.name ?? "";
872
+ const label = (id) => modelDisplayLabel(state2.entities[id]) ?? "";
831
873
  const sort = (ids) => ids.sort((a, b) => label(a).localeCompare(label(b)));
832
874
  return { ...state2, activeIds: sort(active), deprecatedIds: sort(deprecated) };
833
875
  }
@@ -1248,13 +1290,13 @@ function selectDeprecatedModels(s3) {
1248
1290
  var allOf = memo((entities) => Object.values(entities));
1249
1291
  var selectAllModels = (s3) => allOf(s3.entities);
1250
1292
  var optionsOf = memo(
1251
- (models) => models.map((m) => ({ value: m.id, label: m.common_name || m.name || m.id, maker: m.maker, costRating: m.cost_rating, speedRating: m.speed_rating }))
1293
+ (models) => models.map((m) => ({ value: m.id, label: modelDisplayLabel(m) || m.id, maker: m.maker, costRating: m.cost_rating, speedRating: m.speed_rating }))
1252
1294
  );
1253
1295
  var selectModelOptions = (s3) => optionsOf(selectActiveModels(s3));
1254
1296
  var allOptionsOf = memo(
1255
1297
  (entities) => Object.values(entities).map((m) => ({
1256
1298
  value: m.id,
1257
- label: m.common_name || m.name || m.id,
1299
+ label: modelDisplayLabel(m) || m.id,
1258
1300
  maker: m.maker,
1259
1301
  isDeprecated: m.is_deprecated ?? false,
1260
1302
  isRetired: m.retired_at != null,
@@ -1267,7 +1309,7 @@ function selectModelLabelById(s3, modelId) {
1267
1309
  if (!modelId) return void 0;
1268
1310
  const m = s3.entities[modelId];
1269
1311
  const identity = s3.identityById[modelId];
1270
- return m?.common_name || m?.name || identity?.common_name || identity?.name || void 0;
1312
+ return modelDisplayLabel(m) || modelDisplayLabel(identity);
1271
1313
  }
1272
1314
  var selectModelNameById = (s3, modelId) => modelId ? s3.entities[modelId]?.name : void 0;
1273
1315
  var selectModelClassName = (s3, modelId, offeringId) => modelClassName(s3.modelClassIndex, modelId, offeringId);
@@ -1285,6 +1327,18 @@ var selectDeprecatedModelsReady = (s3) => (s3.fetchScope === "deprecated" || s3.
1285
1327
  var selectAllModelsReady = (s3) => s3.fetchScope === "all";
1286
1328
  var selectModelIdentityById = (s3, modelId) => s3.identityById[modelId];
1287
1329
  var selectModelIdentityLookupStatus = (s3, modelId) => s3.identityStatusById[modelId] ?? "idle";
1330
+ async function readModelIdentities(client, modelIds) {
1331
+ const ids = [...new Set(modelIds.filter(Boolean))];
1332
+ if (ids.length === 0) return [];
1333
+ const { data, error } = await client.schema("ai").from("model_definition").select("id, name, common_name, is_deprecated").in("id", ids);
1334
+ if (error) throw new Error(error.message);
1335
+ return Array.isArray(data) ? data : [];
1336
+ }
1337
+ async function readPublicModel(client, key) {
1338
+ const { data, error } = await client.schema("ai").from("model_public").select("id, name").eq(catalogKeyColumn(key), key.trim()).maybeSingle();
1339
+ if (error) return { row: null, error: error.message };
1340
+ return { row: data ?? null, error: null };
1341
+ }
1288
1342
 
1289
1343
  // models/catalog/store.ts
1290
1344
  var IDLE = { status: "idle", models: [], error: null, loadedAt: null };
@@ -1554,8 +1608,16 @@ function createModelCatalog(config) {
1554
1608
  errorSink,
1555
1609
  ...config.isSignedIn ? { isSignedIn: config.isSignedIn } : {}
1556
1610
  });
1611
+ const refresh = async () => {
1612
+ const variants = Object.keys(state2).filter((v) => state2[v].status !== "idle");
1613
+ await Promise.all([records.refresh(), ...variants.map((v) => run(v))]);
1614
+ };
1615
+ const stopFollowing = config.followChanges === false ? () => {
1616
+ } : onModelCatalogChanged(() => void refresh());
1557
1617
  return {
1558
1618
  records,
1619
+ refresh,
1620
+ dispose: stopFollowing,
1559
1621
  getState: () => state2,
1560
1622
  subscribe(listener) {
1561
1623
  listeners.add(listener);
@@ -2771,6 +2833,7 @@ export {
2771
2833
  TOP_TIER_COST_RATING,
2772
2834
  TOP_TIER_LOCKED_TITLE,
2773
2835
  acceptsTextInstructions,
2836
+ announceModelCatalogChanged,
2774
2837
  blendedPrice,
2775
2838
  buildModelClassIndex,
2776
2839
  buildSuccessionIndex,
@@ -2794,6 +2857,7 @@ export {
2794
2857
  fitsMemory,
2795
2858
  fitsNeed,
2796
2859
  fitsThisComputer,
2860
+ followModelCatalogChanges,
2797
2861
  formatMemoryBytes,
2798
2862
  fromDiscovery,
2799
2863
  ggufEngine,
@@ -2811,21 +2875,25 @@ export {
2811
2875
  isListedVariant,
2812
2876
  isLocalModelCatalog,
2813
2877
  isManagedAgentModelCapability,
2878
+ isModelCatalogChange,
2814
2879
  isProjectorName,
2815
2880
  isSupersedable,
2816
2881
  isTopTierRating,
2817
2882
  judgeHeaderFacts,
2818
2883
  mergeAuditRecordIntoCapabilities,
2819
2884
  missingShards,
2885
+ modelCatalogRevision,
2820
2886
  modelClassKnobFor,
2821
2887
  modelClassName,
2822
2888
  modelClassPinOf,
2889
+ modelDisplayLabel,
2823
2890
  modelsForSelectionPurpose,
2824
2891
  normalizeArch,
2825
2892
  normalizeModel,
2826
2893
  normalizeModels,
2827
2894
  normalizeOs,
2828
2895
  normalizePromptSettings,
2896
+ onModelCatalogChanged,
2829
2897
  orderCatalogGroups,
2830
2898
  orderCatalogModels,
2831
2899
  outputFormatControlKey,
@@ -2837,6 +2905,8 @@ export {
2837
2905
  readCuratedRows,
2838
2906
  readGgufHeader,
2839
2907
  readModelCatalog,
2908
+ readModelIdentities,
2909
+ readPublicModel,
2840
2910
  reconcileModelFavoriteIds,
2841
2911
  reportModelDiagnostic,
2842
2912
  requireCanonicalCapabilities,
@@ -29,10 +29,12 @@ __export(react_exports, {
29
29
  useLocalModels: () => useLocalModels,
30
30
  useModelAccess: () => useModelAccess,
31
31
  useModelCatalog: () => useModelCatalog,
32
+ useModelCatalogRevision: () => useModelCatalogRevision,
32
33
  useModelCatalogStore: () => useModelCatalogStore,
33
34
  useModelCatalogUiPorts: () => useModelCatalogUiPorts,
34
35
  useModelFavorites: () => useModelFavorites,
35
- useModelRecordsSelector: () => useModelRecordsSelector
36
+ useModelRecordsSelector: () => useModelRecordsSelector,
37
+ useOnModelCatalogChanged: () => useOnModelCatalogChanged
36
38
  });
37
39
  module.exports = __toCommonJS(react_exports);
38
40
 
@@ -2289,6 +2291,25 @@ function ModelListDropdown({
2289
2291
 
2290
2292
  // models/react/records.ts
2291
2293
  var import_react3 = require("react");
2294
+
2295
+ // models/catalog/changes.ts
2296
+ var KEY = /* @__PURE__ */ Symbol.for("ai-matrx.agents.model-catalog-changes");
2297
+ function hub() {
2298
+ const g = globalThis;
2299
+ return g[KEY] ??= { listeners: /* @__PURE__ */ new Set(), revision: 0 };
2300
+ }
2301
+ function onModelCatalogChanged(listener) {
2302
+ const h = hub();
2303
+ h.listeners.add(listener);
2304
+ return () => {
2305
+ h.listeners.delete(listener);
2306
+ };
2307
+ }
2308
+ function modelCatalogRevision() {
2309
+ return hub().revision;
2310
+ }
2311
+
2312
+ // models/react/records.ts
2292
2313
  function useModelRecordsSelector(records, select) {
2293
2314
  return (0, import_react3.useSyncExternalStore)(
2294
2315
  records.subscribe,
@@ -2296,6 +2317,15 @@ function useModelRecordsSelector(records, select) {
2296
2317
  () => select(records.getState())
2297
2318
  );
2298
2319
  }
2320
+ var subscribeRevision = (listener) => onModelCatalogChanged(() => listener());
2321
+ function useModelCatalogRevision() {
2322
+ return (0, import_react3.useSyncExternalStore)(subscribeRevision, modelCatalogRevision, modelCatalogRevision);
2323
+ }
2324
+ function useOnModelCatalogChanged(onChange) {
2325
+ const latest = (0, import_react3.useRef)(onChange);
2326
+ latest.current = onChange;
2327
+ (0, import_react3.useEffect)(() => onModelCatalogChanged((payload) => latest.current(payload)), []);
2328
+ }
2299
2329
 
2300
2330
  // models/react/ModelDiscovery.tsx
2301
2331
  var import_format3 = require("@ai-matrx/kit/format");
@@ -398,6 +398,14 @@ interface ModelCatalog {
398
398
  load(variant: ModelCatalogVariant): Promise<void>;
399
399
  /** Re-read the variant now (rows stay visible while it runs). Never throws. */
400
400
  reload(variant: ModelCatalogVariant): Promise<void>;
401
+ /**
402
+ * The catalog changed upstream (`catalog_changed`): re-read every picker variant already read and every
403
+ * record already read (`records.refresh()`). Called by itself on every `announceModelCatalogChanged`
404
+ * (catalog/changes.ts) unless `followChanges: false`. Never throws.
405
+ */
406
+ refresh(): Promise<void>;
407
+ /** Stop following `catalog_changed` (a test, or a catalog a host discards). */
408
+ dispose(): void;
401
409
  /**
402
410
  * The RECORDS part (agent core B3): options list, full records with resolved controls, class
403
411
  * configs, the class index and identities — what chat's retired `modelRegistry` slice held.
@@ -650,8 +658,30 @@ interface ModelListDropdownBaseProps {
650
658
  }
651
659
  declare function ModelListDropdown({ value: savedValue, localModels: localModelsProp, access: accessProp, onValueChange, inputModalities, outputModalities, selectionPurpose, allowedModelIds, catalogVariant, emptyOptionLabel, onClear, placeholder, priorityModelIds, pinnedOfferingId, onOfferingPinChange, modelOnly, className, triggerVariant, presentation, id, disabled, disabledTitle, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: ModelListDropdownProps): react.JSX.Element;
652
660
 
661
+ /**
662
+ * `@ai-matrx/agents/models` — the ONE subscription to `catalog_changed`.
663
+ *
664
+ * The server publishes `catalog_changed` on the platform bus for every admin edit and direct SQL write to
665
+ * the model catalog. A host binds its bus client ONCE with `followModelCatalogChanges(subscribe)` (the web's
666
+ * directive registry, Matrx 2's IPC forward of the main-process bus); every model catalog
667
+ * (`createModelCatalog`: picker rows + records) re-reads itself on the notice, and any other reader
668
+ * subscribes here (`onModelCatalogChanged`, or `useModelCatalogRevision` / `useOnModelCatalogChanged` in
669
+ * `@ai-matrx/agents/models/react`). No surface keeps a model cache that misses the event.
670
+ *
671
+ * The hub lives on `globalThis` so two bundled copies of this module share one set of listeners.
672
+ */
673
+ /** A `catalog_changed` payload: `{ catalog?: "models" | ..., change?, model_id? }`. */
674
+ type ModelCatalogChangePayload = Record<string, unknown>;
675
+
653
676
  /** Subscribe to a slice of a model catalog's records. `select` must return a stable value for unchanged state. */
654
677
  declare function useModelRecordsSelector<T>(records: ModelRecords, select: (state: ModelRecordsState) => T): T;
678
+ /**
679
+ * How many `catalog_changed` notices this page has seen. A reader that holds its OWN rows (the admin model
680
+ * editor) puts it in its effect dependencies / query key, so an edit made anywhere re-reads it.
681
+ */
682
+ declare function useModelCatalogRevision(): number;
683
+ /** Run `onChange` on every model catalog change notice (the latest callback; never stale). */
684
+ declare function useOnModelCatalogChanged(onChange: (payload: ModelCatalogChangePayload) => void): void;
655
685
 
656
686
  /**
657
687
  * `@ai-matrx/agents/models` — downloadable (local) models: the catalog shape every client reads, the
@@ -1010,4 +1040,4 @@ interface DiscoveryResultViewProps {
1010
1040
  /** A resolved model: its header, the source's reason when the originals will not run, and every version judged. */
1011
1041
  declare function DiscoveryResultView({ result, device, runtimes, want, chooseLabel, mode, onChoose, renderVariantActions, renderActions }: DiscoveryResultViewProps): react.JSX.Element;
1012
1042
 
1013
- export { type DiscoveryKind, type DiscoveryMode, type DiscoveryPort, DiscoveryResultView, type DiscoveryResultViewProps, type DiscoverySearchHit, type DiscoverySearchPage, type DiscoverySearchQuery, type DiscoverySort, type LocalModelEntry, type LocalModelsPort, type ModelAccessPort, type ModelCatalogLinkComponent, ModelCatalogProvider, type ModelCatalogProviderProps, type ModelCatalogUiPorts, ModelDiscovery, type ModelDiscoveryProps, ModelListDropdown, type ModelListDropdownProps, YOUR_KEY_LABEL, useLocalModels, useModelAccess, useModelCatalog, useModelCatalogStore, useModelCatalogUiPorts, useModelFavorites, useModelRecordsSelector };
1043
+ export { type DiscoveryKind, type DiscoveryMode, type DiscoveryPort, DiscoveryResultView, type DiscoveryResultViewProps, type DiscoverySearchHit, type DiscoverySearchPage, type DiscoverySearchQuery, type DiscoverySort, type LocalModelEntry, type LocalModelsPort, type ModelAccessPort, type ModelCatalogLinkComponent, ModelCatalogProvider, type ModelCatalogProviderProps, type ModelCatalogUiPorts, ModelDiscovery, type ModelDiscoveryProps, ModelListDropdown, type ModelListDropdownProps, YOUR_KEY_LABEL, useLocalModels, useModelAccess, useModelCatalog, useModelCatalogRevision, useModelCatalogStore, useModelCatalogUiPorts, useModelFavorites, useModelRecordsSelector, useOnModelCatalogChanged };
@@ -398,6 +398,14 @@ interface ModelCatalog {
398
398
  load(variant: ModelCatalogVariant): Promise<void>;
399
399
  /** Re-read the variant now (rows stay visible while it runs). Never throws. */
400
400
  reload(variant: ModelCatalogVariant): Promise<void>;
401
+ /**
402
+ * The catalog changed upstream (`catalog_changed`): re-read every picker variant already read and every
403
+ * record already read (`records.refresh()`). Called by itself on every `announceModelCatalogChanged`
404
+ * (catalog/changes.ts) unless `followChanges: false`. Never throws.
405
+ */
406
+ refresh(): Promise<void>;
407
+ /** Stop following `catalog_changed` (a test, or a catalog a host discards). */
408
+ dispose(): void;
401
409
  /**
402
410
  * The RECORDS part (agent core B3): options list, full records with resolved controls, class
403
411
  * configs, the class index and identities — what chat's retired `modelRegistry` slice held.
@@ -650,8 +658,30 @@ interface ModelListDropdownBaseProps {
650
658
  }
651
659
  declare function ModelListDropdown({ value: savedValue, localModels: localModelsProp, access: accessProp, onValueChange, inputModalities, outputModalities, selectionPurpose, allowedModelIds, catalogVariant, emptyOptionLabel, onClear, placeholder, priorityModelIds, pinnedOfferingId, onOfferingPinChange, modelOnly, className, triggerVariant, presentation, id, disabled, disabledTitle, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: ModelListDropdownProps): react.JSX.Element;
652
660
 
661
+ /**
662
+ * `@ai-matrx/agents/models` — the ONE subscription to `catalog_changed`.
663
+ *
664
+ * The server publishes `catalog_changed` on the platform bus for every admin edit and direct SQL write to
665
+ * the model catalog. A host binds its bus client ONCE with `followModelCatalogChanges(subscribe)` (the web's
666
+ * directive registry, Matrx 2's IPC forward of the main-process bus); every model catalog
667
+ * (`createModelCatalog`: picker rows + records) re-reads itself on the notice, and any other reader
668
+ * subscribes here (`onModelCatalogChanged`, or `useModelCatalogRevision` / `useOnModelCatalogChanged` in
669
+ * `@ai-matrx/agents/models/react`). No surface keeps a model cache that misses the event.
670
+ *
671
+ * The hub lives on `globalThis` so two bundled copies of this module share one set of listeners.
672
+ */
673
+ /** A `catalog_changed` payload: `{ catalog?: "models" | ..., change?, model_id? }`. */
674
+ type ModelCatalogChangePayload = Record<string, unknown>;
675
+
653
676
  /** Subscribe to a slice of a model catalog's records. `select` must return a stable value for unchanged state. */
654
677
  declare function useModelRecordsSelector<T>(records: ModelRecords, select: (state: ModelRecordsState) => T): T;
678
+ /**
679
+ * How many `catalog_changed` notices this page has seen. A reader that holds its OWN rows (the admin model
680
+ * editor) puts it in its effect dependencies / query key, so an edit made anywhere re-reads it.
681
+ */
682
+ declare function useModelCatalogRevision(): number;
683
+ /** Run `onChange` on every model catalog change notice (the latest callback; never stale). */
684
+ declare function useOnModelCatalogChanged(onChange: (payload: ModelCatalogChangePayload) => void): void;
655
685
 
656
686
  /**
657
687
  * `@ai-matrx/agents/models` — downloadable (local) models: the catalog shape every client reads, the
@@ -1010,4 +1040,4 @@ interface DiscoveryResultViewProps {
1010
1040
  /** A resolved model: its header, the source's reason when the originals will not run, and every version judged. */
1011
1041
  declare function DiscoveryResultView({ result, device, runtimes, want, chooseLabel, mode, onChoose, renderVariantActions, renderActions }: DiscoveryResultViewProps): react.JSX.Element;
1012
1042
 
1013
- export { type DiscoveryKind, type DiscoveryMode, type DiscoveryPort, DiscoveryResultView, type DiscoveryResultViewProps, type DiscoverySearchHit, type DiscoverySearchPage, type DiscoverySearchQuery, type DiscoverySort, type LocalModelEntry, type LocalModelsPort, type ModelAccessPort, type ModelCatalogLinkComponent, ModelCatalogProvider, type ModelCatalogProviderProps, type ModelCatalogUiPorts, ModelDiscovery, type ModelDiscoveryProps, ModelListDropdown, type ModelListDropdownProps, YOUR_KEY_LABEL, useLocalModels, useModelAccess, useModelCatalog, useModelCatalogStore, useModelCatalogUiPorts, useModelFavorites, useModelRecordsSelector };
1043
+ export { type DiscoveryKind, type DiscoveryMode, type DiscoveryPort, DiscoveryResultView, type DiscoveryResultViewProps, type DiscoverySearchHit, type DiscoverySearchPage, type DiscoverySearchQuery, type DiscoverySort, type LocalModelEntry, type LocalModelsPort, type ModelAccessPort, type ModelCatalogLinkComponent, ModelCatalogProvider, type ModelCatalogProviderProps, type ModelCatalogUiPorts, ModelDiscovery, type ModelDiscoveryProps, ModelListDropdown, type ModelListDropdownProps, YOUR_KEY_LABEL, useLocalModels, useModelAccess, useModelCatalog, useModelCatalogRevision, useModelCatalogStore, useModelCatalogUiPorts, useModelFavorites, useModelRecordsSelector, useOnModelCatalogChanged };
@@ -2280,7 +2280,26 @@ function ModelListDropdown({
2280
2280
  }
2281
2281
 
2282
2282
  // models/react/records.ts
2283
- import { useSyncExternalStore as useSyncExternalStore2 } from "react";
2283
+ import { useEffect as useEffect3, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
2284
+
2285
+ // models/catalog/changes.ts
2286
+ var KEY = /* @__PURE__ */ Symbol.for("ai-matrx.agents.model-catalog-changes");
2287
+ function hub() {
2288
+ const g = globalThis;
2289
+ return g[KEY] ??= { listeners: /* @__PURE__ */ new Set(), revision: 0 };
2290
+ }
2291
+ function onModelCatalogChanged(listener) {
2292
+ const h = hub();
2293
+ h.listeners.add(listener);
2294
+ return () => {
2295
+ h.listeners.delete(listener);
2296
+ };
2297
+ }
2298
+ function modelCatalogRevision() {
2299
+ return hub().revision;
2300
+ }
2301
+
2302
+ // models/react/records.ts
2284
2303
  function useModelRecordsSelector(records, select) {
2285
2304
  return useSyncExternalStore2(
2286
2305
  records.subscribe,
@@ -2288,10 +2307,19 @@ function useModelRecordsSelector(records, select) {
2288
2307
  () => select(records.getState())
2289
2308
  );
2290
2309
  }
2310
+ var subscribeRevision = (listener) => onModelCatalogChanged(() => listener());
2311
+ function useModelCatalogRevision() {
2312
+ return useSyncExternalStore2(subscribeRevision, modelCatalogRevision, modelCatalogRevision);
2313
+ }
2314
+ function useOnModelCatalogChanged(onChange) {
2315
+ const latest = useRef2(onChange);
2316
+ latest.current = onChange;
2317
+ useEffect3(() => onModelCatalogChanged((payload) => latest.current(payload)), []);
2318
+ }
2291
2319
 
2292
2320
  // models/react/ModelDiscovery.tsx
2293
2321
  import { formatCount as formatCount2, formatFileSize } from "@ai-matrx/kit/format";
2294
- import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
2322
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState2 } from "react";
2295
2323
  import { Avatar, AvatarFallback, AvatarImage, Collapsible, CollapsibleContent, CollapsibleTrigger, ErrorNotice } from "@ai-matrx/design-system";
2296
2324
  import { Badge, Button, Field, SearchField, Select, Tabs } from "@ai-matrx/design-system/controls";
2297
2325
 
@@ -2570,8 +2598,8 @@ function ModelDiscovery({
2570
2598
  const result = controlled !== void 0 ? controlled : own;
2571
2599
  const [resolving, setResolving] = useState2(null);
2572
2600
  const [resolveError, setResolveError] = useState2(null);
2573
- const abortRef = useRef2(null);
2574
- useEffect3(() => () => abortRef.current?.abort(), []);
2601
+ const abortRef = useRef3(null);
2602
+ useEffect4(() => () => abortRef.current?.abort(), []);
2575
2603
  const resolve = async (link) => {
2576
2604
  const trimmed = link.trim();
2577
2605
  if (!trimmed) return;
@@ -2661,7 +2689,7 @@ function SearchTab({ port, onPick }) {
2661
2689
  const [page, setPage] = useState2(null);
2662
2690
  const [busy, setBusy] = useState2(false);
2663
2691
  const [error, setError] = useState2(null);
2664
- const seq = useRef2(0);
2692
+ const seq = useRef3(0);
2665
2693
  const run = async (cursor) => {
2666
2694
  if (!port.search) return;
2667
2695
  const mine = ++seq.current;
@@ -2677,7 +2705,7 @@ function SearchTab({ port, onPick }) {
2677
2705
  if (mine === seq.current) setBusy(false);
2678
2706
  }
2679
2707
  };
2680
- useEffect3(() => {
2708
+ useEffect4(() => {
2681
2709
  if (port.search) void run(null);
2682
2710
  }, [kind, sort]);
2683
2711
  if (!port.search) return /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground", children: "Search is not available here yet. Paste a link instead." });
@@ -2814,8 +2842,10 @@ export {
2814
2842
  useLocalModels,
2815
2843
  useModelAccess,
2816
2844
  useModelCatalog,
2845
+ useModelCatalogRevision,
2817
2846
  useModelCatalogStore,
2818
2847
  useModelCatalogUiPorts,
2819
2848
  useModelFavorites,
2820
- useModelRecordsSelector
2849
+ useModelRecordsSelector,
2850
+ useOnModelCatalogChanged
2821
2851
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/agents",
3
- "version": "0.59.257",
3
+ "version": "0.59.258",
4
4
  "description": "Portable AI Matrx agent stream protocol, event projection, and safe result-presentation primitives.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "files": [
26
26
  "dist",
27
+ "scripts/check-model-catalog-reads.mjs",
27
28
  "README.md",
28
29
  "LICENSE",
29
30
  "CHANGELOG.md"
@@ -509,11 +510,11 @@
509
510
  "sideEffects": false,
510
511
  "dependencies": {
511
512
  "@ai-matrx/alchemy": "latest",
513
+ "@ai-matrx/api": "latest",
512
514
  "@ai-matrx/associations": "latest",
513
515
  "@ai-matrx/content-ir": "latest",
514
- "@ai-matrx/api": "latest",
515
- "@ai-matrx/design-system": "latest",
516
516
  "@ai-matrx/data": "latest",
517
+ "@ai-matrx/design-system": "latest",
517
518
  "@ai-matrx/diff": "latest",
518
519
  "@ai-matrx/icons": "latest",
519
520
  "@ai-matrx/kit": "latest"
@@ -555,11 +556,11 @@
555
556
  "scripts": {
556
557
  "build": "pnpm clean && node ./scripts/build-parts.mjs && node ../scripts/guard-exit.mjs ../scripts/check-node-env-not-inlined.mjs",
557
558
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
558
- "check:package": "node ./scripts/check-catalog-reader-columns.mjs && node ./scripts/check-catalog-reader-columns.mjs --self-test && node ../scripts/check-no-model-literals.mjs && node ../scripts/check-no-model-literals.mjs --self-test && node ../scripts/check-agents-below-chat.mjs && node ../scripts/check-agents-below-chat.mjs --self-test && pnpm build && node ../scripts/check-exports-agreement.mjs && publint && pnpm verify:tarball && node ../scripts/check-tarball-relative-imports.mjs --self-test && node ../scripts/check-tarball-relative-imports.mjs --pack . && node ../scripts/check-package-rules.mjs --self-test && node ../scripts/check-package-rules.mjs .",
559
+ "check:package": "node ./scripts/check-model-catalog-reads.mjs && node ./scripts/check-model-catalog-reads.mjs --self-test && node ./scripts/check-catalog-reader-columns.mjs && node ./scripts/check-catalog-reader-columns.mjs --self-test && node ../scripts/check-no-model-literals.mjs && node ../scripts/check-no-model-literals.mjs --self-test && node ../scripts/check-agents-below-chat.mjs && node ../scripts/check-agents-below-chat.mjs --self-test && pnpm build && node ../scripts/check-exports-agreement.mjs && publint && pnpm verify:tarball && node ../scripts/check-tarball-relative-imports.mjs --self-test && node ../scripts/check-tarball-relative-imports.mjs --pack . && node ../scripts/check-package-rules.mjs --self-test && node ../scripts/check-package-rules.mjs .",
559
560
  "typecheck": "tsc --noEmit",
560
561
  "test": "vitest run",
561
562
  "verify:tarball": "node ./scripts/verify-tarball.mjs",
562
563
  "typegen:self-test": "node scripts/typegen/drop-guard.self-test.mjs && node scripts/typegen/check-api-types-fresh.mjs --self-test"
563
564
  },
564
- "gitHead": "0a2b8c9356bcba5cf0338a6c2c63b1c6058374ef"
565
+ "gitHead": "48dc0c53e766ad507fa37f41893b2b358be229f4"
565
566
  }
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ONE MODEL CATALOG READER (owner ruling 2026-10-23). Every surface that names or lists a model reads the
4
+ * model catalog through `@ai-matrx/agents/models` (records / readModelCatalog, subscribed to
5
+ * `catalog_changed` in models/catalog/changes.ts) and computes the name with `modelDisplayLabel`
6
+ * (models/label.ts), so an admin edit reaches every open app within seconds.
7
+ *
8
+ * Fails when a file outside apps/shared/matrx-agents/models/:
9
+ * R1 reads ai.model_definition / model_public / model_offering / model_config / model_variant /
10
+ * model_presentation (`.from("…")`) or the catalog RPCs (`admin_model_catalog`, `resolve_model_config`);
11
+ * R2 computes a model label by hand (`x.common_name || x.name`, `?? …name`) instead of `modelDisplayLabel`.
12
+ * ALLOWED lists the few readers that keep their own read on purpose, each with why and how it follows the
13
+ * event. Test files are fixtures and are not scanned.
14
+ *
15
+ * node apps/shared/matrx-agents/scripts/check-model-catalog-reads.mjs [--root <repo>] [--extra <dir>]... [--self-test]
16
+ * `--extra` adds a scan root (the red proof plants an untracked scratch file there). `--scan` replaces the
17
+ * default roots (Matrx 2: `node node_modules/@ai-matrx/agents/scripts/check-model-catalog-reads.mjs --root . --scan app`).
18
+ */
19
+ import { mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { join, relative, resolve } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+
24
+ const here = fileURLToPath(new URL(".", import.meta.url));
25
+ const args = process.argv.slice(2);
26
+ const flag = (name) => {
27
+ const out = [];
28
+ for (let i = 0; i < args.length; i++) if (args[i] === name && args[i + 1]) out.push(args[++i]);
29
+ return out;
30
+ };
31
+ const ROOT = resolve(flag("--root")[0] ?? join(here, "../../../.."));
32
+ // `--scan <dir>` replaces the aidream roots: Matrx 2 runs this file from its installed
33
+ // node_modules/@ai-matrx/agents with `--root . --scan app` (one guard, two repos).
34
+ const ROOTS = [...(flag("--scan").length ? flag("--scan") : ["apps/shared", "apps/matrx-frontend"]), ...flag("--extra")];
35
+
36
+ /** The reader module: the only place the catalog relations are read and the label is computed. */
37
+ const HOME = "apps/shared/matrx-agents/models/";
38
+
39
+ /** Files that keep their own catalog read, on purpose. Each says why and how it follows catalog_changed. */
40
+ export const ALLOWED = {
41
+ // The admin model EDITOR: reads and writes every column of the rows it edits. Every container that shows
42
+ // its rows re-reads on the event (useOnModelCatalogChanged / useModelCatalogRevision).
43
+ "apps/matrx-frontend/features/ai-models/service.ts": "admin editor CRUD; containers follow catalog_changed",
44
+ "apps/matrx-frontend/features/ai-models/discovery/api.ts": "admin discovery CRUD; UnifiedModelKindTable follows catalog_changed",
45
+ "apps/matrx-frontend/features/ai-models/translation/data.ts": "admin translation grid; settings-translation keys its query on the revision",
46
+ // cx-dashboard names models on the server per request; CxDashboardLayoutClient router.refresh()es on the event.
47
+ "apps/matrx-frontend/features/cx-dashboard/service.ts": "server-rendered admin page; layout refreshes on catalog_changed",
48
+ // The public JSON API for external callers (anon key, CDN cached by design) — not a screen.
49
+ "apps/matrx-frontend/app/api/ai-models/route.ts": "public CDN-cached JSON API for external callers, not a screen",
50
+ // A security probe that reads the relations to prove their grants.
51
+ "apps/matrx-frontend/scripts/check-rls-on.ts": "RLS probe, not a reader",
52
+ };
53
+
54
+ const SKIP_DIR = new Set(["node_modules", "dist", ".next", ".turbo", "coverage", "generated", "__tests__", "__fixtures__"]);
55
+ const isTest = (p) => /\.(test|spec)\.[cm]?[jt]sx?$/.test(p) || /\.d\.ts$/.test(p) || /\.generated\./.test(p);
56
+
57
+ const R1 = /\.from\(\s*["'`](model_definition|model_public|model_offering|model_config|model_variant|model_presentation)["'`]|\.rpc\(\s*["'`](admin_model_catalog|resolve_model_config)["'`]/g;
58
+ const R2 = /\bcommon_name\s*(\|\||\?\?)\s*[\w$?.]*\bname\b/g;
59
+
60
+ function* walk(dir) {
61
+ let names;
62
+ try {
63
+ names = readdirSync(dir);
64
+ } catch {
65
+ return;
66
+ }
67
+ for (const n of names) {
68
+ if (SKIP_DIR.has(n)) continue;
69
+ const p = join(dir, n);
70
+ const st = statSync(p);
71
+ if (st.isDirectory()) yield* walk(p);
72
+ else if (/\.(ts|tsx|mts|cts|js|jsx|mjs)$/.test(n) && !isTest(n)) yield p;
73
+ }
74
+ }
75
+
76
+ const lineOf = (src, index) => src.slice(0, index).split("\n").length;
77
+ function scan(roots) {
78
+ const findings = [];
79
+ for (const r of roots) {
80
+ for (const file of walk(resolve(ROOT, r))) {
81
+ const rel = relative(ROOT, file).split("\\").join("/");
82
+ if (rel.startsWith(HOME) || rel === "apps/shared/matrx-agents/scripts/check-model-catalog-reads.mjs") continue;
83
+ if (ALLOWED[rel]) continue;
84
+ const src = readFileSync(file, "utf8");
85
+ for (const [rule, re, what] of [
86
+ ["R1", R1, "reads the model catalog directly — use @ai-matrx/agents/models (records / chat's useModelLabel / readModelIdentities)"],
87
+ ["R2", R2, "computes a model label by hand — use modelDisplayLabel(row) from @ai-matrx/agents/models"],
88
+ ]) {
89
+ re.lastIndex = 0;
90
+ for (let m; (m = re.exec(src)); ) findings.push(`${rel}:${lineOf(src, m.index)} ${rule} ${what} [${m[0].replace(/\s+/g, " ")}]`);
91
+ }
92
+ }
93
+ }
94
+ return findings;
95
+ }
96
+
97
+ if (args.includes("--self-test")) {
98
+ // Falsifiability: a planted direct read and a hand-made label must each fail; the lawful form must pass.
99
+ const dir = mkdtempSync(join(tmpdir(), "model-catalog-reads-"));
100
+ try {
101
+ writeFileSync(join(dir, "bad.ts"), 'export const r = (db: any) => db.schema("ai").from("model_definition").select("id");\nexport const l = (m: any) =>\n m?.common_name ??\n m?.name;\n');
102
+ writeFileSync(join(dir, "good.ts"), 'import { modelDisplayLabel } from "@ai-matrx/agents/models";\nexport const l = (m: any) => modelDisplayLabel(m) ?? m.common_name ?? null;\n');
103
+ const got = scan([dir]);
104
+ const ok = got.length === 2 && got.some((f) => f.includes("bad.ts:1 R1")) && got.some((f) => f.includes("bad.ts:3 R2"));
105
+ if (!ok) {
106
+ console.error(`check-model-catalog-reads --self-test: FAILED — expected R1+R2 on bad.ts only, got:\n ${got.join("\n ") || "(nothing)"}`);
107
+ process.exit(1);
108
+ }
109
+ console.log("check-model-catalog-reads --self-test: OK — a planted read and a hand-made label both go red; modelDisplayLabel passes.");
110
+ process.exit(0);
111
+ } finally {
112
+ rmSync(dir, { recursive: true, force: true });
113
+ }
114
+ }
115
+
116
+ const findings = scan(ROOTS);
117
+ if (findings.length) {
118
+ console.error(`check-model-catalog-reads: ${findings.length} finding(s) — every model name and list goes through @ai-matrx/agents/models so catalog_changed reaches it:`);
119
+ for (const f of findings) console.error(` ${f}`);
120
+ process.exit(1);
121
+ }
122
+ console.log(`check-model-catalog-reads: OK — no model catalog read or hand-made model label outside ${HOME} (${Object.keys(ALLOWED).length} allowed readers).`);