@ai-matrx/agents 0.59.269 → 0.59.271

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,20 @@
1
1
  # Changelog
2
2
 
3
- ## 0.59.269
3
+ ## 0.59.271
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.270
9
+
10
+ Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 4c08cba6c4 8a4ed656ea -- apps/shared/matrx-agents/src`
11
+ has no shipped-source changes outside tests, fixtures and docs). No consumer action required.
12
+
13
+ ## 0.59.269
14
+
15
+ Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 5b7c48a99c 4c08cba6c4 -- apps/shared/matrx-agents/src`
16
+ has no shipped-source changes outside tests, fixtures and docs). No consumer action required.
17
+
8
18
  ## 0.59.268
9
19
 
10
20
  Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 06a190b44e 5b7c48a99c -- apps/shared/matrx-agents/src`
@@ -43,6 +43,7 @@ __export(context_exports, {
43
43
  contextValueChars: () => contextValueChars,
44
44
  deliveredFields: () => deliveredFields,
45
45
  formatChars: () => formatChars,
46
+ hasNothingToSend: () => hasNothingToSend,
46
47
  isRichEnvelope: () => isRichEnvelope,
47
48
  isServerAuthoritativeKey: () => isServerAuthoritativeKey,
48
49
  isServerResolved: () => isServerResolved,
@@ -281,6 +282,16 @@ function isRichEnvelope(value) {
281
282
  function isLazyPointer(value) {
282
283
  return isPlainObject(value) && !hasOwn(value, "content") && onlyRichKeys(value) && hasLazySource(value);
283
284
  }
285
+ function hasNothingToSend(value) {
286
+ if (value === void 0 || value === null) return true;
287
+ if (typeof value === "string") return value.trim() === "";
288
+ if (Array.isArray(value)) return value.length === 0;
289
+ if (!isPlainObject(value)) return false;
290
+ if (value.__kind !== void 0) return false;
291
+ if (isRichEnvelope(value)) return !hasLazySource(value) && hasNothingToSend(value.content);
292
+ if (isLazyPointer(value)) return false;
293
+ return Object.keys(value).length === 0;
294
+ }
284
295
  function splitContextValue(value) {
285
296
  if (isRichEnvelope(value) || isLazyPointer(value)) {
286
297
  const { content = null, ...overrides } = value;
@@ -424,6 +424,14 @@ declare const RICH_ENVELOPE_KEYS: ReadonlySet<string>;
424
424
  * (a note `{ id, title, content }`) is raw content, not an envelope.
425
425
  */
426
426
  declare function isRichEnvelope(value: unknown): value is Record<string, unknown>;
427
+ /**
428
+ * True for a value with NOTHING to send: absent, `null`, a blank string, an empty list or an
429
+ * empty object — or a rich envelope whose `content` is one of those and that names no `source`
430
+ * for the server to read. Such a value is not a row (live walk 2026-10-23: Side Chat listed five
431
+ * included 0-character rows — Current selection, Text before/after selection, Primary content,
432
+ * Custom fields — and counted them). A directive the server acts on by shape is never "nothing".
433
+ */
434
+ declare function hasNothingToSend(value: unknown): boolean;
427
435
  /**
428
436
  * Split one wire value into `{ content, overrides }` exactly as aidream
429
437
  * `_split_rich_context_value`: a rich envelope or lazy pointer → its `content`
@@ -604,5 +612,5 @@ declare function attachmentContextKey(provider: string, resourceRef: string): st
604
612
  /** Compact size for a tabular column: `950`, `18.4k`, `184k`, `1.2M`, `—` for unknown. */
605
613
  declare function formatChars(n: number | null | undefined): string;
606
614
 
607
- export { CONTEXT_FETCHABLE_ROWS, CONTEXT_ROW_BLOCKS, CONTEXT_RULES_FEATURE, CONVERSATION_DOCUMENT_CONTEXT_KEYS, CONVERSATION_SCRATCHPAD_PREFIX, DEFAULT_INLINE_CAP, DEFAULT_INLINE_MAX_CHARS, DEFAULT_SURFACE_KEY, LAZY_SOURCE_DEFAULT_MAX, OWN_CONVERSATION_WITHHELD, PAGE_OFF_WITHHELD, PERSON_CONTEXT_VALUES, RESERVED_CONSUMED_KEYS, RICH_ENVELOPE_KEYS, SERVER_AUTHORITATIVE_KEYS, applyReceiptToRows, attachmentContextKey, blocksForRow, buildContextWire, compareReceipt, contextValueChars, deliveredFields, formatChars, isRichEnvelope, isServerAuthoritativeKey, isServerResolved, isWireDirective, lookupSavedRule, lookupSavedRuleFor, neverThePage, pageContextWithheldKeys, pageContextWithholds, receiptRowToResolved, resolveContextRow, resolveContextRule, serverDefaultMax, splitContextValue, systemRowsToResolved, unclaimedBlocks, withheldKeys };
615
+ export { CONTEXT_FETCHABLE_ROWS, CONTEXT_ROW_BLOCKS, CONTEXT_RULES_FEATURE, CONVERSATION_DOCUMENT_CONTEXT_KEYS, CONVERSATION_SCRATCHPAD_PREFIX, DEFAULT_INLINE_CAP, DEFAULT_INLINE_MAX_CHARS, DEFAULT_SURFACE_KEY, LAZY_SOURCE_DEFAULT_MAX, OWN_CONVERSATION_WITHHELD, PAGE_OFF_WITHHELD, PERSON_CONTEXT_VALUES, RESERVED_CONSUMED_KEYS, RICH_ENVELOPE_KEYS, SERVER_AUTHORITATIVE_KEYS, applyReceiptToRows, attachmentContextKey, blocksForRow, buildContextWire, compareReceipt, contextValueChars, deliveredFields, formatChars, hasNothingToSend, isRichEnvelope, isServerAuthoritativeKey, isServerResolved, isWireDirective, lookupSavedRule, lookupSavedRuleFor, neverThePage, pageContextWithheldKeys, pageContextWithholds, receiptRowToResolved, resolveContextRow, resolveContextRule, serverDefaultMax, splitContextValue, systemRowsToResolved, unclaimedBlocks, withheldKeys };
608
616
  export type { ContextAgentLayer, ContextBlockedBy, ContextConsumedAs, ContextDeliveredRef, ContextDelivery, ContextLayer, ContextReceipt, ContextReceiptBlock, ContextReceiptMismatch, ContextReceiptRow, ContextRowDelivery, ContextRowOrigin, ContextRowSource, ContextRuleInput, ContextRuleLayers, ContextRuleResult, ContextSurfaceLayer, ContextViewKind, ContextViewLoader, ContextViewTarget, ContextViewedText, PageContextDirective, PageContextMode, PageContextValueFacts, ResolvedContextRow, SavedContextRule, SavedContextRuleRows };
@@ -424,6 +424,14 @@ declare const RICH_ENVELOPE_KEYS: ReadonlySet<string>;
424
424
  * (a note `{ id, title, content }`) is raw content, not an envelope.
425
425
  */
426
426
  declare function isRichEnvelope(value: unknown): value is Record<string, unknown>;
427
+ /**
428
+ * True for a value with NOTHING to send: absent, `null`, a blank string, an empty list or an
429
+ * empty object — or a rich envelope whose `content` is one of those and that names no `source`
430
+ * for the server to read. Such a value is not a row (live walk 2026-10-23: Side Chat listed five
431
+ * included 0-character rows — Current selection, Text before/after selection, Primary content,
432
+ * Custom fields — and counted them). A directive the server acts on by shape is never "nothing".
433
+ */
434
+ declare function hasNothingToSend(value: unknown): boolean;
427
435
  /**
428
436
  * Split one wire value into `{ content, overrides }` exactly as aidream
429
437
  * `_split_rich_context_value`: a rich envelope or lazy pointer → its `content`
@@ -604,5 +612,5 @@ declare function attachmentContextKey(provider: string, resourceRef: string): st
604
612
  /** Compact size for a tabular column: `950`, `18.4k`, `184k`, `1.2M`, `—` for unknown. */
605
613
  declare function formatChars(n: number | null | undefined): string;
606
614
 
607
- export { CONTEXT_FETCHABLE_ROWS, CONTEXT_ROW_BLOCKS, CONTEXT_RULES_FEATURE, CONVERSATION_DOCUMENT_CONTEXT_KEYS, CONVERSATION_SCRATCHPAD_PREFIX, DEFAULT_INLINE_CAP, DEFAULT_INLINE_MAX_CHARS, DEFAULT_SURFACE_KEY, LAZY_SOURCE_DEFAULT_MAX, OWN_CONVERSATION_WITHHELD, PAGE_OFF_WITHHELD, PERSON_CONTEXT_VALUES, RESERVED_CONSUMED_KEYS, RICH_ENVELOPE_KEYS, SERVER_AUTHORITATIVE_KEYS, applyReceiptToRows, attachmentContextKey, blocksForRow, buildContextWire, compareReceipt, contextValueChars, deliveredFields, formatChars, isRichEnvelope, isServerAuthoritativeKey, isServerResolved, isWireDirective, lookupSavedRule, lookupSavedRuleFor, neverThePage, pageContextWithheldKeys, pageContextWithholds, receiptRowToResolved, resolveContextRow, resolveContextRule, serverDefaultMax, splitContextValue, systemRowsToResolved, unclaimedBlocks, withheldKeys };
615
+ export { CONTEXT_FETCHABLE_ROWS, CONTEXT_ROW_BLOCKS, CONTEXT_RULES_FEATURE, CONVERSATION_DOCUMENT_CONTEXT_KEYS, CONVERSATION_SCRATCHPAD_PREFIX, DEFAULT_INLINE_CAP, DEFAULT_INLINE_MAX_CHARS, DEFAULT_SURFACE_KEY, LAZY_SOURCE_DEFAULT_MAX, OWN_CONVERSATION_WITHHELD, PAGE_OFF_WITHHELD, PERSON_CONTEXT_VALUES, RESERVED_CONSUMED_KEYS, RICH_ENVELOPE_KEYS, SERVER_AUTHORITATIVE_KEYS, applyReceiptToRows, attachmentContextKey, blocksForRow, buildContextWire, compareReceipt, contextValueChars, deliveredFields, formatChars, hasNothingToSend, isRichEnvelope, isServerAuthoritativeKey, isServerResolved, isWireDirective, lookupSavedRule, lookupSavedRuleFor, neverThePage, pageContextWithheldKeys, pageContextWithholds, receiptRowToResolved, resolveContextRow, resolveContextRule, serverDefaultMax, splitContextValue, systemRowsToResolved, unclaimedBlocks, withheldKeys };
608
616
  export type { ContextAgentLayer, ContextBlockedBy, ContextConsumedAs, ContextDeliveredRef, ContextDelivery, ContextLayer, ContextReceipt, ContextReceiptBlock, ContextReceiptMismatch, ContextReceiptRow, ContextRowDelivery, ContextRowOrigin, ContextRowSource, ContextRuleInput, ContextRuleLayers, ContextRuleResult, ContextSurfaceLayer, ContextViewKind, ContextViewLoader, ContextViewTarget, ContextViewedText, PageContextDirective, PageContextMode, PageContextValueFacts, ResolvedContextRow, SavedContextRule, SavedContextRuleRows };
@@ -218,6 +218,16 @@ function isRichEnvelope(value) {
218
218
  function isLazyPointer(value) {
219
219
  return isPlainObject(value) && !hasOwn(value, "content") && onlyRichKeys(value) && hasLazySource(value);
220
220
  }
221
+ function hasNothingToSend(value) {
222
+ if (value === void 0 || value === null) return true;
223
+ if (typeof value === "string") return value.trim() === "";
224
+ if (Array.isArray(value)) return value.length === 0;
225
+ if (!isPlainObject(value)) return false;
226
+ if (value.__kind !== void 0) return false;
227
+ if (isRichEnvelope(value)) return !hasLazySource(value) && hasNothingToSend(value.content);
228
+ if (isLazyPointer(value)) return false;
229
+ return Object.keys(value).length === 0;
230
+ }
221
231
  function splitContextValue(value) {
222
232
  if (isRichEnvelope(value) || isLazyPointer(value)) {
223
233
  const { content = null, ...overrides } = value;
@@ -506,6 +516,7 @@ export {
506
516
  contextValueChars,
507
517
  deliveredFields,
508
518
  formatChars,
519
+ hasNothingToSend,
509
520
  isRichEnvelope,
510
521
  isServerAuthoritativeKey,
511
522
  isServerResolved,
@@ -376,6 +376,9 @@ function offByDefaultReason(row) {
376
376
  if (row.include || row.decided_by.include !== "page") return null;
377
377
  return row.layers?.surface?.auto_context === false ? "Off by default on this page" : null;
378
378
  }
379
+ function deliveredTextForPerson(text) {
380
+ return text.replace(/<object\b[^>]*>/g, (tag) => tag.replace(/\s+key="[^"]*"/g, ""));
381
+ }
379
382
 
380
383
  // context/react/InlineMaxInput.tsx
381
384
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -893,6 +896,7 @@ var import_icons7 = require("@ai-matrx/design-system/icons");
893
896
  var import_jsx_runtime4 = require("react/jsx-runtime");
894
897
  var BLIND_TOOLTIP = "Not sent to this model";
895
898
  var BLIND_LABEL = "Not read";
899
+ var PENDING_LABEL = "Included values";
896
900
  function IconButton({
897
901
  label,
898
902
  onClick,
@@ -934,6 +938,7 @@ function ContextRulesChip({
934
938
  open: openProp,
935
939
  onOpenChange,
936
940
  pressed,
941
+ countPending = false,
937
942
  className,
938
943
  variant = "outline"
939
944
  }) {
@@ -945,7 +950,7 @@ function ContextRulesChip({
945
950
  };
946
951
  const { count, charsLabel } = summarizeContextRows(rows);
947
952
  const name = label?.trim() ?? "";
948
- const faceName = name ? `${name} \xB7 ${count} included` : `${count} included`;
953
+ const faceName = countPending ? name || PENDING_LABEL : name ? `${name} \xB7 ${count} included` : `${count} included`;
949
954
  const anyOverride = rows.some(hasOverride);
950
955
  const blind = !modelReadsContext;
951
956
  const comfortable = !!isMobile;
@@ -964,7 +969,7 @@ function ContextRulesChip({
964
969
  icon: eyeOpen ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EyeIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_icons7.EyeOffIcon, {}),
965
970
  ...isMobile ? { onClick: () => setOpen(true) } : {},
966
971
  className: (0, import_design_system4.cn)("min-w-0", className),
967
- children: `${name ? `${name} \xB7 ` : ""}${count}`
972
+ children: countPending ? name || null : `${name ? `${name} \xB7 ` : ""}${count}`
968
973
  }
969
974
  );
970
975
  const faceTip = blind ? BLIND_TOOLTIP : name ? null : faceName;
@@ -990,7 +995,7 @@ function ContextRulesChip({
990
995
  children: [
991
996
  eyeOpen ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EyeIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_icons7.EyeOffIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
992
997
  headerName ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-0 truncate text-sm font-medium", children: headerName }) : null,
993
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: `${count} \xB7 ${charsLabel} chars` }),
998
+ countPending ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: `${count} \xB7 ${charsLabel} chars` }),
994
999
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-auto flex shrink-0 items-center gap-0.5", children: [
995
1000
  blind ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mr-1 text-xs text-muted-foreground", children: BLIND_LABEL }) : null,
996
1001
  canMaster ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -1020,7 +1025,7 @@ function ContextRulesChip({
1020
1025
  ]
1021
1026
  }
1022
1027
  ),
1023
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: (0, import_design_system4.cn)("min-h-0 overflow-y-auto", comfortable ? "max-h-[70dvh]" : "max-h-[min(24rem,60vh)]"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1028
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: (0, import_design_system4.cn)("min-h-0 overflow-y-auto", comfortable ? "max-h-[70dvh]" : "max-h-[min(24rem,60vh)]"), children: countPending ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-testid": "context-rules-pending", role: "status", "aria-busy": "true", className: "p-3", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system4.Skeleton, { className: "h-16 w-full" }) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1024
1029
  ContextRulesTable,
1025
1030
  {
1026
1031
  rows,
@@ -1193,7 +1198,7 @@ function DeliveredSection({
1193
1198
  "whitespace-pre-wrap break-words rounded border border-border",
1194
1199
  "bg-muted/40 p-2 font-mono text-[11px] text-foreground"
1195
1200
  ),
1196
- children: loaded.text
1201
+ children: deliveredTextForPerson(loaded.text)
1197
1202
  }
1198
1203
  )
1199
1204
  ] });
@@ -364,6 +364,13 @@ interface ContextRulesChipProps {
364
364
  * Omitted: the chip is a plain popover trigger, no pressed state.
365
365
  */
366
366
  pressed?: boolean | undefined;
367
+ /**
368
+ * True while what decides INCLUSION (the person's saved rules, a kill switch) has not answered:
369
+ * the face shows its eye and no number, and the opened surface shows no count and no switches
370
+ * yet — never a number that changes when it is opened (live walk 2026-10-23: 5 on the face, 3
371
+ * in the popover). Omitted: the rows are final.
372
+ */
373
+ countPending?: boolean | undefined;
367
374
  /** The face's skin: `outline` (default) or `quiet` (a composer row). */
368
375
  variant?: "outline" | "quiet";
369
376
  /** Placement only. */
@@ -374,7 +381,7 @@ interface ContextRulesChipProps {
374
381
  * "N · chars" summary, page master switch, reset-all, full view) over the
375
382
  * compact rules table. Popover on desktop, bottom sheet on mobile.
376
383
  */
377
- declare function ContextRulesChip({ label, rows, cap, on, onToggleAll, hierarchy, onSetInclude, onChange, onResetAll, onOpenFullView, modelReadsContext, onOpenRow, isMobile, readOnly, open: openProp, onOpenChange, pressed, className, variant, }: ContextRulesChipProps): React.JSX.Element;
384
+ declare function ContextRulesChip({ label, rows, cap, on, onToggleAll, hierarchy, onSetInclude, onChange, onResetAll, onOpenFullView, modelReadsContext, onOpenRow, isMobile, readOnly, open: openProp, onOpenChange, pressed, countPending, className, variant, }: ContextRulesChipProps): React.JSX.Element;
378
385
 
379
386
  interface ContextRulesPanelBodyProps {
380
387
  rows: readonly ResolvedContextRow[];
@@ -364,6 +364,13 @@ interface ContextRulesChipProps {
364
364
  * Omitted: the chip is a plain popover trigger, no pressed state.
365
365
  */
366
366
  pressed?: boolean | undefined;
367
+ /**
368
+ * True while what decides INCLUSION (the person's saved rules, a kill switch) has not answered:
369
+ * the face shows its eye and no number, and the opened surface shows no count and no switches
370
+ * yet — never a number that changes when it is opened (live walk 2026-10-23: 5 on the face, 3
371
+ * in the popover). Omitted: the rows are final.
372
+ */
373
+ countPending?: boolean | undefined;
367
374
  /** The face's skin: `outline` (default) or `quiet` (a composer row). */
368
375
  variant?: "outline" | "quiet";
369
376
  /** Placement only. */
@@ -374,7 +381,7 @@ interface ContextRulesChipProps {
374
381
  * "N · chars" summary, page master switch, reset-all, full view) over the
375
382
  * compact rules table. Popover on desktop, bottom sheet on mobile.
376
383
  */
377
- declare function ContextRulesChip({ label, rows, cap, on, onToggleAll, hierarchy, onSetInclude, onChange, onResetAll, onOpenFullView, modelReadsContext, onOpenRow, isMobile, readOnly, open: openProp, onOpenChange, pressed, className, variant, }: ContextRulesChipProps): React.JSX.Element;
384
+ declare function ContextRulesChip({ label, rows, cap, on, onToggleAll, hierarchy, onSetInclude, onChange, onResetAll, onOpenFullView, modelReadsContext, onOpenRow, isMobile, readOnly, open: openProp, onOpenChange, pressed, countPending, className, variant, }: ContextRulesChipProps): React.JSX.Element;
378
385
 
379
386
  interface ContextRulesPanelBodyProps {
380
387
  rows: readonly ResolvedContextRow[];
@@ -344,6 +344,9 @@ function offByDefaultReason(row) {
344
344
  if (row.include || row.decided_by.include !== "page") return null;
345
345
  return row.layers?.surface?.auto_context === false ? "Off by default on this page" : null;
346
346
  }
347
+ function deliveredTextForPerson(text) {
348
+ return text.replace(/<object\b[^>]*>/g, (tag) => tag.replace(/\s+key="[^"]*"/g, ""));
349
+ }
347
350
 
348
351
  // context/react/InlineMaxInput.tsx
349
352
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -859,6 +862,7 @@ import { Button } from "@ai-matrx/design-system/controls";
859
862
  import {
860
863
  BottomSheet,
861
864
  Popover as Popover2,
865
+ Skeleton,
862
866
  PopoverContent as PopoverContent2,
863
867
  PopoverTrigger as PopoverTrigger2,
864
868
  Switch as Switch2,
@@ -872,6 +876,7 @@ import { EyeOffIcon, PanelRightIcon } from "@ai-matrx/design-system/icons";
872
876
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
873
877
  var BLIND_TOOLTIP = "Not sent to this model";
874
878
  var BLIND_LABEL = "Not read";
879
+ var PENDING_LABEL = "Included values";
875
880
  function IconButton({
876
881
  label,
877
882
  onClick,
@@ -913,6 +918,7 @@ function ContextRulesChip({
913
918
  open: openProp,
914
919
  onOpenChange,
915
920
  pressed,
921
+ countPending = false,
916
922
  className,
917
923
  variant = "outline"
918
924
  }) {
@@ -924,7 +930,7 @@ function ContextRulesChip({
924
930
  };
925
931
  const { count, charsLabel } = summarizeContextRows(rows);
926
932
  const name = label?.trim() ?? "";
927
- const faceName = name ? `${name} \xB7 ${count} included` : `${count} included`;
933
+ const faceName = countPending ? name || PENDING_LABEL : name ? `${name} \xB7 ${count} included` : `${count} included`;
928
934
  const anyOverride = rows.some(hasOverride);
929
935
  const blind = !modelReadsContext;
930
936
  const comfortable = !!isMobile;
@@ -943,7 +949,7 @@ function ContextRulesChip({
943
949
  icon: eyeOpen ? /* @__PURE__ */ jsx4(EyeIcon, {}) : /* @__PURE__ */ jsx4(EyeOffIcon, {}),
944
950
  ...isMobile ? { onClick: () => setOpen(true) } : {},
945
951
  className: cn4("min-w-0", className),
946
- children: `${name ? `${name} \xB7 ` : ""}${count}`
952
+ children: countPending ? name || null : `${name ? `${name} \xB7 ` : ""}${count}`
947
953
  }
948
954
  );
949
955
  const faceTip = blind ? BLIND_TOOLTIP : name ? null : faceName;
@@ -969,7 +975,7 @@ function ContextRulesChip({
969
975
  children: [
970
976
  eyeOpen ? /* @__PURE__ */ jsx4(EyeIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ jsx4(EyeOffIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
971
977
  headerName ? /* @__PURE__ */ jsx4("span", { className: "min-w-0 truncate text-sm font-medium", children: headerName }) : null,
972
- /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: `${count} \xB7 ${charsLabel} chars` }),
978
+ countPending ? null : /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: `${count} \xB7 ${charsLabel} chars` }),
973
979
  /* @__PURE__ */ jsxs4("div", { className: "ml-auto flex shrink-0 items-center gap-0.5", children: [
974
980
  blind ? /* @__PURE__ */ jsx4("span", { className: "mr-1 text-xs text-muted-foreground", children: BLIND_LABEL }) : null,
975
981
  canMaster ? /* @__PURE__ */ jsx4(
@@ -999,7 +1005,7 @@ function ContextRulesChip({
999
1005
  ]
1000
1006
  }
1001
1007
  ),
1002
- /* @__PURE__ */ jsx4("div", { className: cn4("min-h-0 overflow-y-auto", comfortable ? "max-h-[70dvh]" : "max-h-[min(24rem,60vh)]"), children: /* @__PURE__ */ jsx4(
1008
+ /* @__PURE__ */ jsx4("div", { className: cn4("min-h-0 overflow-y-auto", comfortable ? "max-h-[70dvh]" : "max-h-[min(24rem,60vh)]"), children: countPending ? /* @__PURE__ */ jsx4("div", { "data-testid": "context-rules-pending", role: "status", "aria-busy": "true", className: "p-3", children: /* @__PURE__ */ jsx4(Skeleton, { className: "h-16 w-full" }) }) : /* @__PURE__ */ jsx4(
1003
1009
  ContextRulesTable,
1004
1010
  {
1005
1011
  rows,
@@ -1046,7 +1052,7 @@ import { ChevronLeftIcon, SearchIcon } from "@ai-matrx/design-system/icons";
1046
1052
 
1047
1053
  // context/react/ContextDeliveredValue.tsx
1048
1054
  import * as React4 from "react";
1049
- import { Skeleton, cn as cn5 } from "@ai-matrx/design-system";
1055
+ import { Skeleton as Skeleton2, cn as cn5 } from "@ai-matrx/design-system";
1050
1056
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1051
1057
  function ContextDeliveredValue({
1052
1058
  row,
@@ -1154,7 +1160,7 @@ function DeliveredSection({
1154
1160
  }
1155
1161
  ) : null
1156
1162
  ] }),
1157
- unavailable ? null : !load ? /* @__PURE__ */ jsx5("span", { className: "text-xs text-muted-foreground", children: "\u2014" }) : loaded.state === "loading" ? /* @__PURE__ */ jsx5(Skeleton, { "data-testid": "context-delivered-loading", className: "h-16 w-full" }) : loaded.state === "error" ? (
1163
+ unavailable ? null : !load ? /* @__PURE__ */ jsx5("span", { className: "text-xs text-muted-foreground", children: "\u2014" }) : loaded.state === "loading" ? /* @__PURE__ */ jsx5(Skeleton2, { "data-testid": "context-delivered-loading", className: "h-16 w-full" }) : loaded.state === "error" ? (
1158
1164
  // Calm and one line: the server's words never reach the person (the host's loader files the error).
1159
1165
  /* @__PURE__ */ jsxs5("span", { "data-testid": "context-delivered-unavailable", className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
1160
1166
  /* @__PURE__ */ jsx5("span", { className: "min-w-0 truncate", children: "Text not kept for this message" }),
@@ -1177,7 +1183,7 @@ function DeliveredSection({
1177
1183
  "whitespace-pre-wrap break-words rounded border border-border",
1178
1184
  "bg-muted/40 p-2 font-mono text-[11px] text-foreground"
1179
1185
  ),
1180
- children: loaded.text
1186
+ children: deliveredTextForPerson(loaded.text)
1181
1187
  }
1182
1188
  )
1183
1189
  ] });
@@ -25,6 +25,8 @@ __export(models_exports, {
25
25
  DEFAULT_CAPABILITIES: () => DEFAULT_CAPABILITIES,
26
26
  DEVICE_FILE_USABLE_STATES: () => DEVICE_FILE_USABLE_STATES,
27
27
  DEVICE_OWN_FILE_USABLE_STATES: () => DEVICE_OWN_FILE_USABLE_STATES,
28
+ DEVICE_RULE_TWIN_FIXTURE: () => DEVICE_RULE_TWIN_FIXTURE,
29
+ DEVICE_SERVED_SETTINGS_KEY: () => DEVICE_SERVED_SETTINGS_KEY,
28
30
  DEVICE_SERVED_VIA: () => DEVICE_SERVED_VIA,
29
31
  DeviceModelUnavailableError: () => DeviceModelUnavailableError,
30
32
  EMPTY_MODEL_RECORDS: () => EMPTY_MODEL_RECORDS,
@@ -72,6 +74,7 @@ __export(models_exports, {
72
74
  fitsNeed: () => fitsNeed,
73
75
  fitsThisComputer: () => fitsThisComputer,
74
76
  followModelCatalogChanges: () => followModelCatalogChanges,
77
+ followModelCatalogVisibility: () => followModelCatalogVisibility,
75
78
  formatMemoryBytes: () => formatMemoryBytes,
76
79
  fromDiscovery: () => fromDiscovery,
77
80
  ggufEngine: () => ggufEngine,
@@ -120,6 +123,7 @@ __export(models_exports, {
120
123
  readDeviceModelFiles: () => readDeviceModelFiles,
121
124
  readGgufHeader: () => readGgufHeader,
122
125
  readModelCatalog: () => readModelCatalog,
126
+ readModelCatalogStamp: () => readModelCatalogStamp,
123
127
  readModelIdentities: () => readModelIdentities,
124
128
  readPublicModel: () => readPublicModel,
125
129
  reconcileModelFavoriteIds: () => reconcileModelFavoriteIds,
@@ -935,42 +939,6 @@ var OUTPUT_MODALITIES = [
935
939
  "decision"
936
940
  ];
937
941
 
938
- // models/catalog/changes.ts
939
- var KEY2 = /* @__PURE__ */ Symbol.for("ai-matrx.agents.model-catalog-changes");
940
- function hub() {
941
- const g = globalThis;
942
- return g[KEY2] ??= { listeners: /* @__PURE__ */ new Set(), revision: 0 };
943
- }
944
- function isModelCatalogChange(payload) {
945
- const catalog = payload?.["catalog"];
946
- return catalog === void 0 || catalog === null || catalog === "models";
947
- }
948
- function onModelCatalogChanged(listener) {
949
- const h = hub();
950
- h.listeners.add(listener);
951
- return () => {
952
- h.listeners.delete(listener);
953
- };
954
- }
955
- function modelCatalogRevision() {
956
- return hub().revision;
957
- }
958
- function announceModelCatalogChanged(payload = {}) {
959
- if (!isModelCatalogChange(payload)) return;
960
- const h = hub();
961
- h.revision += 1;
962
- for (const l of [...h.listeners]) {
963
- try {
964
- l(payload);
965
- } catch (err) {
966
- console.error("[@ai-matrx/agents/models] a catalog_changed listener threw", err);
967
- }
968
- }
969
- }
970
- function followModelCatalogChanges(subscribe) {
971
- return subscribe("catalog_changed", (payload) => announceModelCatalogChanged(payload ?? {}));
972
- }
973
-
974
942
  // models/label.ts
975
943
  function modelDisplayLabel(row) {
976
944
  if (!row) return void 0;
@@ -1562,6 +1530,102 @@ async function readPublicModel(client, key) {
1562
1530
  if (error) return { row: null, error: error.message };
1563
1531
  return { row: data ?? null, error: null };
1564
1532
  }
1533
+ async function readModelCatalogStamp(client) {
1534
+ const q = client.schema("ai").from("model_definition").select("updated_at").order("updated_at", { ascending: false, nullsFirst: false });
1535
+ if (!q.limit) throw new Error("readModelCatalogStamp: the client has no .limit() (a supabase-js client does)");
1536
+ const { data, error } = await q.limit(1).maybeSingle();
1537
+ if (error) throw new Error(error.message);
1538
+ const stamp = data?.updated_at;
1539
+ return typeof stamp === "string" ? stamp : null;
1540
+ }
1541
+
1542
+ // models/catalog/changes.ts
1543
+ var KEY2 = /* @__PURE__ */ Symbol.for("ai-matrx.agents.model-catalog-changes");
1544
+ function hub() {
1545
+ const g = globalThis;
1546
+ return g[KEY2] ??= { listeners: /* @__PURE__ */ new Set(), revision: 0 };
1547
+ }
1548
+ function isModelCatalogChange(payload) {
1549
+ const catalog = payload?.["catalog"];
1550
+ return catalog === void 0 || catalog === null || catalog === "models";
1551
+ }
1552
+ function onModelCatalogChanged(listener) {
1553
+ const h = hub();
1554
+ h.listeners.add(listener);
1555
+ return () => {
1556
+ h.listeners.delete(listener);
1557
+ };
1558
+ }
1559
+ function modelCatalogRevision() {
1560
+ return hub().revision;
1561
+ }
1562
+ function announceModelCatalogChanged(payload = {}) {
1563
+ if (!isModelCatalogChange(payload)) return;
1564
+ const h = hub();
1565
+ h.revision += 1;
1566
+ for (const l of [...h.listeners]) {
1567
+ try {
1568
+ l(payload);
1569
+ } catch (err) {
1570
+ console.error("[@ai-matrx/agents/models] a catalog_changed listener threw", err);
1571
+ }
1572
+ }
1573
+ }
1574
+ function followModelCatalogChanges(subscribe) {
1575
+ return subscribe("catalog_changed", (payload) => announceModelCatalogChanged(payload ?? {}));
1576
+ }
1577
+ function followModelCatalogVisibility(options) {
1578
+ const page = options.page ?? (typeof document !== "undefined" && typeof window !== "undefined" ? { document, window } : null);
1579
+ if (!page) return () => {
1580
+ };
1581
+ const minIntervalMs = options.minIntervalMs ?? 2e4;
1582
+ const now = options.now ?? (() => Date.now());
1583
+ const sink = options.errorSink ?? ((e) => {
1584
+ console.error(`[@ai-matrx/agents/models] ${e.code}: ${e.message}`);
1585
+ });
1586
+ let baseline;
1587
+ let inFlight = null;
1588
+ let lastShownRead = -Infinity;
1589
+ const stamp = () => inFlight ??= readModelCatalogStamp(options.client).finally(() => {
1590
+ inFlight = null;
1591
+ });
1592
+ const fail = (err) => sink({ code: "model-catalog-stamp-failed", message: err instanceof Error ? err.message : String(err) });
1593
+ const learnBaseline = () => {
1594
+ if (baseline !== void 0) return;
1595
+ void stamp().then((s4) => {
1596
+ if (baseline === void 0) baseline = s4;
1597
+ }, fail);
1598
+ };
1599
+ const onHidden = () => {
1600
+ if (page.document.visibilityState === "hidden") learnBaseline();
1601
+ };
1602
+ const onShown = () => {
1603
+ if (page.document.visibilityState === "hidden") return;
1604
+ const t = now();
1605
+ if (t - lastShownRead < minIntervalMs) return;
1606
+ lastShownRead = t;
1607
+ const known = baseline;
1608
+ if (known === void 0) return learnBaseline();
1609
+ void stamp().then((s4) => {
1610
+ if (s4 === known) return;
1611
+ baseline = s4;
1612
+ announceModelCatalogChanged({ catalog: "models", change: "catch_up_visible" });
1613
+ }, fail);
1614
+ };
1615
+ const offChanges = onModelCatalogChanged(() => {
1616
+ baseline = void 0;
1617
+ if (page.document.visibilityState === "hidden") learnBaseline();
1618
+ });
1619
+ const onVisibility = () => page.document.visibilityState === "hidden" ? onHidden() : onShown();
1620
+ page.document.addEventListener("visibilitychange", onVisibility);
1621
+ page.window.addEventListener("focus", onShown);
1622
+ if (page.document.visibilityState === "hidden") learnBaseline();
1623
+ return () => {
1624
+ offChanges();
1625
+ page.document.removeEventListener("visibilitychange", onVisibility);
1626
+ page.window.removeEventListener("focus", onShown);
1627
+ };
1628
+ }
1565
1629
 
1566
1630
  // models/catalog/store.ts
1567
1631
  var IDLE = { status: "idle", models: [], error: null, loadedAt: null };
@@ -2747,6 +2811,8 @@ var import_sha256 = require("@ai-matrx/kit/sha256");
2747
2811
 
2748
2812
  // models/device-models.ts
2749
2813
  var DEVICE_SERVED_VIA = "device";
2814
+ var DEVICE_SERVED_SETTINGS_KEY = "served_via";
2815
+ var DEVICE_RULE_TWIN_FIXTURE = "apps/shared/matrx-agents/models/__tests__/device-models.test.ts";
2750
2816
  var DEVICE_FILE_USABLE_STATES = Object.freeze(["present"]);
2751
2817
  var DEVICE_OWN_FILE_USABLE_STATES = Object.freeze(["present", "unmatched"]);
2752
2818
  var s2 = (v) => typeof v === "string" ? v : "";
@@ -641,6 +641,8 @@ interface RecordsQuery extends PromiseLike<PgResult> {
641
641
  nullsFirst?: boolean;
642
642
  }): RecordsQuery;
643
643
  maybeSingle(): PromiseLike<PgResult>;
644
+ /** Only the catalog stamp (`readModelCatalogStamp`) uses it. */
645
+ limit?(count: number): RecordsQuery;
644
646
  }
645
647
  interface ModelRecordsClient {
646
648
  schema(name: string): {
@@ -791,6 +793,12 @@ declare function readPublicModel(client: PublicModelClient, key: string): Promis
791
793
  } | null;
792
794
  error: string | null;
793
795
  }>;
796
+ /**
797
+ * THE catalog stamp: the newest `ai.model_definition.updated_at` (one row, one column) — the cheap read a
798
+ * tab makes to learn whether the catalog changed while it was not listening (catalog/changes.ts
799
+ * `followModelCatalogVisibility`). Null when the catalog is empty. Throws the database's error.
800
+ */
801
+ declare function readModelCatalogStamp(client: ModelRecordsClient): Promise<string | null>;
794
802
 
795
803
  /**
796
804
  * `@ai-matrx/agents/models` — `createModelCatalog`: the headless model catalog store.
@@ -1706,8 +1714,15 @@ declare function curatedList<T extends CuratedModel>(models: readonly T[], { off
1706
1714
  * listed in `device_model_file_ids` when present.
1707
1715
  */
1708
1716
  type Row$1 = Record<string, unknown>;
1709
- /** How a device-served run says it is served: run settings `{ served_via: "device", … }`. */
1717
+ /** The `provider` value of a device-served run: `config_overrides.served_via = { provider: "device", … }`. */
1710
1718
  declare const DEVICE_SERVED_VIA: "device";
1719
+ /** Where a device-served run carries its DeviceServedSettings on the agent-run request: `config_overrides.served_via`. */
1720
+ declare const DEVICE_SERVED_SETTINGS_KEY: "served_via";
1721
+ /**
1722
+ * The server's Python twin (the device provider) MUST mirror the usable states, the required-artifact rule
1723
+ * and the resolveDeviceModel refusals above; this test file is the fixture both sides prove against.
1724
+ */
1725
+ declare const DEVICE_RULE_TWIN_FIXTURE: "apps/shared/matrx-agents/models/__tests__/device-models.test.ts";
1711
1726
  /**
1712
1727
  * The ai.device_model_file states whose catalog file can be loaded (the column holds present, missing,
1713
1728
  * corrupt, unmatched, hashing, unverified_match). Only `present` is a verified copy of the catalog artifact;
@@ -1727,8 +1742,9 @@ interface DeviceAvailability {
1727
1742
  device_model_file_ids: string[];
1728
1743
  }
1729
1744
  /** The run settings of a device-served model. There is no ai.provider row and no pricing row: the ledger counts 0 points. */
1745
+ /** Carried at `config_overrides.served_via` (DEVICE_SERVED_SETTINGS_KEY) on the agent-run request. */
1730
1746
  interface DeviceServedSettings {
1731
- served_via: typeof DEVICE_SERVED_VIA;
1747
+ provider: typeof DEVICE_SERVED_VIA;
1732
1748
  /** The computer (public.app_instances.id). */
1733
1749
  app_instance_id: string;
1734
1750
  /** ai.model_definition.id, or an ai.device_model_file.id (artifact_id null) for a person's own model. */
@@ -2024,7 +2040,12 @@ declare function modelDisplayLabel(row: ModelLabelSource | null | undefined): st
2024
2040
  * `@ai-matrx/agents/models/react`). No surface keeps a model cache that misses the event.
2025
2041
  *
2026
2042
  * The hub lives on `globalThis` so two bundled copies of this module share one set of listeners.
2043
+ *
2044
+ * A tab that is not listening (the bus room not joined yet, or asleep) catches up when it comes to the
2045
+ * front: `followModelCatalogVisibility({ client })` reads the catalog stamp when the tab hides and again
2046
+ * when it shows or takes focus (throttled), and announces a change when the stamp moved.
2027
2047
  */
2048
+
2028
2049
  /** A `catalog_changed` payload: `{ catalog?: "models" | ..., change?, model_id? }`. */
2029
2050
  type ModelCatalogChangePayload = Record<string, unknown>;
2030
2051
  /** The bus client's subscribe call: `(action, handler) => unsubscribe` (the web's `registerDirectiveHandler`). */
@@ -2039,6 +2060,32 @@ declare function modelCatalogRevision(): number;
2039
2060
  declare function announceModelCatalogChanged(payload?: ModelCatalogChangePayload): void;
2040
2061
  /** Bind the host's bus client: every `catalog_changed` it delivers reaches every model reader. Returns the unbind. */
2041
2062
  declare function followModelCatalogChanges(subscribe: ModelCatalogDirectiveSubscribe): () => void;
2063
+ interface ModelCatalogVisibilityOptions {
2064
+ /** The client the stamp is read with (supabase-js fits as is). */
2065
+ client: ModelRecordsClient;
2066
+ /** At most one stamp read per this many ms when the tab shows / takes focus. Default 20 s. */
2067
+ minIntervalMs?: number;
2068
+ /** The page (tests pass a double); defaults to the global `document` / `window`. */
2069
+ page?: {
2070
+ document: Pick<Document, "visibilityState" | "addEventListener" | "removeEventListener">;
2071
+ window: Pick<Window, "addEventListener" | "removeEventListener">;
2072
+ };
2073
+ /** Where a failed stamp read is said. Default: console.error, tagged. */
2074
+ errorSink?: (event: {
2075
+ code: string;
2076
+ message: string;
2077
+ }) => void;
2078
+ now?: () => number;
2079
+ }
2080
+ /**
2081
+ * When the tab shows or takes focus, read the catalog stamp (one row) and, when it moved since the tab
2082
+ * last knew it, announce a model catalog change through the SAME door as the bus (every catalog re-reads
2083
+ * what it read; every reader following `onModelCatalogChanged` re-reads). The baseline is taken when the
2084
+ * tab hides (or at bind when it starts hidden), and refreshed by every announced change. Reads already in
2085
+ * flight are joined; shows/focuses within `minIntervalMs` share the last read. Returns the unbind. No-op
2086
+ * where there is no document (a server render, a worker).
2087
+ */
2088
+ declare function followModelCatalogVisibility(options: ModelCatalogVisibilityOptions): () => void;
2042
2089
 
2043
- export { CHAT_DEFAULT_MODEL_KNOB, CONTENT_TYPES, DEFAULT_CAPABILITIES, DEVICE_FILE_USABLE_STATES, DEVICE_OWN_FILE_USABLE_STATES, DEVICE_SERVED_VIA, DeviceModelUnavailableError, EMPTY_MODEL_RECORDS, EXCLUDED_REVIEW_STATES, FEATURE_BUCKETS, FEATURE_BUCKET_ORDER, FEATURE_KEYS, FILE_TYPE_QUANT, GgufError, INPUT_MODALITIES, INTERACTION_MODES, MAX_SUCCESSION_HOPS, MODEL_CATALOG_KINDS, MODEL_FAVORITE_ENTITY_TYPE, NEW_AGENT_DEFAULT_MODEL_KNOB, OUTPUT_MODALITIES, SERVICE_LABEL, SERVICE_LABEL_PLURAL, TOP_TIER_COST_RATING, TOP_TIER_LOCKED_TITLE, acceptsTextInstructions, announceModelCatalogChanged, availableOnDevices, 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, readDeviceModelFiles, readGgufHeader, readModelCatalog, readModelIdentities, readPublicModel, reconcileModelFavoriteIds, reportModelDiagnostic, requireCanonicalCapabilities, requiredArtifactIds, requiredArtifacts, resetSuccessionLogForTests, resolveDeviceModel, 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, toDeviceModelFile, validateModelSet, variantVerdict, withModelClass };
2044
- export type { AdminOffering, AdminPricingBand, ArtifactUse, AuditCapabilitiesRecord, AvailabilityVariant, CapabilitiesParseContext, CatalogKeyColumn, CatalogKindInput, CatalogModel, CatalogTier, CheapestAdequateOptions, CompatStatus, ContentType, CuratedCatalogClient, CuratedModel, CuratedPresentation, CuratedRows, CuratedVariant, DecidedBy, DeviceAvailability, DeviceGraphics, DeviceHardware, DeviceModelFile, DeviceModelResolution, DeviceServedSettings, DiscoveryAmbiguity, DiscoveryArtifact, DiscoveryMedia, DiscoveryResult, DiscoveryRuntime, DiscoverySource, DiscoveryVariant, FeatureBucket, FeatureBucketMeta, FeatureKey, FitCapabilities, GgufHeader, GgufRead, GgufTensor, GroupedFeatures, HardwareClass, HardwareClasses, HeaderFlag, HeaderJudgement, HeaderRefusal, Interaction, InteractionMode, LocalArtifact, LocalCatalogClient, LocalCatalogQuery, LocalCompatRow, LocalModel, LocalModelCatalog, LocalPresentation, LocalRuntime, LocalRuntimeBuild, LocalVariant, Modality, ModelCapabilities, ModelCatalog, ModelCatalogChangePayload, ModelCatalogClient, ModelCatalogConfig, ModelCatalogDirectiveSubscribe, ModelCatalogErrorSink, ModelCatalogPgError, ModelCatalogPgResult, ModelCatalogQuery, ModelCatalogState, ModelCatalogVariant, ModelCatalogVariantState, ModelClassConfig, ModelClassEntry, ModelClassIndex, ModelDiagnosticEntry, ModelDiagnosticsSink, ModelFavorites, ModelFavoritesCache, ModelFavoritesClient, ModelFavoritesConfig, ModelFavoritesErrorSink, ModelFavoritesState, ModelFetchScope, ModelFileHandle, ModelFit, ModelIdentityRow, ModelJson, ModelLabelSource, ModelNeed, ModelOfferingRow, ModelOption, ModelOptionWithLifecycle, ModelRecord, ModelRecordFetchType, ModelRecords, ModelRecordsClient, ModelRecordsConfig, ModelRecordsState, ModelSelectionPurpose, ModelSetEnv, ModelSetFacts, ModelSetValidation, NewAgentModel, NoSelection, OrderedLike, PreferredModelChoice, PreferredModelChoicePorts, PriceTier, PricedCatalogModel, PublicModelClient, RankOptions, RankedVariant, ReadModelCatalogOptions, SelectDevice, Selection, ShardName, SuccessionClient, SuccessionIndex, SuccessionNode, VariantDiscovery, VariantVerdict, Want };
2090
+ export { CHAT_DEFAULT_MODEL_KNOB, CONTENT_TYPES, DEFAULT_CAPABILITIES, DEVICE_FILE_USABLE_STATES, DEVICE_OWN_FILE_USABLE_STATES, DEVICE_RULE_TWIN_FIXTURE, DEVICE_SERVED_SETTINGS_KEY, DEVICE_SERVED_VIA, DeviceModelUnavailableError, EMPTY_MODEL_RECORDS, EXCLUDED_REVIEW_STATES, FEATURE_BUCKETS, FEATURE_BUCKET_ORDER, FEATURE_KEYS, FILE_TYPE_QUANT, GgufError, INPUT_MODALITIES, INTERACTION_MODES, MAX_SUCCESSION_HOPS, MODEL_CATALOG_KINDS, MODEL_FAVORITE_ENTITY_TYPE, NEW_AGENT_DEFAULT_MODEL_KNOB, OUTPUT_MODALITIES, SERVICE_LABEL, SERVICE_LABEL_PLURAL, TOP_TIER_COST_RATING, TOP_TIER_LOCKED_TITLE, acceptsTextInstructions, announceModelCatalogChanged, availableOnDevices, blendedPrice, buildModelClassIndex, buildSuccessionIndex, catalogFingerprint, catalogForApp, catalogKeyColumn, catalogKindOf, cheapestAdequateModel, classConfigKey, collapseTiersByClass, costRatingTier, costRatingWord, createModelCatalog, createModelFavorites, createModelRecords, curatedList, displayOrderOf, favoriteIdsEqual, fetchCuratedModels, findNonCanonicalCapabilityValues, fitsMemory, fitsNeed, fitsThisComputer, followModelCatalogChanges, followModelCatalogVisibility, 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, readDeviceModelFiles, readGgufHeader, readModelCatalog, readModelCatalogStamp, readModelIdentities, readPublicModel, reconcileModelFavoriteIds, reportModelDiagnostic, requireCanonicalCapabilities, requiredArtifactIds, requiredArtifacts, resetSuccessionLogForTests, resolveDeviceModel, 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, toDeviceModelFile, validateModelSet, variantVerdict, withModelClass };
2091
+ export type { AdminOffering, AdminPricingBand, ArtifactUse, AuditCapabilitiesRecord, AvailabilityVariant, CapabilitiesParseContext, CatalogKeyColumn, CatalogKindInput, CatalogModel, CatalogTier, CheapestAdequateOptions, CompatStatus, ContentType, CuratedCatalogClient, CuratedModel, CuratedPresentation, CuratedRows, CuratedVariant, DecidedBy, DeviceAvailability, DeviceGraphics, DeviceHardware, DeviceModelFile, DeviceModelResolution, DeviceServedSettings, DiscoveryAmbiguity, DiscoveryArtifact, DiscoveryMedia, DiscoveryResult, DiscoveryRuntime, DiscoverySource, DiscoveryVariant, FeatureBucket, FeatureBucketMeta, FeatureKey, FitCapabilities, GgufHeader, GgufRead, GgufTensor, GroupedFeatures, HardwareClass, HardwareClasses, HeaderFlag, HeaderJudgement, HeaderRefusal, Interaction, InteractionMode, LocalArtifact, LocalCatalogClient, LocalCatalogQuery, LocalCompatRow, LocalModel, LocalModelCatalog, LocalPresentation, LocalRuntime, LocalRuntimeBuild, LocalVariant, Modality, ModelCapabilities, ModelCatalog, ModelCatalogChangePayload, ModelCatalogClient, ModelCatalogConfig, ModelCatalogDirectiveSubscribe, ModelCatalogErrorSink, ModelCatalogPgError, ModelCatalogPgResult, ModelCatalogQuery, ModelCatalogState, ModelCatalogVariant, ModelCatalogVariantState, ModelCatalogVisibilityOptions, ModelClassConfig, ModelClassEntry, ModelClassIndex, ModelDiagnosticEntry, ModelDiagnosticsSink, ModelFavorites, ModelFavoritesCache, ModelFavoritesClient, ModelFavoritesConfig, ModelFavoritesErrorSink, ModelFavoritesState, ModelFetchScope, ModelFileHandle, ModelFit, ModelIdentityRow, ModelJson, ModelLabelSource, ModelNeed, ModelOfferingRow, ModelOption, ModelOptionWithLifecycle, ModelRecord, ModelRecordFetchType, ModelRecords, ModelRecordsClient, ModelRecordsConfig, ModelRecordsState, ModelSelectionPurpose, ModelSetEnv, ModelSetFacts, ModelSetValidation, NewAgentModel, NoSelection, OrderedLike, PreferredModelChoice, PreferredModelChoicePorts, PriceTier, PricedCatalogModel, PublicModelClient, RankOptions, RankedVariant, ReadModelCatalogOptions, SelectDevice, Selection, ShardName, SuccessionClient, SuccessionIndex, SuccessionNode, VariantDiscovery, VariantVerdict, Want };
@@ -641,6 +641,8 @@ interface RecordsQuery extends PromiseLike<PgResult> {
641
641
  nullsFirst?: boolean;
642
642
  }): RecordsQuery;
643
643
  maybeSingle(): PromiseLike<PgResult>;
644
+ /** Only the catalog stamp (`readModelCatalogStamp`) uses it. */
645
+ limit?(count: number): RecordsQuery;
644
646
  }
645
647
  interface ModelRecordsClient {
646
648
  schema(name: string): {
@@ -791,6 +793,12 @@ declare function readPublicModel(client: PublicModelClient, key: string): Promis
791
793
  } | null;
792
794
  error: string | null;
793
795
  }>;
796
+ /**
797
+ * THE catalog stamp: the newest `ai.model_definition.updated_at` (one row, one column) — the cheap read a
798
+ * tab makes to learn whether the catalog changed while it was not listening (catalog/changes.ts
799
+ * `followModelCatalogVisibility`). Null when the catalog is empty. Throws the database's error.
800
+ */
801
+ declare function readModelCatalogStamp(client: ModelRecordsClient): Promise<string | null>;
794
802
 
795
803
  /**
796
804
  * `@ai-matrx/agents/models` — `createModelCatalog`: the headless model catalog store.
@@ -1706,8 +1714,15 @@ declare function curatedList<T extends CuratedModel>(models: readonly T[], { off
1706
1714
  * listed in `device_model_file_ids` when present.
1707
1715
  */
1708
1716
  type Row$1 = Record<string, unknown>;
1709
- /** How a device-served run says it is served: run settings `{ served_via: "device", … }`. */
1717
+ /** The `provider` value of a device-served run: `config_overrides.served_via = { provider: "device", … }`. */
1710
1718
  declare const DEVICE_SERVED_VIA: "device";
1719
+ /** Where a device-served run carries its DeviceServedSettings on the agent-run request: `config_overrides.served_via`. */
1720
+ declare const DEVICE_SERVED_SETTINGS_KEY: "served_via";
1721
+ /**
1722
+ * The server's Python twin (the device provider) MUST mirror the usable states, the required-artifact rule
1723
+ * and the resolveDeviceModel refusals above; this test file is the fixture both sides prove against.
1724
+ */
1725
+ declare const DEVICE_RULE_TWIN_FIXTURE: "apps/shared/matrx-agents/models/__tests__/device-models.test.ts";
1711
1726
  /**
1712
1727
  * The ai.device_model_file states whose catalog file can be loaded (the column holds present, missing,
1713
1728
  * corrupt, unmatched, hashing, unverified_match). Only `present` is a verified copy of the catalog artifact;
@@ -1727,8 +1742,9 @@ interface DeviceAvailability {
1727
1742
  device_model_file_ids: string[];
1728
1743
  }
1729
1744
  /** The run settings of a device-served model. There is no ai.provider row and no pricing row: the ledger counts 0 points. */
1745
+ /** Carried at `config_overrides.served_via` (DEVICE_SERVED_SETTINGS_KEY) on the agent-run request. */
1730
1746
  interface DeviceServedSettings {
1731
- served_via: typeof DEVICE_SERVED_VIA;
1747
+ provider: typeof DEVICE_SERVED_VIA;
1732
1748
  /** The computer (public.app_instances.id). */
1733
1749
  app_instance_id: string;
1734
1750
  /** ai.model_definition.id, or an ai.device_model_file.id (artifact_id null) for a person's own model. */
@@ -2024,7 +2040,12 @@ declare function modelDisplayLabel(row: ModelLabelSource | null | undefined): st
2024
2040
  * `@ai-matrx/agents/models/react`). No surface keeps a model cache that misses the event.
2025
2041
  *
2026
2042
  * The hub lives on `globalThis` so two bundled copies of this module share one set of listeners.
2043
+ *
2044
+ * A tab that is not listening (the bus room not joined yet, or asleep) catches up when it comes to the
2045
+ * front: `followModelCatalogVisibility({ client })` reads the catalog stamp when the tab hides and again
2046
+ * when it shows or takes focus (throttled), and announces a change when the stamp moved.
2027
2047
  */
2048
+
2028
2049
  /** A `catalog_changed` payload: `{ catalog?: "models" | ..., change?, model_id? }`. */
2029
2050
  type ModelCatalogChangePayload = Record<string, unknown>;
2030
2051
  /** The bus client's subscribe call: `(action, handler) => unsubscribe` (the web's `registerDirectiveHandler`). */
@@ -2039,6 +2060,32 @@ declare function modelCatalogRevision(): number;
2039
2060
  declare function announceModelCatalogChanged(payload?: ModelCatalogChangePayload): void;
2040
2061
  /** Bind the host's bus client: every `catalog_changed` it delivers reaches every model reader. Returns the unbind. */
2041
2062
  declare function followModelCatalogChanges(subscribe: ModelCatalogDirectiveSubscribe): () => void;
2063
+ interface ModelCatalogVisibilityOptions {
2064
+ /** The client the stamp is read with (supabase-js fits as is). */
2065
+ client: ModelRecordsClient;
2066
+ /** At most one stamp read per this many ms when the tab shows / takes focus. Default 20 s. */
2067
+ minIntervalMs?: number;
2068
+ /** The page (tests pass a double); defaults to the global `document` / `window`. */
2069
+ page?: {
2070
+ document: Pick<Document, "visibilityState" | "addEventListener" | "removeEventListener">;
2071
+ window: Pick<Window, "addEventListener" | "removeEventListener">;
2072
+ };
2073
+ /** Where a failed stamp read is said. Default: console.error, tagged. */
2074
+ errorSink?: (event: {
2075
+ code: string;
2076
+ message: string;
2077
+ }) => void;
2078
+ now?: () => number;
2079
+ }
2080
+ /**
2081
+ * When the tab shows or takes focus, read the catalog stamp (one row) and, when it moved since the tab
2082
+ * last knew it, announce a model catalog change through the SAME door as the bus (every catalog re-reads
2083
+ * what it read; every reader following `onModelCatalogChanged` re-reads). The baseline is taken when the
2084
+ * tab hides (or at bind when it starts hidden), and refreshed by every announced change. Reads already in
2085
+ * flight are joined; shows/focuses within `minIntervalMs` share the last read. Returns the unbind. No-op
2086
+ * where there is no document (a server render, a worker).
2087
+ */
2088
+ declare function followModelCatalogVisibility(options: ModelCatalogVisibilityOptions): () => void;
2042
2089
 
2043
- export { CHAT_DEFAULT_MODEL_KNOB, CONTENT_TYPES, DEFAULT_CAPABILITIES, DEVICE_FILE_USABLE_STATES, DEVICE_OWN_FILE_USABLE_STATES, DEVICE_SERVED_VIA, DeviceModelUnavailableError, EMPTY_MODEL_RECORDS, EXCLUDED_REVIEW_STATES, FEATURE_BUCKETS, FEATURE_BUCKET_ORDER, FEATURE_KEYS, FILE_TYPE_QUANT, GgufError, INPUT_MODALITIES, INTERACTION_MODES, MAX_SUCCESSION_HOPS, MODEL_CATALOG_KINDS, MODEL_FAVORITE_ENTITY_TYPE, NEW_AGENT_DEFAULT_MODEL_KNOB, OUTPUT_MODALITIES, SERVICE_LABEL, SERVICE_LABEL_PLURAL, TOP_TIER_COST_RATING, TOP_TIER_LOCKED_TITLE, acceptsTextInstructions, announceModelCatalogChanged, availableOnDevices, 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, readDeviceModelFiles, readGgufHeader, readModelCatalog, readModelIdentities, readPublicModel, reconcileModelFavoriteIds, reportModelDiagnostic, requireCanonicalCapabilities, requiredArtifactIds, requiredArtifacts, resetSuccessionLogForTests, resolveDeviceModel, 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, toDeviceModelFile, validateModelSet, variantVerdict, withModelClass };
2044
- export type { AdminOffering, AdminPricingBand, ArtifactUse, AuditCapabilitiesRecord, AvailabilityVariant, CapabilitiesParseContext, CatalogKeyColumn, CatalogKindInput, CatalogModel, CatalogTier, CheapestAdequateOptions, CompatStatus, ContentType, CuratedCatalogClient, CuratedModel, CuratedPresentation, CuratedRows, CuratedVariant, DecidedBy, DeviceAvailability, DeviceGraphics, DeviceHardware, DeviceModelFile, DeviceModelResolution, DeviceServedSettings, DiscoveryAmbiguity, DiscoveryArtifact, DiscoveryMedia, DiscoveryResult, DiscoveryRuntime, DiscoverySource, DiscoveryVariant, FeatureBucket, FeatureBucketMeta, FeatureKey, FitCapabilities, GgufHeader, GgufRead, GgufTensor, GroupedFeatures, HardwareClass, HardwareClasses, HeaderFlag, HeaderJudgement, HeaderRefusal, Interaction, InteractionMode, LocalArtifact, LocalCatalogClient, LocalCatalogQuery, LocalCompatRow, LocalModel, LocalModelCatalog, LocalPresentation, LocalRuntime, LocalRuntimeBuild, LocalVariant, Modality, ModelCapabilities, ModelCatalog, ModelCatalogChangePayload, ModelCatalogClient, ModelCatalogConfig, ModelCatalogDirectiveSubscribe, ModelCatalogErrorSink, ModelCatalogPgError, ModelCatalogPgResult, ModelCatalogQuery, ModelCatalogState, ModelCatalogVariant, ModelCatalogVariantState, ModelClassConfig, ModelClassEntry, ModelClassIndex, ModelDiagnosticEntry, ModelDiagnosticsSink, ModelFavorites, ModelFavoritesCache, ModelFavoritesClient, ModelFavoritesConfig, ModelFavoritesErrorSink, ModelFavoritesState, ModelFetchScope, ModelFileHandle, ModelFit, ModelIdentityRow, ModelJson, ModelLabelSource, ModelNeed, ModelOfferingRow, ModelOption, ModelOptionWithLifecycle, ModelRecord, ModelRecordFetchType, ModelRecords, ModelRecordsClient, ModelRecordsConfig, ModelRecordsState, ModelSelectionPurpose, ModelSetEnv, ModelSetFacts, ModelSetValidation, NewAgentModel, NoSelection, OrderedLike, PreferredModelChoice, PreferredModelChoicePorts, PriceTier, PricedCatalogModel, PublicModelClient, RankOptions, RankedVariant, ReadModelCatalogOptions, SelectDevice, Selection, ShardName, SuccessionClient, SuccessionIndex, SuccessionNode, VariantDiscovery, VariantVerdict, Want };
2090
+ export { CHAT_DEFAULT_MODEL_KNOB, CONTENT_TYPES, DEFAULT_CAPABILITIES, DEVICE_FILE_USABLE_STATES, DEVICE_OWN_FILE_USABLE_STATES, DEVICE_RULE_TWIN_FIXTURE, DEVICE_SERVED_SETTINGS_KEY, DEVICE_SERVED_VIA, DeviceModelUnavailableError, EMPTY_MODEL_RECORDS, EXCLUDED_REVIEW_STATES, FEATURE_BUCKETS, FEATURE_BUCKET_ORDER, FEATURE_KEYS, FILE_TYPE_QUANT, GgufError, INPUT_MODALITIES, INTERACTION_MODES, MAX_SUCCESSION_HOPS, MODEL_CATALOG_KINDS, MODEL_FAVORITE_ENTITY_TYPE, NEW_AGENT_DEFAULT_MODEL_KNOB, OUTPUT_MODALITIES, SERVICE_LABEL, SERVICE_LABEL_PLURAL, TOP_TIER_COST_RATING, TOP_TIER_LOCKED_TITLE, acceptsTextInstructions, announceModelCatalogChanged, availableOnDevices, blendedPrice, buildModelClassIndex, buildSuccessionIndex, catalogFingerprint, catalogForApp, catalogKeyColumn, catalogKindOf, cheapestAdequateModel, classConfigKey, collapseTiersByClass, costRatingTier, costRatingWord, createModelCatalog, createModelFavorites, createModelRecords, curatedList, displayOrderOf, favoriteIdsEqual, fetchCuratedModels, findNonCanonicalCapabilityValues, fitsMemory, fitsNeed, fitsThisComputer, followModelCatalogChanges, followModelCatalogVisibility, 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, readDeviceModelFiles, readGgufHeader, readModelCatalog, readModelCatalogStamp, readModelIdentities, readPublicModel, reconcileModelFavoriteIds, reportModelDiagnostic, requireCanonicalCapabilities, requiredArtifactIds, requiredArtifacts, resetSuccessionLogForTests, resolveDeviceModel, 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, toDeviceModelFile, validateModelSet, variantVerdict, withModelClass };
2091
+ export type { AdminOffering, AdminPricingBand, ArtifactUse, AuditCapabilitiesRecord, AvailabilityVariant, CapabilitiesParseContext, CatalogKeyColumn, CatalogKindInput, CatalogModel, CatalogTier, CheapestAdequateOptions, CompatStatus, ContentType, CuratedCatalogClient, CuratedModel, CuratedPresentation, CuratedRows, CuratedVariant, DecidedBy, DeviceAvailability, DeviceGraphics, DeviceHardware, DeviceModelFile, DeviceModelResolution, DeviceServedSettings, DiscoveryAmbiguity, DiscoveryArtifact, DiscoveryMedia, DiscoveryResult, DiscoveryRuntime, DiscoverySource, DiscoveryVariant, FeatureBucket, FeatureBucketMeta, FeatureKey, FitCapabilities, GgufHeader, GgufRead, GgufTensor, GroupedFeatures, HardwareClass, HardwareClasses, HeaderFlag, HeaderJudgement, HeaderRefusal, Interaction, InteractionMode, LocalArtifact, LocalCatalogClient, LocalCatalogQuery, LocalCompatRow, LocalModel, LocalModelCatalog, LocalPresentation, LocalRuntime, LocalRuntimeBuild, LocalVariant, Modality, ModelCapabilities, ModelCatalog, ModelCatalogChangePayload, ModelCatalogClient, ModelCatalogConfig, ModelCatalogDirectiveSubscribe, ModelCatalogErrorSink, ModelCatalogPgError, ModelCatalogPgResult, ModelCatalogQuery, ModelCatalogState, ModelCatalogVariant, ModelCatalogVariantState, ModelCatalogVisibilityOptions, ModelClassConfig, ModelClassEntry, ModelClassIndex, ModelDiagnosticEntry, ModelDiagnosticsSink, ModelFavorites, ModelFavoritesCache, ModelFavoritesClient, ModelFavoritesConfig, ModelFavoritesErrorSink, ModelFavoritesState, ModelFetchScope, ModelFileHandle, ModelFit, ModelIdentityRow, ModelJson, ModelLabelSource, ModelNeed, ModelOfferingRow, ModelOption, ModelOptionWithLifecycle, ModelRecord, ModelRecordFetchType, ModelRecords, ModelRecordsClient, ModelRecordsConfig, ModelRecordsState, ModelSelectionPurpose, ModelSetEnv, ModelSetFacts, ModelSetValidation, NewAgentModel, NoSelection, OrderedLike, PreferredModelChoice, PreferredModelChoicePorts, PriceTier, PricedCatalogModel, PublicModelClient, RankOptions, RankedVariant, ReadModelCatalogOptions, SelectDevice, Selection, ShardName, SuccessionClient, SuccessionIndex, SuccessionNode, VariantDiscovery, VariantVerdict, Want };
@@ -759,42 +759,6 @@ 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
762
  // models/label.ts
799
763
  function modelDisplayLabel(row) {
800
764
  if (!row) return void 0;
@@ -1386,6 +1350,102 @@ async function readPublicModel(client, key) {
1386
1350
  if (error) return { row: null, error: error.message };
1387
1351
  return { row: data ?? null, error: null };
1388
1352
  }
1353
+ async function readModelCatalogStamp(client) {
1354
+ const q = client.schema("ai").from("model_definition").select("updated_at").order("updated_at", { ascending: false, nullsFirst: false });
1355
+ if (!q.limit) throw new Error("readModelCatalogStamp: the client has no .limit() (a supabase-js client does)");
1356
+ const { data, error } = await q.limit(1).maybeSingle();
1357
+ if (error) throw new Error(error.message);
1358
+ const stamp = data?.updated_at;
1359
+ return typeof stamp === "string" ? stamp : null;
1360
+ }
1361
+
1362
+ // models/catalog/changes.ts
1363
+ var KEY2 = /* @__PURE__ */ Symbol.for("ai-matrx.agents.model-catalog-changes");
1364
+ function hub() {
1365
+ const g = globalThis;
1366
+ return g[KEY2] ??= { listeners: /* @__PURE__ */ new Set(), revision: 0 };
1367
+ }
1368
+ function isModelCatalogChange(payload) {
1369
+ const catalog = payload?.["catalog"];
1370
+ return catalog === void 0 || catalog === null || catalog === "models";
1371
+ }
1372
+ function onModelCatalogChanged(listener) {
1373
+ const h = hub();
1374
+ h.listeners.add(listener);
1375
+ return () => {
1376
+ h.listeners.delete(listener);
1377
+ };
1378
+ }
1379
+ function modelCatalogRevision() {
1380
+ return hub().revision;
1381
+ }
1382
+ function announceModelCatalogChanged(payload = {}) {
1383
+ if (!isModelCatalogChange(payload)) return;
1384
+ const h = hub();
1385
+ h.revision += 1;
1386
+ for (const l of [...h.listeners]) {
1387
+ try {
1388
+ l(payload);
1389
+ } catch (err) {
1390
+ console.error("[@ai-matrx/agents/models] a catalog_changed listener threw", err);
1391
+ }
1392
+ }
1393
+ }
1394
+ function followModelCatalogChanges(subscribe) {
1395
+ return subscribe("catalog_changed", (payload) => announceModelCatalogChanged(payload ?? {}));
1396
+ }
1397
+ function followModelCatalogVisibility(options) {
1398
+ const page = options.page ?? (typeof document !== "undefined" && typeof window !== "undefined" ? { document, window } : null);
1399
+ if (!page) return () => {
1400
+ };
1401
+ const minIntervalMs = options.minIntervalMs ?? 2e4;
1402
+ const now = options.now ?? (() => Date.now());
1403
+ const sink = options.errorSink ?? ((e) => {
1404
+ console.error(`[@ai-matrx/agents/models] ${e.code}: ${e.message}`);
1405
+ });
1406
+ let baseline;
1407
+ let inFlight = null;
1408
+ let lastShownRead = -Infinity;
1409
+ const stamp = () => inFlight ??= readModelCatalogStamp(options.client).finally(() => {
1410
+ inFlight = null;
1411
+ });
1412
+ const fail = (err) => sink({ code: "model-catalog-stamp-failed", message: err instanceof Error ? err.message : String(err) });
1413
+ const learnBaseline = () => {
1414
+ if (baseline !== void 0) return;
1415
+ void stamp().then((s4) => {
1416
+ if (baseline === void 0) baseline = s4;
1417
+ }, fail);
1418
+ };
1419
+ const onHidden = () => {
1420
+ if (page.document.visibilityState === "hidden") learnBaseline();
1421
+ };
1422
+ const onShown = () => {
1423
+ if (page.document.visibilityState === "hidden") return;
1424
+ const t = now();
1425
+ if (t - lastShownRead < minIntervalMs) return;
1426
+ lastShownRead = t;
1427
+ const known = baseline;
1428
+ if (known === void 0) return learnBaseline();
1429
+ void stamp().then((s4) => {
1430
+ if (s4 === known) return;
1431
+ baseline = s4;
1432
+ announceModelCatalogChanged({ catalog: "models", change: "catch_up_visible" });
1433
+ }, fail);
1434
+ };
1435
+ const offChanges = onModelCatalogChanged(() => {
1436
+ baseline = void 0;
1437
+ if (page.document.visibilityState === "hidden") learnBaseline();
1438
+ });
1439
+ const onVisibility = () => page.document.visibilityState === "hidden" ? onHidden() : onShown();
1440
+ page.document.addEventListener("visibilitychange", onVisibility);
1441
+ page.window.addEventListener("focus", onShown);
1442
+ if (page.document.visibilityState === "hidden") learnBaseline();
1443
+ return () => {
1444
+ offChanges();
1445
+ page.document.removeEventListener("visibilitychange", onVisibility);
1446
+ page.window.removeEventListener("focus", onShown);
1447
+ };
1448
+ }
1389
1449
 
1390
1450
  // models/catalog/store.ts
1391
1451
  var IDLE = { status: "idle", models: [], error: null, loadedAt: null };
@@ -2571,6 +2631,8 @@ import { sha256Hex } from "@ai-matrx/kit/sha256";
2571
2631
 
2572
2632
  // models/device-models.ts
2573
2633
  var DEVICE_SERVED_VIA = "device";
2634
+ var DEVICE_SERVED_SETTINGS_KEY = "served_via";
2635
+ var DEVICE_RULE_TWIN_FIXTURE = "apps/shared/matrx-agents/models/__tests__/device-models.test.ts";
2574
2636
  var DEVICE_FILE_USABLE_STATES = Object.freeze(["present"]);
2575
2637
  var DEVICE_OWN_FILE_USABLE_STATES = Object.freeze(["present", "unmatched"]);
2576
2638
  var s2 = (v) => typeof v === "string" ? v : "";
@@ -2980,6 +3042,8 @@ export {
2980
3042
  DEFAULT_CAPABILITIES,
2981
3043
  DEVICE_FILE_USABLE_STATES,
2982
3044
  DEVICE_OWN_FILE_USABLE_STATES,
3045
+ DEVICE_RULE_TWIN_FIXTURE,
3046
+ DEVICE_SERVED_SETTINGS_KEY,
2983
3047
  DEVICE_SERVED_VIA,
2984
3048
  DeviceModelUnavailableError,
2985
3049
  EMPTY_MODEL_RECORDS,
@@ -3027,6 +3091,7 @@ export {
3027
3091
  fitsNeed,
3028
3092
  fitsThisComputer,
3029
3093
  followModelCatalogChanges,
3094
+ followModelCatalogVisibility,
3030
3095
  formatMemoryBytes,
3031
3096
  fromDiscovery,
3032
3097
  ggufEngine,
@@ -3075,6 +3140,7 @@ export {
3075
3140
  readDeviceModelFiles,
3076
3141
  readGgufHeader,
3077
3142
  readModelCatalog,
3143
+ readModelCatalogStamp,
3078
3144
  readModelIdentities,
3079
3145
  readPublicModel,
3080
3146
  reconcileModelFavoriteIds,
@@ -675,7 +675,12 @@ declare function ModelListDropdown({ value: savedValue, localModels: localModels
675
675
  * `@ai-matrx/agents/models/react`). No surface keeps a model cache that misses the event.
676
676
  *
677
677
  * The hub lives on `globalThis` so two bundled copies of this module share one set of listeners.
678
+ *
679
+ * A tab that is not listening (the bus room not joined yet, or asleep) catches up when it comes to the
680
+ * front: `followModelCatalogVisibility({ client })` reads the catalog stamp when the tab hides and again
681
+ * when it shows or takes focus (throttled), and announces a change when the stamp moved.
678
682
  */
683
+
679
684
  /** A `catalog_changed` payload: `{ catalog?: "models" | ..., change?, model_id? }`. */
680
685
  type ModelCatalogChangePayload = Record<string, unknown>;
681
686
 
@@ -675,7 +675,12 @@ declare function ModelListDropdown({ value: savedValue, localModels: localModels
675
675
  * `@ai-matrx/agents/models/react`). No surface keeps a model cache that misses the event.
676
676
  *
677
677
  * The hub lives on `globalThis` so two bundled copies of this module share one set of listeners.
678
+ *
679
+ * A tab that is not listening (the bus room not joined yet, or asleep) catches up when it comes to the
680
+ * front: `followModelCatalogVisibility({ client })` reads the catalog stamp when the tab hides and again
681
+ * when it shows or takes focus (throttled), and announces a change when the stamp moved.
678
682
  */
683
+
679
684
  /** A `catalog_changed` payload: `{ catalog?: "models" | ..., change?, model_id? }`. */
680
685
  type ModelCatalogChangePayload = Record<string, unknown>;
681
686
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/agents",
3
- "version": "0.59.269",
3
+ "version": "0.59.271",
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",
@@ -518,23 +518,5 @@
518
518
  "optional": true
519
519
  }
520
520
  },
521
- "matrx": {
522
- "charter": "The headless agent core and the server API contract.",
523
- "owns": [
524
- "transport",
525
- "stream protocol",
526
- "mandates",
527
- "agent catalog and picker",
528
- "models",
529
- "skills",
530
- "generated server types"
531
- ],
532
- "mustNotOwn": [
533
- "chat UI",
534
- "Redux",
535
- "the agent builder"
536
- ],
537
- "layer": 5
538
- },
539
- "gitHead": "4c08cba6c4ebffa2299d1307c43d2229e29a8afb"
521
+ "gitHead": "2c48e6413332bdf7ea9f92366e3ad0e4129ddd52"
540
522
  }