@ai-matrx/agents 0.59.263 → 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/context/react/index.cjs +19 -10
- package/dist/context/react/index.d.cts +0 -6
- package/dist/context/react/index.d.ts +0 -6
- package/dist/context/react/index.js +19 -10
- 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/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
<!-- matrx-auto-git-docs-resolution-needed-delete-this-when-resolved — two versions follow: LOCAL first, then GITHUB. (LOCAL latest 2026-10-23 13:10; GITHUB latest 2026-10-23 13:07; LOCAL lacks 3 of GITHUB's 5 new lines; GITHUB lacks 26 of LOCAL's 26 new lines; recover: git show c3687a0589:'apps/shared/matrx-agents/CHANGELOG.md' / f84d7bf4e5:'apps/shared/matrx-agents/CHANGELOG.md') Delete these three marker lines when resolved. -->
|
|
4
|
+
|
|
5
|
+
## 0.59.265
|
|
6
|
+
### tagged rows without the catalog (FW-014, composer)
|
|
7
|
+
|
|
8
|
+
- `ensureTaggedRows(tags)` + `readAgentTaggedRows` (`agent.agx_picker_tagged`, live 2026-10-23, reach verified
|
|
9
|
+
against `agx_get_list_full`): the caller's live agents carrying any tag, never the whole catalog.
|
|
10
|
+
Consumer action: none.
|
|
11
|
+
|
|
12
|
+
### the agent menu opens on one thin page, never the catalog (FW-014)
|
|
13
|
+
|
|
14
|
+
- Every picker (`AgentListDropdown`, `AgentListPanel`, `AgentListInlinePicker`, native `AgentPickerBody`)
|
|
15
|
+
now opens on ONE page of `agent.agx_picker_page` — favorites, then the agents the person used most
|
|
16
|
+
recently, then name; 50 rows; only the columns a row renders; the tab counts on page 1 — and reads the
|
|
17
|
+
next page on scroll. Typing still searches `agx_search`. Measured in SQL: page 1 3.4 KB (ordinary account)
|
|
18
|
+
/ 32 KB (admin) in ~0.2–0.4 s, where opening the menu used to read 673 / 1,476 full rows (~340 KB, 2.6 s).
|
|
19
|
+
The whole catalog is read only when the person opens the category/tag facets, picks another sort, a
|
|
20
|
+
favorites/access filter, or the host passes `agentFilter` — or when another surface already holds it fresh.
|
|
21
|
+
- New on the catalog: `ensurePickerPage(query)`, `loadMorePickerPage(query)`, `ensureAgentRows(ids)` (full
|
|
22
|
+
rows by id through `agent.agx_get_by_ids`), `state.pickerPages`, `agentPickerPageKey`, `pickerReads`.
|
|
23
|
+
New data doors: `readAgentPickerPage`, `readAgentRowsByIds`, `PICKER_PAGE_SIZE`. `AgentSummary` gains
|
|
24
|
+
`isThin` (a page row: no description/model/offering/task/source/sharer) and `lastUsedAt`.
|
|
25
|
+
- A thin row never replaces a full one; hovering a thin row reads that one row (web and native detail
|
|
26
|
+
cards render the full row when it lands); the pinned current agent is read by id when it is not on page 1.
|
|
27
|
+
- `useAgentSummary` reads the one row by id (`ensureAgentRows`) instead of loading the whole catalog;
|
|
28
|
+
server search remains the fallback for an id that read does not return.
|
|
29
|
+
- `useAgentListCore` returns `paged`, `hasMore`, `loadingMore`, `loadMore`, `totalCount`,
|
|
30
|
+
`ensureFullCatalog`; `AgentListContent` (web) takes `hasMore` / `onReachEnd` / `loadingMore` /
|
|
31
|
+
`totalCount`, native takes `onReachEnd` / `totalCount`.
|
|
32
|
+
- Knob: `createAgentCatalog({ pickerReads: "catalog" })` restores the read-everything-on-open behavior (a
|
|
33
|
+
host whose client cannot reach the `agent` schema; tests of the full-list rules). Default `"page"`.
|
|
34
|
+
- Consumer action: none for hosts using supabase-js (needs `agent.agx_picker_page`, live since
|
|
35
|
+
2026-10-23). A host that reads `catalog.getState().rows` expecting the whole catalog after a picker
|
|
36
|
+
opened must call `ensureLoaded()` itself — the picker no longer loads it. A host component that
|
|
37
|
+
renders a hovered agent's description from the picker's object should read the store row
|
|
38
|
+
(`getAgent(id)`), which `ensureAgentRows` fills.
|
|
39
|
+
|
|
40
|
+
### no second star read after a catalog or search read (FW-014)
|
|
41
|
+
<!-- matrx-auto-git-docs-resolution-needed-delete-this-when-resolved — GITHUB version below -->
|
|
42
|
+
## 0.59.264
|
|
43
|
+
|
|
44
|
+
Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 6201da503d aa81498da0 -- apps/shared/matrx-agents/src`
|
|
45
|
+
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.
|
|
46
|
+
|
|
3
47
|
## 0.59.263
|
|
48
|
+
|
|
49
|
+
Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff de2b958734 6201da503d -- apps/shared/matrx-agents/src`
|
|
50
|
+
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.
|
|
51
|
+
|
|
52
|
+
## 0.59.262
|
|
4
53
|
### no second star read after a catalog or search read (FW-014)
|
|
54
|
+
<!-- matrx-auto-git-docs-resolution-needed-delete-this-when-resolved — end of both versions -->
|
|
5
55
|
|
|
6
56
|
- `ensureLoaded()` and `searchServer()` no longer follow each read with `ues_get_bulk('agent', every id)`.
|
|
7
57
|
Every catalog read (`agx_get_list_full`, `agx_search`, `agent.agx_get_by_ids`) already returns
|
package/dist/catalog/index.cjs
CHANGED
|
@@ -256,7 +256,8 @@ function toAgentSummary(raw) {
|
|
|
256
256
|
orchestra: orchestraBadge(raw["orchestra"]),
|
|
257
257
|
// Present only when the read carries the column: absent ≠ "no pin".
|
|
258
258
|
..."offering_id" in raw ? { offeringId: str(raw["offering_id"]) } : {},
|
|
259
|
-
..."organization_name" in raw ? { organizationName: str(raw["organization_name"]) } : {}
|
|
259
|
+
..."organization_name" in raw ? { organizationName: str(raw["organization_name"]) } : {},
|
|
260
|
+
..."last_used_at" in raw ? { lastUsedAt: str(raw["last_used_at"]) } : {}
|
|
260
261
|
};
|
|
261
262
|
}
|
|
262
263
|
|
|
@@ -395,20 +396,77 @@ async function readFavoriteAgentRows(client, favorites) {
|
|
|
395
396
|
)
|
|
396
397
|
);
|
|
397
398
|
if (ids.length === 0) return [];
|
|
399
|
+
const rows = await readAgentRowsByIds(client, ids, "agent.agx_get_by_ids (pinned)");
|
|
400
|
+
return rows.map((summary) => summary.isFavorite ? summary : { ...summary, isFavorite: true });
|
|
401
|
+
}
|
|
402
|
+
async function readAgentRowsByIds(client, agentIds, label = "agent.agx_get_by_ids") {
|
|
403
|
+
if (typeof client.schema !== "function") {
|
|
404
|
+
throw new AgentPinnedReadUnsupported("the client has no schema()");
|
|
405
|
+
}
|
|
406
|
+
const ids = Array.from(new Set(agentIds.filter((id) => typeof id === "string" && id.length > 0)));
|
|
398
407
|
const rows = [];
|
|
399
408
|
for (let at = 0; at < ids.length; at += PINNED_ID_CHUNK) {
|
|
400
409
|
const chunk = ids.slice(at, at + PINNED_ID_CHUNK);
|
|
401
410
|
const { data, error } = await client.schema("agent").rpc("agx_get_by_ids", { p_ids: chunk });
|
|
402
411
|
if (error) {
|
|
403
|
-
throw new AgentCatalogReadError(
|
|
412
|
+
throw new AgentCatalogReadError(label, pgDetail(error));
|
|
404
413
|
}
|
|
405
414
|
for (const item of Array.isArray(data) ? data : []) {
|
|
406
415
|
const summary = toAgentSummary(item);
|
|
407
|
-
if (summary) rows.push(summary
|
|
416
|
+
if (summary) rows.push(summary);
|
|
408
417
|
}
|
|
409
418
|
}
|
|
410
419
|
return rows;
|
|
411
420
|
}
|
|
421
|
+
var PICKER_PAGE_SIZE = 50;
|
|
422
|
+
function countOf(record, key) {
|
|
423
|
+
const value = record[key];
|
|
424
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
425
|
+
}
|
|
426
|
+
async function readAgentPickerPage(client, query, cursor = null, limit = PICKER_PAGE_SIZE) {
|
|
427
|
+
if (typeof client.schema !== "function") {
|
|
428
|
+
throw new AgentPinnedReadUnsupported("the client has no schema()");
|
|
429
|
+
}
|
|
430
|
+
const { data, error } = await client.schema("agent").rpc("agx_picker_page", {
|
|
431
|
+
p_tab: query.tab,
|
|
432
|
+
p_archive: query.archive,
|
|
433
|
+
p_include_quick: query.includeQuick,
|
|
434
|
+
p_cursor: cursor ?? null,
|
|
435
|
+
p_limit: limit
|
|
436
|
+
});
|
|
437
|
+
if (error) throw new AgentCatalogReadError("agent.agx_picker_page", pgDetail(error));
|
|
438
|
+
const record = typeof data === "object" && data !== null && !Array.isArray(data) ? data : null;
|
|
439
|
+
if (!record) {
|
|
440
|
+
throw new AgentCatalogReadError("agent.agx_picker_page", "the page came back without a body");
|
|
441
|
+
}
|
|
442
|
+
const rows = [];
|
|
443
|
+
for (const item of Array.isArray(record["rows"]) ? record["rows"] : []) {
|
|
444
|
+
const summary = toAgentSummary(item);
|
|
445
|
+
if (summary) rows.push({ ...summary, isThin: true });
|
|
446
|
+
}
|
|
447
|
+
const rawCounts = record["counts"];
|
|
448
|
+
const counts = typeof rawCounts === "object" && rawCounts !== null && !Array.isArray(rawCounts) ? {
|
|
449
|
+
mine: countOf(rawCounts, "mine"),
|
|
450
|
+
shared: countOf(rawCounts, "shared"),
|
|
451
|
+
all: countOf(rawCounts, "all"),
|
|
452
|
+
system: countOf(rawCounts, "system"),
|
|
453
|
+
archivedHidden: countOf(rawCounts, "archived_hidden")
|
|
454
|
+
} : null;
|
|
455
|
+
return { rows, nextCursor: record["next_cursor"] ?? null, counts };
|
|
456
|
+
}
|
|
457
|
+
async function readAgentTaggedRows(client, tags) {
|
|
458
|
+
if (typeof client.schema !== "function") {
|
|
459
|
+
throw new AgentPinnedReadUnsupported("the client has no schema()");
|
|
460
|
+
}
|
|
461
|
+
const { data, error } = await client.schema("agent").rpc("agx_picker_tagged", { p_tags: [...tags] });
|
|
462
|
+
if (error) throw new AgentCatalogReadError("agent.agx_picker_tagged", pgDetail(error));
|
|
463
|
+
const rows = [];
|
|
464
|
+
for (const item of Array.isArray(data) ? data : []) {
|
|
465
|
+
const summary = toAgentSummary(item);
|
|
466
|
+
if (summary) rows.push({ ...summary, isThin: true });
|
|
467
|
+
}
|
|
468
|
+
return rows;
|
|
469
|
+
}
|
|
412
470
|
|
|
413
471
|
// catalog/default-row.ts
|
|
414
472
|
var CACHE_PREFIX = "ai-matrx.agents.catalog.default-row:";
|
|
@@ -746,6 +804,9 @@ function readsOrchestraColumn(data) {
|
|
|
746
804
|
// catalog/store.ts
|
|
747
805
|
var LIST_TTL_MS = 15 * 60 * 1e3;
|
|
748
806
|
var LIST_STALE_MS = 4 * 60 * 60 * 1e3;
|
|
807
|
+
function agentPickerPageKey(query) {
|
|
808
|
+
return `${query.tab}|${query.archive}|${query.includeQuick ? "q" : "-"}`;
|
|
809
|
+
}
|
|
749
810
|
function createDefaultErrorSink() {
|
|
750
811
|
let announced = false;
|
|
751
812
|
return (event) => {
|
|
@@ -829,7 +890,8 @@ function createAgentCatalog(config) {
|
|
|
829
890
|
error: null,
|
|
830
891
|
consumers: {},
|
|
831
892
|
defaultRows: {},
|
|
832
|
-
favoriteRowsStatus: "idle"
|
|
893
|
+
favoriteRowsStatus: "idle",
|
|
894
|
+
pickerPages: {}
|
|
833
895
|
};
|
|
834
896
|
const listeners = /* @__PURE__ */ new Set();
|
|
835
897
|
function publish(next) {
|
|
@@ -839,7 +901,10 @@ function createAgentCatalog(config) {
|
|
|
839
901
|
function mergeRows(incoming) {
|
|
840
902
|
if (incoming.length === 0) return;
|
|
841
903
|
const byId = { ...state.byId };
|
|
842
|
-
for (const row of incoming)
|
|
904
|
+
for (const row of incoming) {
|
|
905
|
+
const held = byId[row.id];
|
|
906
|
+
byId[row.id] = row.isThin && held && !held.isThin ? { ...held, isFavorite: row.isFavorite, ...row.lastUsedAt !== void 0 ? { lastUsedAt: row.lastUsedAt } : {} } : row;
|
|
907
|
+
}
|
|
843
908
|
publish({ ...state, byId, rows: Object.values(byId) });
|
|
844
909
|
}
|
|
845
910
|
function replaceRows(incoming) {
|
|
@@ -977,6 +1042,164 @@ function createAgentCatalog(config) {
|
|
|
977
1042
|
favoriteRowsInFlight = run;
|
|
978
1043
|
return run;
|
|
979
1044
|
}
|
|
1045
|
+
const pickerInFlight = /* @__PURE__ */ new Map();
|
|
1046
|
+
function writePickerPage(key, next) {
|
|
1047
|
+
publish({ ...state, pickerPages: { ...state.pickerPages, [key]: next } });
|
|
1048
|
+
}
|
|
1049
|
+
async function readPickerPage(query, first) {
|
|
1050
|
+
const key = agentPickerPageKey(query);
|
|
1051
|
+
const prior = state.pickerPages[key];
|
|
1052
|
+
if (getUserIdOrNull() === null) {
|
|
1053
|
+
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() });
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const base = {
|
|
1057
|
+
ids: [],
|
|
1058
|
+
nextCursor: null,
|
|
1059
|
+
done: false,
|
|
1060
|
+
status: "loading",
|
|
1061
|
+
loadingMore: false,
|
|
1062
|
+
error: null,
|
|
1063
|
+
counts: null,
|
|
1064
|
+
fetchedAt: 0
|
|
1065
|
+
};
|
|
1066
|
+
if (first) {
|
|
1067
|
+
if (!prior || prior.status === "failed") writePickerPage(key, { ...prior ?? base, status: "loading", error: null });
|
|
1068
|
+
} else if (prior) {
|
|
1069
|
+
writePickerPage(key, { ...prior, loadingMore: true, error: null });
|
|
1070
|
+
}
|
|
1071
|
+
try {
|
|
1072
|
+
const page = await readAgentPickerPage(client, query, first ? null : prior?.nextCursor ?? null);
|
|
1073
|
+
mergeRows(page.rows);
|
|
1074
|
+
const current = state.pickerPages[key];
|
|
1075
|
+
const keptIds = first ? [] : current?.ids ?? [];
|
|
1076
|
+
const seen = new Set(keptIds);
|
|
1077
|
+
const ids = [...keptIds];
|
|
1078
|
+
for (const row of page.rows) if (!seen.has(row.id)) {
|
|
1079
|
+
seen.add(row.id);
|
|
1080
|
+
ids.push(row.id);
|
|
1081
|
+
}
|
|
1082
|
+
writePickerPage(key, {
|
|
1083
|
+
ids,
|
|
1084
|
+
nextCursor: page.nextCursor,
|
|
1085
|
+
done: page.nextCursor === null || page.nextCursor === void 0,
|
|
1086
|
+
status: "succeeded",
|
|
1087
|
+
loadingMore: false,
|
|
1088
|
+
error: null,
|
|
1089
|
+
counts: page.counts ?? current?.counts ?? null,
|
|
1090
|
+
fetchedAt: first ? Date.now() : current?.fetchedAt ?? Date.now()
|
|
1091
|
+
});
|
|
1092
|
+
} catch (e) {
|
|
1093
|
+
if (e instanceof AgentPinnedReadUnsupported) {
|
|
1094
|
+
errorSink({
|
|
1095
|
+
code: "picker_page_unsupported",
|
|
1096
|
+
message: `the agent picker fell back to the whole catalog (${e.message}); pass a supabase-js client`,
|
|
1097
|
+
context: { catalogId }
|
|
1098
|
+
});
|
|
1099
|
+
const current2 = state.pickerPages[key];
|
|
1100
|
+
writePickerPage(key, { ...current2 ?? base, status: "failed", loadingMore: false, error: "unsupported" });
|
|
1101
|
+
await ensureLoaded();
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
const message = errorMessage(e);
|
|
1105
|
+
const current = state.pickerPages[key];
|
|
1106
|
+
writePickerPage(key, { ...current ?? base, status: "failed", loadingMore: false, error: message });
|
|
1107
|
+
errorSink({
|
|
1108
|
+
code: "picker_page_failed",
|
|
1109
|
+
message: `the agent list could not be read: ${message}`,
|
|
1110
|
+
context: { catalogId, query }
|
|
1111
|
+
});
|
|
1112
|
+
throw e;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
function runPicker(key, run) {
|
|
1116
|
+
const existing = pickerInFlight.get(key);
|
|
1117
|
+
if (existing) return existing;
|
|
1118
|
+
const promise = run().finally(() => {
|
|
1119
|
+
if (pickerInFlight.get(key) === promise) pickerInFlight.delete(key);
|
|
1120
|
+
});
|
|
1121
|
+
pickerInFlight.set(key, promise);
|
|
1122
|
+
return promise;
|
|
1123
|
+
}
|
|
1124
|
+
function ensurePickerPage(query) {
|
|
1125
|
+
const key = agentPickerPageKey(query);
|
|
1126
|
+
const held = state.pickerPages[key];
|
|
1127
|
+
if (held && held.status === "succeeded" && Date.now() - held.fetchedAt < LIST_TTL_MS) {
|
|
1128
|
+
return Promise.resolve();
|
|
1129
|
+
}
|
|
1130
|
+
return runPicker(key, () => readPickerPage(query, true));
|
|
1131
|
+
}
|
|
1132
|
+
function loadMorePickerPage(query) {
|
|
1133
|
+
const key = agentPickerPageKey(query);
|
|
1134
|
+
const held = state.pickerPages[key];
|
|
1135
|
+
if (!held || held.done || held.status !== "succeeded" || held.loadingMore) return Promise.resolve();
|
|
1136
|
+
return runPicker(key, () => readPickerPage(query, false));
|
|
1137
|
+
}
|
|
1138
|
+
const rowsInFlight = /* @__PURE__ */ new Map();
|
|
1139
|
+
async function ensureAgentRows(agentIds) {
|
|
1140
|
+
if (getUserIdOrNull() === null) return;
|
|
1141
|
+
const waits = [];
|
|
1142
|
+
const wanted = [];
|
|
1143
|
+
for (const id of agentIds) {
|
|
1144
|
+
if (!id || id.startsWith("mandate:")) continue;
|
|
1145
|
+
const held = state.byId[id];
|
|
1146
|
+
if (held && !held.isThin) continue;
|
|
1147
|
+
const flying = rowsInFlight.get(id);
|
|
1148
|
+
if (flying) waits.push(flying);
|
|
1149
|
+
else wanted.push(id);
|
|
1150
|
+
}
|
|
1151
|
+
if (wanted.length > 0) {
|
|
1152
|
+
const run = (async () => {
|
|
1153
|
+
try {
|
|
1154
|
+
mergeRows(await readAgentRowsByIds(client, wanted));
|
|
1155
|
+
} catch (e) {
|
|
1156
|
+
errorSink({
|
|
1157
|
+
code: "agent_rows_read_failed",
|
|
1158
|
+
message: `agent details could not be read: ${errorMessage(e)}`,
|
|
1159
|
+
context: { catalogId, count: wanted.length }
|
|
1160
|
+
});
|
|
1161
|
+
throw e;
|
|
1162
|
+
}
|
|
1163
|
+
})();
|
|
1164
|
+
const settled = run.finally(() => {
|
|
1165
|
+
for (const id of wanted) if (rowsInFlight.get(id) === settled) rowsInFlight.delete(id);
|
|
1166
|
+
});
|
|
1167
|
+
for (const id of wanted) rowsInFlight.set(id, settled);
|
|
1168
|
+
waits.push(settled);
|
|
1169
|
+
}
|
|
1170
|
+
await Promise.all(waits);
|
|
1171
|
+
}
|
|
1172
|
+
const taggedAt = /* @__PURE__ */ new Map();
|
|
1173
|
+
const taggedInFlight = /* @__PURE__ */ new Map();
|
|
1174
|
+
function ensureTaggedRows(tags) {
|
|
1175
|
+
if (tags.length === 0 || isFresh() || getUserIdOrNull() === null) return Promise.resolve();
|
|
1176
|
+
const key = [...tags].sort().join("|");
|
|
1177
|
+
const at = taggedAt.get(key);
|
|
1178
|
+
if (at !== void 0 && Date.now() - at < LIST_TTL_MS) return Promise.resolve();
|
|
1179
|
+
const flying = taggedInFlight.get(key);
|
|
1180
|
+
if (flying) return flying;
|
|
1181
|
+
const run = (async () => {
|
|
1182
|
+
try {
|
|
1183
|
+
mergeRows(await readAgentTaggedRows(client, tags));
|
|
1184
|
+
taggedAt.set(key, Date.now());
|
|
1185
|
+
} catch (e) {
|
|
1186
|
+
if (e instanceof AgentPinnedReadUnsupported) {
|
|
1187
|
+
await ensureLoaded();
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
errorSink({
|
|
1191
|
+
code: "tagged_rows_read_failed",
|
|
1192
|
+
message: `agents tagged ${key} could not be read: ${errorMessage(e)}`,
|
|
1193
|
+
context: { catalogId, tags }
|
|
1194
|
+
});
|
|
1195
|
+
throw e;
|
|
1196
|
+
}
|
|
1197
|
+
})().finally(() => {
|
|
1198
|
+
if (taggedInFlight.get(key) === run) taggedInFlight.delete(key);
|
|
1199
|
+
});
|
|
1200
|
+
taggedInFlight.set(key, run);
|
|
1201
|
+
return run;
|
|
1202
|
+
}
|
|
980
1203
|
function getConsumer(consumerId) {
|
|
981
1204
|
return state.consumers[consumerId] ?? consumerDefaults;
|
|
982
1205
|
}
|
|
@@ -1171,6 +1394,7 @@ function createAgentCatalog(config) {
|
|
|
1171
1394
|
errorSink,
|
|
1172
1395
|
notifier,
|
|
1173
1396
|
canResolveMandates: transport !== void 0,
|
|
1397
|
+
pickerReads: config.pickerReads === "catalog" ? "catalog" : "page",
|
|
1174
1398
|
consumerDefaults,
|
|
1175
1399
|
getState: () => state,
|
|
1176
1400
|
subscribe: (listener) => {
|
|
@@ -1184,6 +1408,10 @@ function createAgentCatalog(config) {
|
|
|
1184
1408
|
ensureLoaded,
|
|
1185
1409
|
ensureFavoriteRows,
|
|
1186
1410
|
seedFavoriteRows,
|
|
1411
|
+
ensurePickerPage,
|
|
1412
|
+
loadMorePickerPage,
|
|
1413
|
+
ensureAgentRows,
|
|
1414
|
+
ensureTaggedRows,
|
|
1187
1415
|
isFresh,
|
|
1188
1416
|
isStale,
|
|
1189
1417
|
getConsumer,
|
package/dist/catalog/index.d.cts
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
|
*
|