@ai-matrx/agents 0.9.0 → 0.9.2

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,205 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.2 — 2026-09-09
4
+
5
+ **THE ARCHIVED-ITEMS LAW, APPLIED TO THE PICKER — the first instance of a
6
+ platform-wide class.**
7
+
8
+ Arman, 2026-09-09, asked whether the Public tab should hide archived agents
9
+ like its other tabs (`common-docs/policies/archived-items.md`):
10
+
11
+ > "Archived agents should be treated like all other archived items everywhere
12
+ > within our codebase. … why does agents have to be any different than
13
+ > workflows or employees or HR documents or anything else that we have?
14
+ > They're not any different. They are the same, and they should be treated the
15
+ > same. … everything should have an archive filter, and the default should
16
+ > always hide archived, but seeing archived items should be one or two clicks
17
+ > away. But don't go and do this for agents and forget that this is a system
18
+ > wide decision for every single item everywhere in our system."
19
+
20
+ ### Consumer action: none
21
+
22
+ No export was removed, renamed or re-signed. Additive only:
23
+ `ARCHIVE_FILTER_OPTIONS`, `ARCHIVE_FILTER_ORDER`, `nextArchFilter`,
24
+ `isAgentArchFilter`, `AgentArchFilterOption`, `AgentCatalogDefaults`,
25
+ `agentArchiveFilterChipLabel` / `…StateLabel` / `…ControlLabel`,
26
+ `agentListFooterLabel`, `AgentCatalog.consumerDefaults`,
27
+ `useAgentListCore({ initialArchFilter })` and its `archivedHiddenCount` /
28
+ `footerLabel` returns, plus `data-testid="agent-archive-filter"` /
29
+ `"agent-list-footer"` (and their native `testID` twins). Every host adopts this
30
+ by taking `latest`.
31
+
32
+ ### Behavioural deltas (all deliberate, the first was the defect)
33
+
34
+ **THE BUILTIN ARCHIVE EXEMPTION IS DELETED.** `filterBuiltinTypeAgents` applied
35
+ no archive filter, on the theory that "a builtin belongs to the platform and is
36
+ never the user's to archive". The database disagreed: `Foundry Planner`
37
+ (`001b22f6-fa4d-40c6-a1a4-8575d4d04379`) is an archived builtin, and it
38
+ rendered at Public index 216, sat inside the "413 agents" badge and footer, and
39
+ was pickable — while Mine / Shared / All hid their archived rows. One picker,
40
+ two rules. `selectAgentTabCounts(...).system` and
41
+ `selectTotalBuiltinAgentsCount` now count under the same rule the tab renders
42
+ under, so badge == footer == rows holds on Public too.
43
+
44
+ **THE ARCHIVE FILTER IS NOW A VISIBLE CONTROL, on web and native.** One chip on
45
+ the filter bar with the platform's three states — *hide archived* (default) →
46
+ *show all* → *archived only* → back — one click from opening the picker, on
47
+ every tab, and present even for a consumer whose `visibleTabs` removes the tab
48
+ strip. Its accessible name always says the state it is in and the state one
49
+ click moves it to. It counts toward `activeFilterCount`, so a list showing
50
+ archived rows offers the reset control that puts them away.
51
+
52
+ **THE FOOTER IS HONEST ABOUT WHAT IT IS HIDING.** `agentListFooterLabel` prints
53
+ `"412 agents · 4 archived hidden"`, `"416 agents · archived shown"` or
54
+ `"3 archived shown"` — never a bare count that implies the list is everything.
55
+
56
+ **THE DEFAULT IS A KNOB** (root law 6; law clause 6):
57
+ `createAgentCatalog({ defaults: { archiveFilter } })` catalog-wide, platform
58
+ default `"active"`, with a per-consumer override
59
+ (`useAgentListCore({ initialArchFilter })`). An illegal value throws
60
+ `AgentCatalogConfigError` rather than being silently ignored. `resetFilters`
61
+ returns to the KNOB's value, not to the hardcoded platform one.
62
+
63
+ ### Proof, and how it can fail
64
+
65
+ `catalog/__tests__/archive-law.test.ts` asserts, per archive state × per tab,
66
+ that the rendered rows are exactly what the law says, that the badge and the
67
+ footer are that same number, that the default hides archived rows on EVERY tab
68
+ (Public included), that one action reveals them, and that the knob works and
69
+ screams. The expectation is restated from the policy text and imports nothing
70
+ from `catalog/selectors.ts`. Both shells (`catalog/react/archive-law.test.tsx`,
71
+ `catalog/native/__tests__/archive-law.test.tsx`) measure the RENDERED screen.
72
+
73
+ Falsifiability: reinstating the deleted exemption in `filterBuiltinTypeAgents`
74
+ was verified to turn 6 of the 11 kernel tests red before shipping, and the old
75
+ rule is written out in the suite so its answers are asserted to differ.
76
+ `total-order-fixture.json` now carries an archived builtin, generated from the
77
+ rule by `scripts/generate-total-order-fixture.mjs`.
78
+ `parity-expected.json` is left byte-identical — it is a RECORDING of
79
+ matrx-frontend, whose exemption is the defect — and the law is applied to that
80
+ recording in exactly one place, with a test proving the transform changes 30
81
+ cases and drops only builtin ids.
82
+
83
+ ## 0.9.1 — 2026-09-08
84
+
85
+ **TWO SCREENS TOLD THE SAME USER TWO DIFFERENT TRUTHS. BOTH ARE FIXED HERE, IN
86
+ THE PACKAGE, AT THE CLASS.**
87
+
88
+ Independent final verification of the picker unification (2026-09-08, same
89
+ account signed in on both surfaces) found the tab strip, the counts, the
90
+ Mine/Shared/All order, the five sort options, the tri-state favourites control,
91
+ the 42 categories, the 871 tags and four tag-only searches identical between
92
+ `aimatrx.com/chat/new` and `workflows.aimatrx.com`. Two things were not.
93
+
94
+ ### Consumer action: none
95
+
96
+ No export was removed, renamed or re-signed. Three symbols were ADDED —
97
+ `compareAgentIds`, `agentMatchesArchiveFilter`, `selectAgentTabCounts` — and the
98
+ web `AgentRow` gained a `data-testid` matching the native row's `testID`.
99
+ Every host adopts this by taking `latest`; there is nothing to change.
100
+
101
+ ### Behavioural deltas (both deliberate, both were defects)
102
+
103
+ **F1 — every sort is now a TOTAL order, ending on `id` ASC.**
104
+
105
+ The Public tab diverged at index 10. 17 rows tied on
106
+ `updated_at = 2026-09-08T06:52:46.430326+00:00`, and the two hosts fetch the
107
+ same RPC through different transports:
108
+
109
+ | host | request | order of tied rows |
110
+ |---|---|---|
111
+ | matrx-frontend | `POST /rpc/agx_get_list_full` (one shot) | the RPC's own |
112
+ | workflow-studio | `…?order=id.asc&offset=0&limit=1000` (`readAllRows`) | id ASC |
113
+
114
+ Neither client was wrong about the ROWS. The sort was not total: each
115
+ comparator branch returned `0` for tied rows, so `Array.prototype.sort` left
116
+ them wherever the transport had put them. `sortFilteredAgents` and
117
+ `applyAgentSortComparator` now end on the same final key the database itself
118
+ ends on — `public.agx_get_list` is
119
+ `ORDER BY is_favorite DESC, updated_at DESC, id` — giving:
120
+
121
+ searching: score DESC -> server rank ASC -> sort key -> id ASC
122
+ not searching: favourites first (when asked) -> sort key -> id ASC
123
+
124
+ Comparison is a codepoint `<`/`>`, never `localeCompare`: Postgres orders
125
+ `uuid` by its raw bytes and a collator is free to fold case or ignore the
126
+ dashes, which would diverge from the server on exactly these ties. A sort key
127
+ that yields `NaN` (an unparseable timestamp) now collapses to the id key
128
+ instead of defeating it.
129
+
130
+ *What the frontend used to do on ties: whatever order the RPC page came back
131
+ in.* That was never a decision — it was the absence of one, and it happened to
132
+ look stable only until a second host fetched the same rows a different way. The
133
+ order is now DEFINED, is the server's own, and is a function of the rows alone.
134
+
135
+ **F5 — a tab badge counts exactly what the tab renders.**
136
+
137
+ The Mine badge said 416 while the list rendered 412 rows and the footer said
138
+ "412 agents": 4 owned and 2 shared ARCHIVED agents were counted by the badge
139
+ and (correctly) hidden by the list. One screen, three numbers, one of them
140
+ untrue about agents the user cannot see or reach from there. This is "a screen
141
+ never lies", so it is a fix and not a knob.
142
+
143
+ `selectAgentTabCounts(rows, archFilter)` is the new consumer-aware badge
144
+ source used by `useAgentListCore`, and the four plain aggregate counts
145
+ (`selectTotalUserAgentsCount`, `…Owned…`, `…Shared…`, `…Favorite…`) now honour
146
+ the same archive rule the list uses. Flip the archive filter and the badges
147
+ follow the list into the archive. `selectTotalBuiltinAgentsCount` is
148
+ deliberately unchanged: `filterBuiltinTypeAgents` applies no archive filter
149
+ (a builtin belongs to the platform and is never the user's to archive), so it
150
+ already counted exactly what it rendered.
151
+
152
+ ### The proof
153
+
154
+ `catalog/__tests__/parity.test.ts`'s 66-row fixture had NO ties, which is why
155
+ 293 green cases never saw F1. The new fixture does:
156
+ `catalog/__tests__/total-order-fixture.json` — 42 rows, every one on that exact
157
+ live `updated_at`, 26 of them also tied on `created_at`, with name and category
158
+ tie groups on top, ids deliberately uncorrelated with emission order.
159
+
160
+ `total-order-expected.json` is generated **from the RULE** by
161
+ `scripts/generate-total-order-fixture.mjs` — favourites -> key -> id ASC,
162
+ written out independently and importing nothing from `catalog/selectors.ts`.
163
+ Regenerating it by running the shipped comparator would have made the test a
164
+ mirror of the code; it measures the specification instead.
165
+
166
+ 720 cases (tab x sort x favouritesFirst x archive x favourite x access) are
167
+ asserted three times over — kernel (`catalog/__tests__/total-order.test.ts`),
168
+ web shell (`catalog/react/total-order.test.tsx`) and native shell
169
+ (`catalog/native/__tests__/total-order.test.tsx`) — each reading the RENDERED
170
+ row ids, not the selector's return value.
171
+
172
+ Falsifiability, on every leg:
173
+
174
+ - the same rows are fed in four different input orders (identity, two seeded
175
+ shuffles, reversed) and the answer must be byte-identical — this is the live
176
+ defect, reproduced as a test;
177
+ - both shells mount the picker twice with the row array reversed and compare
178
+ screen to screen;
179
+ - deleting the id tiebreaker turns 4 of the 10 kernel tests red (verified
180
+ before shipping), and a key-only sort of the same fixture is asserted to
181
+ disagree with itself under reversal;
182
+ - the F5 legs assert the OLD counting rule produces a DIFFERENT number on this
183
+ fixture, so "badge == rows" cannot pass vacuously.
184
+
185
+ `parity.test.ts`'s count assertion now states the F5 delta explicitly — the
186
+ recorded matrx-frontend number minus the archived rows the list hides — rather
187
+ than reproducing a screen that lies. Its 293-case ordering matrix and the
188
+ native shell's 293-case matrix are UNCHANGED and still green: the old fixture
189
+ had no ties, so F1 moved nothing that was already defined.
190
+
3
191
  ## 0.9.0 — 2026-09-08
4
192
 
5
193
  **THE ONE PICKER NOW RENDERS ON REACT NATIVE — `@ai-matrx/agents/catalog/native`.**
6
194
 
7
- Matrx Mobile (Expo 54 / RN 0.81) was the last client still hand-rolling its
195
+ > 🚨 **Correction, 2026-09-09 (Arman): this entry has NO CONSUMER.** The `matrx-mobile` repo
196
+ > this release was built against is retired junk and is not a consumer of anything; there is no
197
+ > current mobile package, and the new one has not been started. `./catalog/native` remains
198
+ > published and gate-verified, but nothing ships on it. See common-docs
199
+ > `projects/npm-package-extraction/DECISIONS.md` C32 / `systems/mandates/DECISIONS.md` D24. The
200
+ > release notes below are preserved as written; the repo work they describe is void.
201
+
202
+ The retired `matrx-mobile` app (Expo 54 / RN 0.81) was the last thing still hand-rolling its
8
203
  agent list. Its sheet showed four HARDCODED agents plus a Supabase read of a
9
204
  `prompts` table that **does not exist in the platform database** — a read whose
10
205
  PostgREST error was swallowed into an empty array, so the list silently never
@@ -25,6 +25,8 @@ __export(catalog_exports, {
25
25
  AGENT_LIST_ITEMS_PER_PAGE: () => AGENT_LIST_ITEMS_PER_PAGE,
26
26
  AGENT_NONE_SENTINEL: () => AGENT_NONE_SENTINEL,
27
27
  AGENT_SEARCH_LIMIT: () => AGENT_SEARCH_LIMIT,
28
+ ARCHIVE_FILTER_OPTIONS: () => ARCHIVE_FILTER_OPTIONS,
29
+ ARCHIVE_FILTER_ORDER: () => ARCHIVE_FILTER_ORDER,
28
30
  AgentCatalogConfigError: () => AgentCatalogConfigError,
29
31
  AgentCatalogReadError: () => AgentCatalogReadError,
30
32
  DEFAULT_AGENT_CATALOG_LABELS: () => DEFAULT_AGENT_CATALOG_LABELS,
@@ -32,12 +34,18 @@ __export(catalog_exports, {
32
34
  MandateDefaultRowError: () => MandateDefaultRowError,
33
35
  SORT_OPTIONS: () => SORT_OPTIONS,
34
36
  _resetCatalogGlobalState: () => _resetCatalogGlobalState,
37
+ agentArchiveFilterChipLabel: () => agentArchiveFilterChipLabel,
38
+ agentArchiveFilterControlLabel: () => agentArchiveFilterControlLabel,
39
+ agentArchiveFilterStateLabel: () => agentArchiveFilterStateLabel,
35
40
  agentConsumerHasActiveFilters: () => agentConsumerHasActiveFilters,
36
41
  agentListEmptyLabel: () => agentListEmptyLabel,
42
+ agentListFooterLabel: () => agentListFooterLabel,
43
+ agentMatchesArchiveFilter: () => agentMatchesArchiveFilter,
37
44
  agentMatchesSearch: () => agentMatchesSearch,
38
45
  applyAgentSortComparator: () => applyAgentSortComparator,
39
46
  asAgentId: () => asAgentId,
40
47
  assertAgentCatalogSchema: () => assertAgentCatalogSchema,
48
+ compareAgentIds: () => compareAgentIds,
41
49
  computeAgentSearchScore: () => computeAgentSearchScore,
42
50
  createAgentCatalog: () => createAgentCatalog,
43
51
  createMemoryStorage: () => createMemoryStorage,
@@ -47,6 +55,7 @@ __export(catalog_exports, {
47
55
  filterBuiltinTypeAgents: () => filterBuiltinTypeAgents,
48
56
  filterUserTypeAgents: () => filterUserTypeAgents,
49
57
  getRegisteredAgentCatalog: () => getRegisteredAgentCatalog,
58
+ isAgentArchFilter: () => isAgentArchFilter,
50
59
  isMandateAgentId: () => isMandateAgentId,
51
60
  makeSelectFilteredAgents: () => makeSelectFilteredAgents,
52
61
  makeSelectFilteredAgentsCount: () => makeSelectFilteredAgentsCount,
@@ -62,6 +71,7 @@ __export(catalog_exports, {
62
71
  memoize1: () => memoize1,
63
72
  memoize2: () => memoize2,
64
73
  memoize3: () => memoize3,
74
+ nextArchFilter: () => nextArchFilter,
65
75
  orchestraDelegatesLine: () => orchestraDelegatesLine,
66
76
  orchestraDepthLine: () => orchestraDepthLine,
67
77
  orchestraLabel: () => orchestraLabel,
@@ -73,6 +83,7 @@ __export(catalog_exports, {
73
83
  readSingleAgentRow: () => readSingleAgentRow,
74
84
  resolveDefaultRow: () => resolveDefaultRow,
75
85
  searchAgentsOnServer: () => searchAgentsOnServer,
86
+ selectAgentTabCounts: () => selectAgentTabCounts,
76
87
  selectAllAgentCategories: () => selectAllAgentCategories,
77
88
  selectAllAgentTags: () => selectAllAgentTags,
78
89
  selectAllSystemAgentCategories: () => selectAllSystemAgentCategories,
@@ -110,6 +121,24 @@ var SORT_OPTIONS = [
110
121
  { value: "name-desc", label: "Z \u2192 A" },
111
122
  { value: "category-asc", label: "Category" }
112
123
  ];
124
+ var ARCHIVE_FILTER_ORDER = [
125
+ "active",
126
+ "both",
127
+ "archived"
128
+ ];
129
+ var ARCHIVE_FILTER_OPTIONS = [
130
+ { value: "active", label: "Hide archived", chip: "Archive" },
131
+ { value: "both", label: "Show all", chip: "All + archived" },
132
+ { value: "archived", label: "Archived only", chip: "Archived only" }
133
+ ];
134
+ function nextArchFilter(current) {
135
+ const index = ARCHIVE_FILTER_ORDER.indexOf(current);
136
+ if (index < 0) return ARCHIVE_FILTER_ORDER[0];
137
+ return ARCHIVE_FILTER_ORDER[(index + 1) % ARCHIVE_FILTER_ORDER.length];
138
+ }
139
+ function isAgentArchFilter(value) {
140
+ return value === "active" || value === "archived" || value === "both";
141
+ }
113
142
  var AGENT_NONE_SENTINEL = "__none__";
114
143
  var DEFAULT_AGENT_CONSUMER_STATE = {
115
144
  tab: "mine",
@@ -314,8 +343,8 @@ async function readSingleAgentRow(client, agentId) {
314
343
  }
315
344
  return null;
316
345
  }
317
- async function writeAgentFavorite(client, agentId, isFavorite) {
318
- const { error } = await client.schema("agent").from("definition").update({ is_favorite: isFavorite }).eq("id", agentId);
346
+ async function writeAgentFavorite(client, agentId, isFavorite2) {
347
+ const { error } = await client.schema("agent").from("definition").update({ is_favorite: isFavorite2 }).eq("id", agentId);
319
348
  if (error) {
320
349
  throw new AgentCatalogReadError(
321
350
  "agent.definition.is_favorite",
@@ -732,6 +761,18 @@ function createAgentCatalog(config) {
732
761
  remedy: "pass `identity: { requireUserId() }` \u2014 never a silent anonymous read"
733
762
  });
734
763
  }
764
+ const archiveDefault = config.defaults?.archiveFilter;
765
+ if (archiveDefault !== void 0 && !isAgentArchFilter(archiveDefault)) {
766
+ throw new AgentCatalogConfigError({
767
+ code: "invalid_archive_default",
768
+ message: `defaults.archiveFilter was ${JSON.stringify(archiveDefault)}, which is not one of the three states of the platform archive filter`,
769
+ remedy: 'pass "active" (hide archived), "both" (show all) or "archived" (archived only)'
770
+ });
771
+ }
772
+ const consumerDefaults = {
773
+ ...DEFAULT_AGENT_CONSUMER_STATE,
774
+ ...archiveDefault ? { archFilter: archiveDefault } : {}
775
+ };
735
776
  const catalogId = config.catalogId ?? "default";
736
777
  const client = config.client;
737
778
  const identity = config.identity;
@@ -832,7 +873,7 @@ function createAgentCatalog(config) {
832
873
  return run;
833
874
  }
834
875
  function getConsumer(consumerId) {
835
- return state.consumers[consumerId] ?? DEFAULT_AGENT_CONSUMER_STATE;
876
+ return state.consumers[consumerId] ?? consumerDefaults;
836
877
  }
837
878
  function writeConsumer(consumerId, next) {
838
879
  publish({
@@ -843,7 +884,7 @@ function createAgentCatalog(config) {
843
884
  function registerConsumer(consumerId, initial) {
844
885
  if (state.consumers[consumerId]) return;
845
886
  writeConsumer(consumerId, {
846
- ...DEFAULT_AGENT_CONSUMER_STATE,
887
+ ...consumerDefaults,
847
888
  ...initial ?? {}
848
889
  });
849
890
  }
@@ -854,9 +895,7 @@ function createAgentCatalog(config) {
854
895
  publish({ ...state, consumers });
855
896
  }
856
897
  function setConsumerFilter(consumerId, patch) {
857
- const current = state.consumers[consumerId] ?? {
858
- ...DEFAULT_AGENT_CONSUMER_STATE
859
- };
898
+ const current = state.consumers[consumerId] ?? { ...consumerDefaults };
860
899
  writeConsumer(consumerId, {
861
900
  ...current,
862
901
  ...patch,
@@ -873,9 +912,7 @@ function createAgentCatalog(config) {
873
912
  );
874
913
  }
875
914
  function setConsumerServerSearch(consumerId, args) {
876
- const current = state.consumers[consumerId] ?? {
877
- ...DEFAULT_AGENT_CONSUMER_STATE
878
- };
915
+ const current = state.consumers[consumerId] ?? { ...consumerDefaults };
879
916
  const next = { ...current };
880
917
  if (args.matchedIds !== void 0) next.serverMatchedIds = args.matchedIds;
881
918
  if (args.isSearching !== void 0) next.isServerSearching = args.isSearching;
@@ -886,14 +923,14 @@ function createAgentCatalog(config) {
886
923
  }
887
924
  function resetConsumerFilters(consumerId) {
888
925
  if (!state.consumers[consumerId]) return;
889
- writeConsumer(consumerId, { ...DEFAULT_AGENT_CONSUMER_STATE });
926
+ writeConsumer(consumerId, { ...consumerDefaults });
890
927
  }
891
928
  async function searchServer(query, deep = false) {
892
929
  const result = await searchAgentsOnServer(client, { query, deep });
893
930
  mergeRows(result.rows);
894
931
  return result.ids;
895
932
  }
896
- async function setFavorite(agentId, isFavorite) {
933
+ async function setFavorite(agentId, isFavorite2) {
897
934
  const existing = state.byId[agentId];
898
935
  if (!existing) {
899
936
  errorSink({
@@ -902,15 +939,15 @@ function createAgentCatalog(config) {
902
939
  });
903
940
  return;
904
941
  }
905
- mergeRows([{ ...existing, isFavorite }]);
942
+ mergeRows([{ ...existing, isFavorite: isFavorite2 }]);
906
943
  try {
907
- await writeAgentFavorite(client, agentId, isFavorite);
944
+ await writeAgentFavorite(client, agentId, isFavorite2);
908
945
  } catch (e) {
909
946
  mergeRows([existing]);
910
947
  errorSink({
911
948
  code: "favorite_write_failed",
912
949
  message: `the favorite could not be saved: ${errorMessage(e)}`,
913
- context: { agentId, isFavorite }
950
+ context: { agentId, isFavorite: isFavorite2 }
914
951
  });
915
952
  notifier?.({
916
953
  level: "error",
@@ -1016,6 +1053,7 @@ function createAgentCatalog(config) {
1016
1053
  errorSink,
1017
1054
  notifier,
1018
1055
  canResolveMandates: transport !== void 0,
1056
+ consumerDefaults,
1019
1057
  getState: () => state,
1020
1058
  subscribe: (listener) => {
1021
1059
  listeners.add(listener);
@@ -1093,6 +1131,26 @@ function shouldDefaultAgentListToPublicTab(args) {
1093
1131
  if (!args.agentsLoaded) return false;
1094
1132
  return args.ownedCount === 0;
1095
1133
  }
1134
+ function agentArchiveFilterChipLabel(archFilter) {
1135
+ return ARCHIVE_FILTER_OPTIONS.find((o) => o.value === archFilter)?.chip ?? "Archive";
1136
+ }
1137
+ function agentArchiveFilterStateLabel(archFilter) {
1138
+ return ARCHIVE_FILTER_OPTIONS.find((o) => o.value === archFilter)?.label ?? "Hide archived";
1139
+ }
1140
+ function agentArchiveFilterControlLabel(archFilter) {
1141
+ return `Archive filter: ${agentArchiveFilterStateLabel(
1142
+ archFilter
1143
+ ).toLowerCase()}. Click for ${agentArchiveFilterStateLabel(
1144
+ nextArchFilter(archFilter)
1145
+ ).toLowerCase()}`;
1146
+ }
1147
+ function agentListFooterLabel(args) {
1148
+ const { count, archFilter, archivedHiddenCount } = args;
1149
+ if (archFilter === "archived") return `${count} archived shown`;
1150
+ const base = `${count} agent${count !== 1 ? "s" : ""}`;
1151
+ if (archFilter === "both") return `${base} \xB7 archived shown`;
1152
+ return archivedHiddenCount > 0 ? `${base} \xB7 ${archivedHiddenCount} archived hidden` : base;
1153
+ }
1096
1154
 
1097
1155
  // catalog/score.ts
1098
1156
  function computeAgentSearchScore(agent, query) {
@@ -1189,7 +1247,19 @@ function memoize3(fn) {
1189
1247
  return last;
1190
1248
  };
1191
1249
  }
1250
+ function compareAgentIds(a, b) {
1251
+ const idA = a.id ?? "";
1252
+ const idB = b.id ?? "";
1253
+ if (idA < idB) return -1;
1254
+ if (idA > idB) return 1;
1255
+ return 0;
1256
+ }
1192
1257
  function applyAgentSortComparator(a, b, sortBy) {
1258
+ const primary = compareAgentSortKey(a, b, sortBy);
1259
+ if (!Number.isFinite(primary) || primary === 0) return compareAgentIds(a, b);
1260
+ return primary;
1261
+ }
1262
+ function compareAgentSortKey(a, b, sortBy) {
1193
1263
  switch (sortBy) {
1194
1264
  case "name-asc":
1195
1265
  return (a.name ?? "").localeCompare(b.name ?? "");
@@ -1291,8 +1361,7 @@ function filterUserTypeAgents(agents, consumer) {
1291
1361
  if (tab === "mine" && agent.isOwner !== true) return false;
1292
1362
  if (tab === "shared" && !(agent.isOwner === false && agent.accessLevel != null))
1293
1363
  return false;
1294
- if (archFilter === "active" && agent.isArchived) return false;
1295
- if (archFilter === "archived" && !agent.isArchived) return false;
1364
+ if (!agentMatchesArchiveFilter(agent, archFilter)) return false;
1296
1365
  if (favFilter === "yes" && !agent.isFavorite) return false;
1297
1366
  if (favFilter === "no" && agent.isFavorite) return false;
1298
1367
  if (accessFilter === "owned" && agent.isOwner !== true) return false;
@@ -1322,9 +1391,10 @@ function filterUserTypeAgents(agents, consumer) {
1322
1391
  return sortFilteredAgents(filtered, consumer);
1323
1392
  }
1324
1393
  function filterBuiltinTypeAgents(agents, consumer) {
1325
- const { searchTerm, favFilter, includedCats, includedTags } = consumer;
1394
+ const { searchTerm, favFilter, archFilter, includedCats, includedTags } = consumer;
1326
1395
  const serverMatched = new Set(consumer.serverMatchedIds);
1327
1396
  const filtered = agents.filter((agent) => {
1397
+ if (!agentMatchesArchiveFilter(agent, archFilter)) return false;
1328
1398
  if (favFilter === "yes" && !agent.isFavorite) return false;
1329
1399
  if (favFilter === "no" && agent.isFavorite) return false;
1330
1400
  if (includedCats.length > 0) {
@@ -1437,21 +1507,54 @@ var makeSelectFilteredBuiltinAgentsCount = () => {
1437
1507
  function agentConsumerHasActiveFilters(consumer, defaults) {
1438
1508
  return consumer.tab !== defaults.tab || consumer.sortBy !== defaults.sortBy || consumer.searchTerm !== "" || consumer.includedCats.length > 0 || consumer.includedTags.length > 0 || consumer.favFilter !== defaults.favFilter || consumer.archFilter !== defaults.archFilter || consumer.accessFilter !== defaults.accessFilter || consumer.favoritesFirst !== defaults.favoritesFirst;
1439
1509
  }
1510
+ function agentMatchesArchiveFilter(agent, archFilter) {
1511
+ if (archFilter === "active") return !agent.isArchived;
1512
+ if (archFilter === "archived") return Boolean(agent.isArchived);
1513
+ return true;
1514
+ }
1515
+ function countBuiltinRows(agents, archFilter) {
1516
+ let n = 0;
1517
+ for (const a of agents) {
1518
+ if (a.agentType !== "builtin") continue;
1519
+ if (!agentMatchesArchiveFilter(a, archFilter)) continue;
1520
+ n += 1;
1521
+ }
1522
+ return n;
1523
+ }
1524
+ function countUserRows(agents, archFilter, predicate) {
1525
+ let n = 0;
1526
+ for (const a of agents) {
1527
+ if (a.agentType !== "user") continue;
1528
+ if (!agentMatchesArchiveFilter(a, archFilter)) continue;
1529
+ if (predicate(a)) n += 1;
1530
+ }
1531
+ return n;
1532
+ }
1533
+ var isOwned = (a) => a.isOwner === true;
1534
+ var isShared = (a) => a.isOwner === false && a.accessLevel != null;
1535
+ var isAny = () => true;
1536
+ var isFavorite = (a) => Boolean(a.isFavorite);
1537
+ var selectAgentTabCounts = memoize2(
1538
+ (agents, archFilter) => ({
1539
+ mine: countUserRows(agents, archFilter, isOwned),
1540
+ shared: countUserRows(agents, archFilter, isShared),
1541
+ all: countUserRows(agents, archFilter, isAny),
1542
+ system: countBuiltinRows(agents, archFilter)
1543
+ })
1544
+ );
1440
1545
  var selectTotalUserAgentsCount = memoize1(
1441
- (agents) => selectUserTypeAgents(agents).length
1546
+ (agents) => countUserRows(agents, "active", isAny)
1442
1547
  );
1443
1548
  var selectTotalOwnedAgentsCount = memoize1(
1444
- (agents) => selectUserTypeAgents(agents).filter((a) => a.isOwner === true).length
1549
+ (agents) => countUserRows(agents, "active", isOwned)
1445
1550
  );
1446
1551
  var selectTotalSharedAgentsCount = memoize1(
1447
- (agents) => selectUserTypeAgents(agents).filter(
1448
- (a) => a.isOwner === false && a.accessLevel != null
1449
- ).length
1552
+ (agents) => countUserRows(agents, "active", isShared)
1450
1553
  );
1451
1554
  var selectTotalBuiltinAgentsCount = memoize1(
1452
- (agents) => selectBuiltinTypeAgents(agents).length
1555
+ (agents) => countBuiltinRows(agents, "active")
1453
1556
  );
1454
1557
  var selectTotalFavoriteAgentsCount = memoize1(
1455
- (agents) => selectUserTypeAgents(agents).filter((a) => a.isFavorite).length
1558
+ (agents) => countUserRows(agents, "active", isFavorite)
1456
1559
  );
1457
1560
  //# sourceMappingURL=index.cjs.map