@ai-matrx/agents 0.59.264 → 0.59.265
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/dist/catalog/index.cjs +233 -5
- package/dist/catalog/index.d.cts +164 -83
- package/dist/catalog/index.d.ts +164 -83
- package/dist/catalog/index.js +233 -5
- package/dist/catalog/native/index.cjs +123 -24
- package/dist/catalog/native/index.d.cts +150 -42
- package/dist/catalog/native/index.d.ts +150 -42
- package/dist/catalog/native/index.js +120 -21
- package/dist/catalog/react/index.cjs +143 -20
- package/dist/catalog/react/index.d.cts +170 -44
- package/dist/catalog/react/index.d.ts +170 -44
- package/dist/catalog/react/index.js +143 -20
- package/dist/generated/provision-offers.cjs +0 -1
- package/dist/generated/provision-offers.d.cts +53 -23
- package/dist/generated/provision-offers.d.ts +53 -23
- package/dist/generated/provision-offers.js +0 -1
- package/dist/models/index.cjs +48 -1
- package/dist/models/index.d.cts +6 -0
- package/dist/models/index.d.ts +6 -0
- package/dist/models/index.js +48 -1
- package/dist/models/react/index.d.cts +6 -0
- package/dist/models/react/index.d.ts +6 -0
- package/dist/quick/index.d.cts +1 -1
- package/dist/quick/index.d.ts +1 -1
- package/dist/{types-DZhDqrVE.d.cts → types-5pX8AZBM.d.cts} +10 -0
- package/dist/{types-DZhDqrVE.d.ts → types-5pX8AZBM.d.ts} +10 -0
- package/package.json +2 -28
package/dist/catalog/index.d.ts
CHANGED
|
@@ -128,6 +128,16 @@ interface AgentSummary {
|
|
|
128
128
|
* host can tell "the default" from a stored agent without parsing the id.
|
|
129
129
|
*/
|
|
130
130
|
mandateKey?: string;
|
|
131
|
+
/**
|
|
132
|
+
* THE THIN ROW (FW-014). `true` when this row came from the picker's page
|
|
133
|
+
* read (`agent.agx_picker_page`), which carries only what a picker row
|
|
134
|
+
* renders: no description, model, offering, task, source or sharer. A
|
|
135
|
+
* consumer that needs those asks `catalog.ensureAgentRows([id])` (hover,
|
|
136
|
+
* open), which replaces it with the full row. Absent = a full row.
|
|
137
|
+
*/
|
|
138
|
+
isThin?: boolean;
|
|
139
|
+
/** When the caller last used this agent (their latest conversation with it). Page reads only. */
|
|
140
|
+
lastUsedAt?: string | null;
|
|
131
141
|
}
|
|
132
142
|
type AgentSortOption = "updated-desc" | "created-desc" | "name-asc" | "name-desc" | "category-asc";
|
|
133
143
|
/**
|
|
@@ -528,6 +538,15 @@ interface AgentCatalogConfig {
|
|
|
528
538
|
transport?: AgentCatalogTransport;
|
|
529
539
|
/** OPTIONAL — label overrides. */
|
|
530
540
|
labels?: Partial<AgentCatalogLabels>;
|
|
541
|
+
/**
|
|
542
|
+
* How a picker reads its list (FW-014). DEFAULT `"page"`: the menu opens on
|
|
543
|
+
* one server page (`agent.agx_picker_page`, ~50 thin rows + counts) and pages
|
|
544
|
+
* on scroll; the whole catalog is read only when the person narrows by
|
|
545
|
+
* category/tag/sort/filter. `"catalog"`: every picker reads the whole catalog
|
|
546
|
+
* on open (the pre-2026-10-23 behavior) — for a host whose client cannot
|
|
547
|
+
* reach the `agent` schema, and for tests of the full-list rules.
|
|
548
|
+
*/
|
|
549
|
+
pickerReads?: "page" | "catalog";
|
|
531
550
|
/**
|
|
532
551
|
* OPTIONAL — the host's starting values for per-consumer view state.
|
|
533
552
|
* See `AgentCatalogDefaults`.
|
|
@@ -579,6 +598,111 @@ type AgentCatalogResolveHref = (agent: AgentSummary) => string;
|
|
|
579
598
|
*/
|
|
580
599
|
type AgentCatalogOpenPeek = (agent: AgentSummary) => void;
|
|
581
600
|
|
|
601
|
+
/**
|
|
602
|
+
* catalog/data.ts — @ai-matrx/agents/catalog
|
|
603
|
+
*
|
|
604
|
+
* Every database call the catalog makes, and nothing else. Five of them:
|
|
605
|
+
*
|
|
606
|
+
* 1. `agx_get_list_full()` — the complete catalog (owned + directly-shared
|
|
607
|
+
* + org-shared user agents, unioned with active builtins).
|
|
608
|
+
* 2. `agx_search(p_query, p_deep, p_limit, p_offset)` — tier-2 server search.
|
|
609
|
+
* 3. `agx_resolve_agent_address(p_ids)` — the single-agent name read, used
|
|
610
|
+
* when a mandate's Holder is not in the caller's own catalog.
|
|
611
|
+
* 4. The caller's stars arrive ON the rows: every read above computes
|
|
612
|
+
* `is_favorite` server-side from `platform.user_entity_state`
|
|
613
|
+
* (`platform.my_favorite`). There is no second star read (FW-014; the
|
|
614
|
+
* `ues_get_bulk` overlay over every id was retired 2026-10-23).
|
|
615
|
+
* 5. ONE write: the caller's star, `ues_set` through the same port. The
|
|
616
|
+
* retired `agent.definition.is_favorite` column is never read or written.
|
|
617
|
+
* 6. THE PINNED READ (2026-10-09; by-ids 2026-10-23): the caller's starred
|
|
618
|
+
* ids (`ues_list` through the favorites port), then
|
|
619
|
+
* `agent.agx_get_by_ids(p_ids)` for exactly those ids. A sidebar's Pinned
|
|
620
|
+
* section shows a handful of rows; it never pays for the whole catalog,
|
|
621
|
+
* in the browser or on the server, to find them.
|
|
622
|
+
* 7. THE PICKER PAGE (FW-014, 2026-10-23): `agent.agx_picker_page` — the
|
|
623
|
+
* menu opens with ~50 thin rows (favorites, recently used, name) and the
|
|
624
|
+
* tab counts; scrolling reads the next keyset page. `agent.agx_get_by_ids`
|
|
625
|
+
* fills in a full row on hover/open. The whole-catalog read above remains
|
|
626
|
+
* for surfaces that genuinely list everything, and for a picker whose
|
|
627
|
+
* person narrows by category or tag (the facets need every row).
|
|
628
|
+
*
|
|
629
|
+
* 🚨 THE LIST READ IS COMPLETE OR IT THROWS. PostgREST caps every response at
|
|
630
|
+
* `db-max-rows` (1000 on Matrx Main) and reports success — matrx-frontend's
|
|
631
|
+
* `fetchAgentsListFull` issued a bare `.rpc()` and would silently have
|
|
632
|
+
* returned 1000 rows of a larger catalog (the ~400-builtin truncation
|
|
633
|
+
* incident is the near-miss). Here the read goes through `readAllRows` with
|
|
634
|
+
* `{ count: "exact" }` and a stable `.order("id")`, so a truncation is a
|
|
635
|
+
* loud `IncompleteReadError` instead of a confidently short picker.
|
|
636
|
+
* The DB's own favourites-first/`updated_at` order is irrelevant: every row
|
|
637
|
+
* is re-sorted client-side by `sortFilteredAgents`.
|
|
638
|
+
*/
|
|
639
|
+
|
|
640
|
+
/** Server search page size — the frontend's `AGENT_SEARCH_LIMIT`. */
|
|
641
|
+
declare const AGENT_SEARCH_LIMIT = 50;
|
|
642
|
+
declare class AgentCatalogReadError extends Error {
|
|
643
|
+
readonly name = "AgentCatalogReadError";
|
|
644
|
+
readonly source: string;
|
|
645
|
+
constructor(source: string, detail: string);
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Read the WHOLE agent catalog. Throws rather than returning a partial
|
|
649
|
+
* list — see the file header.
|
|
650
|
+
*/
|
|
651
|
+
declare function readAgentCatalogRows(client: AgentCatalogClient): Promise<AgentSummary[]>;
|
|
652
|
+
interface AgentServerSearchResult {
|
|
653
|
+
/** Matched ids in SERVER RANK ORDER. */
|
|
654
|
+
ids: string[];
|
|
655
|
+
/** Full rows for every hit — merged additively into the registry. */
|
|
656
|
+
rows: AgentSummary[];
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Tier-2 server search. Purely ADDITIVE: it widens what the local filter
|
|
660
|
+
* admits and never removes a row the local scorer already matched.
|
|
661
|
+
*/
|
|
662
|
+
declare function searchAgentsOnServer(client: AgentCatalogClient, args: {
|
|
663
|
+
query: string;
|
|
664
|
+
deep?: boolean;
|
|
665
|
+
limit?: number;
|
|
666
|
+
}): Promise<AgentServerSearchResult>;
|
|
667
|
+
/**
|
|
668
|
+
* The single-agent read, used when a mandate's resolved Holder is not in the
|
|
669
|
+
* caller's own catalog (a platform agent nobody has shared, most often).
|
|
670
|
+
*
|
|
671
|
+
* Two existing doors, in order — never a new RPC:
|
|
672
|
+
* 1. `agx_search` with the id as the query. The scorer awards an exact id
|
|
673
|
+
* match 100000, so the row comes back first, and it is the FULL 19-column
|
|
674
|
+
* row (name AND description AND category AND tags).
|
|
675
|
+
* 2. `agx_resolve_agent_address([id])` — the frontend's own name resolver.
|
|
676
|
+
* Name and type only, so the row renders with an honest name and no
|
|
677
|
+
* description rather than a fabricated one.
|
|
678
|
+
*
|
|
679
|
+
* Returns `null` when neither door can name the agent. The caller SCREAMS;
|
|
680
|
+
* it never invents a label.
|
|
681
|
+
*/
|
|
682
|
+
declare function readSingleAgentRow(client: AgentCatalogClient, agentId: string): Promise<AgentSummary | null>;
|
|
683
|
+
/**
|
|
684
|
+
* THE ONE WRITE: the caller's star on one agent, in
|
|
685
|
+
* `platform.user_entity_state` through the favorites port. Throws an
|
|
686
|
+
* `AgentCatalogReadError` carrying the refusal in words.
|
|
687
|
+
*/
|
|
688
|
+
declare function writeAgentFavorite(favorites: AgentCatalogFavorites, agentId: string, isFavorite: boolean): Promise<void>;
|
|
689
|
+
/** Which slice of the caller's catalog a picker page reads. */
|
|
690
|
+
interface AgentPickerPageQuery {
|
|
691
|
+
/** `all_system` = the admin "All" tab that blends system agents in. */
|
|
692
|
+
tab: "mine" | "shared" | "all" | "all_system" | "system";
|
|
693
|
+
archive: "active" | "archived" | "both";
|
|
694
|
+
includeQuick: boolean;
|
|
695
|
+
}
|
|
696
|
+
/** The tab badges, counted server-side over the same reach as the rows. */
|
|
697
|
+
interface AgentPickerCounts {
|
|
698
|
+
mine: number;
|
|
699
|
+
shared: number;
|
|
700
|
+
all: number;
|
|
701
|
+
system: number;
|
|
702
|
+
/** Archived rows the active filter hides behind THIS tab (0 unless archive = active). */
|
|
703
|
+
archivedHidden: number;
|
|
704
|
+
}
|
|
705
|
+
|
|
582
706
|
/**
|
|
583
707
|
* catalog/default-row-types.ts — @ai-matrx/agents/catalog
|
|
584
708
|
*
|
|
@@ -704,6 +828,20 @@ declare function assertAgentCatalogSchema(client: AgentCatalogClient, options?:
|
|
|
704
828
|
*/
|
|
705
829
|
|
|
706
830
|
type AgentCatalogStatus = "idle" | "loading" | "succeeded" | "failed";
|
|
831
|
+
/** One picker slice (tab × archive × quick) read a page at a time (FW-014). */
|
|
832
|
+
interface AgentPickerPageState {
|
|
833
|
+
/** Row ids in server order across every page read so far. */
|
|
834
|
+
ids: string[];
|
|
835
|
+
/** Keyset position of the next page; `null` once the last page is in. */
|
|
836
|
+
nextCursor: unknown;
|
|
837
|
+
done: boolean;
|
|
838
|
+
status: AgentCatalogStatus;
|
|
839
|
+
/** True while a next page (not the first) is in flight. */
|
|
840
|
+
loadingMore: boolean;
|
|
841
|
+
error: string | null;
|
|
842
|
+
counts: AgentPickerCounts | null;
|
|
843
|
+
fetchedAt: number;
|
|
844
|
+
}
|
|
707
845
|
interface AgentCatalogState {
|
|
708
846
|
/** Every row, one stable array reference per change (memoization depends on it). */
|
|
709
847
|
rows: AgentSummary[];
|
|
@@ -719,6 +857,8 @@ interface AgentCatalogState {
|
|
|
719
857
|
* or by a complete catalog load, whichever came first.
|
|
720
858
|
*/
|
|
721
859
|
favoriteRowsStatus: AgentCatalogStatus;
|
|
860
|
+
/** The picker's paged slices, keyed by `agentPickerPageKey` (FW-014). */
|
|
861
|
+
pickerPages: Record<string, AgentPickerPageState>;
|
|
722
862
|
}
|
|
723
863
|
interface AgentCatalog {
|
|
724
864
|
readonly catalogId: string;
|
|
@@ -727,6 +867,8 @@ interface AgentCatalog {
|
|
|
727
867
|
readonly notifier: AgentCatalogNotifier | undefined;
|
|
728
868
|
/** True when this catalog was given a transport and can resolve mandates. */
|
|
729
869
|
readonly canResolveMandates: boolean;
|
|
870
|
+
/** `AgentCatalogConfig.pickerReads`, resolved (default `"page"`). */
|
|
871
|
+
readonly pickerReads: "page" | "catalog";
|
|
730
872
|
/**
|
|
731
873
|
* The consumer state a brand-new consumer starts from, AFTER the host's
|
|
732
874
|
* `defaults` knob is applied. Read it instead of
|
|
@@ -765,6 +907,28 @@ interface AgentCatalog {
|
|
|
765
907
|
* makes no request. Rows that do not parse are skipped.
|
|
766
908
|
*/
|
|
767
909
|
seedFavoriteRows(rawRows: unknown[]): void;
|
|
910
|
+
/**
|
|
911
|
+
* THE PICKER'S OPEN (FW-014): the first page (~50 thin rows) of one slice,
|
|
912
|
+
* plus its tab counts — never the whole catalog. Fresh for the list's TTL;
|
|
913
|
+
* deduped in flight; safe on every open.
|
|
914
|
+
*/
|
|
915
|
+
ensurePickerPage(query: AgentPickerPageQuery): Promise<void>;
|
|
916
|
+
/** The next page of a slice (scroll). No-op when the slice is done or already loading. */
|
|
917
|
+
loadMorePickerPage(query: AgentPickerPageQuery): Promise<void>;
|
|
918
|
+
/**
|
|
919
|
+
* FULL rows for these ids (`agent.agx_get_by_ids`), merged into the registry:
|
|
920
|
+
* a hover card's details, the pinned current agent, a name for an id that is
|
|
921
|
+
* not on page 1. Ids already held as full rows are not asked again; deduped
|
|
922
|
+
* in flight.
|
|
923
|
+
*/
|
|
924
|
+
ensureAgentRows(agentIds: readonly string[]): Promise<void>;
|
|
925
|
+
/**
|
|
926
|
+
* The caller's live agents carrying any of these tags, merged into the
|
|
927
|
+
* registry (`agent.agx_picker_tagged`) — the composer's quick agents and
|
|
928
|
+
* presets, never the catalog. Fresh for the list's TTL; deduped in flight;
|
|
929
|
+
* answered at once when the whole catalog is already fresh.
|
|
930
|
+
*/
|
|
931
|
+
ensureTaggedRows(tags: readonly string[]): Promise<void>;
|
|
768
932
|
/** True when the last load is inside the 15-minute TTL. */
|
|
769
933
|
isFresh(): boolean;
|
|
770
934
|
/** True when the last load is older than 4 hours (tab-restore threshold). */
|
|
@@ -799,89 +963,6 @@ declare function createAgentCatalog(config: AgentCatalogConfig): AgentCatalog;
|
|
|
799
963
|
*/
|
|
800
964
|
declare function getRegisteredAgentCatalog(catalogId?: string): AgentCatalog | undefined;
|
|
801
965
|
|
|
802
|
-
/**
|
|
803
|
-
* catalog/data.ts — @ai-matrx/agents/catalog
|
|
804
|
-
*
|
|
805
|
-
* Every database call the catalog makes, and nothing else. Five of them:
|
|
806
|
-
*
|
|
807
|
-
* 1. `agx_get_list_full()` — the complete catalog (owned + directly-shared
|
|
808
|
-
* + org-shared user agents, unioned with active builtins).
|
|
809
|
-
* 2. `agx_search(p_query, p_deep, p_limit, p_offset)` — tier-2 server search.
|
|
810
|
-
* 3. `agx_resolve_agent_address(p_ids)` — the single-agent name read, used
|
|
811
|
-
* when a mandate's Holder is not in the caller's own catalog.
|
|
812
|
-
* 4. The caller's stars arrive ON the rows: every read above computes
|
|
813
|
-
* `is_favorite` server-side from `platform.user_entity_state`
|
|
814
|
-
* (`platform.my_favorite`). There is no second star read (FW-014; the
|
|
815
|
-
* `ues_get_bulk` overlay over every id was retired 2026-10-23).
|
|
816
|
-
* 5. ONE write: the caller's star, `ues_set` through the same port. The
|
|
817
|
-
* retired `agent.definition.is_favorite` column is never read or written.
|
|
818
|
-
* 6. THE PINNED READ (2026-10-09; by-ids 2026-10-23): the caller's starred
|
|
819
|
-
* ids (`ues_list` through the favorites port), then
|
|
820
|
-
* `agent.agx_get_by_ids(p_ids)` for exactly those ids. A sidebar's Pinned
|
|
821
|
-
* section shows a handful of rows; it never pays for the whole catalog,
|
|
822
|
-
* in the browser or on the server, to find them.
|
|
823
|
-
*
|
|
824
|
-
* 🚨 THE LIST READ IS COMPLETE OR IT THROWS. PostgREST caps every response at
|
|
825
|
-
* `db-max-rows` (1000 on Matrx Main) and reports success — matrx-frontend's
|
|
826
|
-
* `fetchAgentsListFull` issued a bare `.rpc()` and would silently have
|
|
827
|
-
* returned 1000 rows of a larger catalog (the ~400-builtin truncation
|
|
828
|
-
* incident is the near-miss). Here the read goes through `readAllRows` with
|
|
829
|
-
* `{ count: "exact" }` and a stable `.order("id")`, so a truncation is a
|
|
830
|
-
* loud `IncompleteReadError` instead of a confidently short picker.
|
|
831
|
-
* The DB's own favourites-first/`updated_at` order is irrelevant: every row
|
|
832
|
-
* is re-sorted client-side by `sortFilteredAgents`.
|
|
833
|
-
*/
|
|
834
|
-
|
|
835
|
-
/** Server search page size — the frontend's `AGENT_SEARCH_LIMIT`. */
|
|
836
|
-
declare const AGENT_SEARCH_LIMIT = 50;
|
|
837
|
-
declare class AgentCatalogReadError extends Error {
|
|
838
|
-
readonly name = "AgentCatalogReadError";
|
|
839
|
-
readonly source: string;
|
|
840
|
-
constructor(source: string, detail: string);
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
|
-
* Read the WHOLE agent catalog. Throws rather than returning a partial
|
|
844
|
-
* list — see the file header.
|
|
845
|
-
*/
|
|
846
|
-
declare function readAgentCatalogRows(client: AgentCatalogClient): Promise<AgentSummary[]>;
|
|
847
|
-
interface AgentServerSearchResult {
|
|
848
|
-
/** Matched ids in SERVER RANK ORDER. */
|
|
849
|
-
ids: string[];
|
|
850
|
-
/** Full rows for every hit — merged additively into the registry. */
|
|
851
|
-
rows: AgentSummary[];
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* Tier-2 server search. Purely ADDITIVE: it widens what the local filter
|
|
855
|
-
* admits and never removes a row the local scorer already matched.
|
|
856
|
-
*/
|
|
857
|
-
declare function searchAgentsOnServer(client: AgentCatalogClient, args: {
|
|
858
|
-
query: string;
|
|
859
|
-
deep?: boolean;
|
|
860
|
-
limit?: number;
|
|
861
|
-
}): Promise<AgentServerSearchResult>;
|
|
862
|
-
/**
|
|
863
|
-
* The single-agent read, used when a mandate's resolved Holder is not in the
|
|
864
|
-
* caller's own catalog (a platform agent nobody has shared, most often).
|
|
865
|
-
*
|
|
866
|
-
* Two existing doors, in order — never a new RPC:
|
|
867
|
-
* 1. `agx_search` with the id as the query. The scorer awards an exact id
|
|
868
|
-
* match 100000, so the row comes back first, and it is the FULL 19-column
|
|
869
|
-
* row (name AND description AND category AND tags).
|
|
870
|
-
* 2. `agx_resolve_agent_address([id])` — the frontend's own name resolver.
|
|
871
|
-
* Name and type only, so the row renders with an honest name and no
|
|
872
|
-
* description rather than a fabricated one.
|
|
873
|
-
*
|
|
874
|
-
* Returns `null` when neither door can name the agent. The caller SCREAMS;
|
|
875
|
-
* it never invents a label.
|
|
876
|
-
*/
|
|
877
|
-
declare function readSingleAgentRow(client: AgentCatalogClient, agentId: string): Promise<AgentSummary | null>;
|
|
878
|
-
/**
|
|
879
|
-
* THE ONE WRITE: the caller's star on one agent, in
|
|
880
|
-
* `platform.user_entity_state` through the favorites port. Throws an
|
|
881
|
-
* `AgentCatalogReadError` carrying the refusal in words.
|
|
882
|
-
*/
|
|
883
|
-
declare function writeAgentFavorite(favorites: AgentCatalogFavorites, agentId: string, isFavorite: boolean): Promise<void>;
|
|
884
|
-
|
|
885
966
|
/**
|
|
886
967
|
* catalog/rows.ts — @ai-matrx/agents/catalog
|
|
887
968
|
*
|
package/dist/catalog/index.js
CHANGED
|
@@ -150,7 +150,8 @@ function toAgentSummary(raw) {
|
|
|
150
150
|
orchestra: orchestraBadge(raw["orchestra"]),
|
|
151
151
|
// Present only when the read carries the column: absent ≠ "no pin".
|
|
152
152
|
..."offering_id" in raw ? { offeringId: str(raw["offering_id"]) } : {},
|
|
153
|
-
..."organization_name" in raw ? { organizationName: str(raw["organization_name"]) } : {}
|
|
153
|
+
..."organization_name" in raw ? { organizationName: str(raw["organization_name"]) } : {},
|
|
154
|
+
..."last_used_at" in raw ? { lastUsedAt: str(raw["last_used_at"]) } : {}
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -289,20 +290,77 @@ async function readFavoriteAgentRows(client, favorites) {
|
|
|
289
290
|
)
|
|
290
291
|
);
|
|
291
292
|
if (ids.length === 0) return [];
|
|
293
|
+
const rows = await readAgentRowsByIds(client, ids, "agent.agx_get_by_ids (pinned)");
|
|
294
|
+
return rows.map((summary) => summary.isFavorite ? summary : { ...summary, isFavorite: true });
|
|
295
|
+
}
|
|
296
|
+
async function readAgentRowsByIds(client, agentIds, label = "agent.agx_get_by_ids") {
|
|
297
|
+
if (typeof client.schema !== "function") {
|
|
298
|
+
throw new AgentPinnedReadUnsupported("the client has no schema()");
|
|
299
|
+
}
|
|
300
|
+
const ids = Array.from(new Set(agentIds.filter((id) => typeof id === "string" && id.length > 0)));
|
|
292
301
|
const rows = [];
|
|
293
302
|
for (let at = 0; at < ids.length; at += PINNED_ID_CHUNK) {
|
|
294
303
|
const chunk = ids.slice(at, at + PINNED_ID_CHUNK);
|
|
295
304
|
const { data, error } = await client.schema("agent").rpc("agx_get_by_ids", { p_ids: chunk });
|
|
296
305
|
if (error) {
|
|
297
|
-
throw new AgentCatalogReadError(
|
|
306
|
+
throw new AgentCatalogReadError(label, pgDetail(error));
|
|
298
307
|
}
|
|
299
308
|
for (const item of Array.isArray(data) ? data : []) {
|
|
300
309
|
const summary = toAgentSummary(item);
|
|
301
|
-
if (summary) rows.push(summary
|
|
310
|
+
if (summary) rows.push(summary);
|
|
302
311
|
}
|
|
303
312
|
}
|
|
304
313
|
return rows;
|
|
305
314
|
}
|
|
315
|
+
var PICKER_PAGE_SIZE = 50;
|
|
316
|
+
function countOf(record, key) {
|
|
317
|
+
const value = record[key];
|
|
318
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
319
|
+
}
|
|
320
|
+
async function readAgentPickerPage(client, query, cursor = null, limit = PICKER_PAGE_SIZE) {
|
|
321
|
+
if (typeof client.schema !== "function") {
|
|
322
|
+
throw new AgentPinnedReadUnsupported("the client has no schema()");
|
|
323
|
+
}
|
|
324
|
+
const { data, error } = await client.schema("agent").rpc("agx_picker_page", {
|
|
325
|
+
p_tab: query.tab,
|
|
326
|
+
p_archive: query.archive,
|
|
327
|
+
p_include_quick: query.includeQuick,
|
|
328
|
+
p_cursor: cursor ?? null,
|
|
329
|
+
p_limit: limit
|
|
330
|
+
});
|
|
331
|
+
if (error) throw new AgentCatalogReadError("agent.agx_picker_page", pgDetail(error));
|
|
332
|
+
const record = typeof data === "object" && data !== null && !Array.isArray(data) ? data : null;
|
|
333
|
+
if (!record) {
|
|
334
|
+
throw new AgentCatalogReadError("agent.agx_picker_page", "the page came back without a body");
|
|
335
|
+
}
|
|
336
|
+
const rows = [];
|
|
337
|
+
for (const item of Array.isArray(record["rows"]) ? record["rows"] : []) {
|
|
338
|
+
const summary = toAgentSummary(item);
|
|
339
|
+
if (summary) rows.push({ ...summary, isThin: true });
|
|
340
|
+
}
|
|
341
|
+
const rawCounts = record["counts"];
|
|
342
|
+
const counts = typeof rawCounts === "object" && rawCounts !== null && !Array.isArray(rawCounts) ? {
|
|
343
|
+
mine: countOf(rawCounts, "mine"),
|
|
344
|
+
shared: countOf(rawCounts, "shared"),
|
|
345
|
+
all: countOf(rawCounts, "all"),
|
|
346
|
+
system: countOf(rawCounts, "system"),
|
|
347
|
+
archivedHidden: countOf(rawCounts, "archived_hidden")
|
|
348
|
+
} : null;
|
|
349
|
+
return { rows, nextCursor: record["next_cursor"] ?? null, counts };
|
|
350
|
+
}
|
|
351
|
+
async function readAgentTaggedRows(client, tags) {
|
|
352
|
+
if (typeof client.schema !== "function") {
|
|
353
|
+
throw new AgentPinnedReadUnsupported("the client has no schema()");
|
|
354
|
+
}
|
|
355
|
+
const { data, error } = await client.schema("agent").rpc("agx_picker_tagged", { p_tags: [...tags] });
|
|
356
|
+
if (error) throw new AgentCatalogReadError("agent.agx_picker_tagged", pgDetail(error));
|
|
357
|
+
const rows = [];
|
|
358
|
+
for (const item of Array.isArray(data) ? data : []) {
|
|
359
|
+
const summary = toAgentSummary(item);
|
|
360
|
+
if (summary) rows.push({ ...summary, isThin: true });
|
|
361
|
+
}
|
|
362
|
+
return rows;
|
|
363
|
+
}
|
|
306
364
|
|
|
307
365
|
// catalog/default-row.ts
|
|
308
366
|
var CACHE_PREFIX = "ai-matrx.agents.catalog.default-row:";
|
|
@@ -640,6 +698,9 @@ function readsOrchestraColumn(data) {
|
|
|
640
698
|
// catalog/store.ts
|
|
641
699
|
var LIST_TTL_MS = 15 * 60 * 1e3;
|
|
642
700
|
var LIST_STALE_MS = 4 * 60 * 60 * 1e3;
|
|
701
|
+
function agentPickerPageKey(query) {
|
|
702
|
+
return `${query.tab}|${query.archive}|${query.includeQuick ? "q" : "-"}`;
|
|
703
|
+
}
|
|
643
704
|
function createDefaultErrorSink() {
|
|
644
705
|
let announced = false;
|
|
645
706
|
return (event) => {
|
|
@@ -723,7 +784,8 @@ function createAgentCatalog(config) {
|
|
|
723
784
|
error: null,
|
|
724
785
|
consumers: {},
|
|
725
786
|
defaultRows: {},
|
|
726
|
-
favoriteRowsStatus: "idle"
|
|
787
|
+
favoriteRowsStatus: "idle",
|
|
788
|
+
pickerPages: {}
|
|
727
789
|
};
|
|
728
790
|
const listeners = /* @__PURE__ */ new Set();
|
|
729
791
|
function publish(next) {
|
|
@@ -733,7 +795,10 @@ function createAgentCatalog(config) {
|
|
|
733
795
|
function mergeRows(incoming) {
|
|
734
796
|
if (incoming.length === 0) return;
|
|
735
797
|
const byId = { ...state.byId };
|
|
736
|
-
for (const row of incoming)
|
|
798
|
+
for (const row of incoming) {
|
|
799
|
+
const held = byId[row.id];
|
|
800
|
+
byId[row.id] = row.isThin && held && !held.isThin ? { ...held, isFavorite: row.isFavorite, ...row.lastUsedAt !== void 0 ? { lastUsedAt: row.lastUsedAt } : {} } : row;
|
|
801
|
+
}
|
|
737
802
|
publish({ ...state, byId, rows: Object.values(byId) });
|
|
738
803
|
}
|
|
739
804
|
function replaceRows(incoming) {
|
|
@@ -871,6 +936,164 @@ function createAgentCatalog(config) {
|
|
|
871
936
|
favoriteRowsInFlight = run;
|
|
872
937
|
return run;
|
|
873
938
|
}
|
|
939
|
+
const pickerInFlight = /* @__PURE__ */ new Map();
|
|
940
|
+
function writePickerPage(key, next) {
|
|
941
|
+
publish({ ...state, pickerPages: { ...state.pickerPages, [key]: next } });
|
|
942
|
+
}
|
|
943
|
+
async function readPickerPage(query, first) {
|
|
944
|
+
const key = agentPickerPageKey(query);
|
|
945
|
+
const prior = state.pickerPages[key];
|
|
946
|
+
if (getUserIdOrNull() === null) {
|
|
947
|
+
writePickerPage(key, { ids: [], nextCursor: null, done: true, status: "succeeded", loadingMore: false, error: null, counts: { mine: 0, shared: 0, all: 0, system: 0, archivedHidden: 0 }, fetchedAt: Date.now() });
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
const base = {
|
|
951
|
+
ids: [],
|
|
952
|
+
nextCursor: null,
|
|
953
|
+
done: false,
|
|
954
|
+
status: "loading",
|
|
955
|
+
loadingMore: false,
|
|
956
|
+
error: null,
|
|
957
|
+
counts: null,
|
|
958
|
+
fetchedAt: 0
|
|
959
|
+
};
|
|
960
|
+
if (first) {
|
|
961
|
+
if (!prior || prior.status === "failed") writePickerPage(key, { ...prior ?? base, status: "loading", error: null });
|
|
962
|
+
} else if (prior) {
|
|
963
|
+
writePickerPage(key, { ...prior, loadingMore: true, error: null });
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
const page = await readAgentPickerPage(client, query, first ? null : prior?.nextCursor ?? null);
|
|
967
|
+
mergeRows(page.rows);
|
|
968
|
+
const current = state.pickerPages[key];
|
|
969
|
+
const keptIds = first ? [] : current?.ids ?? [];
|
|
970
|
+
const seen = new Set(keptIds);
|
|
971
|
+
const ids = [...keptIds];
|
|
972
|
+
for (const row of page.rows) if (!seen.has(row.id)) {
|
|
973
|
+
seen.add(row.id);
|
|
974
|
+
ids.push(row.id);
|
|
975
|
+
}
|
|
976
|
+
writePickerPage(key, {
|
|
977
|
+
ids,
|
|
978
|
+
nextCursor: page.nextCursor,
|
|
979
|
+
done: page.nextCursor === null || page.nextCursor === void 0,
|
|
980
|
+
status: "succeeded",
|
|
981
|
+
loadingMore: false,
|
|
982
|
+
error: null,
|
|
983
|
+
counts: page.counts ?? current?.counts ?? null,
|
|
984
|
+
fetchedAt: first ? Date.now() : current?.fetchedAt ?? Date.now()
|
|
985
|
+
});
|
|
986
|
+
} catch (e) {
|
|
987
|
+
if (e instanceof AgentPinnedReadUnsupported) {
|
|
988
|
+
errorSink({
|
|
989
|
+
code: "picker_page_unsupported",
|
|
990
|
+
message: `the agent picker fell back to the whole catalog (${e.message}); pass a supabase-js client`,
|
|
991
|
+
context: { catalogId }
|
|
992
|
+
});
|
|
993
|
+
const current2 = state.pickerPages[key];
|
|
994
|
+
writePickerPage(key, { ...current2 ?? base, status: "failed", loadingMore: false, error: "unsupported" });
|
|
995
|
+
await ensureLoaded();
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
const message = errorMessage(e);
|
|
999
|
+
const current = state.pickerPages[key];
|
|
1000
|
+
writePickerPage(key, { ...current ?? base, status: "failed", loadingMore: false, error: message });
|
|
1001
|
+
errorSink({
|
|
1002
|
+
code: "picker_page_failed",
|
|
1003
|
+
message: `the agent list could not be read: ${message}`,
|
|
1004
|
+
context: { catalogId, query }
|
|
1005
|
+
});
|
|
1006
|
+
throw e;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
function runPicker(key, run) {
|
|
1010
|
+
const existing = pickerInFlight.get(key);
|
|
1011
|
+
if (existing) return existing;
|
|
1012
|
+
const promise = run().finally(() => {
|
|
1013
|
+
if (pickerInFlight.get(key) === promise) pickerInFlight.delete(key);
|
|
1014
|
+
});
|
|
1015
|
+
pickerInFlight.set(key, promise);
|
|
1016
|
+
return promise;
|
|
1017
|
+
}
|
|
1018
|
+
function ensurePickerPage(query) {
|
|
1019
|
+
const key = agentPickerPageKey(query);
|
|
1020
|
+
const held = state.pickerPages[key];
|
|
1021
|
+
if (held && held.status === "succeeded" && Date.now() - held.fetchedAt < LIST_TTL_MS) {
|
|
1022
|
+
return Promise.resolve();
|
|
1023
|
+
}
|
|
1024
|
+
return runPicker(key, () => readPickerPage(query, true));
|
|
1025
|
+
}
|
|
1026
|
+
function loadMorePickerPage(query) {
|
|
1027
|
+
const key = agentPickerPageKey(query);
|
|
1028
|
+
const held = state.pickerPages[key];
|
|
1029
|
+
if (!held || held.done || held.status !== "succeeded" || held.loadingMore) return Promise.resolve();
|
|
1030
|
+
return runPicker(key, () => readPickerPage(query, false));
|
|
1031
|
+
}
|
|
1032
|
+
const rowsInFlight = /* @__PURE__ */ new Map();
|
|
1033
|
+
async function ensureAgentRows(agentIds) {
|
|
1034
|
+
if (getUserIdOrNull() === null) return;
|
|
1035
|
+
const waits = [];
|
|
1036
|
+
const wanted = [];
|
|
1037
|
+
for (const id of agentIds) {
|
|
1038
|
+
if (!id || id.startsWith("mandate:")) continue;
|
|
1039
|
+
const held = state.byId[id];
|
|
1040
|
+
if (held && !held.isThin) continue;
|
|
1041
|
+
const flying = rowsInFlight.get(id);
|
|
1042
|
+
if (flying) waits.push(flying);
|
|
1043
|
+
else wanted.push(id);
|
|
1044
|
+
}
|
|
1045
|
+
if (wanted.length > 0) {
|
|
1046
|
+
const run = (async () => {
|
|
1047
|
+
try {
|
|
1048
|
+
mergeRows(await readAgentRowsByIds(client, wanted));
|
|
1049
|
+
} catch (e) {
|
|
1050
|
+
errorSink({
|
|
1051
|
+
code: "agent_rows_read_failed",
|
|
1052
|
+
message: `agent details could not be read: ${errorMessage(e)}`,
|
|
1053
|
+
context: { catalogId, count: wanted.length }
|
|
1054
|
+
});
|
|
1055
|
+
throw e;
|
|
1056
|
+
}
|
|
1057
|
+
})();
|
|
1058
|
+
const settled = run.finally(() => {
|
|
1059
|
+
for (const id of wanted) if (rowsInFlight.get(id) === settled) rowsInFlight.delete(id);
|
|
1060
|
+
});
|
|
1061
|
+
for (const id of wanted) rowsInFlight.set(id, settled);
|
|
1062
|
+
waits.push(settled);
|
|
1063
|
+
}
|
|
1064
|
+
await Promise.all(waits);
|
|
1065
|
+
}
|
|
1066
|
+
const taggedAt = /* @__PURE__ */ new Map();
|
|
1067
|
+
const taggedInFlight = /* @__PURE__ */ new Map();
|
|
1068
|
+
function ensureTaggedRows(tags) {
|
|
1069
|
+
if (tags.length === 0 || isFresh() || getUserIdOrNull() === null) return Promise.resolve();
|
|
1070
|
+
const key = [...tags].sort().join("|");
|
|
1071
|
+
const at = taggedAt.get(key);
|
|
1072
|
+
if (at !== void 0 && Date.now() - at < LIST_TTL_MS) return Promise.resolve();
|
|
1073
|
+
const flying = taggedInFlight.get(key);
|
|
1074
|
+
if (flying) return flying;
|
|
1075
|
+
const run = (async () => {
|
|
1076
|
+
try {
|
|
1077
|
+
mergeRows(await readAgentTaggedRows(client, tags));
|
|
1078
|
+
taggedAt.set(key, Date.now());
|
|
1079
|
+
} catch (e) {
|
|
1080
|
+
if (e instanceof AgentPinnedReadUnsupported) {
|
|
1081
|
+
await ensureLoaded();
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
errorSink({
|
|
1085
|
+
code: "tagged_rows_read_failed",
|
|
1086
|
+
message: `agents tagged ${key} could not be read: ${errorMessage(e)}`,
|
|
1087
|
+
context: { catalogId, tags }
|
|
1088
|
+
});
|
|
1089
|
+
throw e;
|
|
1090
|
+
}
|
|
1091
|
+
})().finally(() => {
|
|
1092
|
+
if (taggedInFlight.get(key) === run) taggedInFlight.delete(key);
|
|
1093
|
+
});
|
|
1094
|
+
taggedInFlight.set(key, run);
|
|
1095
|
+
return run;
|
|
1096
|
+
}
|
|
874
1097
|
function getConsumer(consumerId) {
|
|
875
1098
|
return state.consumers[consumerId] ?? consumerDefaults;
|
|
876
1099
|
}
|
|
@@ -1065,6 +1288,7 @@ function createAgentCatalog(config) {
|
|
|
1065
1288
|
errorSink,
|
|
1066
1289
|
notifier,
|
|
1067
1290
|
canResolveMandates: transport !== void 0,
|
|
1291
|
+
pickerReads: config.pickerReads === "catalog" ? "catalog" : "page",
|
|
1068
1292
|
consumerDefaults,
|
|
1069
1293
|
getState: () => state,
|
|
1070
1294
|
subscribe: (listener) => {
|
|
@@ -1078,6 +1302,10 @@ function createAgentCatalog(config) {
|
|
|
1078
1302
|
ensureLoaded,
|
|
1079
1303
|
ensureFavoriteRows,
|
|
1080
1304
|
seedFavoriteRows,
|
|
1305
|
+
ensurePickerPage,
|
|
1306
|
+
loadMorePickerPage,
|
|
1307
|
+
ensureAgentRows,
|
|
1308
|
+
ensureTaggedRows,
|
|
1081
1309
|
isFresh,
|
|
1082
1310
|
isStale,
|
|
1083
1311
|
getConsumer,
|