@yejiming/dsh-data-agent 0.0.12 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.en.md +97 -16
  2. package/README.md +97 -16
  3. package/conformance/dsh-ecosystem/baseline.json +59 -0
  4. package/conformance/dsh-ecosystem/dependencies.json +41 -0
  5. package/conformance/dsh-ecosystem/fixtures/host-degraded.fixture.json +12 -0
  6. package/conformance/dsh-ecosystem/fixtures/host-eligible.fixture.json +13 -0
  7. package/conformance/dsh-ecosystem/fixtures/host-rejected.fixture.json +12 -0
  8. package/conformance/dsh-ecosystem/fixtures/profiles/native-only/package.json +8 -0
  9. package/conformance/dsh-ecosystem/fixtures/profiles/native-plus-adapter/package.json +9 -0
  10. package/conformance/dsh-ecosystem/inventory.json +100 -0
  11. package/conformance/dsh-ecosystem/restrictions.json +20 -0
  12. package/cordis.patch.yml +6 -3
  13. package/dsh-plugin.json +72 -0
  14. package/lib/catalog-DEJqOXRo.js +1944 -0
  15. package/lib/catalog-identity-CVftmvQL.js +96 -0
  16. package/lib/client.js +3465 -138
  17. package/lib/client.js.map +1 -1
  18. package/lib/command-CzzSPmag.js +1719 -0
  19. package/lib/command.js +2 -2
  20. package/lib/{connections-5sfdEDsG.js → connections-CFXOZTHZ.js} +964 -68
  21. package/lib/ecosystem.js +19 -0
  22. package/lib/index.js +392 -34
  23. package/lib/routes.js +260 -8
  24. package/lib/{tool-DgL0fBfj.js → tool-DNkywSph.js} +367 -168
  25. package/lib/tool.js +1 -1
  26. package/lib/types/catalog-adapters.d.ts +52 -0
  27. package/lib/types/catalog-ai.d.ts +49 -0
  28. package/lib/types/catalog-command.d.ts +28 -0
  29. package/lib/types/catalog-identity.d.ts +23 -0
  30. package/lib/types/catalog-storage.d.ts +265 -0
  31. package/lib/types/catalog-tools.d.ts +5 -0
  32. package/lib/types/catalog-tui.d.ts +18 -0
  33. package/lib/types/catalog-types.d.ts +1376 -0
  34. package/lib/types/catalog.d.ts +59 -0
  35. package/lib/types/client/CatalogPanel.d.ts +15 -0
  36. package/lib/types/client/DataAgentWorkbench.d.ts +1 -2
  37. package/lib/types/client/QueryResultTable.d.ts +13 -0
  38. package/lib/types/client/catalog-client.d.ts +57 -0
  39. package/lib/types/client/locales.d.ts +290 -0
  40. package/lib/types/client/persistence.d.ts +3 -1
  41. package/lib/types/client/query-export.d.ts +11 -0
  42. package/lib/types/client-discovery.d.ts +3 -4
  43. package/lib/types/clients.d.ts +14 -8
  44. package/lib/types/command.d.ts +16 -5
  45. package/lib/types/connections.d.ts +42 -4
  46. package/lib/types/database-types.d.ts +23 -0
  47. package/lib/types/defaults.d.ts +26 -0
  48. package/lib/types/ecosystem.d.ts +13 -0
  49. package/lib/types/index.d.ts +58 -15
  50. package/lib/types/query.d.ts +13 -11
  51. package/lib/types/sql.d.ts +9 -1
  52. package/lib/types/storage.d.ts +11 -1
  53. package/lib/types/structured-read.d.ts +2 -2
  54. package/lib/types/structured.d.ts +1 -1
  55. package/lib/types/tool.d.ts +5 -5
  56. package/lib/types/tui-connection-form.d.ts +21 -12
  57. package/package.json +30 -4
  58. package/preset/data-agent/agent.cordis.yml +13 -2
  59. package/lib/command-DuCpwVbl.js +0 -875
  60. package/lib/defaults-DP4RyRh1.js +0 -21
@@ -0,0 +1,19 @@
1
+ import { defineFacet } from "@dsh-std/sdk";
2
+ //#region src/ecosystem.ts
3
+ /**
4
+ * Community v0.15 host facet.
5
+ *
6
+ * This facet intentionally publishes no commands, tools, UI, routes, storage,
7
+ * credentials, or database effects. The existing Cordis bundle remains the
8
+ * sole functional runtime so hosts may discover the ecosystem declaration
9
+ * without double-registering data-agent behavior.
10
+ */
11
+ /** Stable degraded snapshot for declaration-only ecosystem discovery. */
12
+ const ECOSYSTEM_SNAPSHOT = Object.freeze({
13
+ state: "degraded",
14
+ message: "Native Cordis runtime owns all data-agent effects; this facet publishes declarations only.",
15
+ extensions: Object.freeze([])
16
+ });
17
+ const ecosystemFacet = defineFacet(() => void 0, () => void 0, () => ECOSYSTEM_SNAPSHOT);
18
+ //#endregion
19
+ export { ECOSYSTEM_SNAPSHOT, ecosystemFacet as default };
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { o as clientsSchema, t as createConnectionService } from "./connections-5sfdEDsG.js";
2
- import { a as DEFAULT_PRESET_ID, i as DEFAULT_MAX_RESULT_CHARS, o as DEFAULT_QUERY_TIMEOUT_MS, r as DEFAULT_MAX_QUERY_CHARS, t as DEFAULT_CONNECT_TIMEOUT_MS } from "./defaults-DP4RyRh1.js";
3
- import { r as apply$1 } from "./command-DuCpwVbl.js";
4
- import { n as apply$2 } from "./tool-DgL0fBfj.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.
@@ -29,19 +324,13 @@ const CONNECTION_STORAGE_DOMAIN = "data_agent_connections";
29
324
  /** Durable profile schema. There is deliberately no `password` field. */
30
325
  const persistedConnectionProfileSchema = z$1.object({
31
326
  name: z$1.string().min(1).optional(),
32
- type: z$1.enum([
33
- "mysql",
34
- "postgres",
35
- "sqlite",
36
- "oracle",
37
- "hive",
38
- "impala"
39
- ]),
327
+ type: z$1.enum(DATABASE_TYPES),
40
328
  host: z$1.string().optional(),
41
329
  port: z$1.number().int().min(1).max(65535).optional(),
42
330
  user: z$1.string().optional(),
43
331
  database: z$1.string().min(1),
44
332
  readonly: z$1.boolean().optional(),
333
+ secure: z$1.boolean().optional(),
45
334
  passwordRef: z$1.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional(),
46
335
  credentialMode: z$1.enum([
47
336
  "none",
@@ -57,19 +346,13 @@ const sessionConnectionBindingSchema = z$1.object({
57
346
  }).strict();
58
347
  /** Session form draft schema. Secret-shaped fields are rejected by strict mode. */
59
348
  const persistedConnectionFormDraftSchema = z$1.object({
60
- type: z$1.enum([
61
- "mysql",
62
- "postgres",
63
- "sqlite",
64
- "oracle",
65
- "hive",
66
- "impala"
67
- ]),
349
+ type: z$1.enum(DATABASE_TYPES),
68
350
  host: z$1.string(),
69
351
  port: z$1.string(),
70
352
  user: z$1.string(),
71
353
  database: z$1.string(),
72
354
  readonly: z$1.boolean(),
355
+ secure: z$1.boolean().optional(),
73
356
  updatedAt: z$1.string().min(1)
74
357
  }).strict();
75
358
  /** Single source of truth for the storage layout and durable validation. */
@@ -82,6 +365,15 @@ const connectionStorageSpec = defineDomain({
82
365
  drafts: domainTable(persistedConnectionFormDraftSchema)
83
366
  }
84
367
  });
368
+ /** Select the newest successful profile with a deterministic id tie-break. */
369
+ function latestConnectionProfile(entries) {
370
+ let latest;
371
+ for (const [profileId, profile] of entries) if (latest === void 0 || profile.updatedAt > latest.profile.updatedAt || profile.updatedAt === latest.profile.updatedAt && profileId > latest.profileId) latest = {
372
+ profileId,
373
+ profile
374
+ };
375
+ return latest;
376
+ }
85
377
  /** Project a typed DSH domain handle onto the service's persistence seam. */
86
378
  function createDomainConnectionPersistence(domain) {
87
379
  const profiles = domain.table("profiles");
@@ -91,6 +383,15 @@ function createDomainConnectionPersistence(domain) {
91
383
  getProfile(profileId) {
92
384
  return profiles.get(profileId);
93
385
  },
386
+ getLatestProfile() {
387
+ return latestConnectionProfile(profiles.entries());
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
  },
@@ -111,6 +412,9 @@ function createDomainConnectionPersistence(domain) {
111
412
  },
112
413
  putDraft(sessionId, draft) {
113
414
  return drafts.put(sessionId, draft);
415
+ },
416
+ deleteDraft(sessionId) {
417
+ return drafts.delete(sessionId);
114
418
  }
115
419
  };
116
420
  }
@@ -120,9 +424,10 @@ function createDomainConnectionPersistence(domain) {
120
424
  * Data Agent profile entry. The host row provides the
121
425
  * `dataAgentConnections` service (shared non-secret profile/binding storage;
122
426
  * temporary passwords stay process-local), seeds config connections (`connections`, `'*'` =
123
- * 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
124
428
  * `$DSH_HOME/.agent-presets/`, and preloads the preset-scoped database tools
125
- * 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.
126
431
  *
127
432
  * The HTTP routes live in the separate `./routes` entry
128
433
  * (`@yejiming/dsh-data-agent/routes`, cordis row `data-agent-routes`) so
@@ -138,8 +443,10 @@ const name = "data-agent";
138
443
  /** Services required before the profile entry can mount its preset layer. */
139
444
  const inject = [
140
445
  "agentPresets",
446
+ "agents",
141
447
  "commands",
142
448
  "credentials",
449
+ "llm",
143
450
  "subprocess",
144
451
  "tools"
145
452
  ];
@@ -150,6 +457,14 @@ const Config = z.object({
150
457
  connectTimeoutMs: z.number().step(1).min(1e3).default(DEFAULT_CONNECT_TIMEOUT_MS),
151
458
  introspectMaxTables: z.number().step(1).min(1).default(500),
152
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),
153
468
  maxResultChars: z.number().step(1).min(1024).default(DEFAULT_MAX_RESULT_CHARS),
154
469
  maxRows: z.number().step(1).min(1).default(100),
155
470
  maxQueryChars: z.number().step(1).min(1024).default(DEFAULT_MAX_QUERY_CHARS),
@@ -163,13 +478,17 @@ const Config = z.object({
163
478
  z.const("sqlite"),
164
479
  z.const("oracle"),
165
480
  z.const("hive"),
166
- z.const("impala")
481
+ z.const("impala"),
482
+ z.const("clickhouse"),
483
+ z.const("doris"),
484
+ z.const("sqlserver")
167
485
  ]),
168
486
  host: z.string(),
169
487
  port: z.natural(),
170
488
  user: z.string(),
171
489
  database: z.string(),
172
490
  readonly: z.boolean(),
491
+ secure: z.boolean(),
173
492
  passwordRef: z.string().pattern(/^[A-Za-z_][A-Za-z0-9_]*$/),
174
493
  password: z.never().hidden()
175
494
  })).default({})
@@ -210,7 +529,11 @@ async function installPreset(ctx, presetId) {
210
529
  }
211
530
  }
212
531
  /** SHA-256 values of unmodified package-owned compositions safe to migrate. */
213
- 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
+ ]);
214
537
  /** Public for regression tests of the non-destructive preset migration gate. */
215
538
  function isLegacyManagedPreset(source) {
216
539
  return LEGACY_MANAGED_PRESET_SHA256.has(createHash("sha256").update(source).digest("hex"));
@@ -246,14 +569,14 @@ function missingProfileDependencyMessage(profile) {
246
569
  return `data-agent preset is visible, but its profile-preloaded capabilities are absent from profile "${profile}". Run: ${profileInstallCommand(profile)}`;
247
570
  }
248
571
  /**
249
- * Register the statically imported database tools and command under the exact
572
+ * Register the statically imported database tools and surface adapters under the exact
250
573
  * standing key owned by the data-agent preset. Selecting the preset performs
251
574
  * no package import and only links the agent scope to this key.
252
575
  */
253
- async function mountPresetCapabilities(ctx, key, scopeTag, config) {
576
+ async function mountPresetCapabilities(ctx, key, scopeTag, config, commandOptions = {}) {
254
577
  const scoped = ctx.extend({ [scopeTag]: key });
255
578
  apply$2(scoped, config);
256
- apply$1(scoped);
579
+ apply$1(scoped, commandOptions);
257
580
  }
258
581
  /** Read the host-owned scope tag from AgentPresets' already-created standing mount. */
259
582
  async function standingScopeTag(ctx, presetId, key) {
@@ -273,12 +596,21 @@ async function standingScopeTag(ctx, presetId, key) {
273
596
  * @param config - validated loader configuration.
274
597
  */
275
598
  async function apply(ctx, config) {
599
+ if (config.catalogPageSize > config.catalogMaxPageSize) throw new Error("data-agent: catalogPageSize cannot exceed catalogMaxPageSize");
276
600
  const resolved = {
277
601
  presetId: config.presetId,
278
602
  installPreset: config.installPreset,
279
603
  connectTimeoutMs: config.connectTimeoutMs,
280
604
  introspectMaxTables: config.introspectMaxTables,
281
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,
282
614
  maxResultChars: config.maxResultChars,
283
615
  maxRows: config.maxRows,
284
616
  maxQueryChars: config.maxQueryChars,
@@ -287,15 +619,18 @@ async function apply(ctx, config) {
287
619
  clients: config.clients,
288
620
  connections: config.connections
289
621
  };
290
- const mountService = (scope, persistence) => {
622
+ const mountService = (scope, persistence, preferredProfileIds) => {
291
623
  const store = createConnectionService(scope, {
292
624
  connectTimeoutMs: resolved.connectTimeoutMs,
293
625
  queryTimeoutMs: resolved.queryTimeoutMs,
626
+ catalogQueryTimeoutMs: resolved.catalogQueryTimeoutMs,
627
+ catalogMaxResultChars: resolved.catalogMaxResultChars,
294
628
  maxResultChars: resolved.maxResultChars,
295
629
  maxQueryChars: resolved.maxQueryChars,
296
630
  introspectMaxTables: resolved.introspectMaxTables,
297
631
  readonly: resolved.readonly,
298
- clients: resolved.clients
632
+ clients: resolved.clients,
633
+ ...preferredProfileIds !== void 0 ? { preferredProfileIds } : {}
299
634
  }, persistence);
300
635
  scope.provide("dataAgentConnections", store);
301
636
  for (const [sessionId, spec] of Object.entries(resolved.connections)) {
@@ -306,20 +641,43 @@ async function apply(ctx, config) {
306
641
  ...spec.port !== void 0 ? { port: spec.port } : {},
307
642
  ...spec.user !== void 0 ? { user: spec.user } : {},
308
643
  ...spec.passwordRef !== void 0 ? { passwordRef: spec.passwordRef } : {},
309
- ...spec.readonly !== void 0 ? { readonly: spec.readonly } : {}
644
+ ...spec.readonly !== void 0 ? { readonly: spec.readonly } : {},
645
+ ...spec.secure !== void 0 ? { secure: spec.secure } : {}
310
646
  };
311
647
  store.set(sessionId, connection);
312
648
  }
649
+ return store;
313
650
  };
314
651
  const presetReady = resolved.installPreset ? await installPreset(ctx, resolved.presetId) : false;
652
+ let connectionPersistence;
653
+ let catalogPersistence;
315
654
  if (resolved.persistConnections) {
316
- const domain = await (await ensureStorageDomain(ctx)).open(connectionStorageSpec);
655
+ const storageDomain = await ensureStorageDomain(ctx);
656
+ const domain = await storageDomain.open(connectionStorageSpec);
317
657
  ctx.effect(() => () => domain.close(), "data-agent: close connection storage domain");
318
- 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);
319
662
  } else {
320
- ctx.logger.warn("data-agent: persistConnections=false; connection state is process-local and cannot restore across Web/TUI");
321
- 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();
322
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");
323
681
  if (presetReady) {
324
682
  const standingKey = await ctx.agentPresets.standingKeyFor(resolved.presetId);
325
683
  await mountPresetCapabilities(ctx, standingKey, await standingScopeTag(ctx, resolved.presetId, standingKey), {