@ai-matrx/agents 0.59.263 → 0.59.265

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.
@@ -263,7 +263,7 @@ function AgentCatalogProvider({
263
263
  var import_react_native12 = require("react-native");
264
264
 
265
265
  // catalog/native/AgentPickerBody.tsx
266
- var import_react6 = require("react");
266
+ var import_react7 = require("react");
267
267
  var import_react_native11 = require("react-native");
268
268
 
269
269
  // catalog/shared/useAgentListCore-react.ts
@@ -841,6 +841,14 @@ function agentListFooterLabel(args) {
841
841
  return archivedHiddenCount > 0 ? `${base} \xB7 ${archivedHiddenCount} archived hidden` : base;
842
842
  }
843
843
 
844
+ // catalog/store.ts
845
+ var import_core = require("@ai-matrx/associations/core");
846
+ var LIST_TTL_MS = 15 * 60 * 1e3;
847
+ var LIST_STALE_MS = 4 * 60 * 60 * 1e3;
848
+ function agentPickerPageKey(query) {
849
+ return `${query.tab}|${query.archive}|${query.includeQuick ? "q" : "-"}`;
850
+ }
851
+
844
852
  // catalog/shared/useAgentListCore-react.ts
845
853
  function useAgentListCore({
846
854
  consumerId,
@@ -870,19 +878,60 @@ function useAgentListCore({
870
878
  const allRows = useAgentCatalogRows();
871
879
  const rows = (0, import_react4.useMemo)(() => includeQuickAgents ? allRows : withoutQuickAgents(allRows), [allRows, includeQuickAgents]);
872
880
  const userId = catalog.getUserIdOrNull();
873
- const tabCountsForFilter = selectAgentTabCounts(rows, consumer.state.archFilter);
881
+ const status = useAgentCatalogState(selectStatus);
882
+ const [setAsideLabel, setSetAsideLabel] = (0, import_react4.useState)(null);
883
+ const activeAgentFilter = agentFilter && setAsideLabel !== agentFilter.label ? agentFilter : null;
884
+ const [facetsWanted, setFacetsWanted] = (0, import_react4.useState)(false);
885
+ const needsFullCatalog = facetsWanted || activeAgentFilter !== null || consumer.state.includedCats.length > 0 || consumer.state.includedTags.length > 0 || consumer.state.favFilter !== "all" || consumer.state.accessFilter !== "any" || !consumer.state.favoritesFirst || consumer.state.sortBy !== "updated-desc";
886
+ const fullCatalogReady = status === "succeeded" && catalog.isFresh();
887
+ const pageQuery = {
888
+ tab: includeSystemInAll && consumer.state.tab === "all" ? "all_system" : consumer.state.tab,
889
+ archive: consumer.state.archFilter,
890
+ includeQuick: includeQuickAgents
891
+ };
892
+ const pageKey = agentPickerPageKey(pageQuery);
893
+ const selectPage = (0, import_react4.useCallback)((state) => state.pickerPages[pageKey], [pageKey]);
894
+ const pageState = useAgentCatalogState(selectPage);
895
+ const pageUnsupported = pageState?.error === "unsupported";
896
+ const paged = catalog.pickerReads === "page" && !needsFullCatalog && !fullCatalogReady && !pageUnsupported;
897
+ const selectById = (0, import_react4.useCallback)((state) => state.byId, []);
898
+ const byId = useAgentCatalogState(selectById);
899
+ const pageRows = (0, import_react4.useMemo)(() => {
900
+ if (!pageState) return [];
901
+ const out = [];
902
+ for (const id of pageState.ids) {
903
+ const row = byId[id];
904
+ if (row) out.push(row);
905
+ }
906
+ return out;
907
+ }, [pageState, byId]);
908
+ const tabCountsForFilter = paged ? {
909
+ mine: pageState?.counts?.mine ?? 0,
910
+ shared: pageState?.counts?.shared ?? 0,
911
+ all: (pageState?.counts?.all ?? 0) + (includeSystemInAll ? pageState?.counts?.system ?? 0 : 0),
912
+ system: pageState?.counts?.system ?? 0
913
+ } : selectAgentTabCounts(rows, consumer.state.archFilter);
874
914
  const ownedCount = tabCountsForFilter.mine;
875
915
  const selectFiltered = (0, import_react4.useMemo)(
876
916
  () => makeSelectFilteredAgents(includeSystemInAll),
877
917
  [includeSystemInAll]
878
918
  );
879
- const filteredAgents = selectFiltered(rows, consumer.state);
919
+ const searchPool = (0, import_react4.useMemo)(() => {
920
+ if (!paged || !consumer.state.searchTerm) return null;
921
+ const pool = /* @__PURE__ */ new Map();
922
+ for (const row of pageRows) pool.set(row.id, row);
923
+ for (const id of consumer.state.serverMatchedIds) {
924
+ const row = byId[id];
925
+ if (row) pool.set(id, row);
926
+ }
927
+ const list = [...pool.values()];
928
+ return includeQuickAgents ? list : withoutQuickAgents(list);
929
+ }, [paged, consumer.state.searchTerm, consumer.state.serverMatchedIds, pageRows, byId, includeQuickAgents]);
930
+ const filteredAgents = paged ? searchPool ? selectFiltered(searchPool, consumer.state) : pageRows : selectFiltered(rows, consumer.state);
880
931
  const excludedAgentIds = (0, import_react4.useMemo)(
881
932
  () => new Set(excludeAgentIds),
882
933
  [excludeAgentIds]
883
934
  );
884
- const [setAsideLabel, setSetAsideLabel] = (0, import_react4.useState)(null);
885
- const activeAgentFilter = agentFilter && setAsideLabel !== agentFilter.label ? agentFilter : null;
886
935
  const filterTest = activeAgentFilter?.test;
887
936
  const applyExcludes = (0, import_react4.useCallback)(
888
937
  (list) => excludedAgentIds.size === 0 && !filterTest ? list : list.filter(
@@ -905,23 +954,44 @@ function useAgentListCore({
905
954
  () => ({ ...consumer.state, archFilter: "archived" }),
906
955
  [consumer.state]
907
956
  );
908
- const archivedHiddenCount = consumer.archFilter === "active" ? applyExcludes(selectArchivedProbe(rows, archiveProbeState)).length : 0;
909
- const status = useAgentCatalogState(selectStatus);
957
+ const archivedHiddenCount = consumer.archFilter !== "active" || paged && consumer.state.searchTerm ? 0 : paged ? pageState?.counts?.archivedHidden ?? 0 : applyExcludes(selectArchivedProbe(rows, archiveProbeState)).length;
910
958
  const activeAgentId = activeAgentIdOverride ?? null;
911
- const pinnedAgent = activeAgentId ? catalog.getAgent(activeAgentId) : void 0;
959
+ const pinnedAgent = activeAgentId ? byId[activeAgentId] : void 0;
912
960
  const onSystemTab = consumer.tab === "system";
913
961
  const allCategories = onSystemTab ? selectAllSystemAgentCategories(rows) : selectAllAgentCategories(rows);
914
962
  const allTags = onSystemTab ? selectAllSystemAgentTags(rows) : selectAllAgentTags(rows);
915
963
  const tabCounts = tabCountsForFilter;
916
- const isLoading = status === "loading";
917
- const agentsLoaded = status === "succeeded" || status === "failed";
964
+ const [engaged, setEngaged] = (0, import_react4.useState)(false);
965
+ const pageStatus = pageState?.status ?? "idle";
966
+ const isLoading = paged ? (pageStatus === "loading" || pageStatus === "idle") && pageRows.length === 0 && engaged : status === "loading";
967
+ const agentsLoaded = paged ? pageStatus === "succeeded" || pageStatus === "failed" : status === "succeeded" || status === "failed";
918
968
  const ensureLoaded = (0, import_react4.useCallback)(() => {
919
969
  if (!hasFetchedRef.current) {
920
970
  hasFetchedRef.current = true;
921
- void catalog.ensureLoaded().catch(() => {
922
- });
971
+ setEngaged(true);
923
972
  }
924
- }, [catalog]);
973
+ }, []);
974
+ const pageQueryRef = (0, import_react4.useRef)(pageQuery);
975
+ pageQueryRef.current = pageQuery;
976
+ (0, import_react4.useEffect)(() => {
977
+ if (!engaged) return;
978
+ if (paged) void catalog.ensurePickerPage(pageQueryRef.current).catch(() => void 0);
979
+ else void catalog.ensureLoaded().catch(() => void 0);
980
+ }, [engaged, paged, pageKey, catalog]);
981
+ const loadMore = (0, import_react4.useCallback)(() => {
982
+ if (!paged) return;
983
+ void catalog.loadMorePickerPage(pageQueryRef.current).catch(() => void 0);
984
+ }, [paged, catalog]);
985
+ const ensureFullCatalog = (0, import_react4.useCallback)(() => {
986
+ setFacetsWanted(true);
987
+ }, []);
988
+ const hasMore = paged && pageStatus === "succeeded" && pageState?.done === false;
989
+ const loadingMore = paged && pageState?.loadingMore === true;
990
+ const pinnedMissing = Boolean(activeAgentId) && pinnedAgent === void 0;
991
+ (0, import_react4.useEffect)(() => {
992
+ if (!engaged || !pinnedMissing || !activeAgentId) return;
993
+ void catalog.ensureAgentRows([activeAgentId]).catch(() => void 0);
994
+ }, [engaged, pinnedMissing, activeAgentId, catalog]);
925
995
  const selectDefaultRow = (0, import_react4.useCallback)(
926
996
  (state) => defaultMandateKey ? state.defaultRows[defaultMandateKey] : void 0,
927
997
  [defaultMandateKey]
@@ -998,13 +1068,14 @@ function useAgentListCore({
998
1068
  const handleAgentHover = (0, import_react4.useCallback)(
999
1069
  (agent, panelOpen) => {
1000
1070
  if (panelOpen) return;
1071
+ if (agent.isThin) void catalog.ensureAgentRows([agent.id]).catch(() => void 0);
1001
1072
  if (hoverLeaveTimerRef.current) {
1002
1073
  clearTimeout(hoverLeaveTimerRef.current);
1003
1074
  hoverLeaveTimerRef.current = null;
1004
1075
  }
1005
1076
  setHoveredAgent(agent);
1006
1077
  },
1007
- []
1078
+ [catalog]
1008
1079
  );
1009
1080
  const handleAgentHoverEnd = (0, import_react4.useCallback)(
1010
1081
  (agent, onClear) => {
@@ -1035,8 +1106,10 @@ function useAgentListCore({
1035
1106
  },
1036
1107
  []
1037
1108
  );
1109
+ const tabTotal = consumer.state.tab === "mine" ? tabCountsForFilter.mine : consumer.state.tab === "shared" ? tabCountsForFilter.shared : consumer.state.tab === "system" ? tabCountsForFilter.system : tabCountsForFilter.all;
1110
+ const totalCount = paged && !consumer.state.searchTerm && excludedAgentIds.size === 0 ? Math.max(tabTotal, agents.length) : agents.length;
1038
1111
  const footerLabel = agentListFooterLabel({
1039
- count: agents.length,
1112
+ count: totalCount,
1040
1113
  archFilter: consumer.archFilter,
1041
1114
  archivedHiddenCount
1042
1115
  });
@@ -1060,6 +1133,14 @@ function useAgentListCore({
1060
1133
  hoveredAgent,
1061
1134
  setHoveredAgent,
1062
1135
  ensureLoaded,
1136
+ /** FW-014: the list reads a page at a time; true while the slice has more. */
1137
+ paged,
1138
+ hasMore,
1139
+ loadingMore,
1140
+ loadMore,
1141
+ /** The tab's total (server count when paged) — the footer's number. */
1142
+ totalCount,
1143
+ ensureFullCatalog,
1063
1144
  handleSelectAgent,
1064
1145
  handleAgentHover,
1065
1146
  handleAgentHoverEnd,
@@ -1079,6 +1160,7 @@ var EMPTY_EXCLUDES = [];
1079
1160
  var selectStatus = (state) => state.status;
1080
1161
 
1081
1162
  // catalog/native/AgentDetailCard.tsx
1163
+ var import_react6 = require("react");
1082
1164
  var import_react_native4 = require("react-native");
1083
1165
 
1084
1166
  // catalog/orchestra.ts
@@ -1406,8 +1488,14 @@ function formatDate(value) {
1406
1488
  year: "numeric"
1407
1489
  });
1408
1490
  }
1409
- function AgentDetailCard({ agent, onSelect }) {
1410
- const { errorSink } = useAgentCatalog();
1491
+ function AgentDetailCard({ agent: given, onSelect }) {
1492
+ const catalog = useAgentCatalog();
1493
+ const { errorSink } = catalog;
1494
+ const held = (0, import_react6.useSyncExternalStore)(catalog.subscribe, () => catalog.getState().byId[given.id], () => catalog.getState().byId[given.id]);
1495
+ const agent = held && !held.isThin ? held : given;
1496
+ (0, import_react6.useEffect)(() => {
1497
+ if (given.isThin && !(held && !held.isThin)) void catalog.ensureAgentRows([given.id]).catch(() => void 0);
1498
+ }, [catalog, given.id, given.isThin, held]);
1411
1499
  const { renderModelRef, openPeek } = useAgentCatalogUiPorts();
1412
1500
  const theme = useAgentCatalogNativeTheme();
1413
1501
  const isDefaultRow = isMandateAgentId(agent.id);
@@ -2455,7 +2543,9 @@ function AgentListContent({
2455
2543
  pinnedAgent = null,
2456
2544
  defaultRow = null,
2457
2545
  autoFocusSearch = false,
2458
- archivedHiddenCount = 0
2546
+ archivedHiddenCount = 0,
2547
+ onReachEnd,
2548
+ totalCount
2459
2549
  }) {
2460
2550
  const catalog = useAgentCatalog();
2461
2551
  const theme = useAgentCatalogNativeTheme();
@@ -2640,6 +2730,7 @@ function AgentListContent({
2640
2730
  data: items,
2641
2731
  keyExtractor: (item) => item.key,
2642
2732
  keyboardShouldPersistTaps: "handled",
2733
+ ...onReachEnd ? { onEndReached: onReachEnd, onEndReachedThreshold: 0.5 } : {},
2643
2734
  renderItem: ({ item }) => {
2644
2735
  if (item.kind === "divider") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.View, { style: hairline });
2645
2736
  if (item.kind === "label") {
@@ -2693,7 +2784,7 @@ function AgentListContent({
2693
2784
  fontVariant: ["tabular-nums"]
2694
2785
  },
2695
2786
  children: agentListFooterLabel({
2696
- count: agents.length,
2787
+ count: totalCount ?? agents.length,
2697
2788
  archFilter: consumer.archFilter,
2698
2789
  archivedHiddenCount
2699
2790
  })
@@ -2858,9 +2949,9 @@ function AgentPickerBody({
2858
2949
  compact = false,
2859
2950
  onAfterSelect
2860
2951
  }) {
2861
- const [detailAgent, setDetailAgent] = (0, import_react6.useState)(null);
2862
- const [subView, setSubView] = (0, import_react6.useState)(null);
2863
- const [panelSearch, setPanelSearch] = (0, import_react6.useState)("");
2952
+ const [detailAgent, setDetailAgent] = (0, import_react7.useState)(null);
2953
+ const [subView, setSubView] = (0, import_react7.useState)(null);
2954
+ const [panelSearch, setPanelSearch] = (0, import_react7.useState)("");
2864
2955
  const {
2865
2956
  agents,
2866
2957
  archivedHiddenCount,
@@ -2873,6 +2964,9 @@ function AgentPickerBody({
2873
2964
  pinnedAgent,
2874
2965
  defaultRow,
2875
2966
  ensureLoaded,
2967
+ loadMore,
2968
+ totalCount,
2969
+ ensureFullCatalog,
2876
2970
  handleSelectAgent: coreSelectAgent
2877
2971
  } = useAgentListCore({
2878
2972
  consumerId,
@@ -2885,7 +2979,7 @@ function AgentPickerBody({
2885
2979
  ...excludeAgentIds ? { excludeAgentIds } : {},
2886
2980
  ...defaultMandateKey ? { defaultMandateKey } : {}
2887
2981
  });
2888
- (0, import_react6.useEffect)(() => {
2982
+ (0, import_react7.useEffect)(() => {
2889
2983
  ensureLoaded();
2890
2984
  }, [ensureLoaded]);
2891
2985
  const selectAgent = (agent) => {
@@ -2931,11 +3025,16 @@ function AgentPickerBody({
2931
3025
  allTags,
2932
3026
  onSelectAgent: selectAgent,
2933
3027
  onDetailPress: setDetailAgent,
2934
- onFilterChipPress: setSubView,
3028
+ onFilterChipPress: (panel) => {
3029
+ if (panel !== "sort") ensureFullCatalog();
3030
+ setSubView(panel);
3031
+ },
2935
3032
  onReset: consumer.resetFilters,
2936
3033
  activeFilterCount,
2937
3034
  tabCounts,
2938
3035
  archivedHiddenCount,
3036
+ onReachEnd: loadMore,
3037
+ totalCount,
2939
3038
  ...visibleTabs ? { visibleTabs } : {},
2940
3039
  ...systemTabLabel ?? (includeSystemInAll ? "System" : void 0) ? {
2941
3040
  systemTabLabel: systemTabLabel ?? (includeSystemInAll ? "System" : void 0)
@@ -1,45 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentType, ReactNode } from 'react';
3
- import { A as AgentSummary, a as AgentConsumerState, b as AgentConsumerPatch, c as AgentTab, d as AgentSortOption, e as AgentFavFilter, f as AgentArchFilter, g as AgentAccessFilter, O as OrchestraBadge } from '../../types-DZhDqrVE.cjs';
4
-
5
- /**
6
- * catalog/default-row-types.ts — @ai-matrx/agents/catalog
7
- *
8
- * Split out of `default-row.ts` so `global-state.ts` can name the cached
9
- * shape without importing the resolver (and its transport work) — the same
10
- * split-out reflex C8 asks for, at module scale.
11
- */
12
-
13
- /** A resolved default row, cached for first paint and compared for drift. */
14
- interface ResolvedDefaultRow {
15
- mandateKey: string;
16
- /** The `agent.definition` id the mandate resolved to. */
17
- holderId: string;
18
- /** The holder's REAL name, read from the catalog — never hardcoded. */
19
- holderName: string;
20
- holderDescription: string | null;
21
- provenance: string;
22
- /** Epoch ms of this resolution. */
23
- at: number;
24
- }
25
- /** What the picker renders and what it screams, together. */
26
- interface DefaultRowState {
27
- mandateKey: string;
28
- /** The row to render at the top of the list. `null` until resolved. */
29
- row: AgentSummary | null;
30
- /** The last live resolution, or the cached one before the live answer lands. */
31
- resolved: ResolvedDefaultRow | null;
32
- /** True while the resolution is in flight. */
33
- loading: boolean;
34
- /**
35
- * 🚨 THE DRIFT SCREAM. Set when the cached first-paint row and the LIVE
36
- * resolution disagree on holder id or holder name. Rendered as a persistent
37
- * in-picker banner AND sent to the `notifier` port — never console-only.
38
- */
39
- drift: string | null;
40
- /** Set when the mandate could not be resolved at all. Loud, with a remedy. */
41
- error: string | null;
42
- }
3
+ import { A as AgentSummary, a as AgentConsumerState, b as AgentConsumerPatch, c as AgentTab, d as AgentSortOption, e as AgentFavFilter, f as AgentArchFilter, g as AgentAccessFilter, O as OrchestraBadge } from '../../types-5pX8AZBM.cjs';
43
4
 
44
5
  /**
45
6
  * `@ai-matrx/agents/catalog` — THE PORTS.
@@ -116,6 +77,101 @@ type AgentCatalogNavigate = (href: string) => void;
116
77
  */
117
78
  type AgentCatalogOpenPeek = (agent: AgentSummary) => void;
118
79
 
80
+ /**
81
+ * catalog/data.ts — @ai-matrx/agents/catalog
82
+ *
83
+ * Every database call the catalog makes, and nothing else. Five of them:
84
+ *
85
+ * 1. `agx_get_list_full()` — the complete catalog (owned + directly-shared
86
+ * + org-shared user agents, unioned with active builtins).
87
+ * 2. `agx_search(p_query, p_deep, p_limit, p_offset)` — tier-2 server search.
88
+ * 3. `agx_resolve_agent_address(p_ids)` — the single-agent name read, used
89
+ * when a mandate's Holder is not in the caller's own catalog.
90
+ * 4. The caller's stars arrive ON the rows: every read above computes
91
+ * `is_favorite` server-side from `platform.user_entity_state`
92
+ * (`platform.my_favorite`). There is no second star read (FW-014; the
93
+ * `ues_get_bulk` overlay over every id was retired 2026-10-23).
94
+ * 5. ONE write: the caller's star, `ues_set` through the same port. The
95
+ * retired `agent.definition.is_favorite` column is never read or written.
96
+ * 6. THE PINNED READ (2026-10-09; by-ids 2026-10-23): the caller's starred
97
+ * ids (`ues_list` through the favorites port), then
98
+ * `agent.agx_get_by_ids(p_ids)` for exactly those ids. A sidebar's Pinned
99
+ * section shows a handful of rows; it never pays for the whole catalog,
100
+ * in the browser or on the server, to find them.
101
+ * 7. THE PICKER PAGE (FW-014, 2026-10-23): `agent.agx_picker_page` — the
102
+ * menu opens with ~50 thin rows (favorites, recently used, name) and the
103
+ * tab counts; scrolling reads the next keyset page. `agent.agx_get_by_ids`
104
+ * fills in a full row on hover/open. The whole-catalog read above remains
105
+ * for surfaces that genuinely list everything, and for a picker whose
106
+ * person narrows by category or tag (the facets need every row).
107
+ *
108
+ * 🚨 THE LIST READ IS COMPLETE OR IT THROWS. PostgREST caps every response at
109
+ * `db-max-rows` (1000 on Matrx Main) and reports success — matrx-frontend's
110
+ * `fetchAgentsListFull` issued a bare `.rpc()` and would silently have
111
+ * returned 1000 rows of a larger catalog (the ~400-builtin truncation
112
+ * incident is the near-miss). Here the read goes through `readAllRows` with
113
+ * `{ count: "exact" }` and a stable `.order("id")`, so a truncation is a
114
+ * loud `IncompleteReadError` instead of a confidently short picker.
115
+ * The DB's own favourites-first/`updated_at` order is irrelevant: every row
116
+ * is re-sorted client-side by `sortFilteredAgents`.
117
+ */
118
+
119
+ /** Which slice of the caller's catalog a picker page reads. */
120
+ interface AgentPickerPageQuery {
121
+ /** `all_system` = the admin "All" tab that blends system agents in. */
122
+ tab: "mine" | "shared" | "all" | "all_system" | "system";
123
+ archive: "active" | "archived" | "both";
124
+ includeQuick: boolean;
125
+ }
126
+ /** The tab badges, counted server-side over the same reach as the rows. */
127
+ interface AgentPickerCounts {
128
+ mine: number;
129
+ shared: number;
130
+ all: number;
131
+ system: number;
132
+ /** Archived rows the active filter hides behind THIS tab (0 unless archive = active). */
133
+ archivedHidden: number;
134
+ }
135
+
136
+ /**
137
+ * catalog/default-row-types.ts — @ai-matrx/agents/catalog
138
+ *
139
+ * Split out of `default-row.ts` so `global-state.ts` can name the cached
140
+ * shape without importing the resolver (and its transport work) — the same
141
+ * split-out reflex C8 asks for, at module scale.
142
+ */
143
+
144
+ /** A resolved default row, cached for first paint and compared for drift. */
145
+ interface ResolvedDefaultRow {
146
+ mandateKey: string;
147
+ /** The `agent.definition` id the mandate resolved to. */
148
+ holderId: string;
149
+ /** The holder's REAL name, read from the catalog — never hardcoded. */
150
+ holderName: string;
151
+ holderDescription: string | null;
152
+ provenance: string;
153
+ /** Epoch ms of this resolution. */
154
+ at: number;
155
+ }
156
+ /** What the picker renders and what it screams, together. */
157
+ interface DefaultRowState {
158
+ mandateKey: string;
159
+ /** The row to render at the top of the list. `null` until resolved. */
160
+ row: AgentSummary | null;
161
+ /** The last live resolution, or the cached one before the live answer lands. */
162
+ resolved: ResolvedDefaultRow | null;
163
+ /** True while the resolution is in flight. */
164
+ loading: boolean;
165
+ /**
166
+ * 🚨 THE DRIFT SCREAM. Set when the cached first-paint row and the LIVE
167
+ * resolution disagree on holder id or holder name. Rendered as a persistent
168
+ * in-picker banner AND sent to the `notifier` port — never console-only.
169
+ */
170
+ drift: string | null;
171
+ /** Set when the mandate could not be resolved at all. Loud, with a remedy. */
172
+ error: string | null;
173
+ }
174
+
119
175
  /**
120
176
  * catalog/schema.ts — @ai-matrx/agents/catalog
121
177
  *
@@ -184,6 +240,20 @@ interface AssertAgentCatalogSchemaOptions {
184
240
  */
185
241
 
186
242
  type AgentCatalogStatus = "idle" | "loading" | "succeeded" | "failed";
243
+ /** One picker slice (tab × archive × quick) read a page at a time (FW-014). */
244
+ interface AgentPickerPageState {
245
+ /** Row ids in server order across every page read so far. */
246
+ ids: string[];
247
+ /** Keyset position of the next page; `null` once the last page is in. */
248
+ nextCursor: unknown;
249
+ done: boolean;
250
+ status: AgentCatalogStatus;
251
+ /** True while a next page (not the first) is in flight. */
252
+ loadingMore: boolean;
253
+ error: string | null;
254
+ counts: AgentPickerCounts | null;
255
+ fetchedAt: number;
256
+ }
187
257
  interface AgentCatalogState {
188
258
  /** Every row, one stable array reference per change (memoization depends on it). */
189
259
  rows: AgentSummary[];
@@ -199,6 +269,8 @@ interface AgentCatalogState {
199
269
  * or by a complete catalog load, whichever came first.
200
270
  */
201
271
  favoriteRowsStatus: AgentCatalogStatus;
272
+ /** The picker's paged slices, keyed by `agentPickerPageKey` (FW-014). */
273
+ pickerPages: Record<string, AgentPickerPageState>;
202
274
  }
203
275
  interface AgentCatalog {
204
276
  readonly catalogId: string;
@@ -207,6 +279,8 @@ interface AgentCatalog {
207
279
  readonly notifier: AgentCatalogNotifier | undefined;
208
280
  /** True when this catalog was given a transport and can resolve mandates. */
209
281
  readonly canResolveMandates: boolean;
282
+ /** `AgentCatalogConfig.pickerReads`, resolved (default `"page"`). */
283
+ readonly pickerReads: "page" | "catalog";
210
284
  /**
211
285
  * The consumer state a brand-new consumer starts from, AFTER the host's
212
286
  * `defaults` knob is applied. Read it instead of
@@ -245,6 +319,28 @@ interface AgentCatalog {
245
319
  * makes no request. Rows that do not parse are skipped.
246
320
  */
247
321
  seedFavoriteRows(rawRows: unknown[]): void;
322
+ /**
323
+ * THE PICKER'S OPEN (FW-014): the first page (~50 thin rows) of one slice,
324
+ * plus its tab counts — never the whole catalog. Fresh for the list's TTL;
325
+ * deduped in flight; safe on every open.
326
+ */
327
+ ensurePickerPage(query: AgentPickerPageQuery): Promise<void>;
328
+ /** The next page of a slice (scroll). No-op when the slice is done or already loading. */
329
+ loadMorePickerPage(query: AgentPickerPageQuery): Promise<void>;
330
+ /**
331
+ * FULL rows for these ids (`agent.agx_get_by_ids`), merged into the registry:
332
+ * a hover card's details, the pinned current agent, a name for an id that is
333
+ * not on page 1. Ids already held as full rows are not asked again; deduped
334
+ * in flight.
335
+ */
336
+ ensureAgentRows(agentIds: readonly string[]): Promise<void>;
337
+ /**
338
+ * The caller's live agents carrying any of these tags, merged into the
339
+ * registry (`agent.agx_picker_tagged`) — the composer's quick agents and
340
+ * presets, never the catalog. Fresh for the list's TTL; deduped in flight;
341
+ * answered at once when the whole catalog is already fresh.
342
+ */
343
+ ensureTaggedRows(tags: readonly string[]): Promise<void>;
248
344
  /** True when the last load is inside the 15-minute TTL. */
249
345
  isFresh(): boolean;
250
346
  /** True when the last load is older than 4 hours (tab-restore threshold). */
@@ -587,6 +683,14 @@ declare function useAgentListCore({ consumerId, onSelect, navigateTo, activeAgen
587
683
  hoveredAgent: AgentSummary | null;
588
684
  setHoveredAgent: react.Dispatch<react.SetStateAction<AgentSummary | null>>;
589
685
  ensureLoaded: () => void;
686
+ /** FW-014: the list reads a page at a time; true while the slice has more. */
687
+ paged: boolean;
688
+ hasMore: boolean;
689
+ loadingMore: boolean;
690
+ loadMore: () => void;
691
+ /** The tab's total (server count when paged) — the footer's number. */
692
+ totalCount: number;
693
+ ensureFullCatalog: () => void;
590
694
  handleSelectAgent: (agent: AgentSummary) => void;
591
695
  handleAgentHover: (agent: AgentSummary, panelOpen: boolean) => void;
592
696
  handleAgentHoverEnd: (agent: AgentSummary, onClear: () => void) => void;
@@ -732,8 +836,12 @@ interface AgentListContentProps {
732
836
  * `useAgentListCore`. The footer names them, exactly as on the web.
733
837
  */
734
838
  archivedHiddenCount?: number | undefined;
839
+ /** FW-014 paging: asked for the next page when the list nears its end. */
840
+ onReachEnd?: (() => void) | undefined;
841
+ /** The tab's total for the footer (server count when paged). */
842
+ totalCount?: number | undefined;
735
843
  }
736
- declare function AgentListContent({ agents, isLoading, consumer, activeAgentId, allCategories, allTags, onSelectAgent, onDetailPress, onFilterChipPress, onReset, activeFilterCount, tabCounts, visibleTabs, systemTabLabel, pinnedAgent, defaultRow, autoFocusSearch, archivedHiddenCount, }: AgentListContentProps): react.JSX.Element;
844
+ declare function AgentListContent({ agents, isLoading, consumer, activeAgentId, allCategories, allTags, onSelectAgent, onDetailPress, onFilterChipPress, onReset, activeFilterCount, tabCounts, visibleTabs, systemTabLabel, pinnedAgent, defaultRow, autoFocusSearch, archivedHiddenCount, onReachEnd, totalCount, }: AgentListContentProps): react.JSX.Element;
737
845
 
738
846
  interface AgentListTabsProps {
739
847
  consumer: UseAgentConsumerReturn;
@@ -766,7 +874,7 @@ interface AgentDetailCardProps {
766
874
  agent: AgentSummary;
767
875
  onSelect: () => void;
768
876
  }
769
- declare function AgentDetailCard({ agent, onSelect }: AgentDetailCardProps): react.JSX.Element;
877
+ declare function AgentDetailCard({ agent: given, onSelect }: AgentDetailCardProps): react.JSX.Element;
770
878
 
771
879
  type AgentSubViewName = "sort" | "categories" | "tags";
772
880
  interface AgentSubViewProps {