@ai-matrx/agents 0.9.1 → 0.10.0

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,139 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0 — 2026-09-09
4
+
5
+ **`@ai-matrx/agents/mandates` — clients stop typing mandate keys by hand.**
6
+
7
+ A TypeScript client never declares a mandate: aidream's `declare_mandate` is the
8
+ only declaration path in the system. Until now clients still *named* mandates
9
+ with hand-typed string literals and local `mandates.ts` mirror files, so a typo,
10
+ a rename, or a retired key failed at run time as a 404 nobody saw — and the
11
+ release report had no way to resolve a client's references against the key set
12
+ the client actually shipped.
13
+
14
+ This release publishes that key set. `mandates/keys.generated.ts` is emitted by
15
+ THE ONE GENERATOR (`aidream/scripts/mandates_generate.py`, stage (h)) from the
16
+ in-process declarations — the same `declared_mandates()` the boot sync writes the
17
+ database from — and cross-checked against the live `origin='code'` rows on every
18
+ credentialed pass. The key set of every published version is committed beside it
19
+ under `mandates/snapshots/keys.<version>.json` and ships in the tarball, so a
20
+ scan can resolve a client's references against the key set of the exact version
21
+ it depends on (Mandate Declaration & Usage Reporting, DESIGN § 4.5 step 2).
22
+
23
+ ### Consumer action
24
+
25
+ Import keys from `@ai-matrx/agents/mandates`; hand-typed key literals and local
26
+ `mandates.ts` mirrors are retired — lane L6.
27
+
28
+ - `MANDATE_KEYS` — every declared key, keyed by THE IDENTIFIER RULE (`.` → `__`,
29
+ anything outside `[A-Za-z0-9_]` → `_`, a leading digit gets an `_`): so
30
+ `seo.keyword_classifier` is `MANDATE_KEYS.seo__keyword_classifier`.
31
+ - `type MandateKey` — the union. Type every carrier parameter with it: a
32
+ made-up key then fails type-check instead of failing in production.
33
+ `as MandateKey` casts are the same defect the literals were.
34
+ - `isMandateKey(x)` / `assertMandateKey(x, context?)` for anything that crossed
35
+ a wire (a URL segment, a stored preference, a row's `mandate_key`).
36
+ - `MANDATE_FAMILIES` — prefix → members, for a family aidream declares from the
37
+ feature's own roster; `MandateFamilyMember<P>` types one member.
38
+ - `mandateKeyOfApp(appSlug, surfaceSlug?)` / `mandateKeyOfShortcut(slug,
39
+ surfaceSlug?)` for the DB-authored `app.*` / `shortcut.*` families. They return
40
+ a branded `DynamicMandateKey`, deliberately **not** assignable to `MandateKey`
41
+ — no generated union can contain a row a user created five minutes ago. A
42
+ carrier that genuinely accepts either says `MandateKey | DynamicMandateKey`.
43
+ - `RETIRING_MANDATE_KEYS` / `MANDATE_KEYS_META` — the deprecation list and the
44
+ aidream revision + generator version the key set came from.
45
+
46
+ **Two-phase retire.** A key that stops being declared does not disappear: it is
47
+ emitted `@deprecated retiring since <version>`, stays in the `MandateKey` union,
48
+ and leaves only after three already-published versions have shipped it that way.
49
+ A client that lags one release gets a deprecation warning with a remedy, never a
50
+ hard type error it cannot act on until it upgrades.
51
+
52
+ ### Additive only
53
+
54
+ No export was removed, renamed or re-signed. `./mandates` is a new subpath; every
55
+ existing entry is untouched.
56
+
57
+ ## 0.9.2 — 2026-09-09
58
+
59
+ **THE ARCHIVED-ITEMS LAW, APPLIED TO THE PICKER — the first instance of a
60
+ platform-wide class.**
61
+
62
+ Arman, 2026-09-09, asked whether the Public tab should hide archived agents
63
+ like its other tabs (`common-docs/policies/archived-items.md`):
64
+
65
+ > "Archived agents should be treated like all other archived items everywhere
66
+ > within our codebase. … why does agents have to be any different than
67
+ > workflows or employees or HR documents or anything else that we have?
68
+ > They're not any different. They are the same, and they should be treated the
69
+ > same. … everything should have an archive filter, and the default should
70
+ > always hide archived, but seeing archived items should be one or two clicks
71
+ > away. But don't go and do this for agents and forget that this is a system
72
+ > wide decision for every single item everywhere in our system."
73
+
74
+ ### Consumer action: none
75
+
76
+ No export was removed, renamed or re-signed. Additive only:
77
+ `ARCHIVE_FILTER_OPTIONS`, `ARCHIVE_FILTER_ORDER`, `nextArchFilter`,
78
+ `isAgentArchFilter`, `AgentArchFilterOption`, `AgentCatalogDefaults`,
79
+ `agentArchiveFilterChipLabel` / `…StateLabel` / `…ControlLabel`,
80
+ `agentListFooterLabel`, `AgentCatalog.consumerDefaults`,
81
+ `useAgentListCore({ initialArchFilter })` and its `archivedHiddenCount` /
82
+ `footerLabel` returns, plus `data-testid="agent-archive-filter"` /
83
+ `"agent-list-footer"` (and their native `testID` twins). Every host adopts this
84
+ by taking `latest`.
85
+
86
+ ### Behavioural deltas (all deliberate, the first was the defect)
87
+
88
+ **THE BUILTIN ARCHIVE EXEMPTION IS DELETED.** `filterBuiltinTypeAgents` applied
89
+ no archive filter, on the theory that "a builtin belongs to the platform and is
90
+ never the user's to archive". The database disagreed: `Foundry Planner`
91
+ (`001b22f6-fa4d-40c6-a1a4-8575d4d04379`) is an archived builtin, and it
92
+ rendered at Public index 216, sat inside the "413 agents" badge and footer, and
93
+ was pickable — while Mine / Shared / All hid their archived rows. One picker,
94
+ two rules. `selectAgentTabCounts(...).system` and
95
+ `selectTotalBuiltinAgentsCount` now count under the same rule the tab renders
96
+ under, so badge == footer == rows holds on Public too.
97
+
98
+ **THE ARCHIVE FILTER IS NOW A VISIBLE CONTROL, on web and native.** One chip on
99
+ the filter bar with the platform's three states — *hide archived* (default) →
100
+ *show all* → *archived only* → back — one click from opening the picker, on
101
+ every tab, and present even for a consumer whose `visibleTabs` removes the tab
102
+ strip. Its accessible name always says the state it is in and the state one
103
+ click moves it to. It counts toward `activeFilterCount`, so a list showing
104
+ archived rows offers the reset control that puts them away.
105
+
106
+ **THE FOOTER IS HONEST ABOUT WHAT IT IS HIDING.** `agentListFooterLabel` prints
107
+ `"412 agents · 4 archived hidden"`, `"416 agents · archived shown"` or
108
+ `"3 archived shown"` — never a bare count that implies the list is everything.
109
+
110
+ **THE DEFAULT IS A KNOB** (root law 6; law clause 6):
111
+ `createAgentCatalog({ defaults: { archiveFilter } })` catalog-wide, platform
112
+ default `"active"`, with a per-consumer override
113
+ (`useAgentListCore({ initialArchFilter })`). An illegal value throws
114
+ `AgentCatalogConfigError` rather than being silently ignored. `resetFilters`
115
+ returns to the KNOB's value, not to the hardcoded platform one.
116
+
117
+ ### Proof, and how it can fail
118
+
119
+ `catalog/__tests__/archive-law.test.ts` asserts, per archive state × per tab,
120
+ that the rendered rows are exactly what the law says, that the badge and the
121
+ footer are that same number, that the default hides archived rows on EVERY tab
122
+ (Public included), that one action reveals them, and that the knob works and
123
+ screams. The expectation is restated from the policy text and imports nothing
124
+ from `catalog/selectors.ts`. Both shells (`catalog/react/archive-law.test.tsx`,
125
+ `catalog/native/__tests__/archive-law.test.tsx`) measure the RENDERED screen.
126
+
127
+ Falsifiability: reinstating the deleted exemption in `filterBuiltinTypeAgents`
128
+ was verified to turn 6 of the 11 kernel tests red before shipping, and the old
129
+ rule is written out in the suite so its answers are asserted to differ.
130
+ `total-order-fixture.json` now carries an archived builtin, generated from the
131
+ rule by `scripts/generate-total-order-fixture.mjs`.
132
+ `parity-expected.json` is left byte-identical — it is a RECORDING of
133
+ matrx-frontend, whose exemption is the defect — and the law is applied to that
134
+ recording in exactly one place, with a test proving the transform changes 30
135
+ cases and drops only builtin ids.
136
+
3
137
  ## 0.9.1 — 2026-09-08
4
138
 
5
139
  **TWO SCREENS TOLD THE SAME USER TWO DIFFERENT TRUTHS. BOTH ARE FIXED HERE, IN
@@ -112,7 +246,14 @@ had no ties, so F1 moved nothing that was already defined.
112
246
 
113
247
  **THE ONE PICKER NOW RENDERS ON REACT NATIVE — `@ai-matrx/agents/catalog/native`.**
114
248
 
115
- Matrx Mobile (Expo 54 / RN 0.81) was the last client still hand-rolling its
249
+ > 🚨 **Correction, 2026-09-09 (Arman): this entry has NO CONSUMER.** The `matrx-mobile` repo
250
+ > this release was built against is retired junk and is not a consumer of anything; there is no
251
+ > current mobile package, and the new one has not been started. `./catalog/native` remains
252
+ > published and gate-verified, but nothing ships on it. See common-docs
253
+ > `projects/npm-package-extraction/DECISIONS.md` C32 / `systems/mandates/DECISIONS.md` D24. The
254
+ > release notes below are preserved as written; the repo work they describe is void.
255
+
256
+ The retired `matrx-mobile` app (Expo 54 / RN 0.81) was the last thing still hand-rolling its
116
257
  agent list. Its sheet showed four HARDCODED agents plus a Supabase read of a
117
258
  `prompts` table that **does not exist in the platform database** — a read whose
118
259
  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)