@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.
@@ -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.js';
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.js';
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 {
@@ -215,7 +215,7 @@ function AgentCatalogProvider({
215
215
  import { StyleSheet as StyleSheet9, Text as Text9, View as View7 } from "react-native";
216
216
 
217
217
  // catalog/native/AgentPickerBody.tsx
218
- import { useEffect as useEffect3, useState as useState3 } from "react";
218
+ import { useEffect as useEffect4, useState as useState3 } from "react";
219
219
  import { StyleSheet as StyleSheet8, View as View6 } from "react-native";
220
220
 
221
221
  // catalog/shared/useAgentListCore-react.ts
@@ -793,6 +793,14 @@ function agentListFooterLabel(args) {
793
793
  return archivedHiddenCount > 0 ? `${base} \xB7 ${archivedHiddenCount} archived hidden` : base;
794
794
  }
795
795
 
796
+ // catalog/store.ts
797
+ import { createAssociationsStore } from "@ai-matrx/associations/core";
798
+ var LIST_TTL_MS = 15 * 60 * 1e3;
799
+ var LIST_STALE_MS = 4 * 60 * 60 * 1e3;
800
+ function agentPickerPageKey(query) {
801
+ return `${query.tab}|${query.archive}|${query.includeQuick ? "q" : "-"}`;
802
+ }
803
+
796
804
  // catalog/shared/useAgentListCore-react.ts
797
805
  function useAgentListCore({
798
806
  consumerId,
@@ -822,19 +830,60 @@ function useAgentListCore({
822
830
  const allRows = useAgentCatalogRows();
823
831
  const rows = useMemo3(() => includeQuickAgents ? allRows : withoutQuickAgents(allRows), [allRows, includeQuickAgents]);
824
832
  const userId = catalog.getUserIdOrNull();
825
- const tabCountsForFilter = selectAgentTabCounts(rows, consumer.state.archFilter);
833
+ const status = useAgentCatalogState(selectStatus);
834
+ const [setAsideLabel, setSetAsideLabel] = useState2(null);
835
+ const activeAgentFilter = agentFilter && setAsideLabel !== agentFilter.label ? agentFilter : null;
836
+ const [facetsWanted, setFacetsWanted] = useState2(false);
837
+ 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";
838
+ const fullCatalogReady = status === "succeeded" && catalog.isFresh();
839
+ const pageQuery = {
840
+ tab: includeSystemInAll && consumer.state.tab === "all" ? "all_system" : consumer.state.tab,
841
+ archive: consumer.state.archFilter,
842
+ includeQuick: includeQuickAgents
843
+ };
844
+ const pageKey = agentPickerPageKey(pageQuery);
845
+ const selectPage = useCallback3((state) => state.pickerPages[pageKey], [pageKey]);
846
+ const pageState = useAgentCatalogState(selectPage);
847
+ const pageUnsupported = pageState?.error === "unsupported";
848
+ const paged = catalog.pickerReads === "page" && !needsFullCatalog && !fullCatalogReady && !pageUnsupported;
849
+ const selectById = useCallback3((state) => state.byId, []);
850
+ const byId = useAgentCatalogState(selectById);
851
+ const pageRows = useMemo3(() => {
852
+ if (!pageState) return [];
853
+ const out = [];
854
+ for (const id of pageState.ids) {
855
+ const row = byId[id];
856
+ if (row) out.push(row);
857
+ }
858
+ return out;
859
+ }, [pageState, byId]);
860
+ const tabCountsForFilter = paged ? {
861
+ mine: pageState?.counts?.mine ?? 0,
862
+ shared: pageState?.counts?.shared ?? 0,
863
+ all: (pageState?.counts?.all ?? 0) + (includeSystemInAll ? pageState?.counts?.system ?? 0 : 0),
864
+ system: pageState?.counts?.system ?? 0
865
+ } : selectAgentTabCounts(rows, consumer.state.archFilter);
826
866
  const ownedCount = tabCountsForFilter.mine;
827
867
  const selectFiltered = useMemo3(
828
868
  () => makeSelectFilteredAgents(includeSystemInAll),
829
869
  [includeSystemInAll]
830
870
  );
831
- const filteredAgents = selectFiltered(rows, consumer.state);
871
+ const searchPool = useMemo3(() => {
872
+ if (!paged || !consumer.state.searchTerm) return null;
873
+ const pool = /* @__PURE__ */ new Map();
874
+ for (const row of pageRows) pool.set(row.id, row);
875
+ for (const id of consumer.state.serverMatchedIds) {
876
+ const row = byId[id];
877
+ if (row) pool.set(id, row);
878
+ }
879
+ const list = [...pool.values()];
880
+ return includeQuickAgents ? list : withoutQuickAgents(list);
881
+ }, [paged, consumer.state.searchTerm, consumer.state.serverMatchedIds, pageRows, byId, includeQuickAgents]);
882
+ const filteredAgents = paged ? searchPool ? selectFiltered(searchPool, consumer.state) : pageRows : selectFiltered(rows, consumer.state);
832
883
  const excludedAgentIds = useMemo3(
833
884
  () => new Set(excludeAgentIds),
834
885
  [excludeAgentIds]
835
886
  );
836
- const [setAsideLabel, setSetAsideLabel] = useState2(null);
837
- const activeAgentFilter = agentFilter && setAsideLabel !== agentFilter.label ? agentFilter : null;
838
887
  const filterTest = activeAgentFilter?.test;
839
888
  const applyExcludes = useCallback3(
840
889
  (list) => excludedAgentIds.size === 0 && !filterTest ? list : list.filter(
@@ -857,23 +906,44 @@ function useAgentListCore({
857
906
  () => ({ ...consumer.state, archFilter: "archived" }),
858
907
  [consumer.state]
859
908
  );
860
- const archivedHiddenCount = consumer.archFilter === "active" ? applyExcludes(selectArchivedProbe(rows, archiveProbeState)).length : 0;
861
- const status = useAgentCatalogState(selectStatus);
909
+ const archivedHiddenCount = consumer.archFilter !== "active" || paged && consumer.state.searchTerm ? 0 : paged ? pageState?.counts?.archivedHidden ?? 0 : applyExcludes(selectArchivedProbe(rows, archiveProbeState)).length;
862
910
  const activeAgentId = activeAgentIdOverride ?? null;
863
- const pinnedAgent = activeAgentId ? catalog.getAgent(activeAgentId) : void 0;
911
+ const pinnedAgent = activeAgentId ? byId[activeAgentId] : void 0;
864
912
  const onSystemTab = consumer.tab === "system";
865
913
  const allCategories = onSystemTab ? selectAllSystemAgentCategories(rows) : selectAllAgentCategories(rows);
866
914
  const allTags = onSystemTab ? selectAllSystemAgentTags(rows) : selectAllAgentTags(rows);
867
915
  const tabCounts = tabCountsForFilter;
868
- const isLoading = status === "loading";
869
- const agentsLoaded = status === "succeeded" || status === "failed";
916
+ const [engaged, setEngaged] = useState2(false);
917
+ const pageStatus = pageState?.status ?? "idle";
918
+ const isLoading = paged ? (pageStatus === "loading" || pageStatus === "idle") && pageRows.length === 0 && engaged : status === "loading";
919
+ const agentsLoaded = paged ? pageStatus === "succeeded" || pageStatus === "failed" : status === "succeeded" || status === "failed";
870
920
  const ensureLoaded = useCallback3(() => {
871
921
  if (!hasFetchedRef.current) {
872
922
  hasFetchedRef.current = true;
873
- void catalog.ensureLoaded().catch(() => {
874
- });
923
+ setEngaged(true);
875
924
  }
876
- }, [catalog]);
925
+ }, []);
926
+ const pageQueryRef = useRef2(pageQuery);
927
+ pageQueryRef.current = pageQuery;
928
+ useEffect2(() => {
929
+ if (!engaged) return;
930
+ if (paged) void catalog.ensurePickerPage(pageQueryRef.current).catch(() => void 0);
931
+ else void catalog.ensureLoaded().catch(() => void 0);
932
+ }, [engaged, paged, pageKey, catalog]);
933
+ const loadMore = useCallback3(() => {
934
+ if (!paged) return;
935
+ void catalog.loadMorePickerPage(pageQueryRef.current).catch(() => void 0);
936
+ }, [paged, catalog]);
937
+ const ensureFullCatalog = useCallback3(() => {
938
+ setFacetsWanted(true);
939
+ }, []);
940
+ const hasMore = paged && pageStatus === "succeeded" && pageState?.done === false;
941
+ const loadingMore = paged && pageState?.loadingMore === true;
942
+ const pinnedMissing = Boolean(activeAgentId) && pinnedAgent === void 0;
943
+ useEffect2(() => {
944
+ if (!engaged || !pinnedMissing || !activeAgentId) return;
945
+ void catalog.ensureAgentRows([activeAgentId]).catch(() => void 0);
946
+ }, [engaged, pinnedMissing, activeAgentId, catalog]);
877
947
  const selectDefaultRow = useCallback3(
878
948
  (state) => defaultMandateKey ? state.defaultRows[defaultMandateKey] : void 0,
879
949
  [defaultMandateKey]
@@ -950,13 +1020,14 @@ function useAgentListCore({
950
1020
  const handleAgentHover = useCallback3(
951
1021
  (agent, panelOpen) => {
952
1022
  if (panelOpen) return;
1023
+ if (agent.isThin) void catalog.ensureAgentRows([agent.id]).catch(() => void 0);
953
1024
  if (hoverLeaveTimerRef.current) {
954
1025
  clearTimeout(hoverLeaveTimerRef.current);
955
1026
  hoverLeaveTimerRef.current = null;
956
1027
  }
957
1028
  setHoveredAgent(agent);
958
1029
  },
959
- []
1030
+ [catalog]
960
1031
  );
961
1032
  const handleAgentHoverEnd = useCallback3(
962
1033
  (agent, onClear) => {
@@ -987,8 +1058,10 @@ function useAgentListCore({
987
1058
  },
988
1059
  []
989
1060
  );
1061
+ const tabTotal = consumer.state.tab === "mine" ? tabCountsForFilter.mine : consumer.state.tab === "shared" ? tabCountsForFilter.shared : consumer.state.tab === "system" ? tabCountsForFilter.system : tabCountsForFilter.all;
1062
+ const totalCount = paged && !consumer.state.searchTerm && excludedAgentIds.size === 0 ? Math.max(tabTotal, agents.length) : agents.length;
990
1063
  const footerLabel = agentListFooterLabel({
991
- count: agents.length,
1064
+ count: totalCount,
992
1065
  archFilter: consumer.archFilter,
993
1066
  archivedHiddenCount
994
1067
  });
@@ -1012,6 +1085,14 @@ function useAgentListCore({
1012
1085
  hoveredAgent,
1013
1086
  setHoveredAgent,
1014
1087
  ensureLoaded,
1088
+ /** FW-014: the list reads a page at a time; true while the slice has more. */
1089
+ paged,
1090
+ hasMore,
1091
+ loadingMore,
1092
+ loadMore,
1093
+ /** The tab's total (server count when paged) — the footer's number. */
1094
+ totalCount,
1095
+ ensureFullCatalog,
1015
1096
  handleSelectAgent,
1016
1097
  handleAgentHover,
1017
1098
  handleAgentHoverEnd,
@@ -1031,6 +1112,7 @@ var EMPTY_EXCLUDES = [];
1031
1112
  var selectStatus = (state) => state.status;
1032
1113
 
1033
1114
  // catalog/native/AgentDetailCard.tsx
1115
+ import { useEffect as useEffect3, useSyncExternalStore as useSyncExternalStore2 } from "react";
1034
1116
  import { Pressable as Pressable3, ScrollView, StyleSheet, Text as Text3, View } from "react-native";
1035
1117
 
1036
1118
  // catalog/orchestra.ts
@@ -1358,8 +1440,14 @@ function formatDate(value) {
1358
1440
  year: "numeric"
1359
1441
  });
1360
1442
  }
1361
- function AgentDetailCard({ agent, onSelect }) {
1362
- const { errorSink } = useAgentCatalog();
1443
+ function AgentDetailCard({ agent: given, onSelect }) {
1444
+ const catalog = useAgentCatalog();
1445
+ const { errorSink } = catalog;
1446
+ const held = useSyncExternalStore2(catalog.subscribe, () => catalog.getState().byId[given.id], () => catalog.getState().byId[given.id]);
1447
+ const agent = held && !held.isThin ? held : given;
1448
+ useEffect3(() => {
1449
+ if (given.isThin && !(held && !held.isThin)) void catalog.ensureAgentRows([given.id]).catch(() => void 0);
1450
+ }, [catalog, given.id, given.isThin, held]);
1363
1451
  const { renderModelRef, openPeek } = useAgentCatalogUiPorts();
1364
1452
  const theme = useAgentCatalogNativeTheme();
1365
1453
  const isDefaultRow = isMandateAgentId(agent.id);
@@ -2407,7 +2495,9 @@ function AgentListContent({
2407
2495
  pinnedAgent = null,
2408
2496
  defaultRow = null,
2409
2497
  autoFocusSearch = false,
2410
- archivedHiddenCount = 0
2498
+ archivedHiddenCount = 0,
2499
+ onReachEnd,
2500
+ totalCount
2411
2501
  }) {
2412
2502
  const catalog = useAgentCatalog();
2413
2503
  const theme = useAgentCatalogNativeTheme();
@@ -2592,6 +2682,7 @@ function AgentListContent({
2592
2682
  data: items,
2593
2683
  keyExtractor: (item) => item.key,
2594
2684
  keyboardShouldPersistTaps: "handled",
2685
+ ...onReachEnd ? { onEndReached: onReachEnd, onEndReachedThreshold: 0.5 } : {},
2595
2686
  renderItem: ({ item }) => {
2596
2687
  if (item.kind === "divider") return /* @__PURE__ */ jsx10(View4, { style: hairline });
2597
2688
  if (item.kind === "label") {
@@ -2645,7 +2736,7 @@ function AgentListContent({
2645
2736
  fontVariant: ["tabular-nums"]
2646
2737
  },
2647
2738
  children: agentListFooterLabel({
2648
- count: agents.length,
2739
+ count: totalCount ?? agents.length,
2649
2740
  archFilter: consumer.archFilter,
2650
2741
  archivedHiddenCount
2651
2742
  })
@@ -2825,6 +2916,9 @@ function AgentPickerBody({
2825
2916
  pinnedAgent,
2826
2917
  defaultRow,
2827
2918
  ensureLoaded,
2919
+ loadMore,
2920
+ totalCount,
2921
+ ensureFullCatalog,
2828
2922
  handleSelectAgent: coreSelectAgent
2829
2923
  } = useAgentListCore({
2830
2924
  consumerId,
@@ -2837,7 +2931,7 @@ function AgentPickerBody({
2837
2931
  ...excludeAgentIds ? { excludeAgentIds } : {},
2838
2932
  ...defaultMandateKey ? { defaultMandateKey } : {}
2839
2933
  });
2840
- useEffect3(() => {
2934
+ useEffect4(() => {
2841
2935
  ensureLoaded();
2842
2936
  }, [ensureLoaded]);
2843
2937
  const selectAgent = (agent) => {
@@ -2883,11 +2977,16 @@ function AgentPickerBody({
2883
2977
  allTags,
2884
2978
  onSelectAgent: selectAgent,
2885
2979
  onDetailPress: setDetailAgent,
2886
- onFilterChipPress: setSubView,
2980
+ onFilterChipPress: (panel) => {
2981
+ if (panel !== "sort") ensureFullCatalog();
2982
+ setSubView(panel);
2983
+ },
2887
2984
  onReset: consumer.resetFilters,
2888
2985
  activeFilterCount,
2889
2986
  tabCounts,
2890
2987
  archivedHiddenCount,
2988
+ onReachEnd: loadMore,
2989
+ totalCount,
2891
2990
  ...visibleTabs ? { visibleTabs } : {},
2892
2991
  ...systemTabLabel ?? (includeSystemInAll ? "System" : void 0) ? {
2893
2992
  systemTabLabel: systemTabLabel ?? (includeSystemInAll ? "System" : void 0)