@ai-matrx/agents 0.6.2 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,868 @@
1
+ /**
2
+ * `@ai-matrx/agents/catalog` — the row shape and the per-consumer view state.
3
+ *
4
+ * `AgentSummary` is the 19-column row `agx_get_list_full()` returns, camelCased.
5
+ * It is deliberately NOT the editable agent record: dirty tracking, undo,
6
+ * messages, tools and `_fetchStatus` stay in whichever host owns the editor.
7
+ * A list row is a list row.
8
+ *
9
+ * The view-state types are a VERBATIM port of matrx-frontend
10
+ * `features/agents/redux/agent-consumers/slice.ts` — only the coupling seam
11
+ * inverts (Redux reducers become store actions in `./store`).
12
+ */
13
+ /**
14
+ * A branded agent id. Every id that reaches `onSelect` is one of these:
15
+ * either a `agent.definition` UUID, or the `mandate:<key>` reference a
16
+ * default row carries (byte-identical to the ref shape matrx-extend
17
+ * `src/lib/mandates.ts` and matrx-local `desktop/src/lib/mandates.ts`
18
+ * already hand back to their hosts).
19
+ *
20
+ * 🚨 The brand is a STRING LITERAL, never `unique symbol`. This package emits
21
+ * `./catalog` and `./catalog/react` as two separate bundles, so each `.d.ts`
22
+ * carries its own copy of these declarations. A `unique symbol` brand is
23
+ * NOMINAL: two copies of it are unrelated types, so `createAgentCatalog()`
24
+ * from `./catalog` was not assignable to `<AgentCatalogProvider catalog=…>`
25
+ * from `./catalog/react` — the exact two-import wiring the README, the
26
+ * CHANGELOG and every consumer use. A literal brand is structural, so the two
27
+ * copies unify, while a bare `string` still cannot be passed where an
28
+ * `AgentId` is required (which is the whole point of branding). Any future
29
+ * brand in this package follows the same rule.
30
+ */
31
+ type AgentId = string & {
32
+ readonly __agentId: "ai-matrx.agent-id";
33
+ };
34
+ /** Brand a raw string as an `AgentId`. */
35
+ declare function asAgentId(value: string): AgentId;
36
+ /** The `mandate:<key>` ref shape every client already speaks. */
37
+ declare function mandateAgentId(mandateKey: string): AgentId;
38
+ /** True when an id is a mandate default row rather than a stored agent. */
39
+ declare function isMandateAgentId(id: string): boolean;
40
+ /** `agent_type` verbatim. Only `builtin` changes how a row is grouped. */
41
+ type AgentCatalogAgentType = "user" | "builtin";
42
+ /**
43
+ * `access_level` verbatim from `agx_get_list_full`. Widened over the raw
44
+ * string because it is a DB-owned vocabulary: an unknown level renders as
45
+ * itself rather than being coerced into a wrong one.
46
+ */
47
+ type AgentAccessLevel = "owner" | "admin" | "editor" | "viewer" | "system" | (string & {});
48
+ /**
49
+ * One `agx_get_list_full()` row, camelCased. Nineteen columns, no more:
50
+ * anything richer needs a different read and is a different type.
51
+ */
52
+ interface AgentSummary {
53
+ id: AgentId;
54
+ name: string | null;
55
+ description: string | null;
56
+ category: string | null;
57
+ tags: string[];
58
+ agentType: AgentCatalogAgentType;
59
+ modelId: string | null;
60
+ isActive: boolean;
61
+ isArchived: boolean;
62
+ isFavorite: boolean;
63
+ createdBy: string | null;
64
+ organizationId: string | null;
65
+ taskId: string | null;
66
+ sourceAgentId: string | null;
67
+ createdAt: string | null;
68
+ updatedAt: string | null;
69
+ isOwner: boolean | null;
70
+ accessLevel: AgentAccessLevel | null;
71
+ sharedByEmail: string | null;
72
+ /**
73
+ * Set only on a mandate default row — the key it resolved through, so a
74
+ * host can tell "the default" from a stored agent without parsing the id.
75
+ */
76
+ mandateKey?: string;
77
+ }
78
+ type AgentSortOption = "updated-desc" | "created-desc" | "name-asc" | "name-desc" | "category-asc";
79
+ /** Which ownership tab is active in the agent list. */
80
+ type AgentTab = "mine" | "shared" | "all" | "system";
81
+ /** Favorite filter. */
82
+ type AgentFavFilter = "all" | "yes" | "no";
83
+ /** Archive filter. */
84
+ type AgentArchFilter = "active" | "archived" | "both";
85
+ /**
86
+ * Access level filter.
87
+ * 'any' = no restriction (default).
88
+ * 'owned' = only agents the user owns (isOwner = true).
89
+ * 'shared' = only agents shared with the user (isOwner = false).
90
+ * 'editable' = owner + admin + editor.
91
+ */
92
+ type AgentAccessFilter = "any" | "owned" | "shared" | "editable";
93
+ /** Sentinel meaning "include uncategorized / untagged" items. */
94
+ declare const AGENT_NONE_SENTINEL = "__none__";
95
+ interface AgentConsumerState {
96
+ tab: AgentTab;
97
+ sortBy: AgentSortOption;
98
+ searchTerm: string;
99
+ /** INCLUSION model: empty = show all; non-empty = only matching. */
100
+ includedCats: string[];
101
+ /** INCLUSION model: empty = show all; non-empty = only matching. */
102
+ includedTags: string[];
103
+ favFilter: AgentFavFilter;
104
+ archFilter: AgentArchFilter;
105
+ accessFilter: AgentAccessFilter;
106
+ favoritesFirst: boolean;
107
+ /** Current page for owned-agent list items (after the card section). */
108
+ listPage: number;
109
+ /** Current page for shared-agent list items. */
110
+ sharedPage: number;
111
+ /**
112
+ * Ids returned by the last server-side search (`agx_search`), in server rank
113
+ * order. Additive: an agent in this list survives the search filter even
114
+ * when the local scorer gives it 0.
115
+ *
116
+ * That is load-bearing for tier 2. A deep search matches an agent's prompt
117
+ * content, which the client never loads — so the local scorer cannot see it.
118
+ * Without this list the server would return prompt matches and the UI would
119
+ * immediately filter them back out.
120
+ *
121
+ * Server-only matches sort BELOW every locally-scored match, in server rank
122
+ * order, so obvious matches always come first.
123
+ */
124
+ serverMatchedIds: string[];
125
+ /** True while a server search is in flight — drives the search spinner. */
126
+ isServerSearching: boolean;
127
+ /** Tier 2: also search agent prompt content. Opt-in, per consumer. */
128
+ deepSearch: boolean;
129
+ }
130
+ declare const DEFAULT_AGENT_CONSUMER_STATE: AgentConsumerState;
131
+ /** The subset of consumer state a caller may patch (paging is derived). */
132
+ type AgentConsumerPatch = Partial<Omit<AgentConsumerState, "listPage" | "sharedPage">>;
133
+ /**
134
+ * Thrown by `createAgentCatalog` when a REQUIRED port is missing or an
135
+ * optional feature was asked for without the port it needs. Never a warning,
136
+ * never a degraded catalog: a picker that cannot read is not a picker.
137
+ */
138
+ declare class AgentCatalogConfigError extends Error {
139
+ readonly name = "AgentCatalogConfigError";
140
+ /** Machine code, for a host that routes config failures. */
141
+ readonly code: string;
142
+ /** What the host must do, in a sentence a person can act on. */
143
+ readonly remedy: string;
144
+ constructor(args: {
145
+ code: string;
146
+ message: string;
147
+ remedy: string;
148
+ });
149
+ }
150
+
151
+ /**
152
+ * `@ai-matrx/agents/catalog` — THE PORTS.
153
+ *
154
+ * Two are required (`client`, `identity`); every other one ships a working
155
+ * default with a DOCUMENTED degradation, per THE ALL-INCLUSIVE LAW. A host
156
+ * injects identity and a Supabase client and nothing else; the picker's only
157
+ * contract back to the host is `onSelect(agentId)`.
158
+ *
159
+ * React appears nowhere in this file — it is import-inert and legal in any
160
+ * graph, server components included.
161
+ */
162
+
163
+ /** A PostgREST error, structurally. */
164
+ interface PgErrorLike {
165
+ message: string;
166
+ code?: string;
167
+ details?: string | null;
168
+ hint?: string | null;
169
+ }
170
+ /** What every supabase-js call resolves to, structurally. */
171
+ interface PgResultLike {
172
+ data: unknown;
173
+ error: PgErrorLike | null;
174
+ count?: number | null;
175
+ }
176
+ /**
177
+ * The thenable builder supabase-js returns from `.rpc()`. `range` is what
178
+ * makes a COMPLETE list read possible (`readAllRows`); `order` is what makes
179
+ * that paging stable. Both are the real builder's own methods — this is a
180
+ * structural subset, never a re-implementation.
181
+ */
182
+ interface AgentCatalogRpcCall extends PromiseLike<PgResultLike> {
183
+ range(from: number, to: number): PromiseLike<PgResultLike>;
184
+ order(column: string, options?: {
185
+ ascending?: boolean;
186
+ }): AgentCatalogRpcCall;
187
+ }
188
+ /** The one write this package performs: `agent.definition.is_favorite`. */
189
+ interface AgentCatalogTableQuery {
190
+ update(patch: Record<string, unknown>): {
191
+ eq(column: string, value: string): PromiseLike<{
192
+ data?: unknown;
193
+ error: PgErrorLike | null;
194
+ }>;
195
+ };
196
+ }
197
+ /**
198
+ * REQUIRED. A structural subset of `SupabaseClient` — anything that can answer
199
+ * `agx_get_list_full`, `agx_search`, `agx_resolve_agent_address` and write one
200
+ * column on `agent.definition`. A real supabase-js client satisfies it as-is.
201
+ *
202
+ * Absent → `createAgentCatalog` throws `AgentCatalogConfigError`. There is no
203
+ * degraded mode: the package IS the catalogue read.
204
+ */
205
+ interface AgentCatalogClient {
206
+ rpc(fn: string, args?: Record<string, unknown>, options?: {
207
+ count?: "exact" | "planned" | "estimated";
208
+ }): AgentCatalogRpcCall;
209
+ schema(name: string): {
210
+ from(table: string): AgentCatalogTableQuery;
211
+ };
212
+ }
213
+ /**
214
+ * REQUIRED. Who is asking. Needed for the "zero owned agents → open on the
215
+ * public tab" heuristic and for the favorite write.
216
+ *
217
+ * `requireUserId` THROWS when there is no session — never a silent anonymous
218
+ * read. The catalog catches that throw in exactly ONE place (the tab-default
219
+ * heuristic, where a signed-out visitor legitimately lands on the public
220
+ * catalogue) and reports it to the `errorSink` the first time; every other
221
+ * caller lets it propagate.
222
+ */
223
+ interface AgentCatalogIdentity {
224
+ requireUserId(): string;
225
+ }
226
+ /**
227
+ * Every degraded path, every failed read, every drift detection reports here.
228
+ * DEFAULT: a tagged `console.error` that announces ONCE that no sink is bound
229
+ * and names the remedy.
230
+ */
231
+ type AgentCatalogErrorSink = (event: {
232
+ code: string;
233
+ message: string;
234
+ context?: object;
235
+ }) => void;
236
+ /**
237
+ * The drift scream's second channel. DEFAULT: none — and that is safe
238
+ * ONLY because the in-picker persistent banner is the primary scream and
239
+ * always renders. A drift is never console-only.
240
+ */
241
+ type AgentCatalogNotifier = (event: {
242
+ level: "warning" | "error";
243
+ title: string;
244
+ message: string;
245
+ }) => void;
246
+ /**
247
+ * First-paint cache for resolved default rows. DEFAULT: `localStorage` when
248
+ * present, else an in-memory map (so SSR and a Chrome service worker both
249
+ * work). Absent storage degrades to "no first paint of the default row until
250
+ * the mandate resolves" — never a wrong name on screen.
251
+ */
252
+ interface AgentCatalogStorage {
253
+ getItem(key: string): string | null;
254
+ setItem(key: string, value: string): void;
255
+ removeItem(key: string): void;
256
+ }
257
+ /** Every user-visible string the picker renders, overridable per host. */
258
+ interface AgentCatalogLabels {
259
+ /** User-facing label for the `system` ownership tab. */
260
+ publicTab: string;
261
+ /**
262
+ * Badge label on a builtin row. Default `"system"` — the string
263
+ * `AgentRow` actually rendered in matrx-frontend. (The frontend also
264
+ * declared `AGENT_PUBLIC_BADGE_LABEL = "Public"` for this badge and NOTHING
265
+ * ever read it — a constant that named the badge without being the badge.
266
+ * Recorded in the CHANGELOG and dropped rather than ported.)
267
+ */
268
+ systemBadge: string;
269
+ searchPlaceholder: string;
270
+ currentAgentHeading: string;
271
+ emptyDefault: string;
272
+ emptyShared: string;
273
+ emptySystem: string;
274
+ loading: string;
275
+ triggerFallback: string;
276
+ triggerResolving: string;
277
+ triggerUnnameable: string;
278
+ hoverHint: string;
279
+ }
280
+ declare const DEFAULT_AGENT_CATALOG_LABELS: AgentCatalogLabels;
281
+ interface AgentCatalogConfig {
282
+ /** REQUIRED — the Supabase client. */
283
+ client: AgentCatalogClient;
284
+ /** REQUIRED — who is asking. */
285
+ identity: AgentCatalogIdentity;
286
+ /**
287
+ * The catalog's registry key on `globalThis`. Two `createAgentCatalog`
288
+ * calls with the same id are the same catalog (and the second one screams
289
+ * through the errorSink before replacing the first). Default `"default"`.
290
+ */
291
+ catalogId?: string;
292
+ /** OPTIONAL — see `AgentCatalogErrorSink`. */
293
+ errorSink?: AgentCatalogErrorSink;
294
+ /** OPTIONAL — see `AgentCatalogNotifier`. */
295
+ notifier?: AgentCatalogNotifier;
296
+ /** OPTIONAL — see `AgentCatalogStorage`. */
297
+ storage?: AgentCatalogStorage;
298
+ /**
299
+ * OPTIONAL — the package's own `MatrxTransport` (`createMatrxTransport`
300
+ * from `@ai-matrx/agents/matrx`). REQUIRED the moment any picker instance
301
+ * passes a `defaultMandateKey`: THE PLATFORM RULE D-R1 says clients never
302
+ * walk the mandate ladder, so the default row can only be resolved through
303
+ * the aidream door `GET /mandates/{mandate_key}/resolution`. A
304
+ * `defaultMandateKey` with no transport throws `AgentCatalogConfigError`.
305
+ */
306
+ transport?: AgentCatalogTransport;
307
+ /** OPTIONAL — label overrides. */
308
+ labels?: Partial<AgentCatalogLabels>;
309
+ }
310
+ /**
311
+ * The transport seam, structurally identical to `MatrxTransport` from
312
+ * `@ai-matrx/agents/matrx` (declared structurally so `./catalog` never
313
+ * imports the transport module and stays import-inert).
314
+ */
315
+ interface AgentCatalogTransport {
316
+ fetch(path: string, init: {
317
+ method: "GET" | "POST";
318
+ headers: Record<string, string>;
319
+ body?: string;
320
+ signal?: AbortSignal;
321
+ }): Promise<Response>;
322
+ }
323
+ /**
324
+ * Where a row's href goes when someone cmd-clicks it, and where `navigateTo`
325
+ * lands. DEFAULT: `window.location.assign` (so cmd-click and ordinary
326
+ * navigation both work with no host wiring at all).
327
+ */
328
+ type AgentCatalogNavigate = (href: string) => void;
329
+ /** Resolve a per-row href. DEFAULT: `/agents/go/<id>` — the always-valid door. */
330
+ type AgentCatalogResolveHref = (agent: AgentSummary) => string;
331
+ /**
332
+ * The sneak-peek affordance. Absent → the affordance is HIDDEN, never a dead
333
+ * button (nothing fails silently, and nothing looks alive when it is not).
334
+ */
335
+ type AgentCatalogOpenPeek = (agent: AgentSummary) => void;
336
+
337
+ /**
338
+ * catalog/default-row-types.ts — @ai-matrx/agents/catalog
339
+ *
340
+ * Split out of `default-row.ts` so `global-state.ts` can name the cached
341
+ * shape without importing the resolver (and its transport work) — the same
342
+ * split-out reflex C8 asks for, at module scale.
343
+ */
344
+
345
+ /** The wire shape of `GET /mandates/{mandate_key}/resolution`, narrowed. */
346
+ interface MandateResolutionVerdict {
347
+ mandateKey: string;
348
+ holderType: string;
349
+ /** The executable id. `null` for a workflow Holder — a picker cannot run one. */
350
+ agentId: string | null;
351
+ isVersion: boolean;
352
+ /** Always the `agent.definition` id, even when `agentId` is a version. */
353
+ definitionAgentId: string | null;
354
+ /** Which precedence layer answered. Shown so a person can see WHY. */
355
+ provenance: string;
356
+ }
357
+ /** A resolved default row, cached for first paint and compared for drift. */
358
+ interface ResolvedDefaultRow {
359
+ mandateKey: string;
360
+ /** The `agent.definition` id the mandate resolved to. */
361
+ holderId: string;
362
+ /** The holder's REAL name, read from the catalogue — never hardcoded. */
363
+ holderName: string;
364
+ holderDescription: string | null;
365
+ provenance: string;
366
+ /** Epoch ms of this resolution. */
367
+ at: number;
368
+ }
369
+ /** What the picker renders and what it screams, together. */
370
+ interface DefaultRowState {
371
+ mandateKey: string;
372
+ /** The row to render at the top of the list. `null` until resolved. */
373
+ row: AgentSummary | null;
374
+ /** The last live resolution, or the cached one before the live answer lands. */
375
+ resolved: ResolvedDefaultRow | null;
376
+ /** True while the resolution is in flight. */
377
+ loading: boolean;
378
+ /**
379
+ * 🚨 THE DRIFT SCREAM. Set when the cached first-paint row and the LIVE
380
+ * resolution disagree on holder id or holder name. Rendered as a persistent
381
+ * in-picker banner AND sent to the `notifier` port — never console-only.
382
+ */
383
+ drift: string | null;
384
+ /** Set when the mandate could not be resolved at all. Loud, with a remedy. */
385
+ error: string | null;
386
+ }
387
+
388
+ /**
389
+ * catalog/schema.ts — @ai-matrx/agents/catalog
390
+ *
391
+ * THE DEMANDED SCHEMA PROBE (the `@ai-matrx/associations` `assertDemandedSchema`
392
+ * precedent). It proves that the database a host pointed this catalog at can
393
+ * actually answer the three functions the picker demands.
394
+ *
395
+ * 🚨 IT MUST BE ABLE TO FAIL. A probe whose green result is unconditional
396
+ * proves nothing, so `selfTest: true` also probes a fabricated function name
397
+ * and REQUIRES it to come back missing. `catalog/__tests__/schema.test.ts`
398
+ * runs the whole probe against a client whose `rpc` answers PostgreSQL 42883
399
+ * (`undefined_function`, which PostgREST surfaces as PGRST202) and asserts the
400
+ * loud error.
401
+ */
402
+
403
+ interface AgentCatalogSchemaReport {
404
+ ok: boolean;
405
+ /** Demanded functions the database cannot answer. */
406
+ missing: string[];
407
+ /** Functions the probe could not reach (transport/unknown failure). */
408
+ unreachable: {
409
+ fn: string;
410
+ error: unknown;
411
+ }[];
412
+ /** Functions that answered (success or any non-missing refusal). */
413
+ answered: string[];
414
+ /** True when `agent.definition.is_favorite` is writable by this caller. */
415
+ favoriteWritable: boolean | null;
416
+ }
417
+ interface AssertAgentCatalogSchemaOptions {
418
+ /**
419
+ * Also probe a fabricated function name and REQUIRE it to come back
420
+ * missing — proves this probe can fail.
421
+ */
422
+ selfTest?: boolean;
423
+ /** Return the report instead of throwing. The self-test failure ALWAYS throws. */
424
+ throwOnViolation?: boolean;
425
+ /**
426
+ * Also probe the ONE write (`agent.definition.is_favorite`) against an
427
+ * impossible id, so a 42501/permission refusal is seen at boot rather than
428
+ * on a user's first click. Default: on.
429
+ */
430
+ probeWrite?: boolean;
431
+ }
432
+ /**
433
+ * Probe every demanded function. Throws an `Error` naming each missing one
434
+ * (unless `throwOnViolation: false`), and ALWAYS throws when the probe itself
435
+ * cannot be trusted (unreachable database, failed self-test).
436
+ */
437
+ declare function assertAgentCatalogSchema(client: AgentCatalogClient, options?: AssertAgentCatalogSchemaOptions): Promise<AgentCatalogSchemaReport>;
438
+
439
+ /**
440
+ * catalog/store.ts — @ai-matrx/agents/catalog
441
+ *
442
+ * `createAgentCatalog(config)` — the headless kernel. Everything the picker
443
+ * knows lives here: the row registry, the complete-list read with its
444
+ * session-shared in-flight dedupe and freshness window, the tier-2 server
445
+ * search, the per-consumer view state, the ONE write, and the mandate default
446
+ * row with its drift screamer.
447
+ *
448
+ * The host injects a Supabase client and who the user is. Nothing else.
449
+ *
450
+ * The consumer reducers below are a VERBATIM port of matrx-frontend
451
+ * `features/agents/redux/agent-consumers/slice.ts`; only the seam inverts
452
+ * (a Redux reducer becomes a store method, and Immer's draft mutation becomes
453
+ * an explicit copy so the store can publish a new reference).
454
+ */
455
+
456
+ type AgentCatalogStatus = "idle" | "loading" | "succeeded" | "failed";
457
+ interface AgentCatalogState {
458
+ /** Every row, one stable array reference per change (memoization depends on it). */
459
+ rows: AgentSummary[];
460
+ byId: Record<string, AgentSummary>;
461
+ status: AgentCatalogStatus;
462
+ error: string | null;
463
+ consumers: Record<string, AgentConsumerState>;
464
+ /** Keyed by mandate key. */
465
+ defaultRows: Record<string, DefaultRowState>;
466
+ }
467
+ interface AgentCatalog {
468
+ readonly catalogId: string;
469
+ readonly labels: AgentCatalogLabels;
470
+ readonly errorSink: AgentCatalogErrorSink;
471
+ readonly notifier: AgentCatalogNotifier | undefined;
472
+ /** True when this catalog was given a transport and can resolve mandates. */
473
+ readonly canResolveMandates: boolean;
474
+ getState(): AgentCatalogState;
475
+ subscribe(listener: () => void): () => void;
476
+ getAgent(agentId: string): AgentSummary | undefined;
477
+ /**
478
+ * The signed-in user id, or `null` when there is no session. The ONE place
479
+ * `identity.requireUserId()`'s throw is caught — a signed-out visitor
480
+ * legitimately lands on the public catalogue — and it is reported to the
481
+ * errorSink the first time so it is never silent.
482
+ */
483
+ getUserIdOrNull(): string | null;
484
+ /** Load the catalogue if it is not fresh. Safe to call on every mount. */
485
+ ensureLoaded(options?: {
486
+ force?: boolean;
487
+ }): Promise<void>;
488
+ /** True when the last load is inside the 15-minute TTL. */
489
+ isFresh(): boolean;
490
+ /** True when the last load is older than 4 hours (tab-restore threshold). */
491
+ isStale(): boolean;
492
+ getConsumer(consumerId: string): AgentConsumerState;
493
+ registerConsumer(consumerId: string, initial?: AgentConsumerPatch): void;
494
+ unregisterConsumer(consumerId: string): void;
495
+ setConsumerFilter(consumerId: string, patch: AgentConsumerPatch): void;
496
+ setConsumerPage(consumerId: string, which: "list" | "shared", page: number): void;
497
+ setConsumerServerSearch(consumerId: string, args: {
498
+ matchedIds?: string[];
499
+ isSearching?: boolean;
500
+ }): void;
501
+ resetConsumerFilters(consumerId: string): void;
502
+ /** Tier-2 search. Purely additive; merges rows into the registry. */
503
+ searchServer(query: string, deep?: boolean): Promise<string[]>;
504
+ /** THE ONE WRITE. Optimistic, rolled back on failure, loud either way. */
505
+ setFavorite(agentId: string, isFavorite: boolean): Promise<void>;
506
+ /** Resolve (once) the default row for a mandate key. Idempotent. */
507
+ ensureDefaultRow(mandateKey: string): void;
508
+ getDefaultRow(mandateKey: string): DefaultRowState | undefined;
509
+ /** A person acknowledged the drift banner. The notifier already fired. */
510
+ dismissDefaultRowDrift(mandateKey: string): void;
511
+ assertSchema(options?: AssertAgentCatalogSchemaOptions): Promise<AgentCatalogSchemaReport>;
512
+ }
513
+ declare function createAgentCatalog(config: AgentCatalogConfig): AgentCatalog;
514
+ /**
515
+ * The catalog registered under `catalogId`, across loader graphs. Returns
516
+ * `undefined` when none has been created yet — never a silent stub.
517
+ */
518
+ declare function getRegisteredAgentCatalog(catalogId?: string): AgentCatalog | undefined;
519
+
520
+ /**
521
+ * catalog/data.ts — @ai-matrx/agents/catalog
522
+ *
523
+ * Every database call the catalog makes, and nothing else. Four of them:
524
+ *
525
+ * 1. `agx_get_list_full()` — the complete catalogue (owned + directly-shared
526
+ * + org-shared user agents, unioned with active builtins).
527
+ * 2. `agx_search(p_query, p_deep, p_limit, p_offset)` — tier-2 server search.
528
+ * 3. `agx_resolve_agent_address(p_ids)` — the single-agent name read, used
529
+ * when a mandate's Holder is not in the caller's own catalogue.
530
+ * 4. ONE write: `agent.definition.is_favorite`.
531
+ *
532
+ * 🚨 THE LIST READ IS COMPLETE OR IT THROWS. PostgREST caps every response at
533
+ * `db-max-rows` (1000 on Matrx Main) and reports success — matrx-frontend's
534
+ * `fetchAgentsListFull` issued a bare `.rpc()` and would silently have
535
+ * returned 1000 rows of a larger catalogue (the ~400-builtin truncation
536
+ * incident is the near-miss). Here the read goes through `readAllRows` with
537
+ * `{ count: "exact" }` and a stable `.order("id")`, so a truncation is a
538
+ * loud `IncompleteReadError` instead of a confidently short picker.
539
+ * The DB's own favourites-first/`updated_at` order is irrelevant: every row
540
+ * is re-sorted client-side by `sortFilteredAgents`.
541
+ */
542
+
543
+ /** Server search page size — the frontend's `AGENT_SEARCH_LIMIT`. */
544
+ declare const AGENT_SEARCH_LIMIT = 50;
545
+ declare class AgentCatalogReadError extends Error {
546
+ readonly name = "AgentCatalogReadError";
547
+ readonly source: string;
548
+ constructor(source: string, detail: string);
549
+ }
550
+ /**
551
+ * Read the WHOLE agent catalogue. Throws rather than returning a partial
552
+ * list — see the file header.
553
+ */
554
+ declare function readAgentCatalogRows(client: AgentCatalogClient): Promise<AgentSummary[]>;
555
+ interface AgentServerSearchResult {
556
+ /** Matched ids in SERVER RANK ORDER. */
557
+ ids: string[];
558
+ /** Full rows for every hit — merged additively into the registry. */
559
+ rows: AgentSummary[];
560
+ }
561
+ /**
562
+ * Tier-2 server search. Purely ADDITIVE: it widens what the local filter
563
+ * admits and never removes a row the local scorer already matched.
564
+ */
565
+ declare function searchAgentsOnServer(client: AgentCatalogClient, args: {
566
+ query: string;
567
+ deep?: boolean;
568
+ limit?: number;
569
+ }): Promise<AgentServerSearchResult>;
570
+ /**
571
+ * The single-agent read, used when a mandate's resolved Holder is not in the
572
+ * caller's own catalogue (a platform agent nobody has shared, most often).
573
+ *
574
+ * Two existing doors, in order — never a new RPC:
575
+ * 1. `agx_search` with the id as the query. The scorer awards an exact id
576
+ * match 100000, so the row comes back first, and it is the FULL 19-column
577
+ * row (name AND description AND category AND tags).
578
+ * 2. `agx_resolve_agent_address([id])` — the frontend's own name resolver.
579
+ * Name and type only, so the row renders with an honest name and no
580
+ * description rather than a fabricated one.
581
+ *
582
+ * Returns `null` when neither door can name the agent. The caller SCREAMS;
583
+ * it never invents a label.
584
+ */
585
+ declare function readSingleAgentRow(client: AgentCatalogClient, agentId: string): Promise<AgentSummary | null>;
586
+ /**
587
+ * THE ONE WRITE. Mirrors matrx-frontend's `saveAgentField` for this field
588
+ * exactly: `agent.definition.is_favorite`, addressed by id.
589
+ */
590
+ declare function writeAgentFavorite(client: AgentCatalogClient, agentId: string, isFavorite: boolean): Promise<void>;
591
+
592
+ /**
593
+ * catalog/rows.ts — @ai-matrx/agents/catalog
594
+ *
595
+ * The ONE ingress for a database row. `agx_get_list_full()` and `agx_search()`
596
+ * return the same 19 columns (search adds two ranking columns), so both come
597
+ * through here and produce the identical `AgentSummary`.
598
+ *
599
+ * Typed over `unknown` on purpose: the truth of this boundary is a
600
+ * HOST-generated Supabase type this package cannot see (the `@ai-matrx/data`
601
+ * 0.2.1 lesson). Narrowing happens here, once, loudly — a row that is not an
602
+ * object is REFUSED rather than half-read.
603
+ */
604
+
605
+ /**
606
+ * Map one raw list/search row. Returns `null` when the row is not an object
607
+ * or carries no id — the caller reports that to the errorSink and drops it;
608
+ * a row with no identity cannot be selected, so rendering it would be a lie.
609
+ */
610
+ declare function toAgentSummary(raw: unknown): AgentSummary | null;
611
+
612
+ /**
613
+ * catalog/default-row.ts — @ai-matrx/agents/catalog
614
+ *
615
+ * THE DEFAULT ROW, and the drift screamer (design D3).
616
+ *
617
+ * What every client used to do: prepend a placeholder row whose id is
618
+ * `mandate:<key>` and whose name and description are HARDCODED, and let the
619
+ * server resolve the real Holder at run time. That is how matrx-local's row
620
+ * came to be labelled "Matrx Desktop Agent" while `local.cloud_chat` actually
621
+ * resolves to General Chat — a label lying on screen, with nothing anywhere
622
+ * that could notice.
623
+ *
624
+ * What happens here instead:
625
+ *
626
+ * - A picker instance passes `defaultMandateKey`. The package resolves the
627
+ * Holder through the aidream door `GET /mandates/{key}/resolution`
628
+ * (verified against `aidream/api/routers/mandate_bindings.py`,
629
+ * `get_mandate_resolution`, mounted at prefix `/mandates`) using the
630
+ * injected `transport`.
631
+ * 🚨 THE PLATFORM RULE D-R1: a client NEVER walks the mandate ladder
632
+ * itself. It asks. That is why a `defaultMandateKey` with no transport is
633
+ * a loud config error and not a DB read.
634
+ * - The row renders the Holder's REAL name and description, read from the
635
+ * catalogue rows already in hand, or through ONE existing single-agent
636
+ * read when the Holder is not in this caller's catalogue.
637
+ * - The last resolution is cached in the `storage` port for first paint.
638
+ * - Whenever the cached row and the LIVE resolution disagree on holder id
639
+ * or holder name, the picker shows a PERSISTENT in-picker banner AND the
640
+ * `notifier` port fires. Never a console line, never only a console line.
641
+ *
642
+ * The row's id stays `mandate:<key>` byte-for-byte, because matrx-extend
643
+ * (`src/lib/mandates.ts`) and matrx-local (`desktop/src/lib/mandates.ts`)
644
+ * already hand that exact ref to their hosts.
645
+ */
646
+
647
+ declare class MandateDefaultRowError extends Error {
648
+ readonly name = "MandateDefaultRowError";
649
+ readonly mandateKey: string;
650
+ readonly remedy: string;
651
+ constructor(args: {
652
+ mandateKey: string;
653
+ message: string;
654
+ remedy: string;
655
+ });
656
+ }
657
+ /** Narrow the server's `MandateResolutionResponse` to what a picker needs. */
658
+ declare function parseMandateResolution(mandateKey: string, raw: unknown): MandateResolutionVerdict;
659
+ /** An in-memory storage, used when the host binds none and there is no DOM. */
660
+ declare function createMemoryStorage(): AgentCatalogStorage;
661
+ /**
662
+ * DEFAULT storage: `localStorage` when it exists AND answers (a private
663
+ * window, a service worker, or SSR all fail differently), else memory.
664
+ */
665
+ declare function defaultStorage(): AgentCatalogStorage;
666
+ declare function readCachedDefaultRow(storage: AgentCatalogStorage, mandateKey: string): ResolvedDefaultRow | null;
667
+ declare function writeCachedDefaultRow(storage: AgentCatalogStorage, resolved: ResolvedDefaultRow): void;
668
+ /** Build the `mandate:<key>` row from a resolved Holder. Never hardcodes text. */
669
+ declare function defaultRowFromResolution(resolved: ResolvedDefaultRow): AgentSummary;
670
+ /**
671
+ * The drift verdict. Cached vs live: a changed holder id OR a changed holder
672
+ * name is drift. Returns the sentence to show, or `null` when they agree.
673
+ */
674
+ declare function detectDefaultRowDrift(cached: ResolvedDefaultRow | null, live: ResolvedDefaultRow): string | null;
675
+ interface ResolveDefaultRowArgs {
676
+ mandateKey: string;
677
+ transport: AgentCatalogTransport;
678
+ client: AgentCatalogClient;
679
+ storage: AgentCatalogStorage;
680
+ errorSink: AgentCatalogErrorSink;
681
+ /** Look the Holder up in the rows this catalog already holds. */
682
+ lookupRow: (agentId: string) => AgentSummary | undefined;
683
+ }
684
+ interface ResolveDefaultRowOutcome {
685
+ resolved: ResolvedDefaultRow;
686
+ row: AgentSummary;
687
+ /** Non-null when the cached first-paint row disagreed with the live answer. */
688
+ drift: string | null;
689
+ }
690
+ /**
691
+ * Ask the server, name the Holder for real, cache the answer, and report
692
+ * drift. Throws `MandateDefaultRowError` when the mandate cannot produce a
693
+ * runnable agent — a picker never renders a default row it cannot name.
694
+ */
695
+ declare function resolveDefaultRow(args: ResolveDefaultRowArgs): Promise<ResolveDefaultRowOutcome>;
696
+
697
+ /**
698
+ * catalog/labels.ts — @ai-matrx/agents/catalog
699
+ *
700
+ * VERBATIM port of matrx-frontend `features/agents/constants/agent-list-labels.ts`.
701
+ * The only seam inverted: the two label constants moved into the `labels` port
702
+ * (`DEFAULT_AGENT_CATALOG_LABELS.publicTab` / `.publicBadge`) so a host can
703
+ * override them; the FUNCTIONS below keep their exact behaviour.
704
+ */
705
+
706
+ declare function agentListEmptyLabel(tab: AgentTab, labels: AgentCatalogLabels): string;
707
+ /** Whether a list picker should default to the public (`system`) tab instead of Mine. */
708
+ declare function shouldDefaultAgentListToPublicTab(args: {
709
+ userId: string | null;
710
+ ownedCount: number;
711
+ agentsLoaded: boolean;
712
+ }): boolean;
713
+
714
+ /**
715
+ * The structural shape a record needs to be searchable. Every field except
716
+ * `id` is optional so both the full gallery record and the slim chat record
717
+ * satisfy it without adapters.
718
+ */
719
+ interface AgentSearchable {
720
+ id: string;
721
+ name?: string | null;
722
+ description?: string | null;
723
+ category?: string | null;
724
+ tags?: string[] | null;
725
+ modelId?: string | null;
726
+ agentType?: string | null;
727
+ outputFormat?: string | null;
728
+ sharedByEmail?: string | null;
729
+ }
730
+ /**
731
+ * Relevance score for an agent against a query. Higher = more relevant.
732
+ * Returns 0 when nothing matches (0 is the "no match" contract — see
733
+ * `agentMatchesSearch`).
734
+ *
735
+ * Scores only fields carried by the list-level fetches. Message content and
736
+ * variable definitions are deliberately excluded: they are not loaded by the
737
+ * list RPCs, so matching them here would be a false promise. Server-side
738
+ * search owns deep content matching.
739
+ */
740
+ declare function computeAgentSearchScore(agent: AgentSearchable, query: string): number;
741
+ /** True when the agent matches the query at all. */
742
+ declare function agentMatchesSearch(agent: AgentSearchable, query: string): boolean;
743
+
744
+ /**
745
+ * catalog/selectors.ts — @ai-matrx/agents/catalog
746
+ *
747
+ * VERBATIM port of matrx-frontend
748
+ * `features/agents/redux/agent-consumers/selectors.ts`.
749
+ *
750
+ * ALL filter, sort, search-scoring, category/tag extraction and pagination
751
+ * logic lives here — not in components. Two coupling seams inverted, nothing
752
+ * else:
753
+ *
754
+ * 1. `createSelector` (reselect) → `memoize1`/`memoize2`/`memoize3` below,
755
+ * the same last-arguments reference-equality memoization reselect
756
+ * defaults to. No Redux, no store types.
757
+ * 2. `AgentDefinitionRecord` → `AgentSummary`, the list-row shape
758
+ * `agx_get_list_full` actually returns.
759
+ *
760
+ * One structural consequence of (2), recorded rather than hidden: the
761
+ * frontend's `selectLiveAgents` filters `isVersion` snapshots out of a store
762
+ * that also holds them. This catalog only ever holds list rows — a version
763
+ * snapshot cannot enter it — so that filter is a no-op here and is not
764
+ * ported. Every ordering, tie-break and count below is byte-identical.
765
+ */
766
+
767
+ declare const AGENT_CARDS_LIMIT_DESKTOP = 8;
768
+ declare const AGENT_CARDS_LIMIT_MOBILE = 4;
769
+ declare const AGENT_LIST_ITEMS_PER_PAGE = 20;
770
+ /**
771
+ * Last-arguments memoization on reference equality — reselect's default
772
+ * `defaultMemoize` behaviour, in eleven lines and with no dependency. A
773
+ * selector built here is memoized PER INSTANCE, exactly like a
774
+ * `createSelector` result, so the factories below keep their contract:
775
+ * build the selector once per (consumerId, …) and reuse it.
776
+ */
777
+ declare function memoize1<A, R>(fn: (a: A) => R): (a: A) => R;
778
+ declare function memoize2<A, B, R>(fn: (a: A, b: B) => R): (a: A, b: B) => R;
779
+ declare function memoize3<A, B, C, R>(fn: (a: A, b: B, c: C) => R): (a: A, b: B, c: C) => R;
780
+ declare function applyAgentSortComparator(a: AgentSummary, b: AgentSummary, sortBy: AgentSortOption): number;
781
+ /** All unique categories across live USER agents, sorted alphabetically. */
782
+ declare const selectAllAgentCategories: (a: readonly AgentSummary[]) => string[];
783
+ /**
784
+ * All unique categories across live SYSTEM (builtin) agents. The system tab
785
+ * gets the same category filter every other tab has — its options just come
786
+ * from a different population.
787
+ */
788
+ declare const selectAllSystemAgentCategories: (a: readonly AgentSummary[]) => string[];
789
+ /** All unique tags across live user agents, sorted alphabetically. */
790
+ declare const selectAllAgentTags: (a: readonly AgentSummary[]) => string[];
791
+ /** All unique tags across live SYSTEM (builtin) agents. */
792
+ declare const selectAllSystemAgentTags: (a: readonly AgentSummary[]) => string[];
793
+ /** Live user-type agents (excludes builtins). */
794
+ declare const selectUserTypeAgents: (a: readonly AgentSummary[]) => AgentSummary[];
795
+ /** Builtin/system agents only. For chat pickers and full catalogues. */
796
+ declare const selectBuiltinTypeAgents: (a: readonly AgentSummary[]) => AgentSummary[];
797
+ declare function sortFilteredAgents(filtered: AgentSummary[], consumer: AgentConsumerState): AgentSummary[];
798
+ /** User-type agents — mine / shared / all tabs with full consumer filters. */
799
+ declare function filterUserTypeAgents(agents: readonly AgentSummary[], consumer: AgentConsumerState): AgentSummary[];
800
+ /**
801
+ * Builtin/system agents (tab === "system").
802
+ *
803
+ * Gets the SAME filter surface every other tab has — search, favorites,
804
+ * category, tags, sort. Ownership/archive/access filters are meaningless here
805
+ * (a builtin is always active and belongs to the platform), so those are the
806
+ * only ones skipped. A picker that offers less on one tab than another is the
807
+ * exact half-built shape this list exists to prevent.
808
+ */
809
+ declare function filterBuiltinTypeAgents(agents: readonly AgentSummary[], consumer: AgentConsumerState): AgentSummary[];
810
+ /** Factory: owned user-type agents with consumer filters applied (ignores tab). */
811
+ declare const makeSelectFilteredOwnedAgents: () => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[];
812
+ /** Factory: shared user-type agents with consumer filters applied (ignores tab). */
813
+ declare const makeSelectFilteredSharedAgents: () => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[];
814
+ /**
815
+ * Factory: filters and sorts agents for a consumer. User tabs (mine / shared /
816
+ * all) draw from user-type agents; the system tab draws from builtins.
817
+ *
818
+ * `includeSystemInAll` is the ADMIN reading of the "All" tab. For a normal
819
+ * user, system agents are a separate catalogue and "All" means "all of MY
820
+ * agents". For an admin surface, system agents ARE their agents — so "All"
821
+ * must be a single blended list, with each row still labelled (the `system`
822
+ * badge on `AgentRow`) so the two are never confused.
823
+ */
824
+ declare const makeSelectFilteredAgents: (includeSystemInAll?: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[];
825
+ /** Factory: filters and sorts builtin agents for the chat agent picker. */
826
+ declare const makeSelectFilteredBuiltinAgents: () => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[];
827
+ interface AgentListPage {
828
+ items: AgentSummary[];
829
+ hasMore: boolean;
830
+ totalAfterCards: number;
831
+ }
832
+ /** Factory: slices filtered owned agents into the "cards" hero section. */
833
+ declare const makeSelectOwnedAgentCards: (isMobile: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[];
834
+ /** Factory: paginated list items for owned agents (everything after cards). */
835
+ declare const makeSelectOwnedAgentListItems: (isMobile: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentListPage;
836
+ /** Factory: slices filtered shared agents into the "cards" hero section. */
837
+ declare const makeSelectSharedAgentCards: (isMobile: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentSummary[];
838
+ /** Factory: paginated list items for shared agents (everything after cards). */
839
+ declare const makeSelectSharedAgentListItems: (isMobile: boolean) => (a: readonly AgentSummary[], b: AgentConsumerState) => AgentListPage;
840
+ /** Factory: total count of filtered agents for a consumer. */
841
+ declare const makeSelectFilteredAgentsCount: () => (a: readonly AgentSummary[], b: AgentConsumerState) => number;
842
+ /** Factory: total count of filtered builtin agents for a consumer. */
843
+ declare const makeSelectFilteredBuiltinAgentsCount: () => (a: readonly AgentSummary[], b: AgentConsumerState) => number;
844
+ /** Returns whether a consumer has any non-default filters active. */
845
+ declare function agentConsumerHasActiveFilters(consumer: AgentConsumerState, defaults: AgentConsumerState): boolean;
846
+ declare const selectTotalUserAgentsCount: (a: readonly AgentSummary[]) => number;
847
+ declare const selectTotalOwnedAgentsCount: (a: readonly AgentSummary[]) => number;
848
+ declare const selectTotalSharedAgentsCount: (a: readonly AgentSummary[]) => number;
849
+ declare const selectTotalBuiltinAgentsCount: (a: readonly AgentSummary[]) => number;
850
+ declare const selectTotalFavoriteAgentsCount: (a: readonly AgentSummary[]) => number;
851
+
852
+ /**
853
+ * catalog/global-state.ts — @ai-matrx/agents/catalog
854
+ *
855
+ * 🚨 MODULE-LEVEL MUTABLE STATE IS BANNED IN THIS PACKAGE. With dual ESM+CJS
856
+ * output and `splitting: false`, each loader graph instantiates its own copy
857
+ * of every module — a module-level variable would silently split the
858
+ * in-flight dedupe (so the "one RPC for ~20 mounting surfaces" collapse
859
+ * would become two), the freshness stamp, the catalog registry, and the
860
+ * default-row cache. Everything session-shared therefore lives on
861
+ * `globalThis` under a `Symbol.for` slot, exactly like
862
+ * `@ai-matrx/kit`'s confirm opener. Never "clean this up" into module locals.
863
+ */
864
+
865
+ /** @internal Test-only: forget every session-shared value. */
866
+ declare function _resetCatalogGlobalState(): void;
867
+
868
+ export { AGENT_CARDS_LIMIT_DESKTOP, AGENT_CARDS_LIMIT_MOBILE, AGENT_LIST_ITEMS_PER_PAGE, AGENT_NONE_SENTINEL, AGENT_SEARCH_LIMIT, type AgentAccessFilter, type AgentAccessLevel, type AgentArchFilter, type AgentCatalog, type AgentCatalogAgentType, type AgentCatalogClient, type AgentCatalogConfig, AgentCatalogConfigError, type AgentCatalogErrorSink, type AgentCatalogIdentity, type AgentCatalogLabels, type AgentCatalogNavigate, type AgentCatalogNotifier, type AgentCatalogOpenPeek, AgentCatalogReadError, type AgentCatalogResolveHref, type AgentCatalogRpcCall, type AgentCatalogSchemaReport, type AgentCatalogState, type AgentCatalogStatus, type AgentCatalogStorage, type AgentCatalogTableQuery, type AgentCatalogTransport, type AgentConsumerPatch, type AgentConsumerState, type AgentFavFilter, type AgentId, type AgentListPage, type AgentSearchable, type AgentServerSearchResult, type AgentSortOption, type AgentSummary, type AgentTab, type AssertAgentCatalogSchemaOptions, DEFAULT_AGENT_CATALOG_LABELS, DEFAULT_AGENT_CONSUMER_STATE, type DefaultRowState, MandateDefaultRowError, type MandateResolutionVerdict, type PgErrorLike, type PgResultLike, type ResolveDefaultRowArgs, type ResolveDefaultRowOutcome, type ResolvedDefaultRow, _resetCatalogGlobalState, agentConsumerHasActiveFilters, agentListEmptyLabel, agentMatchesSearch, applyAgentSortComparator, asAgentId, assertAgentCatalogSchema, computeAgentSearchScore, createAgentCatalog, createMemoryStorage, defaultRowFromResolution, defaultStorage, detectDefaultRowDrift, filterBuiltinTypeAgents, filterUserTypeAgents, getRegisteredAgentCatalog, isMandateAgentId, makeSelectFilteredAgents, makeSelectFilteredAgentsCount, makeSelectFilteredBuiltinAgents, makeSelectFilteredBuiltinAgentsCount, makeSelectFilteredOwnedAgents, makeSelectFilteredSharedAgents, makeSelectOwnedAgentCards, makeSelectOwnedAgentListItems, makeSelectSharedAgentCards, makeSelectSharedAgentListItems, mandateAgentId, memoize1, memoize2, memoize3, parseMandateResolution, readAgentCatalogRows, readCachedDefaultRow, readSingleAgentRow, resolveDefaultRow, searchAgentsOnServer, selectAllAgentCategories, selectAllAgentTags, selectAllSystemAgentCategories, selectAllSystemAgentTags, selectBuiltinTypeAgents, selectTotalBuiltinAgentsCount, selectTotalFavoriteAgentsCount, selectTotalOwnedAgentsCount, selectTotalSharedAgentsCount, selectTotalUserAgentsCount, selectUserTypeAgents, shouldDefaultAgentListToPublicTab, sortFilteredAgents, toAgentSummary, writeAgentFavorite, writeCachedDefaultRow };