@ai-matrx/agents 0.9.1 → 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,5 +1,85 @@
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
+
3
83
  ## 0.9.1 — 2026-09-08
4
84
 
5
85
  **TWO SCREENS TOLD THE SAME USER TWO DIFFERENT TRUTHS. BOTH ARE FIXED HERE, IN
@@ -112,7 +192,14 @@ had no ties, so F1 moved nothing that was already defined.
112
192
 
113
193
  **THE ONE PICKER NOW RENDERS ON REACT NATIVE — `@ai-matrx/agents/catalog/native`.**
114
194
 
115
- 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
116
203
  agent list. Its sheet showed four HARDCODED agents plus a Supabase read of a
117
204
  `prompts` table that **does not exist in the platform database** — a read whose
118
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,8 +34,12 @@ __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,
37
43
  agentMatchesArchiveFilter: () => agentMatchesArchiveFilter,
38
44
  agentMatchesSearch: () => agentMatchesSearch,
39
45
  applyAgentSortComparator: () => applyAgentSortComparator,
@@ -49,6 +55,7 @@ __export(catalog_exports, {
49
55
  filterBuiltinTypeAgents: () => filterBuiltinTypeAgents,
50
56
  filterUserTypeAgents: () => filterUserTypeAgents,
51
57
  getRegisteredAgentCatalog: () => getRegisteredAgentCatalog,
58
+ isAgentArchFilter: () => isAgentArchFilter,
52
59
  isMandateAgentId: () => isMandateAgentId,
53
60
  makeSelectFilteredAgents: () => makeSelectFilteredAgents,
54
61
  makeSelectFilteredAgentsCount: () => makeSelectFilteredAgentsCount,
@@ -64,6 +71,7 @@ __export(catalog_exports, {
64
71
  memoize1: () => memoize1,
65
72
  memoize2: () => memoize2,
66
73
  memoize3: () => memoize3,
74
+ nextArchFilter: () => nextArchFilter,
67
75
  orchestraDelegatesLine: () => orchestraDelegatesLine,
68
76
  orchestraDepthLine: () => orchestraDepthLine,
69
77
  orchestraLabel: () => orchestraLabel,
@@ -113,6 +121,24 @@ var SORT_OPTIONS = [
113
121
  { value: "name-desc", label: "Z \u2192 A" },
114
122
  { value: "category-asc", label: "Category" }
115
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
+ }
116
142
  var AGENT_NONE_SENTINEL = "__none__";
117
143
  var DEFAULT_AGENT_CONSUMER_STATE = {
118
144
  tab: "mine",
@@ -735,6 +761,18 @@ function createAgentCatalog(config) {
735
761
  remedy: "pass `identity: { requireUserId() }` \u2014 never a silent anonymous read"
736
762
  });
737
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
+ };
738
776
  const catalogId = config.catalogId ?? "default";
739
777
  const client = config.client;
740
778
  const identity = config.identity;
@@ -835,7 +873,7 @@ function createAgentCatalog(config) {
835
873
  return run;
836
874
  }
837
875
  function getConsumer(consumerId) {
838
- return state.consumers[consumerId] ?? DEFAULT_AGENT_CONSUMER_STATE;
876
+ return state.consumers[consumerId] ?? consumerDefaults;
839
877
  }
840
878
  function writeConsumer(consumerId, next) {
841
879
  publish({
@@ -846,7 +884,7 @@ function createAgentCatalog(config) {
846
884
  function registerConsumer(consumerId, initial) {
847
885
  if (state.consumers[consumerId]) return;
848
886
  writeConsumer(consumerId, {
849
- ...DEFAULT_AGENT_CONSUMER_STATE,
887
+ ...consumerDefaults,
850
888
  ...initial ?? {}
851
889
  });
852
890
  }
@@ -857,9 +895,7 @@ function createAgentCatalog(config) {
857
895
  publish({ ...state, consumers });
858
896
  }
859
897
  function setConsumerFilter(consumerId, patch) {
860
- const current = state.consumers[consumerId] ?? {
861
- ...DEFAULT_AGENT_CONSUMER_STATE
862
- };
898
+ const current = state.consumers[consumerId] ?? { ...consumerDefaults };
863
899
  writeConsumer(consumerId, {
864
900
  ...current,
865
901
  ...patch,
@@ -876,9 +912,7 @@ function createAgentCatalog(config) {
876
912
  );
877
913
  }
878
914
  function setConsumerServerSearch(consumerId, args) {
879
- const current = state.consumers[consumerId] ?? {
880
- ...DEFAULT_AGENT_CONSUMER_STATE
881
- };
915
+ const current = state.consumers[consumerId] ?? { ...consumerDefaults };
882
916
  const next = { ...current };
883
917
  if (args.matchedIds !== void 0) next.serverMatchedIds = args.matchedIds;
884
918
  if (args.isSearching !== void 0) next.isServerSearching = args.isSearching;
@@ -889,7 +923,7 @@ function createAgentCatalog(config) {
889
923
  }
890
924
  function resetConsumerFilters(consumerId) {
891
925
  if (!state.consumers[consumerId]) return;
892
- writeConsumer(consumerId, { ...DEFAULT_AGENT_CONSUMER_STATE });
926
+ writeConsumer(consumerId, { ...consumerDefaults });
893
927
  }
894
928
  async function searchServer(query, deep = false) {
895
929
  const result = await searchAgentsOnServer(client, { query, deep });
@@ -1019,6 +1053,7 @@ function createAgentCatalog(config) {
1019
1053
  errorSink,
1020
1054
  notifier,
1021
1055
  canResolveMandates: transport !== void 0,
1056
+ consumerDefaults,
1022
1057
  getState: () => state,
1023
1058
  subscribe: (listener) => {
1024
1059
  listeners.add(listener);
@@ -1096,6 +1131,26 @@ function shouldDefaultAgentListToPublicTab(args) {
1096
1131
  if (!args.agentsLoaded) return false;
1097
1132
  return args.ownedCount === 0;
1098
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
+ }
1099
1154
 
1100
1155
  // catalog/score.ts
1101
1156
  function computeAgentSearchScore(agent, query) {
@@ -1306,8 +1361,7 @@ function filterUserTypeAgents(agents, consumer) {
1306
1361
  if (tab === "mine" && agent.isOwner !== true) return false;
1307
1362
  if (tab === "shared" && !(agent.isOwner === false && agent.accessLevel != null))
1308
1363
  return false;
1309
- if (archFilter === "active" && agent.isArchived) return false;
1310
- if (archFilter === "archived" && !agent.isArchived) return false;
1364
+ if (!agentMatchesArchiveFilter(agent, archFilter)) return false;
1311
1365
  if (favFilter === "yes" && !agent.isFavorite) return false;
1312
1366
  if (favFilter === "no" && agent.isFavorite) return false;
1313
1367
  if (accessFilter === "owned" && agent.isOwner !== true) return false;
@@ -1337,9 +1391,10 @@ function filterUserTypeAgents(agents, consumer) {
1337
1391
  return sortFilteredAgents(filtered, consumer);
1338
1392
  }
1339
1393
  function filterBuiltinTypeAgents(agents, consumer) {
1340
- const { searchTerm, favFilter, includedCats, includedTags } = consumer;
1394
+ const { searchTerm, favFilter, archFilter, includedCats, includedTags } = consumer;
1341
1395
  const serverMatched = new Set(consumer.serverMatchedIds);
1342
1396
  const filtered = agents.filter((agent) => {
1397
+ if (!agentMatchesArchiveFilter(agent, archFilter)) return false;
1343
1398
  if (favFilter === "yes" && !agent.isFavorite) return false;
1344
1399
  if (favFilter === "no" && agent.isFavorite) return false;
1345
1400
  if (includedCats.length > 0) {
@@ -1457,6 +1512,15 @@ function agentMatchesArchiveFilter(agent, archFilter) {
1457
1512
  if (archFilter === "archived") return Boolean(agent.isArchived);
1458
1513
  return true;
1459
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
+ }
1460
1524
  function countUserRows(agents, archFilter, predicate) {
1461
1525
  let n = 0;
1462
1526
  for (const a of agents) {
@@ -1475,7 +1539,7 @@ var selectAgentTabCounts = memoize2(
1475
1539
  mine: countUserRows(agents, archFilter, isOwned),
1476
1540
  shared: countUserRows(agents, archFilter, isShared),
1477
1541
  all: countUserRows(agents, archFilter, isAny),
1478
- system: selectBuiltinTypeAgents(agents).length
1542
+ system: countBuiltinRows(agents, archFilter)
1479
1543
  })
1480
1544
  );
1481
1545
  var selectTotalUserAgentsCount = memoize1(
@@ -1488,7 +1552,7 @@ var selectTotalSharedAgentsCount = memoize1(
1488
1552
  (agents) => countUserRows(agents, "active", isShared)
1489
1553
  );
1490
1554
  var selectTotalBuiltinAgentsCount = memoize1(
1491
- (agents) => selectBuiltinTypeAgents(agents).length
1555
+ (agents) => countBuiltinRows(agents, "active")
1492
1556
  );
1493
1557
  var selectTotalFavoriteAgentsCount = memoize1(
1494
1558
  (agents) => countUserRows(agents, "active", isFavorite)