/** * `@ai-matrx/agents/catalog` β€” the row shape and the per-consumer view state. * * `AgentSummary` is the 19-column row `agx_get_list_full()` returns, camelCased. * It is deliberately NOT the editable agent record: dirty tracking, undo, * messages, tools and `_fetchStatus` stay in whichever host owns the editor. * A list row is a list row. * * The view-state types are a VERBATIM port of matrx-frontend * `features/agents/redux/agent-consumers/slice.ts` β€” only the coupling seam * inverts (Redux reducers become store actions in `./store`). */ /** * A branded agent id. Every id that reaches `onSelect` is one of these: * either a `agent.definition` UUID, or the `mandate:` reference a * default row carries (byte-identical to the ref shape matrx-extend * `src/lib/mandates.ts` and matrx-local `desktop/src/lib/mandates.ts` * already hand back to their hosts). * * 🚨 The brand is a STRING LITERAL, never `unique symbol`. This package emits * `./catalog` and `./catalog/react` as two separate bundles, so each `.d.ts` * carries its own copy of these declarations. A `unique symbol` brand is * NOMINAL: two copies of it are unrelated types, so `createAgentCatalog()` * from `./catalog` was not assignable to `` * from `./catalog/react` β€” the exact two-import wiring the README, the * CHANGELOG and every consumer use. A literal brand is structural, so the two * copies unify, while a bare `string` still cannot be passed where an * `AgentId` is required (which is the whole point of branding). Any future * brand in this package follows the same rule. */ type AgentId = string & { readonly __agentId: "ai-matrx.agent-id"; }; /** Brand a raw string as an `AgentId`. */ declare function asAgentId(value: string): AgentId; /** The `mandate:` ref shape every client already speaks. */ declare function mandateAgentId(mandateKey: string): AgentId; /** True when an id is a mandate default row rather than a stored agent. */ declare function isMandateAgentId(id: string): boolean; /** `agent_type` verbatim. Only `builtin` changes how a row is grouped. */ type AgentCatalogAgentType = "user" | "builtin"; /** * `access_level` verbatim from `agx_get_list_full`. Widened over the raw * string because it is a DB-owned vocabulary: an unknown level renders as * itself rather than being coerced into a wrong one. */ type AgentAccessLevel = "owner" | "admin" | "editor" | "viewer" | "system" | (string & {}); /** * Present on a catalog row ONLY when that agent is an **Orchestra** β€” a * conductor that delegates to member agents at run time. Choosing one for a * step, a chat or a workflow node runs the whole ensemble, so a person has to * be able to SEE that before choosing, not discover it from the run. * * The DB decides. `agx_get_list_full` / `agx_get_list` / `agx_search` build * this from `platform.associations` (the conductor self-edge plus its member * edges) in one `agx_orchestra_badges()` join β€” never a second fetch, and * never a per-row read. `null` for every ordinary agent. * * History: the badge lived only in the Python `GET /agents` route, so every * client lost it when the pickers moved to the RPC. It is a ROW FIELD now, in * the one read every client shares. */ interface OrchestraBadge { /** How the ensemble runs: `supervisor` | `sequential` | `parallel` | `dag`, or a future mode verbatim. */ mode: string; /** The composition's one-line description, when its author wrote one. */ tagline: string | null; /** * The per-composition delegation-depth budget AS DECLARED, or `null` when * the composition declares none. Deliberately NOT defaulted here: the * platform's fallback ceiling is enforced by the server that runs the * ensemble, and a badge that invented a number would be reporting a promise * nobody made. */ depthBudget: number | null; /** Member edges. From the ROW count, so an unlabelled ensemble still counts. */ memberCount: number; /** Only REAL role titles β€” an ensemble with unlabelled edges yields `[]`. */ memberTitles: string[]; } /** * One `agx_get_list_full()` row, camelCased. Nineteen columns, no more: * anything richer needs a different read and is a different type. */ interface AgentSummary { id: AgentId; name: string | null; description: string | null; category: string | null; tags: string[]; agentType: AgentCatalogAgentType; modelId: string | null; /** * The agent's CLASS pin (`settings.offering_id` β€” which serving class of * the model runs, e.g. "Matrx Lightning"). `null` = no pin (the model's * preferred class runs). ABSENT = the read did not carry the column, so the * class is unknown and a display must name the model alone. */ offeringId?: string | null; isActive: boolean; isArchived: boolean; isFavorite: boolean; createdBy: string | null; organizationId: string | null; /** * The owning organization's NAME, joined by the DB in the same read as the * row (`organization_name` on `agx_get_list_full` / `agx_search`) β€” never a * second per-row query. Two agents can share a name across organizations; * this is what tells them apart. `null` = no organization; ABSENT = the read * did not carry the column (a database that predates it). */ organizationName?: string | null; taskId: string | null; sourceAgentId: string | null; createdAt: string | null; updatedAt: string | null; isOwner: boolean | null; accessLevel: AgentAccessLevel | null; sharedByEmail: string | null; /** * The Orchestra badge, or `null` for an ordinary agent. Strict: every row * carries the key, so a surface reads `agent.orchestra` and never has to ask * whether the column existed. */ orchestra: OrchestraBadge | null; /** * Set only on a mandate default row β€” the key it resolved through, so a * host can tell "the default" from a stored agent without parsing the id. */ mandateKey?: string; /** * THE THIN ROW (FW-014). `true` when this row came from the picker's page * read (`agent.agx_picker_page`), which carries only what a picker row * renders: no description, model, offering, task, source or sharer. A * consumer that needs those asks `catalog.ensureAgentRows([id])` (hover, * open), which replaces it with the full row. Absent = a full row. */ isThin?: boolean; /** When the caller last used this agent (their latest conversation with it). Page reads only. */ lastUsedAt?: string | null; } type AgentSortOption = "updated-desc" | "created-desc" | "name-asc" | "name-desc" | "category-asc"; /** * The sort choices a picker offers, in order. PURE DATA, so it lives in the * pure entry: a server-reachable manifest reads it at module scope, and * importing it from the client-stamped `./catalog/react` bundle dragged React * into a server graph for the sake of five string pairs. `./catalog/react` * re-exports it unchanged, so no consumer has to move. * * (Written "client-stamped", never with the literal directive: this comment is * emitted into `dist/catalog/index.d.ts`, and the tarball canary rightly * refuses that directive anywhere in a PURE entry.) */ declare const SORT_OPTIONS: { value: AgentSortOption; label: string; }[]; /** Which ownership tab is active in the agent list. */ type AgentTab = "mine" | "shared" | "all" | "system"; /** Favorite filter. */ type AgentFavFilter = "all" | "yes" | "no"; /** * The archive filter β€” THE ARCHIVED-ITEMS LAW's ONE control, in this * package's existing vocabulary. * * > "everything should have an archive filter, and the default should always * > hide archived, but seeing archived items should be one or two clicks * > away." β€” Arman, 2026-09-09 (`common-docs/policies/archived-items.md`) * * Three states and no more, identical for every entity on the platform: * * | value | the platform's words | what the list shows | * |------------|----------------------|--------------------------| * | `active` | hide archived | only un-archived rows | * | `both` | show all | every row | * | `archived` | archived only | only archived rows | * * `active` is the platform default, and it is a KNOB β€” see * `AgentCatalogConfig.defaults.archiveFilter`. */ type AgentArchFilter = "active" | "archived" | "both"; /** Every legal archive-filter value, in the order the control cycles them. */ declare const ARCHIVE_FILTER_ORDER: readonly AgentArchFilter[]; interface AgentArchFilterOption { value: AgentArchFilter; /** The platform's own words for the state β€” what a menu row would read. */ label: string; /** What the filter-bar chip reads while this state is the active one. */ chip: string; } /** * 🚨 ONE CONTROL, THREE STATES, SAME WORDS EVERYWHERE. A surface that invents * its own archive vocabulary breaks clause 3 of the law ("the same primitive * everywhere … not a per-feature invention"). */ declare const ARCHIVE_FILTER_OPTIONS: readonly AgentArchFilterOption[]; /** * The next state of the cycling control: hide archived β†’ show all β†’ archived * only β†’ hide archived. Pure, so the web chip and the native chip cannot * drift apart, and so "the control reveals archived rows in ONE action" is a * testable claim rather than a UI opinion. */ declare function nextArchFilter(current: AgentArchFilter): AgentArchFilter; /** True when `value` is one of the three legal states. */ declare function isAgentArchFilter(value: unknown): value is AgentArchFilter; /** * Access level filter. * 'any' = no restriction (default). * 'owned' = only agents the user owns (isOwner = true). * 'shared' = only agents shared with the user (isOwner = false). * 'editable' = owner + admin + editor. */ type AgentAccessFilter = "any" | "owned" | "shared" | "editable"; /** Sentinel meaning "include uncategorized / untagged" items. */ declare const AGENT_NONE_SENTINEL = "__none__"; interface AgentConsumerState { tab: AgentTab; sortBy: AgentSortOption; searchTerm: string; /** INCLUSION model: empty = show all; non-empty = only matching. */ includedCats: string[]; /** INCLUSION model: empty = show all; non-empty = only matching. */ includedTags: string[]; favFilter: AgentFavFilter; archFilter: AgentArchFilter; accessFilter: AgentAccessFilter; favoritesFirst: boolean; /** Current page for owned-agent list items (after the card section). */ listPage: number; /** Current page for shared-agent list items. */ sharedPage: number; /** * Ids returned by the last server-side search (`agx_search`), in server rank * order. Additive: an agent in this list survives the search filter even * when the local scorer gives it 0. * * That is load-bearing for tier 2. A deep search matches an agent's prompt * content, which the client never loads β€” so the local scorer cannot see it. * Without this list the server would return prompt matches and the UI would * immediately filter them back out. * * Server-only matches sort BELOW every locally-scored match, in server rank * order, so obvious matches always come first. */ serverMatchedIds: string[]; /** True while a server search is in flight β€” drives the search spinner. */ isServerSearching: boolean; /** Tier 2: also search agent prompt content. Opt-in, per consumer. */ deepSearch: boolean; } declare const DEFAULT_AGENT_CONSUMER_STATE: AgentConsumerState; /** The subset of consumer state a caller may patch (paging is derived). */ type AgentConsumerPatch = Partial>; /** * Thrown by `createAgentCatalog` when a REQUIRED port is missing or an * optional feature was asked for without the port it needs. Never a warning, * never a degraded catalog: a picker that cannot read is not a picker. */ declare class AgentCatalogConfigError extends Error { readonly name = "AgentCatalogConfigError"; /** Machine code, for a host that routes config failures. */ readonly code: string; /** What the host must do, in a sentence a person can act on. */ readonly remedy: string; constructor(args: { code: string; message: string; remedy: string; }); } /** * `@ai-matrx/agents/catalog` β€” THE PORTS. * * Two are required (`client`, `identity`); every other one ships a working * default with a DOCUMENTED degradation, per THE ALL-INCLUSIVE LAW. A host * injects identity and a Supabase client and nothing else; the picker's only * contract back to the host is `onSelect(agentId)`. * * React appears nowhere in this file β€” it is import-inert and legal in any * graph, server components included. */ /** A PostgREST error, structurally. */ interface PgErrorLike { message: string; code?: string; details?: string | null; hint?: string | null; } /** What every supabase-js call resolves to, structurally. */ interface PgResultLike { data: unknown; error: PgErrorLike | null; count?: number | null; } /** * The thenable builder supabase-js returns from `.rpc()`. `range` is what * makes a COMPLETE list read possible (`readAllRows`); `order` is what makes * that paging stable. Both are the real builder's own methods β€” this is a * structural subset, never a re-implementation. */ interface AgentCatalogRpcCall extends PromiseLike { range(from: number, to: number): PromiseLike; order(column: string, options?: { ascending?: boolean; }): AgentCatalogRpcCall; /** * PostgREST's `in` filter on the function's result rows β€” how the pinned * read asks `agx_get_list_full` for just the starred ids (2026-10-09). * OPTIONAL in the structural type: a host bridge without it gets the whole * catalog instead, announced through the errorSink. */ in?(column: string, values: readonly string[]): AgentCatalogRpcCall; } /** * REQUIRED. A structural subset of `SupabaseClient` β€” anything that can answer * `agx_get_list_full`, `agx_search` and `agx_resolve_agent_address`. A real * supabase-js client satisfies it as-is. The default favorites port also * passes this client directly to `@ai-matrx/associations`, whose generated * database doors call `schema(name).rpc(...)`. `schema` is OPTIONAL: an * `{ rpc }`-only host bridge is adapted by associations (0.14.5+) to answer * the public schema's functions, so a bridge never crashes the pickers. * * Absent β†’ `createAgentCatalog` throws `AgentCatalogConfigError`. There is no * degraded mode: the package IS the catalog read. */ interface AgentCatalogClient { schema?(name: string): { from(table: string): unknown; rpc: AgentCatalogClient["rpc"]; }; rpc(fn: string, args?: Record, options?: { count?: "exact" | "planned" | "estimated"; }): AgentCatalogRpcCall; } /** * A star is PER-PERSON state and lives in `platform.user_entity_state` β€” never * a column on `agent.definition` (the retired `is_favorite` flag sat on the * OWNER's row, so a shared agent could not be starred and two surfaces drifted). * * This is the structural subset of `@ai-matrx/associations/core`'s * `FavoritesServiceApi` the catalog uses β€” the platform's ONE `ues_*` * chokepoint. Results never throw; a refusal comes back as `{ ok: false }`. * * OPTIONAL. DEFAULT: `createAssociationsStore({ dataSource: client, identity, * errorSink }).favorites` β€” the same chokepoint, built on the same client. A * host that already holds a favorites service (matrx-frontend's * `favoritesService`) passes it so there is exactly one instance. */ interface AgentCatalogFavorites { getBulk(entityType: string, entityIds: string[]): Promise<{ ok: true; data: { items: { entityId: string; isFavorite: boolean; }[]; }; } | { ok: false; error: { message: string; code?: string; }; }>; setFavorite(entityType: string, entityId: string, isFavorite: boolean): Promise<{ ok: true; data: unknown; } | { ok: false; error: { message: string; code?: string; }; }>; /** * Every state row the caller owns of one kind (`ues_list`). The pinned read * asks for `"favorite"` to learn WHICH agents to read, so a sidebar never * pulls the whole catalog to show a handful of stars. OPTIONAL: a port * without it makes the pinned read fall back to the whole catalog, loudly. */ list?(kind?: "favorite" | "pinned" | "hidden"): Promise<{ ok: true; data: { items: { entityType: string | null; entityId: string | null; isFavorite: boolean | null; }[]; }; } | { ok: false; error: { message: string; code?: string; }; }>; } /** * REQUIRED. Who is asking. Needed for the "zero owned agents β†’ open on the * public tab" heuristic and for the favorites port. * * `requireUserId` THROWS when there is no session β€” never a silent anonymous * read. The catalog catches that throw in exactly ONE place (the tab-default * heuristic, where a signed-out visitor legitimately lands on the public * catalog) and reports it to the `errorSink` the first time; every other * caller lets it propagate. */ interface AgentCatalogIdentity { requireUserId(): string; /** * OPTIONAL non-throwing twin: the signed-in user id, or `null` for a * signed-out visitor. When bound, `null` is a legitimate STATE (a guest * page), not a failure β€” nothing is reported. Bind it whenever the host can * tell "no session" apart from "identity is broken". */ getUserId?(): string | null; } /** * Every degraded path, every failed read, every drift detection reports here. * DEFAULT: a tagged `console.error` that announces ONCE that no sink is bound * and names the remedy. */ type AgentCatalogErrorSink = (event: { code: string; message: string; context?: object; }) => void; /** * The drift scream's second channel. DEFAULT: none β€” and that is safe * ONLY because the in-picker persistent banner is the primary scream and * always renders. A drift is never console-only. */ type AgentCatalogNotifier = (event: { level: "warning" | "error"; title: string; message: string; }) => void; /** * First-paint cache for resolved default rows. DEFAULT: `localStorage` when * present, else an in-memory map (so SSR and a Chrome service worker both * work). Absent storage degrades to "no first paint of the default row until * the mandate resolves" β€” never a wrong name on screen. */ interface AgentCatalogStorage { getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; } /** * The SAME seam for a platform whose only key-value store is asynchronous. * * 🚨 WHY THIS EXISTS (0.9.0). React Native has no `localStorage`; every Expo * app's store (`@react-native-async-storage/async-storage`, `expo-secure-store`, * MMKV's async face) answers with a promise. Before this, a native host had * exactly two options β€” hand the package a storage that lies by returning * `null` synchronously, or lose the default row's first paint entirely. Both * are the silent degradation this package exists to refuse. * * The shape is `AsyncStorage`'s own, so an Expo host passes AsyncStorage * directly with no adapter. Every method may answer synchronously OR with a * promise, which is what makes `AgentCatalogStorage` a subtype of this one: * the WEB PATH IS UNCHANGED β€” a sync storage still resolves in the same * microtask, and `readCachedDefaultRow`/`writeCachedDefaultRow` keep their * synchronous signatures for every existing caller. */ interface AgentCatalogAsyncStorage { getItem(key: string): string | null | Promise; setItem(key: string, value: string): void | Promise; removeItem(key: string): void | Promise; } /** Either storage shape. What `AgentCatalogConfig.storage` accepts. */ type AgentCatalogStorageLike = AgentCatalogStorage | AgentCatalogAsyncStorage; /** Every user-visible string the picker renders, overridable per host. */ interface AgentCatalogLabels { /** User-facing label for the `system` ownership tab. */ publicTab: string; /** * Badge label on a builtin row. Default `"system"` β€” the string * `AgentRow` actually rendered in matrx-frontend. (The frontend also * declared `AGENT_PUBLIC_BADGE_LABEL = "Public"` for this badge and NOTHING * ever read it β€” a constant that named the badge without being the badge. * Recorded in the CHANGELOG and dropped rather than ported.) */ systemBadge: string; /** * Badge on an archived row. Archived agents stay offered (THE ARCHIVED-ITEMS * LAW: one or two clicks away) and say so on the row itself. Default * `"Archived"`. */ archivedBadge: string; searchPlaceholder: string; currentAgentHeading: string; emptyDefault: string; emptyShared: string; emptySystem: string; /** Empty state for a signed-out visitor β€” there is no catalog to read. */ emptySignedOut: string; loading: string; triggerFallback: string; triggerResolving: string; triggerUnnameable: string; hoverHint: string; } declare const DEFAULT_AGENT_CATALOG_LABELS: AgentCatalogLabels; interface AgentCatalogConfig { /** REQUIRED β€” the Supabase client. */ client: AgentCatalogClient; /** REQUIRED β€” who is asking. */ identity: AgentCatalogIdentity; /** OPTIONAL β€” see `AgentCatalogFavorites`. */ favorites?: AgentCatalogFavorites; /** * The catalog's registry key on `globalThis`. Two `createAgentCatalog` * calls with the same id are the same catalog (and the second one screams * through the errorSink before replacing the first). Default `"default"`. */ catalogId?: string; /** OPTIONAL β€” see `AgentCatalogErrorSink`. */ errorSink?: AgentCatalogErrorSink; /** OPTIONAL β€” see `AgentCatalogNotifier`. */ notifier?: AgentCatalogNotifier; /** * OPTIONAL β€” see `AgentCatalogStorage` and `AgentCatalogAsyncStorage`. A * synchronous store (the web `localStorage` default) and an asynchronous * one (React Native's `AsyncStorage`) are both accepted verbatim. */ storage?: AgentCatalogStorageLike; /** * OPTIONAL β€” the package's own `MatrxTransport` (`createMatrxTransport` * from `@ai-matrx/api`). REQUIRED the moment any picker instance * passes a `defaultMandateKey`: THE PLATFORM RULE D-R1 says clients never * walk the mandate ladder, so the default row can only be resolved through * the aidream door `GET /mandates/{mandate_key}/resolution`. A * `defaultMandateKey` with no transport throws `AgentCatalogConfigError`. */ transport?: AgentCatalogTransport; /** OPTIONAL β€” label overrides. */ labels?: Partial; /** * How a picker reads its list (FW-014). DEFAULT `"page"`: the menu opens on * one server page (`agent.agx_picker_page`, ~50 thin rows + counts) and pages * on scroll; the whole catalog is read only when the person narrows by * category/tag/sort/filter. `"catalog"`: every picker reads the whole catalog * on open (the pre-2026-10-23 behavior) β€” for a host whose client cannot * reach the `agent` schema, and for tests of the full-list rules. */ pickerReads?: "page" | "catalog"; /** * OPTIONAL β€” the host's starting values for per-consumer view state. * See `AgentCatalogDefaults`. */ defaults?: AgentCatalogDefaults; } /** * 🚨 OPINIONS BECOME KNOBS (root law 6). The archive filter's initial state is * a configurable default, not code taste β€” clause 6 of THE ARCHIVED-ITEMS LAW * (`common-docs/policies/archived-items.md`) says so explicitly. * * The PLATFORM default is `"active"` (hide archived) and a host that passes * nothing gets exactly that. A host wires this to whatever its org/user * setting resolves to; an individual picker can still override it per * consumer (`useAgentListCore({ initialArchFilter })`), and the person can * always change it from the control on the filter bar. * * An illegal value is NOT coerced to the default β€” it throws * `AgentCatalogConfigError`, because a silently-ignored setting is a knob that * lies about being a knob. */ interface AgentCatalogDefaults { archiveFilter?: AgentArchFilter; } /** * The transport seam, structurally identical to `MatrxTransport` from * `@ai-matrx/api` (declared structurally so `./catalog` never * imports the transport module and stays import-inert). */ interface AgentCatalogTransport { fetch(path: string, init: { method: "GET" | "POST"; headers: Record; body?: string; signal?: AbortSignal; }): Promise; } /** * Where a row's href goes when someone cmd-clicks it, and where `navigateTo` * lands. DEFAULT: `window.location.assign` (so cmd-click and ordinary * navigation both work with no host wiring at all). */ type AgentCatalogNavigate = (href: string) => void; /** Resolve a per-row href. DEFAULT: `/agents/go/` β€” the always-valid door. */ type AgentCatalogResolveHref = (agent: AgentSummary) => string; /** * The sneak-peek affordance. Absent β†’ the affordance is HIDDEN, never a dead * button (nothing fails silently, and nothing looks alive when it is not). */ type AgentCatalogOpenPeek = (agent: AgentSummary) => void; /** * catalog/data.ts β€” @ai-matrx/agents/catalog * * Every database call the catalog makes, and nothing else. Five of them: * * 1. `agx_get_list_full()` β€” the complete catalog (owned + directly-shared * + org-shared user agents, unioned with active builtins). * 2. `agx_search(p_query, p_deep, p_limit, p_offset)` β€” tier-2 server search. * 3. `agx_resolve_agent_address(p_ids)` β€” the single-agent name read, used * when a mandate's Holder is not in the caller's own catalog. * 4. The caller's stars arrive ON the rows: every read above computes * `is_favorite` server-side from `platform.user_entity_state` * (`platform.my_favorite`). There is no second star read (FW-014; the * `ues_get_bulk` overlay over every id was retired 2026-10-23). * 5. ONE write: the caller's star, `ues_set` through the same port. The * retired `agent.definition.is_favorite` column is never read or written. * 6. THE PINNED READ (2026-10-09; by-ids 2026-10-23): the caller's starred * ids (`ues_list` through the favorites port), then * `agent.agx_get_by_ids(p_ids)` for exactly those ids. A sidebar's Pinned * section shows a handful of rows; it never pays for the whole catalog, * in the browser or on the server, to find them. * 7. THE PICKER PAGE (FW-014, 2026-10-23): `agent.agx_picker_page` β€” the * menu opens with ~50 thin rows (favorites, recently used, name) and the * tab counts; scrolling reads the next keyset page. `agent.agx_get_by_ids` * fills in a full row on hover/open. The whole-catalog read above remains * for surfaces that genuinely list everything, and for a picker whose * person narrows by category or tag (the facets need every row). * * 🚨 THE LIST READ IS COMPLETE OR IT THROWS. PostgREST caps every response at * `db-max-rows` (1000 on Matrx Main) and reports success β€” matrx-frontend's * `fetchAgentsListFull` issued a bare `.rpc()` and would silently have * returned 1000 rows of a larger catalog (the ~400-builtin truncation * incident is the near-miss). Here the read goes through `readAllRows` with * `{ count: "exact" }` and a stable `.order("id")`, so a truncation is a * loud `IncompleteReadError` instead of a confidently short picker. * The DB's own favourites-first/`updated_at` order is irrelevant: every row * is re-sorted client-side by `sortFilteredAgents`. */ /** Server search page size β€” the frontend's `AGENT_SEARCH_LIMIT`. */ declare const AGENT_SEARCH_LIMIT = 50; declare class AgentCatalogReadError extends Error { readonly name = "AgentCatalogReadError"; readonly source: string; constructor(source: string, detail: string); } /** * Read the WHOLE agent catalog. Throws rather than returning a partial * list β€” see the file header. */ declare function readAgentCatalogRows(client: AgentCatalogClient): Promise; interface AgentServerSearchResult { /** Matched ids in SERVER RANK ORDER. */ ids: string[]; /** Full rows for every hit β€” merged additively into the registry. */ rows: AgentSummary[]; } /** * Tier-2 server search. Purely ADDITIVE: it widens what the local filter * admits and never removes a row the local scorer already matched. */ declare function searchAgentsOnServer(client: AgentCatalogClient, args: { query: string; deep?: boolean; limit?: number; }): Promise; /** * The single-agent read, used when a mandate's resolved Holder is not in the * caller's own catalog (a platform agent nobody has shared, most often). * * Two existing doors, in order β€” never a new RPC: * 1. `agx_search` with the id as the query. The scorer awards an exact id * match 100000, so the row comes back first, and it is the FULL 19-column * row (name AND description AND category AND tags). * 2. `agx_resolve_agent_address([id])` β€” the frontend's own name resolver. * Name and type only, so the row renders with an honest name and no * description rather than a fabricated one. * * Returns `null` when neither door can name the agent. The caller SCREAMS; * it never invents a label. */ declare function readSingleAgentRow(client: AgentCatalogClient, agentId: string): Promise; /** * THE ONE WRITE: the caller's star on one agent, in * `platform.user_entity_state` through the favorites port. Throws an * `AgentCatalogReadError` carrying the refusal in words. */ declare function writeAgentFavorite(favorites: AgentCatalogFavorites, agentId: string, isFavorite: boolean): Promise; /** Which slice of the caller's catalog a picker page reads. */ interface AgentPickerPageQuery { /** `all_system` = the admin "All" tab that blends system agents in. */ tab: "mine" | "shared" | "all" | "all_system" | "system"; archive: "active" | "archived" | "both"; includeQuick: boolean; } /** The tab badges, counted server-side over the same reach as the rows. */ interface AgentPickerCounts { mine: number; shared: number; all: number; system: number; /** Archived rows the active filter hides behind THIS tab (0 unless archive = active). */ archivedHidden: number; } /** * catalog/default-row-types.ts β€” @ai-matrx/agents/catalog * * Split out of `default-row.ts` so `global-state.ts` can name the cached * shape without importing the resolver (and its transport work) β€” the same * split-out reflex C8 asks for, at module scale. */ /** The wire shape of `GET /mandates/{mandate_key}/resolution`, narrowed. */ interface MandateResolutionVerdict { mandateKey: string; holderType: string; /** The executable id. `null` for a workflow Holder β€” a picker cannot run one. */ agentId: string | null; isVersion: boolean; /** Always the `agent.definition` id, even when `agentId` is a version. */ definitionAgentId: string | null; /** Which precedence layer answered. Shown so a person can see WHY. */ provenance: string; } /** A resolved default row, cached for first paint and compared for drift. */ interface ResolvedDefaultRow { mandateKey: string; /** The `agent.definition` id the mandate resolved to. */ holderId: string; /** The holder's REAL name, read from the catalog β€” never hardcoded. */ holderName: string; holderDescription: string | null; provenance: string; /** Epoch ms of this resolution. */ at: number; } /** What the picker renders and what it screams, together. */ interface DefaultRowState { mandateKey: string; /** The row to render at the top of the list. `null` until resolved. */ row: AgentSummary | null; /** The last live resolution, or the cached one before the live answer lands. */ resolved: ResolvedDefaultRow | null; /** True while the resolution is in flight. */ loading: boolean; /** * 🚨 THE DRIFT SCREAM. Set when the cached first-paint row and the LIVE * resolution disagree on holder id or holder name. Rendered as a persistent * in-picker banner AND sent to the `notifier` port β€” never console-only. */ drift: string | null; /** Set when the mandate could not be resolved at all. Loud, with a remedy. */ error: string | null; } /** * catalog/schema.ts β€” @ai-matrx/agents/catalog * * THE DEMANDED SCHEMA PROBE (the `@ai-matrx/associations` `assertDemandedSchema` * precedent). It proves that the database a host pointed this catalog at can * actually answer the functions the picker demands β€” the three `agx_*` reads * and `ues_get_bulk`, the star read (`platform.user_entity_state`). * * 🚨 IT MUST BE ABLE TO FAIL. A probe whose green result is unconditional * proves nothing, so `selfTest: true` also probes a fabricated function name * and REQUIRES it to come back missing. `catalog/__tests__/schema.test.ts` * runs the whole probe against a client whose `rpc` answers PostgreSQL 42883 * (`undefined_function`, which PostgREST surfaces as PGRST202) and asserts the * loud error. */ interface AgentCatalogSchemaReport { ok: boolean; /** Demanded functions the database cannot answer. */ missing: string[]; /** Functions the probe could not reach (transport/unknown failure). */ unreachable: { fn: string; error: unknown; }[]; /** Functions that answered (success or any non-missing refusal). */ answered: string[]; /** * Whether `agx_get_list_full` carries the `orchestra` badge column (added to * the platform 2026-09-08). * * NOT a violation when absent β€” the picker degrades to no conductor marker, * which is honest β€” but it is never SILENT either: a `false` here is the one * signal that tells a host "this database predates the badge" instead of * leaving someone to wonder why their Orchestra looks like a plain agent. * `null` means the probe could not tell (the caller can see no agents at * all), never "fine". */ orchestraColumn: boolean | null; } interface AssertAgentCatalogSchemaOptions { /** * Also probe a fabricated function name and REQUIRE it to come back * missing β€” proves this probe can fail. */ selfTest?: boolean; /** Return the report instead of throwing. The self-test failure ALWAYS throws. */ throwOnViolation?: boolean; } /** * Probe every demanded function. Throws an `Error` naming each missing one * (unless `throwOnViolation: false`), and ALWAYS throws when the probe itself * cannot be trusted (unreachable database, failed self-test). */ declare function assertAgentCatalogSchema(client: AgentCatalogClient, options?: AssertAgentCatalogSchemaOptions): Promise; /** * catalog/store.ts β€” @ai-matrx/agents/catalog * * `createAgentCatalog(config)` β€” the headless kernel. Everything the picker * knows lives here: the row registry, the complete-list read with its * session-shared in-flight dedupe and freshness window, the tier-2 server * search, the per-consumer view state, the ONE write, and the mandate default * row with its drift screamer. * * The host injects a Supabase client and who the user is. Nothing else. * * The consumer reducers below are a VERBATIM port of matrx-frontend * `features/agents/redux/agent-consumers/slice.ts`; only the seam inverts * (a Redux reducer becomes a store method, and Immer's draft mutation becomes * an explicit copy so the store can publish a new reference). */ type AgentCatalogStatus = "idle" | "loading" | "succeeded" | "failed"; /** One picker slice (tab Γ— archive Γ— quick) read a page at a time (FW-014). */ interface AgentPickerPageState { /** Row ids in server order across every page read so far. */ ids: string[]; /** Keyset position of the next page; `null` once the last page is in. */ nextCursor: unknown; done: boolean; status: AgentCatalogStatus; /** True while a next page (not the first) is in flight. */ loadingMore: boolean; error: string | null; counts: AgentPickerCounts | null; fetchedAt: number; } interface AgentCatalogState { /** Every row, one stable array reference per change (memoization depends on it). */ rows: AgentSummary[]; byId: Record; status: AgentCatalogStatus; error: string | null; consumers: Record; /** Keyed by mandate key. */ defaultRows: Record; /** * The caller's starred rows (`ensureFavoriteRows`): `succeeded` once every * starred agent the caller can see is in `byId` β€” by the narrow pinned read * or by a complete catalog load, whichever came first. */ favoriteRowsStatus: AgentCatalogStatus; /** The picker's paged slices, keyed by `agentPickerPageKey` (FW-014). */ pickerPages: Record; } interface AgentCatalog { readonly catalogId: string; readonly labels: AgentCatalogLabels; readonly errorSink: AgentCatalogErrorSink; readonly notifier: AgentCatalogNotifier | undefined; /** True when this catalog was given a transport and can resolve mandates. */ readonly canResolveMandates: boolean; /** `AgentCatalogConfig.pickerReads`, resolved (default `"page"`). */ readonly pickerReads: "page" | "catalog"; /** * The consumer state a brand-new consumer starts from, AFTER the host's * `defaults` knob is applied. Read it instead of * `DEFAULT_AGENT_CONSUMER_STATE` whenever the question is "is this filter at * its default?" β€” a host that legitimately defaults the archive filter to * "show all" must not have every control render as though it were modified. */ readonly consumerDefaults: AgentConsumerState; getState(): AgentCatalogState; subscribe(listener: () => void): () => void; getAgent(agentId: string): AgentSummary | undefined; /** * The signed-in user id, or `null` when there is no session. The ONE place * `identity.requireUserId()`'s throw is caught β€” a signed-out visitor * legitimately lands on the public catalog β€” and it is reported to the * errorSink the first time so it is never silent. */ getUserIdOrNull(): string | null; /** Load the catalog if it is not fresh. Safe to call on every mount. */ ensureLoaded(options?: { force?: boolean; }): Promise; /** * Load ONLY the caller's starred agents into the registry (the pinned read β€” * `ues_list` then `agx_get_list_full?id=in.(…)`), never the whole catalog. * Answered at once when a complete load is fresh. Deduped; safe on every * mount. For surfaces that show stars without being a picker (a sidebar's * Pinned section): nothing loads the full list until a picker opens. */ ensureFavoriteRows(): Promise; /** * Answer the pinned read from rows a batched route read already returned * (`agx_get_list_full` rows for the caller's starred agents β€” e.g. the * `chat.route_seed` first-paint read). Merges them like the pinned read * does and marks `favoriteRowsStatus` succeeded, so `ensureFavoriteRows` * makes no request. Rows that do not parse are skipped. */ seedFavoriteRows(rawRows: unknown[]): void; /** * THE PICKER'S OPEN (FW-014): the first page (~50 thin rows) of one slice, * plus its tab counts β€” never the whole catalog. Fresh for the list's TTL; * deduped in flight; safe on every open. */ ensurePickerPage(query: AgentPickerPageQuery): Promise; /** The next page of a slice (scroll). No-op when the slice is done or already loading. */ loadMorePickerPage(query: AgentPickerPageQuery): Promise; /** * FULL rows for these ids (`agent.agx_get_by_ids`), merged into the registry: * a hover card's details, the pinned current agent, a name for an id that is * not on page 1. Ids already held as full rows are not asked again; deduped * in flight. */ ensureAgentRows(agentIds: readonly string[]): Promise; /** * The caller's live agents carrying any of these tags, merged into the * registry (`agent.agx_picker_tagged`) β€” the composer's quick agents and * presets, never the catalog. Fresh for the list's TTL; deduped in flight; * answered at once when the whole catalog is already fresh. */ ensureTaggedRows(tags: readonly string[]): Promise; /** True when the last load is inside the 15-minute TTL. */ isFresh(): boolean; /** True when the last load is older than 4 hours (tab-restore threshold). */ isStale(): boolean; getConsumer(consumerId: string): AgentConsumerState; registerConsumer(consumerId: string, initial?: AgentConsumerPatch): void; unregisterConsumer(consumerId: string): void; setConsumerFilter(consumerId: string, patch: AgentConsumerPatch): void; setConsumerPage(consumerId: string, which: "list" | "shared", page: number): void; setConsumerServerSearch(consumerId: string, args: { matchedIds?: string[]; isSearching?: boolean; }): void; resetConsumerFilters(consumerId: string): void; /** Tier-2 search. Purely additive; merges rows into the registry. */ searchServer(query: string, deep?: boolean): Promise; /** THE ONE WRITE. Optimistic, rolled back on failure, loud either way. */ setFavorite(agentId: string, isFavorite: boolean): Promise; /** * Reflect a write the HOST made elsewhere (a rename, a description, an * archive) into the held row, so every list reading the catalog shows it at * once. Returns the row as it was before β€” pass it back to `restoreAgent` to * roll the change back when the host's write fails. `undefined` = the row is * not held, and nothing changed. */ patchAgent(agentId: string, patch: Partial>): AgentSummary | undefined; /** Put a row back exactly as `patchAgent` returned it (a failed write's rollback). */ restoreAgent(row: AgentSummary): void; /** Resolve (once) the default row for a mandate key. Idempotent. */ ensureDefaultRow(mandateKey: string): void; getDefaultRow(mandateKey: string): DefaultRowState | undefined; /** A person acknowledged the drift banner. The notifier already fired. */ dismissDefaultRowDrift(mandateKey: string): void; /** Re-ask the mandate door for a row that refused (see ensureDefaultRow). */ retryDefaultRow(mandateKey: string): void; assertSchema(options?: AssertAgentCatalogSchemaOptions): Promise; } declare function createAgentCatalog(config: AgentCatalogConfig): AgentCatalog; /** * The catalog registered under `catalogId`, across loader graphs. Returns * `undefined` when none has been created yet β€” never a silent stub. */ declare function getRegisteredAgentCatalog(catalogId?: string): AgentCatalog | undefined; /** * catalog/rows.ts β€” @ai-matrx/agents/catalog * * The ONE ingress for a database row. `agx_get_list_full()` and `agx_search()` * return the same list columns (search adds two ranking columns), so both come * through here and produce the identical `AgentSummary`. * * UNKNOWN COLUMNS ARE IGNORED, BY DESIGN. Every column is read by NAME, so an * additive platform migration (the way `orchestra` arrived) reaches consumers * on `latest` without a package release, and an older database simply yields * `null` for a column it does not have yet. * * Typed over `unknown` on purpose: the truth of this boundary is a * HOST-generated Supabase type this package cannot see (the `@ai-matrx/data` * 0.2.1 lesson). Narrowing happens here, once, loudly β€” a row that is not an * object is REFUSED rather than half-read. */ /** * Map one raw list/search row. Returns `null` when the row is not an object * or carries no id β€” the caller reports that to the errorSink and drops it; * a row with no identity cannot be selected, so rendering it would be a lie. */ declare function toAgentSummary(raw: unknown): AgentSummary | null; /** * catalog/orchestra.ts β€” @ai-matrx/agents/catalog * * Creator words for an Orchestra badge. A VERBATIM port of the helpers the * Workflow Studio's hand-rolled picker carried (`apps/workflow-studio/src/ * hooks/use-agents.ts` before `c0f958b43`), lifted here so every client says * the same sentence about the same ensemble. * * Pure β€” no React, no DOM. The row shape is `OrchestraBadge` in `./types`. */ declare function orchestraModeLabel(mode: string): string; /** Human label for a badge: "Orchestra Β· supervisor Β· 3 members" (singular-safe). */ declare function orchestraLabel(badge: OrchestraBadge): string; /** * The member-call ceiling in Creator language ("Team members can call helpers * up to 2 levels deep"), or `null` when the composition declared none. A badge * never invents the platform default: the server that runs the ensemble owns * that number, and printing a promise nobody made is worse than printing * nothing. */ declare function orchestraDepthLine(badge: OrchestraBadge): string | null; /** * One visible line naming who the Orchestra delegates to, collapsing past * `limit` into "+N more". `null` when no member carries a real role title β€” * "Member, Member, Member" is noise that reads like data. */ declare function orchestraDelegatesLine(badge: OrchestraBadge, limit?: number): string | null; /** * catalog/default-row.ts β€” @ai-matrx/agents/catalog * * THE DEFAULT ROW, and the drift screamer (design D3). * * What every client used to do: prepend a placeholder row whose id is * `mandate:` and whose name and description are HARDCODED, and let the * server resolve the real Holder at run time. That is how matrx-local's row * came to be labeled "Matrx Desktop Agent" while `local.cloud_chat` actually * resolves to General Chat β€” a label lying on screen, with nothing anywhere * that could notice. * * What happens here instead: * * - A picker instance passes `defaultMandateKey`. The package resolves the * Holder through the aidream door `GET /mandates/{key}/resolution` * (verified against `aidream/api/routers/mandate_bindings.py`, * `get_mandate_resolution`, mounted at prefix `/mandates`) using the * injected `transport`. * 🚨 THE PLATFORM RULE D-R1: a client NEVER walks the mandate ladder * itself. It asks. That is why a `defaultMandateKey` with no transport is * a loud config error and not a DB read. * - The row renders the Holder's REAL name and description, read from the * catalog rows already in hand, or through ONE existing single-agent * read when the Holder is not in this caller's catalog. * - The last resolution is cached in the `storage` port for first paint. * - Whenever the cached row and the LIVE resolution disagree on holder id * or holder name, the picker shows a PERSISTENT in-picker banner AND the * `notifier` port fires. Never a console line, never only a console line. * * The row's id stays `mandate:` byte-for-byte, because matrx-extend * (`src/lib/mandates.ts`) and matrx-local (`desktop/src/lib/mandates.ts`) * already hand that exact ref to their hosts. */ declare class MandateDefaultRowError extends Error { readonly name = "MandateDefaultRowError"; readonly mandateKey: string; readonly remedy: string; constructor(args: { mandateKey: string; message: string; remedy: string; }); } /** Narrow the server's `MandateResolutionResponse` to what a picker needs. */ declare function parseMandateResolution(mandateKey: string, raw: unknown): MandateResolutionVerdict; /** An in-memory storage, used when the host binds none and there is no DOM. */ declare function createMemoryStorage(): AgentCatalogStorage; /** * DEFAULT storage: `localStorage` when it exists AND answers (a private * window, a service worker, or SSR all fail differently), else memory. */ declare function defaultStorage(): AgentCatalogStorage; declare function readCachedDefaultRow(storage: AgentCatalogStorage, mandateKey: string): ResolvedDefaultRow | null; declare function writeCachedDefaultRow(storage: AgentCatalogStorage, resolved: ResolvedDefaultRow): void; /** * Read the cached row from EITHER storage shape. A store that throws or answers * with a rejected promise costs a first paint, never correctness. */ declare function readCachedDefaultRowAsync(storage: AgentCatalogStorageLike, mandateKey: string): Promise; /** Write the cached row to EITHER storage shape. */ declare function writeCachedDefaultRowAsync(storage: AgentCatalogStorageLike, resolved: ResolvedDefaultRow): Promise; /** Build the `mandate:` row from a resolved Holder. Never hardcodes text. */ declare function defaultRowFromResolution(resolved: ResolvedDefaultRow): AgentSummary; /** * The drift verdict. Cached vs live: a changed holder id OR a changed holder * name is drift. Returns the sentence to show, or `null` when they agree. */ declare function detectDefaultRowDrift(cached: ResolvedDefaultRow | null, live: ResolvedDefaultRow): string | null; interface ResolveDefaultRowArgs { mandateKey: string; transport: AgentCatalogTransport; client: AgentCatalogClient; storage: AgentCatalogStorageLike; errorSink: AgentCatalogErrorSink; /** * Look the Holder up in the rows this catalog already holds. May answer * asynchronously: the store waits for an in-flight list read before * declaring a miss, so a default row never costs a second network read just * because the verdict outran the list (the list read now also overlays the * caller's stars). */ lookupRow: (agentId: string) => AgentSummary | undefined | Promise; } interface ResolveDefaultRowOutcome { resolved: ResolvedDefaultRow; row: AgentSummary; /** Non-null when the cached first-paint row disagreed with the live answer. */ drift: string | null; } /** * Ask the server, name the Holder for real, cache the answer, and report * drift. Throws `MandateDefaultRowError` when the mandate cannot produce a * runnable agent β€” a picker never renders a default row it cannot name. */ declare function resolveDefaultRow(args: ResolveDefaultRowArgs): Promise; /** * catalog/labels.ts β€” @ai-matrx/agents/catalog * * VERBATIM port of matrx-frontend `features/agents/constants/agent-list-labels.ts`. * The only seam inverted: the two label constants moved into the `labels` port * (`DEFAULT_AGENT_CATALOG_LABELS.publicTab` / `.publicBadge`) so a host can * override them; the FUNCTIONS below keep their exact behavior. */ declare function agentListEmptyLabel(tab: AgentTab, labels: AgentCatalogLabels): string; /** Whether a list picker should default to the public (`system`) tab instead of Mine. */ declare function shouldDefaultAgentListToPublicTab(args: { userId: string | null; ownedCount: number; agentsLoaded: boolean; }): boolean; /** What the filter-bar chip reads in each state. */ declare function agentArchiveFilterChipLabel(archFilter: AgentArchFilter): string; /** The platform's words for the state β€” menus, aria-labels, help text. */ declare function agentArchiveFilterStateLabel(archFilter: AgentArchFilter): string; /** * The accessible name of the ONE control, always naming the state it is IN * and the state one click moves it to. A control whose label does not say * what it currently does is the "dead or dishonest" shape the platform bans. */ declare function agentArchiveFilterControlLabel(archFilter: AgentArchFilter): string; /** * THE FOOTER. It counts exactly what the list rendered and, whenever archived * rows are involved, says so β€” hidden ones included. * * hide archived, nothing archived behind it β†’ "412 agents" * hide archived, 4 archived behind it β†’ "412 agents Β· 4 archived hidden" * show all β†’ "416 agents Β· archived shown" * archived only β†’ "4 archived shown" */ declare function agentListFooterLabel(args: { /** Rows actually rendered. */ count: number; archFilter: AgentArchFilter; /** * Rows this view WOULD render if the filter were flipped to "archived only" * β€” exactly what the default state is keeping off the screen. Only ever * read in the `active` state. */ archivedHiddenCount: number; }): string; /** * The structural shape a record needs to be searchable. Every field except * `id` is optional so both the full gallery record and the slim chat record * satisfy it without adapters. */ interface AgentSearchable { id: string; name?: string | null; description?: string | null; category?: string | null; tags?: string[] | null; modelId?: string | null; agentType?: string | null; outputFormat?: string | null; sharedByEmail?: string | null; } /** * Relevance score for an agent against a query. Higher = more relevant. * Returns 0 when nothing matches (0 is the "no match" contract β€” see * `agentMatchesSearch`). * * Scores only fields carried by the list-level fetches. Message content and * variable definitions are deliberately excluded: they are not loaded by the * list RPCs, so matching them here would be a false promise. Server-side * search owns deep content matching. */ declare function computeAgentSearchScore(agent: AgentSearchable, query: string): number; /** True when the agent matches the query at all. */ declare function agentMatchesSearch(agent: AgentSearchable, query: string): boolean; /** * catalog/selectors.ts β€” @ai-matrx/agents/catalog * * VERBATIM port of matrx-frontend * `features/agents/redux/agent-consumers/selectors.ts`. * * ALL filter, sort, search-scoring, category/tag extraction and pagination * logic lives here β€” not in components. Two coupling seams inverted, nothing * else: * * 1. `createSelector` (reselect) β†’ `memoize1`/`memoize2`/`memoize3` below, * the same last-arguments reference-equality memoization reselect * defaults to. No Redux, no store types. * 2. `AgentDefinitionRecord` β†’ `AgentSummary`, the list-row shape * `agx_get_list_full` actually returns. * * One structural consequence of (2), recorded rather than hidden: the * frontend's `selectLiveAgents` filters `isVersion` snapshots out of a store * that also holds them. This catalog only ever holds list rows β€” a version * snapshot cannot enter it β€” so that filter is a no-op here and is not * ported. * * 🚨 TWO DELIBERATE DEPARTURES FROM THE ORIGINAL, both defects the port * inherited and 0.9.1 fixes at the class (see CHANGELOG 0.9.1): * * F1 β€” every sort now ends on `id` ASC, the same final key `agx_get_list` * ends on, so the order of TIED rows is defined by the rows instead of * by whichever transport the host used to fetch them. * F5 β€” the tab-badge counts honor the archive filter, so the badge, the * footer and the rendered rows are the same number. * * Everything else below is byte-identical to the original. */ declare const AGENT_CARDS_LIMIT_DESKTOP = 8; declare const AGENT_CARDS_LIMIT_MOBILE = 4; declare const AGENT_LIST_ITEMS_PER_PAGE = 20; /** * Last-arguments memoization on reference equality β€” reselect's default * `defaultMemoize` behavior, in eleven lines and with no dependency. A * selector built here is memoized PER INSTANCE, exactly like a * `createSelector` result, so the factories below keep their contract: * build the selector once per (consumerId, …) and reuse it. */ declare function memoize1(fn: (a: A) => R): (a: A) => R; declare function memoize2(fn: (a: A, b: B) => R): (a: A, b: B) => R; declare function memoize3(fn: (a: A, b: B, c: C) => R): (a: A, b: B, c: C) => R; /** * 🚨 THE FINAL TIEBREAKER. `agx_get_list` orders * `is_favorite DESC, updated_at DESC, id` β€” `id` ASCENDING is the unique key * that makes the SERVER's order TOTAL. Every sort in this file ends on the * same key, so the rendered order is a function of the ROWS and never of the * order they happened to arrive in. * * Why a raw `<` / `>` and not `localeCompare`: Postgres orders `uuid` by its * 16 raw bytes. The canonical text form is lowercase hex with dashes at fixed * positions, so a codepoint comparison of those strings reproduces the * database's order exactly. A locale collator does not β€” it is free to fold * case or ignore punctuation, and would silently diverge from the server on * precisely the ties this key exists to settle. */ declare function compareAgentIds(a: AgentSummary, b: AgentSummary): number; /** * The chosen sort key, then `id`. TOTAL for every option. * * Before 0.9.1 each branch below returned 0 for tied rows, which left their * relative order to whatever `Array.prototype.sort` had been handed β€” i.e. to * the transport. A host that fetched the RPC in one shot kept the server's * favourites-first/`updated_at DESC` order for ties; a host that paged it with * `order=id.asc` kept id-ASC. Same rows, same filters, two different screens. * The key below is the server's own, so both now render the server's answer. */ declare function applyAgentSortComparator(a: AgentSummary, b: AgentSummary, sortBy: AgentSortOption): number; /** All unique categories across live USER agents, sorted alphabetically. */ declare const selectAllAgentCategories: (a: readonly AgentSummary[]) => string[]; /** * All unique categories across live SYSTEM (builtin) agents. The system tab * gets the same category filter every other tab has β€” its options just come * from a different population. */ declare const selectAllSystemAgentCategories: (a: readonly AgentSummary[]) => string[]; /** All unique tags across live user agents, sorted alphabetically. */ declare const selectAllAgentTags: (a: readonly AgentSummary[]) => string[]; /** All unique tags across live SYSTEM (builtin) agents. */ declare const selectAllSystemAgentTags: (a: readonly AgentSummary[]) => string[]; /** Live user-type agents (excludes builtins). */ declare const selectUserTypeAgents: (a: readonly AgentSummary[]) => AgentSummary[]; /** Builtin/system agents only. For chat pickers and full catalogs. */ declare const selectBuiltinTypeAgents: (a: readonly AgentSummary[]) => AgentSummary[]; /** * The ONE ordering rule, and it is TOTAL on every path. * * searching: score DESC β†’ server rank ASC β†’ sort key β†’ id ASC * not searching: favorites first (when asked) β†’ sort key β†’ id ASC * * `id` ASC is the final key `agx_get_list` itself ends on, so two hosts that * fetched the same rows through different transports render the same screen. */ declare function sortFilteredAgents(filtered: AgentSummary[], consumer: AgentConsumerState): AgentSummary[]; /** User-type agents β€” mine / shared / all tabs with full consumer filters. */ declare function filterUserTypeAgents(agents: readonly AgentSummary[], consumer: AgentConsumerState): AgentSummary[]; /** * Builtin/system agents (tab === "system"). * * Gets the SAME filter surface every other tab has β€” search, favorites, * category, tags, sort, AND THE ARCHIVE FILTER. Only the ownership/access * filters are skipped, because a builtin has no owner to filter on. A picker * that offers less on one tab than another is the exact half-built shape this * list exists to prevent. * * 🚨 0.9.2 β€” THE BUILTIN ARCHIVE EXEMPTION IS GONE, AND IT IS NOW ILLEGAL. * * Until 0.9.1 this function applied no archive filter, on the theory that "a * builtin belongs to the platform and is never the user's to archive". The * database disagreed: `Foundry Planner` * (`001b22f6-fa4d-40c6-a1a4-8575d4d04379`) is an ARCHIVED builtin, and it * rendered at Public index 216 and was pickable β€” while Mine / Shared / All * hid their archived rows. One picker, two rules, and a person could choose a * retired agent from the one tab that did not obey. * * THE ARCHIVED-ITEMS LAW (`common-docs/policies/archived-items.md`, Arman * 2026-09-09) closed the question for every entity at once: "everything should * have an archive filter, and the default should always hide archived… all * archived items should always be treated the same." Clause 4 makes it a * class, not an instance β€” so there is no per-tab, per-entity or per-surface * exemption left to grant, here or anywhere. */ declare function filterBuiltinTypeAgents(agents: readonly AgentSummary[], consumer: AgentConsumerState): AgentSummary[]; /** Factory: owned user-type agents with consumer filters applied (ignores tab). */ declare const makeSelectFilteredOwnedAgents: () => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[]; /** Factory: shared user-type agents with consumer filters applied (ignores tab). */ declare const makeSelectFilteredSharedAgents: () => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[]; /** * Factory: filters and sorts agents for a consumer. User tabs (mine / shared / * all) draw from user-type agents; the system tab draws from builtins. * * `includeSystemInAll` is the ADMIN reading of the "All" tab. For a normal * user, system agents are a separate catalog and "All" means "all of MY * agents". For an admin surface, system agents ARE their agents β€” so "All" * must be a single blended list, with each row still labeled (the `system` * badge on `AgentRow`) so the two are never confused. */ declare const makeSelectFilteredAgents: (includeSystemInAll?: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[]; /** Factory: filters and sorts builtin agents for the chat agent picker. */ declare const makeSelectFilteredBuiltinAgents: () => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[]; interface AgentListPage { items: AgentSummary[]; hasMore: boolean; totalAfterCards: number; } /** Factory: slices filtered owned agents into the "cards" hero section. */ declare const makeSelectOwnedAgentCards: (isMobile: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[]; /** Factory: paginated list items for owned agents (everything after cards). */ declare const makeSelectOwnedAgentListItems: (isMobile: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentListPage; /** Factory: slices filtered shared agents into the "cards" hero section. */ declare const makeSelectSharedAgentCards: (isMobile: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[]; /** Factory: paginated list items for shared agents (everything after cards). */ declare const makeSelectSharedAgentListItems: (isMobile: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentListPage; /** Factory: total count of filtered agents for a consumer. */ declare const makeSelectFilteredAgentsCount: () => (a: readonly AgentSummary[], b: AgentConsumerState) => number; /** Factory: total count of filtered builtin agents for a consumer. */ declare const makeSelectFilteredBuiltinAgentsCount: () => (a: readonly AgentSummary[], b: AgentConsumerState) => number; /** Returns whether a consumer has any non-default filters active. */ declare function agentConsumerHasActiveFilters(consumer: AgentConsumerState, defaults: AgentConsumerState): boolean; /** * 🚨 A BADGE COUNTS WHAT THE TAB RENDERS. Nothing else. * * Before 0.9.1 these five counted every user-type row, archived included, * while `filterUserTypeAgents` (archive filter `active`, the default) hid the * archived ones. The badge said "Mine 416", the list showed 412 rows and the * footer said "412 agents" β€” one screen, three numbers, one of them a lie * about agents the user cannot see or reach from there. * * `is_archived` is now honored by the same rule the list uses, so the badge, * the footer and the rows agree on every tab and under every archive filter. * * 🚨 0.9.2 β€” AND THERE IS NO LONGER AN EXCEPTION. The system tab used to be * one ("a builtin is never the user's to archive"), which was false in the * database and illegal under THE ARCHIVED-ITEMS LAW. Its badge now counts * under the same archive rule its rows render under, so `Foundry Planner` * leaves the Public badge and the Public list together, or returns to both * together β€” never one without the other. */ declare function agentMatchesArchiveFilter(agent: AgentSummary, archFilter: AgentArchFilter): boolean; /** * The four tab badges for a consumer's CURRENT archive filter β€” the numbers * `useAgentListCore` puts on the tab strip. Flip the archive filter to * "archived" and the badges follow the list into the archive. */ declare const selectAgentTabCounts: (a: readonly AgentSummary[], b: AgentArchFilter) => { mine: number; shared: number; all: number; system: number; }; /** Live user-type agents the default (`active`) list renders. */ declare const selectTotalUserAgentsCount: (a: readonly AgentSummary[]) => number; declare const selectTotalOwnedAgentsCount: (a: readonly AgentSummary[]) => number; declare const selectTotalSharedAgentsCount: (a: readonly AgentSummary[]) => number; /** * Live builtin agents the DEFAULT (`active`) list renders β€” archived builtins * excluded, exactly like every other "total" beside it. Before 0.9.2 this * counted archived builtins too, which is how an archived `Foundry Planner` * reached the Public badge. */ declare const selectTotalBuiltinAgentsCount: (a: readonly AgentSummary[]) => number; declare const selectTotalFavoriteAgentsCount: (a: readonly AgentSummary[]) => number; /** * catalog/global-state.ts β€” @ai-matrx/agents/catalog * * 🚨 MODULE-LEVEL MUTABLE STATE IS BANNED IN THIS PACKAGE. With dual ESM+CJS * output and `splitting: false`, each loader graph instantiates its own copy * of every module β€” a module-level variable would silently split the * in-flight dedupe (so the "one RPC for ~20 mounting surfaces" collapse * would become two), the freshness stamp, the catalog registry, and the * default-row cache. Everything session-shared therefore lives on * `globalThis` under a `Symbol.for` slot, exactly like * `@ai-matrx/kit`'s confirm opener. Never "clean this up" into module locals. */ /** @internal Test-only: forget every session-shared value. */ declare function _resetCatalogGlobalState(): void; export { AGENT_CARDS_LIMIT_DESKTOP, AGENT_CARDS_LIMIT_MOBILE, AGENT_LIST_ITEMS_PER_PAGE, AGENT_NONE_SENTINEL, AGENT_SEARCH_LIMIT, ARCHIVE_FILTER_OPTIONS, ARCHIVE_FILTER_ORDER, AgentCatalogConfigError, AgentCatalogReadError, DEFAULT_AGENT_CATALOG_LABELS, DEFAULT_AGENT_CONSUMER_STATE, MandateDefaultRowError, SORT_OPTIONS, _resetCatalogGlobalState, agentArchiveFilterChipLabel, agentArchiveFilterControlLabel, agentArchiveFilterStateLabel, agentConsumerHasActiveFilters, agentListEmptyLabel, agentListFooterLabel, agentMatchesArchiveFilter, agentMatchesSearch, applyAgentSortComparator, asAgentId, assertAgentCatalogSchema, compareAgentIds, computeAgentSearchScore, createAgentCatalog, createMemoryStorage, defaultRowFromResolution, defaultStorage, detectDefaultRowDrift, filterBuiltinTypeAgents, filterUserTypeAgents, getRegisteredAgentCatalog, isAgentArchFilter, isMandateAgentId, makeSelectFilteredAgents, makeSelectFilteredAgentsCount, makeSelectFilteredBuiltinAgents, makeSelectFilteredBuiltinAgentsCount, makeSelectFilteredOwnedAgents, makeSelectFilteredSharedAgents, makeSelectOwnedAgentCards, makeSelectOwnedAgentListItems, makeSelectSharedAgentCards, makeSelectSharedAgentListItems, mandateAgentId, memoize1, memoize2, memoize3, nextArchFilter, orchestraDelegatesLine, orchestraDepthLine, orchestraLabel, orchestraModeLabel, parseMandateResolution, readAgentCatalogRows, readCachedDefaultRow, readCachedDefaultRowAsync, readSingleAgentRow, resolveDefaultRow, searchAgentsOnServer, selectAgentTabCounts, selectAllAgentCategories, selectAllAgentTags, selectAllSystemAgentCategories, selectAllSystemAgentTags, selectBuiltinTypeAgents, selectTotalBuiltinAgentsCount, selectTotalFavoriteAgentsCount, selectTotalOwnedAgentsCount, selectTotalSharedAgentsCount, selectTotalUserAgentsCount, selectUserTypeAgents, shouldDefaultAgentListToPublicTab, sortFilteredAgents, toAgentSummary, writeAgentFavorite, writeCachedDefaultRow, writeCachedDefaultRowAsync }; export type { AgentAccessFilter, AgentAccessLevel, AgentArchFilter, AgentArchFilterOption, AgentCatalog, AgentCatalogAgentType, AgentCatalogAsyncStorage, AgentCatalogClient, AgentCatalogConfig, AgentCatalogDefaults, AgentCatalogErrorSink, AgentCatalogFavorites, AgentCatalogIdentity, AgentCatalogLabels, AgentCatalogNavigate, AgentCatalogNotifier, AgentCatalogOpenPeek, AgentCatalogResolveHref, AgentCatalogRpcCall, AgentCatalogSchemaReport, AgentCatalogState, AgentCatalogStatus, AgentCatalogStorage, AgentCatalogStorageLike, AgentCatalogTransport, AgentConsumerPatch, AgentConsumerState, AgentFavFilter, AgentId, AgentListPage, AgentSearchable, AgentServerSearchResult, AgentSortOption, AgentSummary, AgentTab, AssertAgentCatalogSchemaOptions, DefaultRowState, MandateResolutionVerdict, OrchestraBadge, PgErrorLike, PgResultLike, ResolveDefaultRowArgs, ResolveDefaultRowOutcome, ResolvedDefaultRow };