import * as react from 'react'; import { ComponentType, ReactNode, Ref, KeyboardEventHandler, InputHTMLAttributes, SVGProps } from 'react'; /** * `@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"; }; /** `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"; /** * 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"; 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; } /** The subset of consumer state a caller may patch (paging is derived). */ type AgentConsumerPatch = Partial>; /** * `@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. */ /** * 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; /** 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; } /** * 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; /** * 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`. */ /** 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. */ /** 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; } /** * 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; } /** * The anchor a row renders for cmd/ctrl-click. DEFAULT (web): a plain `` โ€” * which is a REAL, working link in every framework. A Next.js host passes * `next/link`; a TanStack host passes its own `Link`. On React Native the * default is a pressable `Text` that routes through `navigate`. */ /** * What a row's link handler receives. A DOM `React.MouseEvent` satisfies this * structurally, and the native default link supplies the two methods itself โ€” * so a shared component can call `preventDefault()` on either platform * without a platform check. */ interface AgentCatalogLinkClickEvent { metaKey?: boolean; ctrlKey?: boolean; preventDefault(): void; stopPropagation(): void; } type AgentCatalogLinkComponent = ComponentType<{ href: string; className?: string; target?: string; rel?: string; title?: string; "aria-label"?: string; onClick?: (e: AgentCatalogLinkClickEvent) => void; children: ReactNode; }>; /** * Render a model id as whatever door the host has. DEFAULT: plain text. * `offeringId` is the agent's class pin when the row carries it (`null` = no * pin, the preferred class runs); ABSENT = unknown โ€” name the model alone. */ type AgentCatalogRenderModelRef = (args: { modelId: string; offeringId?: string | null; className?: string; }) => ReactNode; interface AgentCatalogUiPorts { /** DEFAULT (web): `window.location.assign`. DEFAULT (native): a reporting no-op. */ navigate?: AgentCatalogNavigate; /** DEFAULT: a plain `` (web) / a pressable `Text` (native). */ LinkComponent?: AgentCatalogLinkComponent; /** DEFAULT: the model id as plain text. */ renderModelRef?: AgentCatalogRenderModelRef; /** ABSENT: the sneak-peek affordance is HIDDEN โ€” never a dead button. */ openPeek?: AgentCatalogOpenPeek; /** * Portal container for the desktop popover, when the picker is mounted * inside a dialog. DEFAULT: the document body (Radix's own default). * Web only โ€” the native entry ignores it. */ popoverContainer?: HTMLElement | null; } /** The headless catalog this tree is bound to. Throws with no provider. */ declare function useAgentCatalog(): AgentCatalog; /** The UI ports this tree is bound to, with every default already applied. */ declare function useAgentCatalogUiPorts(): Required> & { openPeek: AgentCatalogOpenPeek | undefined; popoverContainer: HTMLElement | null | undefined; }; /** * Subscribe to a slice of catalog state. `useSyncExternalStore` because the * catalog is an external store and React 18/19 concurrency must not tear it. * `select` MUST return a stable reference for unchanged state โ€” every * selector in `../selectors` does. */ declare function useAgentCatalogState(select: (state: AgentCatalogState) => T): T; /** Every row currently in the catalog, as one stable array. */ declare function useAgentCatalogRows(): AgentSummary[]; interface AgentCatalogProviderProps extends AgentCatalogUiPorts { catalog: AgentCatalog; children: ReactNode; } declare function AgentCatalogProvider({ catalog, children, ...ports }: AgentCatalogProviderProps): react.JSX.Element; declare function lookUpAgentOnce(catalog: AgentCatalog, agentId: string): Promise; /** * ONE agent's catalog row โ€” name, description, category, tags, star, access โ€” read straight from * the catalog store (the ONE holder of agent identity; no host mirror). When the row is missing (or * only a thin picker row) that ONE row is read by id (`ensureAgentRows`, deduped) โ€” never the whole * catalog (FW-014); an id that read does not return is looked up by server search, which merges it * into the catalog โ€” once per id per catalog (the Board's remount law: a woken or remounted tile * asks nothing again). * * Takes the catalog explicitly so a host without `AgentCatalogProvider` (chat, workers) binds it. */ declare function useAgentSummary(catalog: AgentCatalog | null | undefined, agentId: string | null | undefined): AgentSummary | undefined; interface UseAgentConsumerReturn { tab: AgentTab; sortBy: AgentSortOption; searchTerm: string; includedCats: string[]; includedTags: string[]; favFilter: AgentFavFilter; archFilter: AgentArchFilter; /** * The archive-filter state this catalog starts consumers in โ€” the platform * default `"active"` unless the host set the knob. What "not the default * state" means on this surface, so the control and the footer can both say * it honestly. */ archFilterDefault: AgentArchFilter; accessFilter: AgentAccessFilter; favoritesFirst: boolean; listPage: number; sharedPage: number; /** The whole slot, for the selectors that take a consumer state. */ state: AgentConsumerState; /** True if any filter differs from its default value. */ hasActiveFilters: boolean; /** Tier 2 search โ€” also match against agent prompt content. */ deepSearch: boolean; /** True while a server-side search is in flight. */ isServerSearching: boolean; setSearchTerm: (value: string) => void; setDeepSearch: (value: boolean) => void; setSortBy: (value: AgentSortOption) => void; setTab: (value: AgentTab) => void; setFavFilter: (value: AgentFavFilter) => void; setArchFilter: (value: AgentArchFilter) => void; setAccessFilter: (value: AgentAccessFilter) => void; /** Add category to inclusion set; if already present, remove (toggle). */ toggleCategory: (cat: string) => void; /** Add tag to inclusion set; if already present, remove (toggle). */ toggleTag: (tag: string) => void; /** * Replace the WHOLE category inclusion set in one write. * * Prefer this over looping `toggleCategory` whenever more than one value * changes. Each `toggleCategory` call computes its next array from the * `includedCats` captured in the CURRENT render, so two toggles fired in the * same tick both start from the same snapshot and the second one's patch * overwrites the first โ€” a "Clear filter" click with three categories active * removes only one. One write of the final set cannot drift that way. */ setIncludedCats: (next: string[]) => void; /** Replace the WHOLE tag inclusion set in one write. See setIncludedCats. */ setIncludedTags: (next: string[]) => void; toggleFavoritesFirst: () => void; loadMoreList: () => void; loadMoreShared: () => void; resetFilters: () => void; } declare function useAgentConsumer(consumerId: string, options?: { /** Delete the slot on unmount. Default false โ€” persistent consumers keep state. */ unregisterOnUnmount?: boolean; /** Applied only when the consumer is first registered (idempotent). */ initialTab?: AgentTab; /** * THE ARCHIVED-ITEMS LAW's per-consumer override of the catalog-wide * `defaults.archiveFilter` knob. Applied only at first registration โ€” the * state belongs to the person after that. */ initialArchFilter?: AgentArchFilter; }): UseAgentConsumerReturn; declare function useAgentListCore({ consumerId, onSelect, navigateTo, activeAgentIdOverride, initialTab, includeSystemInAll, visibleTabs, initialArchFilter, excludeAgentIds, includeQuickAgents, defaultMandateKey, agentFilter, loadOnMount, }: AgentListCoreOptions): { agents: AgentSummary[]; archivedHiddenCount: number; footerLabel: string; isLoading: boolean; activeAgentId: string | null; pinnedAgent: AgentSummary | null; defaultRow: DefaultRowState | null; /** The host filter's chip label while it narrows the list; else null. */ agentFilterLabel: string | null; clearAgentFilter: () => void; resumeAgentFilter: () => void; allCategories: string[]; allTags: string[]; consumer: UseAgentConsumerReturn; tabCounts: AgentListTabCounts; activeFilterCount: number; hoveredAgent: AgentSummary | null; setHoveredAgent: react.Dispatch>; ensureLoaded: () => void; /** FW-014: the list reads a page at a time; true while the slice has more. */ paged: boolean; hasMore: boolean; loadingMore: boolean; loadMore: () => void; /** The tab's total (server count when paged) โ€” the footer's number. */ totalCount: number; ensureFullCatalog: () => void; handleSelectAgent: (agent: AgentSummary) => void; handleAgentHover: (agent: AgentSummary, panelOpen: boolean) => void; handleAgentHoverEnd: (agent: AgentSummary, onClear: () => void) => void; handleDetailPanelMouseEnter: () => void; handleDetailPanelMouseLeave: (onClear: () => void) => void; }; /** * The four ownership counts a tab strip prints. Declared HERE, beside the code * that computes them, so both the web tab strip and the native one name the * same shape (0.9.0 โ€” it used to live in the web-only `AgentListTabs`). * * ๐Ÿšจ These are the numbers on the BADGES, and since 0.9.1 they count exactly * the rows the tab renders under the consumer's own archive filter. A badge * that counted rows the list hid was the screen telling the user something * untrue about her own agents. */ interface AgentListTabCounts { mine: number; shared: number; all: number; system: number; } interface AgentListCoreOptions { consumerId: string; onSelect?: (agentId: string) => void; navigateTo?: string; /** Route-scoped agent id (chat/builder headers). `undefined` = nothing pinned. */ activeAgentIdOverride?: string | null; /** * Tab this surface opens on, applied once on first mount. Admin surfaces * that manage system agents pass `"system"`. When omitted the normal * user heuristic applies (a user with no agents of their own lands on the * public/system catalog). */ initialTab?: AgentTab; /** * ADMIN reading of the "All" tab: blend system agents into it instead of * keeping them to their own tab. See `makeSelectFilteredAgents`. */ includeSystemInAll?: boolean; /** * ๐Ÿšจ THE OWNERSHIP TABS THIS CALL SITE MAY REACH โ€” a HARD restriction, not a * default. * * `AgentListTabs` already hides the buttons this list omits, but hiding a * button never emptied the list behind it: the active tab lives in the * consumer's REMEMBERED state, and `initialTab` is applied exactly once per * mount (`defaultTabAppliedRef`). So a person who had been on "Mine" and * then moved a control that narrows the picker got the personal catalog * with the tab bar removed โ€” the forbidden list, and no way to see that it * was forbidden. That is the defect Arman named on the mandate binding * screen's global rung (VISION-RECONCILIATION D2, 2026-08-31: the picker * opened on "Mine ยท 40"). * * Given here, the tab is COERCED into the allowed set on every change of it, * not just at mount โ€” so a restriction that arrives later is enforced later. */ visibleTabs?: readonly AgentTab[]; /** * THE ARCHIVED-ITEMS LAW's per-consumer override of the catalog-wide * `defaults.archiveFilter` knob, applied once at first registration. Almost * nothing should pass it: the platform answer is "hide archived", and the * person reveals the archive from the control on the filter bar. */ initialArchFilter?: AgentArchFilter; /** Hide records that are invalid for this call site. */ excludeAgentIds?: readonly string[]; /** * Quick agents (`kind: quick`) are ordinary agents and DEFAULT ON: a list with no "My quick * agents" section must never silently drop a person's own agents. Only a picker that sits under * such a section (the composer pill's "Choose any agent") passes false, so nothing shows twice. */ includeQuickAgents?: boolean; /** * Show the platform DEFAULT agent for this mandate key at the top of the * list, resolved live through the server (never a hardcoded name). Requires * the catalog to have been built with a `transport`. */ defaultMandateKey?: string; /** * A HOST-SUPPLIED narrowing of the list (e.g. "agents whose model makes * images"). Never silent: every web presentation renders `label` as a chip * above the rows with a "Show all" control. The pinned current agent stays * visible whether or not it passes. "Show all" calls `onClear` when given * (the host clears its own source of truth); otherwise the filter is set * aside until `resumeAgentFilter()` (the dropdown calls it on every open). */ agentFilter?: AgentListFilter; /** * Load the roster when the hook mounts (DEFAULT true). A dropdown whose * closed trigger names nothing from the roster passes false: the list then * loads when it OPENS (its body calls `ensureLoaded`), never on page paint. */ loadOnMount?: boolean; } /** See `AgentListCoreOptions.agentFilter`. */ interface AgentListFilter { /** The chip's words, e.g. "Makes: Image". Keep it short โ€” one line. */ label: string; /** `true` keeps the agent in the list. */ test: (agent: AgentSummary) => boolean; /** Called by "Show all". Omit to have the picker set the filter aside itself. */ onClear?: () => void; } /** * How long the hover-preview detail panel stays open after the cursor leaves * an agent row (or the panel itself) before it closes. Generous on purpose: * the panel renders to the side of the list, so the user needs ample time to * travel the cursor across the gap to reach it without it vanishing. Paired * with a reserved, non-resizing popover width so the target never moves. */ declare const HOVER_GRACE_MS = 900; /** * THE RULE, ALONE AND TESTABLE: which tab a restricted picker must land on. * * Returns `null` when the current tab is already allowed (nothing to do), and * otherwise the tab to switch to โ€” `initialTab` when that is itself allowed, * else the first allowed tab. */ declare function coerceVisibleTab(current: AgentTab, allowed: readonly AgentTab[], initialTab?: AgentTab): AgentTab | null; interface AgentListDropdownProps { onSelect?: (agentId: string) => void; navigateTo?: string; className?: string; label?: string; /** Custom trigger element โ€” replaces the default text button. */ triggerSlot?: React.ReactNode; /** Remove the border from the default trigger button. */ noBorder?: boolean; /** Use a compact (h-5) trigger instead of the default h-7. */ compact?: boolean; /** Route-scoped agent id โ€” highlights + pins the current agent in the list. */ activeAgentId?: string | null; /** * Which side the desktop popover opens toward. Defaults to Radix's "bottom". * Pass "right" when the trigger lives in a narrow vertical rail so the panel * opens beside the rail instead of covering it. Ignored on mobile (Drawer). */ contentSide?: "top" | "right" | "bottom" | "left"; /** * Isolate this dropdown's filter/tab state under its own consumer id. * Defaults to the shared `agent-list-dropdown` consumer so ordinary chat * triggers keep one remembered set of filters across the app. */ consumerId?: string; /** Tab to open on. Admin surfaces managing system agents pass `"system"`. */ initialTab?: AgentTab; /** ADMIN variant: blend system agents into the "All" tab. */ includeSystemInAll?: boolean; /** Restrict which ownership tabs are available for domain-constrained pickers. */ visibleTabs?: readonly AgentTab[]; /** Label override for the builtin-agent tab. Admin surfaces use "System". */ systemTabLabel?: string; /** Resolve the route used by each named agent door. */ resolveAgentHref?: (agent: AgentSummary) => string; /** Show the active agent above the filtered list. Default true. */ showPinnedAgent?: boolean; /** Hide records that are invalid for this call site. */ excludeAgentIds?: readonly string[]; /** * Offer the platform DEFAULT agent for this mandate key at the top of the * list, named LIVE from the mandate's real Holder. `onSelect` receives * `mandate:` for that row โ€” the ref shape every client already speaks. */ defaultMandateKey?: string; /** * Host-supplied narrowing (e.g. "Makes: Image"). Shown as a chip at the top * of the list with "Show all"; the pinned current agent stays visible. A * filter set aside by "Show all" (no `onClear`) applies again on next open. */ agentFilter?: AgentListFilter; /** Default true. False only under a host's own "My quick agents" section, so nothing shows twice. */ includeQuickAgents?: boolean; /** Told when the list opens or closes โ€” a host that loads its own data on engage. */ onOpenChange?: (open: boolean) => void; } declare function AgentListDropdown({ onSelect, navigateTo, className, label, triggerSlot, noBorder, compact, activeAgentId: activeAgentIdProp, contentSide, consumerId, initialTab, includeSystemInAll, visibleTabs, systemTabLabel, resolveAgentHref: resolveAgentHrefProp, showPinnedAgent, excludeAgentIds, defaultMandateKey, agentFilter, includeQuickAgents, onOpenChange, }: AgentListDropdownProps): react.JSX.Element; /** The desktop picker's fixed footprint: a 340px list + a 340px peek. */ declare const PANEL_WIDTH = "680px"; type AgentListCore = ReturnType; interface AgentListPanelDisplayProps { /** Restrict which ownership tabs are available for domain-constrained pickers. */ visibleTabs?: readonly AgentTab[]; /** Label override for the builtin-agent tab. Admin surfaces use "System". */ systemTabLabel?: string; /** ADMIN variant: blend system agents into the "All" tab (also labels it "System"). */ includeSystemInAll?: boolean; /** Resolve the route used by each named agent door. */ resolveAgentHref?: (agent: AgentSummary) => string; /** Show the active agent above the filtered list. Default true. */ showPinnedAgent?: boolean; /** Focus the search field when the picker appears. Default true. */ autoFocusSearch?: boolean; /** Runs after an agent is chosen (the core has already called `onSelect`). */ onAfterSelect?: (agent: AgentSummary) => void; className?: string; } interface AgentListPanelBodyProps extends AgentListPanelDisplayProps { /** The list core the host holds (`useAgentListCore`). */ core: AgentListCore; } /** The picker body over a core the host already holds. Fills its parent. */ declare function AgentListPanelBody({ core, visibleTabs, systemTabLabel, includeSystemInAll, resolveAgentHref, showPinnedAgent, autoFocusSearch, onAfterSelect, className, }: AgentListPanelBodyProps): react.JSX.Element; interface AgentListPanelProps extends AgentListPanelDisplayProps { /** * Fill the parent instead of the fixed `PANEL_WIDTH` ร— `PANEL_HEIGHT` * footprint โ€” for a resizable host (a window). The list column takes the * extra width; the peek column stays 340px. */ fill?: boolean; /** Unique consumer id for this picker's filter/tab state. */ consumerId: string; /** The chosen agent's id (`mandate:` for a mandate default row). */ onSelect?: (agentId: string) => void; /** Navigate on select instead of (or besides) `onSelect`; `{id}` is replaced. */ navigateTo?: string; /** Highlights + pins this agent in the list. */ activeAgentId?: string | null; /** Tab to open on. Admin surfaces managing system agents pass `"system"`. */ initialTab?: AgentTab; /** Hide records that are invalid for this call site. */ excludeAgentIds?: readonly string[]; /** Offer the mandate's live-resolved default agent at the top of the list. */ defaultMandateKey?: string; /** Host-supplied narrowing, shown as a chip with "Show all". */ agentFilter?: AgentListFilter; /** Default true. False only under a host's own "My quick agents" section, so nothing shows twice. */ includeQuickAgents?: boolean; } /** * The self-contained picker: owns its list core. Desktop renders at the fixed * `PANEL_WIDTH` ร— `PANEL_HEIGHT` footprint (or fills its parent with `fill`); * mobile fills its parent. */ declare function AgentListPanel({ consumerId, onSelect, navigateTo, activeAgentId, initialTab, includeSystemInAll, visibleTabs, excludeAgentIds, defaultMandateKey, agentFilter, includeQuickAgents, resolveAgentHref: resolveAgentHrefProp, className, fill, ...display }: AgentListPanelProps): react.JSX.Element; interface AgentListInlinePickerProps { /** Unique consumer id for filter/tab state (see `useAgentConsumer`). */ consumerId: string; onSelect: (agentId: string) => void; activeAgentId?: string | null; className?: string; /** Auto-focus the search field on mount. Default true. */ autoFocusSearch?: boolean; /** Tab to open on. Admin surfaces that manage system agents pass `"system"`. */ initialTab?: AgentTab; /** ADMIN variant: blend system agents into the "All" tab. */ includeSystemInAll?: boolean; /** Restrict which ownership tabs are available for domain-constrained pickers. */ visibleTabs?: readonly AgentTab[]; /** Label override for the builtin-agent tab. Admin surfaces use "System". */ systemTabLabel?: string; /** Resolve the route used by each named agent door. */ resolveAgentHref?: (agent: AgentSummary) => string; /** Show the globally active agent above the filtered list. Default true. */ showPinnedAgent?: boolean; /** Hide records that are invalid for this call site. */ excludeAgentIds?: readonly string[]; /** Offer the mandate's live-resolved default agent at the top of the list. */ defaultMandateKey?: string; /** Host-supplied narrowing, shown as a chip with "Show all". */ agentFilter?: AgentListFilter; /** Default true. False only under a host's own "My quick agents" section, so nothing shows twice. */ includeQuickAgents?: boolean; } declare function AgentListInlinePicker({ consumerId, onSelect, activeAgentId, className, autoFocusSearch, initialTab, includeSystemInAll, visibleTabs, systemTabLabel, resolveAgentHref, showPinnedAgent, excludeAgentIds, defaultMandateKey, agentFilter, includeQuickAgents, }: AgentListInlinePickerProps): react.JSX.Element; interface AgentListTabsProps { consumer: UseAgentConsumerReturn; tabCounts: AgentListTabCounts; /** Limit the ownership tabs for constrained pickers. */ visibleTabs?: readonly AgentTab[] | undefined; /** * Label for the builtin-agent tab. Users see "Public" (that is what a * platform agent is to them); ADMIN surfaces pass "System", because there * these are the agents they own and maintain. */ systemTabLabel?: string | undefined; } declare function AgentListTabs({ consumer, tabCounts, systemTabLabel, visibleTabs, }: AgentListTabsProps): react.JSX.Element; /** * catalog/react/types.ts โ€” VERBATIM port of matrx-frontend * `features/agents/components/agent-listings/core/types.ts`. */ type RightPanel = "detail" | "sort" | "categories" | "tags" | null; declare const PANEL_HEIGHT = "528px"; /** * ๐Ÿšจ THE PANEL NEVER OUTGROWS THE SPACE IT HAS. * * The popover reserves a FIXED footprint on purpose (a resizing panel in a * narrow rail gets collision-shifted out from under the cursor). But a flat * 528px is taller than the room above a trigger sitting mid-page on a laptop, * and Radix cannot shift what does not fit either way โ€” so the top of the * panel, which is the SEARCH BOX, was rendered off-screen. Measured 2026-09-08 * on the mandate Holder screen at 1280x900: the popover sat at `top: -76`. * * Clamping to Radix's own measurement keeps the footprint fixed whenever there * IS room and merely shortens the list when there is not โ€” the panel still * never resizes while open, because the available height does not change while * the popover is open. */ declare const LIST_MAX_HEIGHT = "min(528px, var(--radix-popper-available-height, 528px))"; /** * ๐Ÿšจ OPEN BELOW THE CONTROL WHENEVER THERE IS ROOM TO USE IT. * * Radix flips a popover to the side with more room using the panel's FULL * height, so a trigger in the middle of a dialog (the mandate "Use my own" * picker on a 1280x900 screen: 418px below, 528px panel) sent the whole panel * ABOVE it โ€” over the dialog's own title and controls, reading as a list * floating at the top of the screen (2026-09-26). This measures the room * below at open time: when a usable list fits there, the panel is sized to it * and pinned to `side="bottom"`; only when it does not is Radix left to flip. * The height is fixed for the whole time the panel is open, so nothing moves * under the cursor (the reason the footprint is fixed at all). * * Returns a px height, or `null` for "use the default footprint and placement". */ declare function panelHeightBelow(trigger: Element | null, options?: { full?: number; minUsable?: number; gap?: number; }): string | null; /** * ๐Ÿšจ A SIDE-OPENING PANEL NEVER LEAVES THE SCREEN. * * A host may ask for `side="left"` / `"right"` (the chat composer's presets * panel opens "All agents" to its left). Radix shifts a side-placed popover * only VERTICALLY, so when the 680px panel does not fit on that side it runs * off the screen edge and the search box, tabs and names are cut off (blind run * PB-03, /chat/new in an 800px pane, 2026-10-01). Measured at open time: keep * the asked side when the panel fits there, else the opposite side when it * fits, else open below, where Radix can shift it horizontally into view. * Vertical sides pass through unchanged. */ declare function sideThatFits(trigger: Element | null, side: "top" | "right" | "bottom" | "left", options?: { width?: number; gap?: number; }): "top" | "right" | "bottom" | "left"; interface AgentListContentProps { agents: AgentSummary[]; isLoading: boolean; consumer: UseAgentConsumerReturn; activeAgentId: string | null; allCategories: string[]; allTags: string[]; inputRef: React.RefObject; onSelectAgent: (a: AgentSummary) => void; /** Per-row href for cmd/ctrl+click. Defaults to `/agents/go/[id]`. */ resolveAgentHref?: ((agent: AgentSummary) => string) | undefined; onReset: () => void; activeFilterCount: number; isMobile: boolean; hoveredAgent: AgentSummary | null; onAgentHover: (a: AgentSummary) => void; onAgentHoverEnd: (a: AgentSummary) => void; onDetailPress: (a: AgentSummary) => void; onFilterChipClick: (panel: "sort" | "categories" | "tags") => void; rightPanel: RightPanel; tabCounts: AgentListTabCounts; /** Limit the ownership tabs shown by this picker. */ visibleTabs?: readonly AgentTab[] | undefined; /** Label override for the builtin tab โ€” admin surfaces pass "System". */ systemTabLabel?: string | undefined; /** Resolved current agent โ€” pinned at the top when the list opens. */ pinnedAgent?: AgentSummary | null | undefined; /** The mandate default row, when this picker was given a `defaultMandateKey`. */ defaultRow?: DefaultRowState | null | undefined; /** When true, scroll the list to the pinned row (top). */ listOpen?: boolean | undefined; /** * Rows the ARCHIVE FILTER is currently keeping off this screen โ€” from * `useAgentListCore`. The footer names them (clause 2 of THE ARCHIVED-ITEMS * LAW: a screen never implies the list is everything). 0 means "nothing * archived is hidden behind this view", which is also what a host that * mounts this component directly and passes nothing gets. */ archivedHiddenCount?: number | undefined; /** * The host filter's label while it narrows this list (from * `useAgentListCore().agentFilterLabel`). Rendered as a chip above the rows * โ€” a host filter is never silent. */ agentFilterLabel?: string | null | undefined; /** "Show all" on the host-filter chip. */ onClearAgentFilter?: (() => void) | undefined; /** * FW-014 paging: true while the list has more rows on the server. The list * asks `onReachEnd` for the next page when the scroll nears the end (or when * the rows do not fill the viewport yet). */ hasMore?: boolean | undefined; onReachEnd?: (() => void) | undefined; /** True while the next page is in flight โ€” a spinner row closes the list. */ loadingMore?: boolean | undefined; /** The tab's total for the footer (server count when paged); defaults to the rendered rows. */ totalCount?: number | undefined; } declare function AgentListContent({ agents, isLoading, consumer, activeAgentId, allCategories, allTags, inputRef, onSelectAgent, resolveAgentHref, onReset, activeFilterCount, isMobile, hoveredAgent, onAgentHover, onAgentHoverEnd, onDetailPress, onFilterChipClick, rightPanel, tabCounts, visibleTabs, systemTabLabel, pinnedAgent, defaultRow, listOpen, archivedHiddenCount, agentFilterLabel, onClearAgentFilter, hasMore, onReachEnd, loadingMore, totalCount, }: AgentListContentProps): react.JSX.Element; interface AgentFilterBarProps { consumer: UseAgentConsumerReturn; allCategories: string[]; allTags: string[]; activeFilterCount: number; isMobile: boolean; rightPanel: RightPanel; onFilterChipClick: (panel: "sort" | "categories" | "tags") => void; onReset: () => void; } /** * The filter bar is IDENTICAL on every tab, system included. A tab that offers * fewer controls than its neighbour is the half-built shape this list exists * to prevent โ€” the system tab's category/tag options simply come from the * builtin population (see `selectAllSystemAgentCategories`). * * ๐Ÿšจ THE ARCHIVE CONTROL IS NOT OPTIONAL (0.9.2). It renders here, on every * tab, for every consumer โ€” including one whose `visibleTabs` removes the tab * strip above it, since this bar is mounted unconditionally by * `AgentListContent`. THE ARCHIVED-ITEMS LAW clause 3: revealing archived * items is one or two clicks away, "on the surface itself โ€” never zero * (impossible) and never buried in another page". One click of this chip * shows all; a second shows archived only; a third hides them again. */ declare function AgentFilterBar({ consumer, allCategories, allTags, activeFilterCount, isMobile, rightPanel, onFilterChipClick, onReset, }: AgentFilterBarProps): react.JSX.Element; interface AgentRowProps { agent: AgentSummary; isActive: boolean; isHovered: boolean; isMobile: boolean; /** Row href. Defaults to `/agents/go/[id]`. */ href?: string | undefined; onClick: () => void; onHover: () => void; onHoverEnd: () => void; onDetailPress: () => void; } declare function AgentRow({ agent, isActive, isHovered, isMobile, href, onClick, onHover, onHoverEnd, onDetailPress, }: AgentRowProps): react.JSX.Element; interface AgentDetailCardProps { agent: AgentSummary; /** ABSENT: no Select button โ€” the card describes the agent already chosen. */ onSelect?: () => void; /** * The host's Duplicate (the composer's agent peek: the viewer's own copy of an agent they do not * own). Drawn behind More, never beside Copy. ABSENT or null: no Duplicate. */ duplicate?: { onDuplicate: () => void; busy?: boolean; } | null; /** A Copy or More menu is open: a hover preview that closes on pointer-leave holds on this. */ onHoldChange?: (held: boolean) => void; } declare function AgentDetailCard({ agent, onSelect, duplicate, onHoldChange }: AgentDetailCardProps): react.JSX.Element; interface AgentSortPanelProps { consumer: UseAgentConsumerReturn; onClose: () => void; } declare function AgentSortPanel({ consumer, onClose }: AgentSortPanelProps): react.JSX.Element; interface AgentCategoriesPanelProps { consumer: UseAgentConsumerReturn; allCategories: string[]; search: string; setSearch: (v: string) => void; onClose: () => void; } declare function AgentCategoriesPanel({ consumer, allCategories, search, setSearch, onClose, }: AgentCategoriesPanelProps): react.JSX.Element; interface AgentTagsPanelProps { consumer: UseAgentConsumerReturn; allTags: string[]; search: string; setSearch: (v: string) => void; onClose: () => void; } declare function AgentTagsPanel({ consumer, allTags, search, setSearch, onClose, }: AgentTagsPanelProps): react.JSX.Element; interface AgentMobileSubViewProps { view: "sort" | "categories" | "tags"; consumer: UseAgentConsumerReturn; allCategories: string[]; allTags: string[]; catSearch: string; setCatSearch: (v: string) => void; tagSearch: string; setTagSearch: (v: string) => void; onBack: () => void; } declare function AgentMobileSubView({ view, consumer, allCategories, allTags, catSearch, setCatSearch, tagSearch, setTagSearch, onBack, }: AgentMobileSubViewProps): react.JSX.Element; interface FavoriteAgentButtonProps { id: string; /** "card" = absolute-positioned corner star, "list" = inline icon button */ variant?: "card" | "list"; disabled?: boolean; } declare function FavoriteAgentButton({ id, variant, disabled, }: FavoriteAgentButtonProps): react.JSX.Element; declare const CatalogSearchField: ({ ref, value, onChange, placeholder, onKeyDown, inputProps, }: { ref?: Ref; value: string; onChange: (v: string) => void; placeholder: string; /** Keyboard handler โ€” the agent list drives its listbox from the field. */ onKeyDown?: KeyboardEventHandler; /** ARIA wiring (combobox role, aria-controls, aria-activedescendant). */ inputProps?: Pick, "role" | "aria-controls" | "aria-activedescendant" | "aria-expanded" | "aria-autocomplete" | "aria-label">; }) => react.JSX.Element; declare function FilterChip({ icon: Icon, label, active, focused, onClick, ariaLabel, testId, }: { icon: ComponentType<{ className?: string; }>; label: string; active: boolean; focused?: boolean; onClick: () => void; /** Accessible name when the visible label alone would not say the state. */ ariaLabel?: string; /** Stable hook so a shell test can measure the control, not a class name. */ testId?: string; }): react.JSX.Element; declare function OptionRow({ label, selected, onClick, }: { label: string; selected: boolean; onClick: () => void; }): react.JSX.Element; declare function CheckRow({ label, checked, onClick, }: { label: string; checked: boolean; onClick: () => void; }): react.JSX.Element; declare function SidePanelHeader({ title, onClose, }: { title: string; onClose: () => void; }): react.JSX.Element; type IconProps = SVGProps & { className?: string; }; /** * The Orchestra marker (Lucide `music-4`): the glyph the Workflow Studio's * picker used for a conductor, kept identical so the same agent wears the same * mark in every client. */ declare const Music4Icon: (props: IconProps) => react.JSX.Element; /** * 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; export { AgentCatalogProvider, AgentCategoriesPanel, AgentDetailCard, AgentFilterBar, AgentListContent, AgentListDropdown, AgentListInlinePicker, AgentListPanel, AgentListPanelBody, AgentListTabs, AgentMobileSubView, AgentRow, AgentSortPanel, AgentTagsPanel, CatalogSearchField, CheckRow, FavoriteAgentButton, FilterChip, HOVER_GRACE_MS, LIST_MAX_HEIGHT, Music4Icon, OptionRow, PANEL_HEIGHT, PANEL_WIDTH, SORT_OPTIONS, SidePanelHeader, coerceVisibleTab, lookUpAgentOnce, orchestraDelegatesLine, orchestraDepthLine, orchestraLabel, orchestraModeLabel, panelHeightBelow, sideThatFits, useAgentCatalog, useAgentCatalogRows, useAgentCatalogState, useAgentCatalogUiPorts, useAgentConsumer, useAgentListCore, useAgentSummary }; export type { AgentCatalogLinkComponent, AgentCatalogProviderProps, AgentCatalogRenderModelRef, AgentCatalogUiPorts, AgentCategoriesPanelProps, AgentDetailCardProps, AgentFilterBarProps, AgentListContentProps, AgentListCoreOptions, AgentListDropdownProps, AgentListFilter, AgentListInlinePickerProps, AgentListPanelBodyProps, AgentListPanelProps, AgentListTabCounts, AgentMobileSubViewProps, AgentRowProps, AgentSortPanelProps, AgentTagsPanelProps, IconProps, OrchestraBadge, RightPanel, UseAgentConsumerReturn };