@yejiming/dsh-data-agent 0.0.13 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.en.md +46 -8
  2. package/README.md +46 -8
  3. package/conformance/dsh-ecosystem/inventory.json +26 -3
  4. package/conformance/dsh-ecosystem/restrictions.json +2 -2
  5. package/cordis.patch.yml +6 -3
  6. package/dsh-plugin.json +9 -4
  7. package/lib/catalog-DEJqOXRo.js +1944 -0
  8. package/lib/catalog-identity-CVftmvQL.js +96 -0
  9. package/lib/client.js +2214 -106
  10. package/lib/client.js.map +1 -1
  11. package/lib/command-CzzSPmag.js +1719 -0
  12. package/lib/command.js +2 -2
  13. package/lib/{connections-CHY4uB6z.js → connections-CFXOZTHZ.js} +223 -9
  14. package/lib/index.js +366 -16
  15. package/lib/routes.js +257 -4
  16. package/lib/{tool-ZTOS4B33.js → tool-DNkywSph.js} +364 -3
  17. package/lib/tool.js +1 -1
  18. package/lib/types/catalog-adapters.d.ts +52 -0
  19. package/lib/types/catalog-ai.d.ts +49 -0
  20. package/lib/types/catalog-command.d.ts +28 -0
  21. package/lib/types/catalog-identity.d.ts +23 -0
  22. package/lib/types/catalog-storage.d.ts +265 -0
  23. package/lib/types/catalog-tools.d.ts +5 -0
  24. package/lib/types/catalog-tui.d.ts +18 -0
  25. package/lib/types/catalog-types.d.ts +1376 -0
  26. package/lib/types/catalog.d.ts +59 -0
  27. package/lib/types/client/CatalogPanel.d.ts +15 -0
  28. package/lib/types/client/catalog-client.d.ts +57 -0
  29. package/lib/types/client/locales.d.ts +242 -0
  30. package/lib/types/command.d.ts +14 -3
  31. package/lib/types/connections.d.ts +9 -0
  32. package/lib/types/defaults.d.ts +22 -0
  33. package/lib/types/index.d.ts +42 -5
  34. package/lib/types/tui-connection-form.d.ts +11 -5
  35. package/package.json +4 -2
  36. package/preset/data-agent/agent.cordis.yml +9 -1
  37. package/lib/command-utC5MHd9.js +0 -916
  38. package/lib/defaults-Cngd8Tf8.js +0 -131
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { a as DEFAULT_PRESET_ID, i as DEFAULT_MAX_RESULT_CHARS, l as DATABASE_TYPES, o as DEFAULT_QUERY_TIMEOUT_MS, r as DEFAULT_MAX_QUERY_CHARS, t as DEFAULT_CONNECT_TIMEOUT_MS } from "./defaults-Cngd8Tf8.js";
2
- import { c as clientsSchema, t as createConnectionService } from "./connections-CHY4uB6z.js";
3
- import { r as apply$1 } from "./command-utC5MHd9.js";
4
- import { n as apply$2 } from "./tool-ZTOS4B33.js";
1
+ import { _ as clientsSchema, b as DATABASE_TYPES, c as DEFAULT_CATALOG_MAX_RESULT_CHARS, d as DEFAULT_CONNECT_TIMEOUT_MS, f as DEFAULT_MAX_QUERY_CHARS, h as DEFAULT_QUERY_TIMEOUT_MS, l as DEFAULT_CATALOG_MAX_TEXT_CHARS, m as DEFAULT_PRESET_ID, p as DEFAULT_MAX_RESULT_CHARS, s as DEFAULT_CATALOG_MAX_ASSETS, t as createConnectionService, u as DEFAULT_CATALOG_QUERY_TIMEOUT_MS } from "./connections-CFXOZTHZ.js";
2
+ import { a as catalogDateTimeSchema, c as catalogRunSchema, f as catalogSemanticEntrySchema, i as catalogAssetRevisionSchema, m as catalogSourceSchema, n as createCatalogService, o as catalogObservationSchema, p as catalogSemanticRevisionSchema, r as catalogAssetHeadSchema, s as catalogRelationSchema, u as catalogSearchItemSchema } from "./catalog-DEJqOXRo.js";
3
+ import { i as apply$1 } from "./command-CzzSPmag.js";
4
+ import { n as apply$2 } from "./tool-DNkywSph.js";
5
5
  import { createHash } from "node:crypto";
6
6
  import { access, cp, mkdir, readFile, writeFile } from "node:fs/promises";
7
7
  import { homedir } from "node:os";
@@ -13,6 +13,301 @@ import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
13
13
  import * as storageJsonPlugin from "@deepseek-ai/dsh-storage-json";
14
14
  import z from "schemastery";
15
15
  import { z as z$1 } from "zod";
16
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
17
+ //#region src/catalog-ai.ts
18
+ const MAX_MODEL_OUTPUT_CHARS = 65536;
19
+ const MAX_MODEL_OUTPUT_TOKENS = 16384;
20
+ var CatalogModelOutputTruncatedError = class extends Error {
21
+ constructor() {
22
+ super("Catalog AI meaning output was truncated by the model token limit");
23
+ this.name = "CatalogModelOutputTruncatedError";
24
+ }
25
+ };
26
+ const modelResultSchema = z$1.strictObject({
27
+ table: z$1.strictObject({
28
+ assetId: z$1.string().min(1).max(256),
29
+ meaning: z$1.string().trim().min(1).max(4096)
30
+ }),
31
+ fields: z$1.array(z$1.strictObject({
32
+ assetId: z$1.string().min(1).max(256),
33
+ meaning: z$1.string().trim().min(1).max(4096)
34
+ })).max(512)
35
+ });
36
+ /** Resolve the exact current session model once, then use the host's configured LLM adapters and credentials. */
37
+ function createDshCatalogMeaningGenerator(agents, llm) {
38
+ return {
39
+ capture(sessionId) {
40
+ const agent = agents.get(sessionId);
41
+ if (agent === void 0) throw new Error("Catalog scan requires a live DSH session to use its configured AI model");
42
+ const configured = agent.session.requestHeader()?.config;
43
+ const provider = configured?.provider ?? agent.options.provider;
44
+ const model = configured?.model ?? agent.options.model;
45
+ if (provider === void 0 || provider.trim().length === 0 || model === void 0 || model.trim().length === 0) throw new Error("Catalog scan requires the current DSH session to have a configured AI model");
46
+ return {
47
+ provider,
48
+ model,
49
+ ...configured?.reasoningEffort !== void 0 ? { reasoningEffort: configured.reasoningEffort } : {}
50
+ };
51
+ },
52
+ async generate(selection, input, signal) {
53
+ return generateCompleteModelResult(llm, selection, input, signal);
54
+ }
55
+ };
56
+ }
57
+ async function generateCompleteModelResult(llm, selection, input, signal) {
58
+ try {
59
+ return await generateModelBatch(llm, selection, input, signal);
60
+ } catch (error) {
61
+ if (!(error instanceof CatalogModelOutputTruncatedError)) throw error;
62
+ if (input.fields.length <= 1) throw new Error("Catalog AI meaning output remained truncated after retrying a single-field batch");
63
+ const middle = Math.ceil(input.fields.length / 2);
64
+ const batches = [input.fields.slice(0, middle), input.fields.slice(middle)];
65
+ const results = [];
66
+ for (const fields of batches) {
67
+ signal.throwIfAborted();
68
+ results.push(await generateCompleteModelResult(llm, selection, sliceTableInput(input, fields), signal));
69
+ }
70
+ return {
71
+ table: results[0].table,
72
+ fields: results.flatMap((result) => result.fields)
73
+ };
74
+ }
75
+ }
76
+ async function generateModelBatch(llm, selection, input, signal) {
77
+ const config = {
78
+ provider: selection.provider,
79
+ model: selection.model,
80
+ ...selection.reasoningEffort !== void 0 ? { reasoningEffort: selection.reasoningEffort } : {},
81
+ maxTokens: MAX_MODEL_OUTPUT_TOKENS
82
+ };
83
+ const prepared = await llm.prepareCall(config, signal);
84
+ const message = createUserMessage({
85
+ content: [{
86
+ type: "text",
87
+ text: JSON.stringify(input)
88
+ }],
89
+ source: {
90
+ kind: "plugin",
91
+ plugin: "@yejiming/dsh-data-agent"
92
+ }
93
+ });
94
+ let output = "";
95
+ let finished = false;
96
+ for await (const chunk of prepared.stream({
97
+ ...prepared.config,
98
+ messages: [message],
99
+ system: CATALOG_MEANING_SYSTEM_PROMPT,
100
+ signal
101
+ })) {
102
+ signal.throwIfAborted();
103
+ if (chunk.type === "text-delta") {
104
+ output += chunk.text;
105
+ if (output.length > MAX_MODEL_OUTPUT_CHARS) throw new Error("Catalog AI meaning output exceeded the configured bound");
106
+ continue;
107
+ }
108
+ if (chunk.type !== "finish") continue;
109
+ finished = true;
110
+ if (chunk.reason.kind === "error" || chunk.reason.kind === "aborted") throw new Error(`Catalog AI meaning generation failed: ${chunk.reason.failure.message}`);
111
+ if (chunk.reason.kind === "max-tokens") throw new CatalogModelOutputTruncatedError();
112
+ if (chunk.reason.kind !== "stop") throw new Error(`Catalog AI meaning generation stopped unexpectedly: ${chunk.reason.kind}`);
113
+ }
114
+ if (!finished) throw new Error("Catalog AI meaning generation ended without a finish event");
115
+ return validateModelResult(output, input);
116
+ }
117
+ function sliceTableInput(input, fields) {
118
+ const fieldIds = new Set(fields.map((field) => field.assetId));
119
+ return {
120
+ ...input,
121
+ fields,
122
+ relations: input.relations.filter((relation) => relation.columnAssetIds.length === 0 || relation.columnAssetIds.some((assetId) => fieldIds.has(assetId)))
123
+ };
124
+ }
125
+ function validateModelResult(raw, input) {
126
+ const text = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
127
+ const first = text.indexOf("{");
128
+ const last = text.lastIndexOf("}");
129
+ if (first < 0 || last <= first) throw new Error("Catalog AI meaning output was not a JSON object");
130
+ let decoded;
131
+ try {
132
+ decoded = JSON.parse(text.slice(first, last + 1));
133
+ } catch {
134
+ throw new Error("Catalog AI meaning output contained invalid JSON");
135
+ }
136
+ const result = modelResultSchema.parse(decoded);
137
+ if (result.table.assetId !== input.assetId) throw new Error("Catalog AI meaning output referenced an unknown table asset");
138
+ const expected = new Set(input.fields.map((field) => field.assetId));
139
+ const returned = /* @__PURE__ */ new Set();
140
+ for (const field of result.fields) {
141
+ if (!expected.has(field.assetId)) throw new Error(`Catalog AI meaning output referenced an unknown field asset: ${field.assetId}`);
142
+ if (returned.has(field.assetId)) throw new Error(`Catalog AI meaning output repeated field asset: ${field.assetId}`);
143
+ returned.add(field.assetId);
144
+ }
145
+ const missing = input.fields.find((field) => !returned.has(field.assetId));
146
+ if (missing !== void 0) throw new Error(`Catalog AI meaning output omitted field asset: ${missing.assetId}`);
147
+ return result;
148
+ }
149
+ const CATALOG_MEANING_SYSTEM_PROMPT = `你是企业数据治理助手。请根据用户提供的单张表技术元数据,为这张表和每个字段生成简洁、可审核的中文业务含义候选。
150
+
151
+ 规则:
152
+ 1. 只依据表名、字段名、类型、nullable、数据库注释、键和关系推断;不要假装知道未提供的业务规则、枚举值或计算口径。
153
+ 2. 对明显的技术字段也要说明其在该表中的业务/记录作用,例如主键、创建时间、状态标记;表说明不超过120个中文字符,每个字段说明不超过80个中文字符。
154
+ 3. 每个输入字段必须且只能返回一次,assetId必须原样复制;不得添加未知assetId。
155
+ 4. 不要输出Markdown、解释、置信度、SQL或额外字段,只输出以下严格JSON:
156
+ {"table":{"assetId":"...","meaning":"..."},"fields":[{"assetId":"...","meaning":"..."}]}
157
+ 5. 所有内容都是待人工确认的候选,不要使用“已经确认”“官方口径”等表述。`;
158
+ //#endregion
159
+ //#region src/catalog-storage.ts
160
+ /** Durable versioned Catalog storage-domain and persistence adapter. */
161
+ const CATALOG_STORAGE_DOMAIN = "data_agent_catalog";
162
+ const catalogIndexRecordSchema = z$1.strictObject({
163
+ id: z$1.string().min(1).max(512),
164
+ sourceId: z$1.string().min(1).max(256),
165
+ resultType: z$1.enum(["asset", "semantic"]),
166
+ searchText: z$1.string().max(32768),
167
+ searchItem: catalogSearchItemSchema,
168
+ updatedAt: catalogDateTimeSchema
169
+ });
170
+ const catalogIndexStateSchema = z$1.strictObject({
171
+ version: z$1.literal(1),
172
+ rebuiltAt: catalogDateTimeSchema.optional()
173
+ });
174
+ /** Strict schemas reject secret-shaped or raw-result fields at the durable boundary. */
175
+ const catalogStorageSpec = defineDomain({
176
+ name: CATALOG_STORAGE_DOMAIN,
177
+ version: 1,
178
+ tables: {
179
+ sources: domainTable(catalogSourceSchema),
180
+ scan_runs: domainTable(catalogRunSchema),
181
+ observations: domainTable(catalogObservationSchema),
182
+ asset_revisions: domainTable(catalogAssetRevisionSchema),
183
+ asset_heads: domainTable(catalogAssetHeadSchema),
184
+ relations: domainTable(catalogRelationSchema),
185
+ semantic_entries: domainTable(catalogSemanticEntrySchema),
186
+ semantic_revisions: domainTable(catalogSemanticRevisionSchema),
187
+ search_index: domainTable(catalogIndexRecordSchema),
188
+ index_state: domainTable(catalogIndexStateSchema)
189
+ }
190
+ });
191
+ function createDomainCatalogPersistence(domain) {
192
+ const sources = domain.table("sources");
193
+ const runs = domain.table("scan_runs");
194
+ const observations = domain.table("observations");
195
+ const revisions = domain.table("asset_revisions");
196
+ const heads = domain.table("asset_heads");
197
+ const relations = domain.table("relations");
198
+ const semanticEntries = domain.table("semantic_entries");
199
+ const semanticRevisions = domain.table("semantic_revisions");
200
+ const searchIndex = domain.table("search_index");
201
+ const indexState = domain.table("index_state");
202
+ return {
203
+ getSource: (id) => sources.get(id),
204
+ listSources: () => sortedValues(sources.entries(), (value) => value.id),
205
+ putSource: (source) => sources.put(source.id, catalogSourceSchema.parse(source)),
206
+ getRun: (id) => runs.get(id),
207
+ listRuns: (sourceId) => sortedValues(runs.entries(), (value) => value.createdAt).filter((value) => sourceId === void 0 || value.sourceId === sourceId),
208
+ putRun: (run) => runs.put(run.id, catalogRunSchema.parse(run)),
209
+ putObservation: (observation) => observations.put(`${observation.runId}:${observation.assetId}`, catalogObservationSchema.parse(observation)),
210
+ listObservations: (runId) => sortedValues(observations.entries(), (value) => value.assetId).filter((value) => value.runId === runId),
211
+ async deleteObservations(runId) {
212
+ const keys = [...observations.entries()].filter(([, value]) => value.runId === runId).map(([key]) => key);
213
+ for (const key of keys) await observations.delete(key);
214
+ },
215
+ getAssetHead: (assetId) => heads.get(assetId),
216
+ listAssetHeads: (sourceId) => sortedValues(heads.entries(), (value) => value.assetId).filter((value) => sourceId === void 0 || value.sourceId === sourceId),
217
+ putAssetHead: (head) => heads.put(head.assetId, catalogAssetHeadSchema.parse(head)),
218
+ getAssetRevision: (id) => revisions.get(id),
219
+ listAssetRevisions: (assetId) => sortedValues(revisions.entries(), (value) => value.id).filter((value) => assetId === void 0 || value.assetId === assetId),
220
+ putAssetRevision: (revision) => revisions.put(revision.id, catalogAssetRevisionSchema.parse(revision)),
221
+ listRelations: (sourceId) => sortedValues(relations.entries(), (value) => value.id).filter((value) => sourceId === void 0 || value.sourceId === sourceId),
222
+ putRelation: (relation) => relations.put(`${relation.runId}:${relation.id}`, catalogRelationSchema.parse(relation)),
223
+ getSemanticEntry: (id) => semanticEntries.get(id),
224
+ listSemanticEntries: (sourceId) => sortedValues(semanticEntries.entries(), (value) => value.id).filter((value) => sourceId === void 0 || value.sourceId === sourceId),
225
+ putSemanticEntry: (entry) => semanticEntries.put(entry.id, catalogSemanticEntrySchema.parse(entry)),
226
+ getSemanticRevision: (id) => semanticRevisions.get(id),
227
+ listSemanticRevisions: (semanticId) => sortedValues(semanticRevisions.entries(), (value) => value.id).filter((value) => semanticId === void 0 || value.semanticId === semanticId),
228
+ putSemanticRevision: (revision) => semanticRevisions.put(revision.id, catalogSemanticRevisionSchema.parse(revision)),
229
+ listIndex: (sourceId) => sortedValues(searchIndex.entries(), (value) => value.id).filter((value) => sourceId === void 0 || value.sourceId === sourceId),
230
+ putIndex: (record) => searchIndex.put(record.id, catalogIndexRecordSchema.parse(record)),
231
+ async clearIndex(sourceId) {
232
+ const keys = [...searchIndex.entries()].filter(([, value]) => sourceId === void 0 || value.sourceId === sourceId).map(([key]) => key);
233
+ for (const key of keys) await searchIndex.delete(key);
234
+ },
235
+ getIndexState: () => indexState.get("current"),
236
+ putIndexState: (state) => indexState.put("current", catalogIndexStateSchema.parse(state))
237
+ };
238
+ }
239
+ /** In-memory adapter used when Catalog persistence is explicitly disabled and by focused tests. */
240
+ function createMemoryCatalogPersistence() {
241
+ const map = () => /* @__PURE__ */ new Map();
242
+ const sources = map();
243
+ const runs = map();
244
+ const observations = map();
245
+ const heads = map();
246
+ const revisions = map();
247
+ const relations = map();
248
+ const entries = map();
249
+ const semanticRevisions = map();
250
+ const index = map();
251
+ let state;
252
+ return {
253
+ getSource: (id) => sources.get(id),
254
+ listSources: () => [...sources.values()].sort((a, b) => a.id.localeCompare(b.id)),
255
+ async putSource(source) {
256
+ sources.set(source.id, catalogSourceSchema.parse(source));
257
+ },
258
+ getRun: (id) => runs.get(id),
259
+ listRuns: (sourceId) => [...runs.values()].filter((value) => sourceId === void 0 || value.sourceId === sourceId).sort((a, b) => a.createdAt.localeCompare(b.createdAt)),
260
+ async putRun(run) {
261
+ runs.set(run.id, catalogRunSchema.parse(run));
262
+ },
263
+ async putObservation(value) {
264
+ observations.set(`${value.runId}:${value.assetId}`, catalogObservationSchema.parse(value));
265
+ },
266
+ listObservations: (runId) => [...observations.values()].filter((value) => value.runId === runId),
267
+ async deleteObservations(runId) {
268
+ for (const [key, value] of observations) if (value.runId === runId) observations.delete(key);
269
+ },
270
+ getAssetHead: (id) => heads.get(id),
271
+ listAssetHeads: (sourceId) => [...heads.values()].filter((value) => sourceId === void 0 || value.sourceId === sourceId),
272
+ async putAssetHead(value) {
273
+ heads.set(value.assetId, catalogAssetHeadSchema.parse(value));
274
+ },
275
+ getAssetRevision: (id) => revisions.get(id),
276
+ listAssetRevisions: (assetId) => [...revisions.values()].filter((value) => assetId === void 0 || value.assetId === assetId),
277
+ async putAssetRevision(value) {
278
+ revisions.set(value.id, catalogAssetRevisionSchema.parse(value));
279
+ },
280
+ listRelations: (sourceId) => [...relations.values()].filter((value) => sourceId === void 0 || value.sourceId === sourceId),
281
+ async putRelation(value) {
282
+ relations.set(`${value.runId}:${value.id}`, catalogRelationSchema.parse(value));
283
+ },
284
+ getSemanticEntry: (id) => entries.get(id),
285
+ listSemanticEntries: (sourceId) => [...entries.values()].filter((value) => sourceId === void 0 || value.sourceId === sourceId),
286
+ async putSemanticEntry(value) {
287
+ entries.set(value.id, catalogSemanticEntrySchema.parse(value));
288
+ },
289
+ getSemanticRevision: (id) => semanticRevisions.get(id),
290
+ listSemanticRevisions: (semanticId) => [...semanticRevisions.values()].filter((value) => semanticId === void 0 || value.semanticId === semanticId),
291
+ async putSemanticRevision(value) {
292
+ semanticRevisions.set(value.id, catalogSemanticRevisionSchema.parse(value));
293
+ },
294
+ listIndex: (sourceId) => [...index.values()].filter((value) => sourceId === void 0 || value.sourceId === sourceId),
295
+ async putIndex(value) {
296
+ index.set(value.id, catalogIndexRecordSchema.parse(value));
297
+ },
298
+ async clearIndex(sourceId) {
299
+ for (const [key, value] of index) if (sourceId === void 0 || value.sourceId === sourceId) index.delete(key);
300
+ },
301
+ getIndexState: () => state,
302
+ async putIndexState(value) {
303
+ state = catalogIndexStateSchema.parse(value);
304
+ }
305
+ };
306
+ }
307
+ function sortedValues(entries, by) {
308
+ return [...entries].map(([, value]) => value).sort((a, b) => by(a).localeCompare(by(b)));
309
+ }
310
+ //#endregion
16
311
  //#region src/storage.ts
17
312
  /**
18
313
  * Durable, non-secret connection profiles, session bindings, and form drafts.
@@ -91,6 +386,12 @@ function createDomainConnectionPersistence(domain) {
91
386
  getLatestProfile() {
92
387
  return latestConnectionProfile(profiles.entries());
93
388
  },
389
+ listProfiles() {
390
+ return [...profiles.entries()].map(([profileId, profile]) => ({
391
+ profileId,
392
+ profile
393
+ })).sort((left, right) => left.profileId.localeCompare(right.profileId));
394
+ },
94
395
  putProfile(profileId, profile) {
95
396
  return profiles.put(profileId, profile);
96
397
  },
@@ -123,9 +424,10 @@ function createDomainConnectionPersistence(domain) {
123
424
  * Data Agent profile entry. The host row provides the
124
425
  * `dataAgentConnections` service (shared non-secret profile/binding storage;
125
426
  * temporary passwords stay process-local), seeds config connections (`connections`, `'*'` =
126
- * wildcard default), installs the `data-agent` agent preset into
427
+ * wildcard default), provides a separate versioned governance Catalog, installs the `data-agent` agent preset into
127
428
  * `$DSH_HOME/.agent-presets/`, and preloads the preset-scoped database tools
128
- * and command through this profile bundle entry.
429
+ * on every surface, while registering `/database` and `/catalog` only while
430
+ * the current Cordis composition actually loads the dsh-tui plugin.
129
431
  *
130
432
  * The HTTP routes live in the separate `./routes` entry
131
433
  * (`@yejiming/dsh-data-agent/routes`, cordis row `data-agent-routes`) so
@@ -141,8 +443,10 @@ const name = "data-agent";
141
443
  /** Services required before the profile entry can mount its preset layer. */
142
444
  const inject = [
143
445
  "agentPresets",
446
+ "agents",
144
447
  "commands",
145
448
  "credentials",
449
+ "llm",
146
450
  "subprocess",
147
451
  "tools"
148
452
  ];
@@ -153,6 +457,14 @@ const Config = z.object({
153
457
  connectTimeoutMs: z.number().step(1).min(1e3).default(DEFAULT_CONNECT_TIMEOUT_MS),
154
458
  introspectMaxTables: z.number().step(1).min(1).default(500),
155
459
  queryTimeoutMs: z.number().step(1).min(1e3).default(DEFAULT_QUERY_TIMEOUT_MS),
460
+ catalogQueryTimeoutMs: z.number().step(1).min(1e3).default(DEFAULT_CATALOG_QUERY_TIMEOUT_MS),
461
+ catalogMaxResultChars: z.number().step(1).min(1024).default(DEFAULT_CATALOG_MAX_RESULT_CHARS),
462
+ catalogSchemaConcurrency: z.number().step(1).min(1).max(16).default(2),
463
+ catalogAssetConcurrency: z.number().step(1).min(1).max(32).default(4),
464
+ catalogMaxAssetsPerRun: z.number().step(1).min(1).max(1e6).default(DEFAULT_CATALOG_MAX_ASSETS),
465
+ catalogMaxTextChars: z.number().step(1).min(256).max(4096).default(DEFAULT_CATALOG_MAX_TEXT_CHARS),
466
+ catalogPageSize: z.number().step(1).min(1).max(200).default(50),
467
+ catalogMaxPageSize: z.number().step(1).min(1).max(200).default(200),
156
468
  maxResultChars: z.number().step(1).min(1024).default(DEFAULT_MAX_RESULT_CHARS),
157
469
  maxRows: z.number().step(1).min(1).default(100),
158
470
  maxQueryChars: z.number().step(1).min(1024).default(DEFAULT_MAX_QUERY_CHARS),
@@ -217,7 +529,11 @@ async function installPreset(ctx, presetId) {
217
529
  }
218
530
  }
219
531
  /** SHA-256 values of unmodified package-owned compositions safe to migrate. */
220
- const LEGACY_MANAGED_PRESET_SHA256 = /* @__PURE__ */ new Set(["bae875a90d638ea78715030246b0f8a9f1a2c3359ca61febb6ceb59d0fcd930a", "d3c6f4049580069eec1c6b7de101f12c7fb30482ad317434afb69afb08a91fc6"]);
532
+ const LEGACY_MANAGED_PRESET_SHA256 = /* @__PURE__ */ new Set([
533
+ "bae875a90d638ea78715030246b0f8a9f1a2c3359ca61febb6ceb59d0fcd930a",
534
+ "d3c6f4049580069eec1c6b7de101f12c7fb30482ad317434afb69afb08a91fc6",
535
+ "11c4b5ef62c5934d1dc7133950bd78622dd68dc4e1075b5f24d0789011d6da9d"
536
+ ]);
221
537
  /** Public for regression tests of the non-destructive preset migration gate. */
222
538
  function isLegacyManagedPreset(source) {
223
539
  return LEGACY_MANAGED_PRESET_SHA256.has(createHash("sha256").update(source).digest("hex"));
@@ -253,14 +569,14 @@ function missingProfileDependencyMessage(profile) {
253
569
  return `data-agent preset is visible, but its profile-preloaded capabilities are absent from profile "${profile}". Run: ${profileInstallCommand(profile)}`;
254
570
  }
255
571
  /**
256
- * Register the statically imported database tools and command under the exact
572
+ * Register the statically imported database tools and surface adapters under the exact
257
573
  * standing key owned by the data-agent preset. Selecting the preset performs
258
574
  * no package import and only links the agent scope to this key.
259
575
  */
260
- async function mountPresetCapabilities(ctx, key, scopeTag, config) {
576
+ async function mountPresetCapabilities(ctx, key, scopeTag, config, commandOptions = {}) {
261
577
  const scoped = ctx.extend({ [scopeTag]: key });
262
578
  apply$2(scoped, config);
263
- apply$1(scoped);
579
+ apply$1(scoped, commandOptions);
264
580
  }
265
581
  /** Read the host-owned scope tag from AgentPresets' already-created standing mount. */
266
582
  async function standingScopeTag(ctx, presetId, key) {
@@ -280,12 +596,21 @@ async function standingScopeTag(ctx, presetId, key) {
280
596
  * @param config - validated loader configuration.
281
597
  */
282
598
  async function apply(ctx, config) {
599
+ if (config.catalogPageSize > config.catalogMaxPageSize) throw new Error("data-agent: catalogPageSize cannot exceed catalogMaxPageSize");
283
600
  const resolved = {
284
601
  presetId: config.presetId,
285
602
  installPreset: config.installPreset,
286
603
  connectTimeoutMs: config.connectTimeoutMs,
287
604
  introspectMaxTables: config.introspectMaxTables,
288
605
  queryTimeoutMs: config.queryTimeoutMs,
606
+ catalogQueryTimeoutMs: config.catalogQueryTimeoutMs,
607
+ catalogMaxResultChars: config.catalogMaxResultChars,
608
+ catalogSchemaConcurrency: config.catalogSchemaConcurrency,
609
+ catalogAssetConcurrency: config.catalogAssetConcurrency,
610
+ catalogMaxAssetsPerRun: config.catalogMaxAssetsPerRun,
611
+ catalogMaxTextChars: config.catalogMaxTextChars,
612
+ catalogPageSize: config.catalogPageSize,
613
+ catalogMaxPageSize: config.catalogMaxPageSize,
289
614
  maxResultChars: config.maxResultChars,
290
615
  maxRows: config.maxRows,
291
616
  maxQueryChars: config.maxQueryChars,
@@ -294,15 +619,18 @@ async function apply(ctx, config) {
294
619
  clients: config.clients,
295
620
  connections: config.connections
296
621
  };
297
- const mountService = (scope, persistence) => {
622
+ const mountService = (scope, persistence, preferredProfileIds) => {
298
623
  const store = createConnectionService(scope, {
299
624
  connectTimeoutMs: resolved.connectTimeoutMs,
300
625
  queryTimeoutMs: resolved.queryTimeoutMs,
626
+ catalogQueryTimeoutMs: resolved.catalogQueryTimeoutMs,
627
+ catalogMaxResultChars: resolved.catalogMaxResultChars,
301
628
  maxResultChars: resolved.maxResultChars,
302
629
  maxQueryChars: resolved.maxQueryChars,
303
630
  introspectMaxTables: resolved.introspectMaxTables,
304
631
  readonly: resolved.readonly,
305
- clients: resolved.clients
632
+ clients: resolved.clients,
633
+ ...preferredProfileIds !== void 0 ? { preferredProfileIds } : {}
306
634
  }, persistence);
307
635
  scope.provide("dataAgentConnections", store);
308
636
  for (const [sessionId, spec] of Object.entries(resolved.connections)) {
@@ -318,16 +646,38 @@ async function apply(ctx, config) {
318
646
  };
319
647
  store.set(sessionId, connection);
320
648
  }
649
+ return store;
321
650
  };
322
651
  const presetReady = resolved.installPreset ? await installPreset(ctx, resolved.presetId) : false;
652
+ let connectionPersistence;
653
+ let catalogPersistence;
323
654
  if (resolved.persistConnections) {
324
- const domain = await (await ensureStorageDomain(ctx)).open(connectionStorageSpec);
655
+ const storageDomain = await ensureStorageDomain(ctx);
656
+ const domain = await storageDomain.open(connectionStorageSpec);
325
657
  ctx.effect(() => () => domain.close(), "data-agent: close connection storage domain");
326
- mountService(ctx, createDomainConnectionPersistence(domain));
658
+ connectionPersistence = createDomainConnectionPersistence(domain);
659
+ const catalogDomain = await storageDomain.open(catalogStorageSpec);
660
+ ctx.effect(() => () => catalogDomain.close(), "data-agent: close Catalog storage domain");
661
+ catalogPersistence = createDomainCatalogPersistence(catalogDomain);
327
662
  } else {
328
- ctx.logger.warn("data-agent: persistConnections=false; connection state is process-local and cannot restore across Web/TUI");
329
- mountService(ctx);
663
+ ctx.logger.warn("data-agent: persistConnections=false; connection and Catalog state are process-local and cannot restore across Web/TUI");
664
+ catalogPersistence = createMemoryCatalogPersistence();
330
665
  }
666
+ const connectionService = mountService(ctx, connectionPersistence, () => catalogPersistence.listSources().map((source) => source.profileId));
667
+ const catalog = await createCatalogService(connectionService, catalogPersistence, {
668
+ maxAssetsPerRun: resolved.catalogMaxAssetsPerRun,
669
+ maxTextChars: resolved.catalogMaxTextChars,
670
+ pageSize: resolved.catalogPageSize,
671
+ maxPageSize: resolved.catalogMaxPageSize,
672
+ schemaConcurrency: resolved.catalogSchemaConcurrency,
673
+ assetConcurrency: resolved.catalogAssetConcurrency,
674
+ meaningGenerator: createDshCatalogMeaningGenerator(ctx.agents, ctx.llm),
675
+ logger: ctx.logger
676
+ });
677
+ ctx.provide("dataAgentCatalog", catalog.read);
678
+ ctx.provide("dataAgentCatalogScanner", catalog.scanner);
679
+ ctx.provide("dataAgentCatalogReview", catalog.review);
680
+ ctx.effect(() => () => catalog.scanner.interruptActiveRuns(), "data-agent: interrupt active Catalog scans");
331
681
  if (presetReady) {
332
682
  const standingKey = await ctx.agentPresets.standingKeyFor(resolved.presetId);
333
683
  await mountPresetCapabilities(ctx, standingKey, await standingScopeTag(ctx, resolved.presetId, standingKey), {