@ai-matrx/agents 0.6.2 → 0.7.0

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