@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.
- package/README.en.md +97 -16
- package/README.md +97 -16
- package/conformance/dsh-ecosystem/baseline.json +59 -0
- package/conformance/dsh-ecosystem/dependencies.json +41 -0
- package/conformance/dsh-ecosystem/fixtures/host-degraded.fixture.json +12 -0
- package/conformance/dsh-ecosystem/fixtures/host-eligible.fixture.json +13 -0
- package/conformance/dsh-ecosystem/fixtures/host-rejected.fixture.json +12 -0
- package/conformance/dsh-ecosystem/fixtures/profiles/native-only/package.json +8 -0
- package/conformance/dsh-ecosystem/fixtures/profiles/native-plus-adapter/package.json +9 -0
- package/conformance/dsh-ecosystem/inventory.json +100 -0
- package/conformance/dsh-ecosystem/restrictions.json +20 -0
- package/cordis.patch.yml +6 -3
- package/dsh-plugin.json +72 -0
- package/lib/catalog-DEJqOXRo.js +1944 -0
- package/lib/catalog-identity-CVftmvQL.js +96 -0
- package/lib/client.js +3465 -138
- package/lib/client.js.map +1 -1
- package/lib/command-CzzSPmag.js +1719 -0
- package/lib/command.js +2 -2
- package/lib/{connections-5sfdEDsG.js → connections-CFXOZTHZ.js} +964 -68
- package/lib/ecosystem.js +19 -0
- package/lib/index.js +392 -34
- package/lib/routes.js +260 -8
- package/lib/{tool-DgL0fBfj.js → tool-DNkywSph.js} +367 -168
- package/lib/tool.js +1 -1
- package/lib/types/catalog-adapters.d.ts +52 -0
- package/lib/types/catalog-ai.d.ts +49 -0
- package/lib/types/catalog-command.d.ts +28 -0
- package/lib/types/catalog-identity.d.ts +23 -0
- package/lib/types/catalog-storage.d.ts +265 -0
- package/lib/types/catalog-tools.d.ts +5 -0
- package/lib/types/catalog-tui.d.ts +18 -0
- package/lib/types/catalog-types.d.ts +1376 -0
- package/lib/types/catalog.d.ts +59 -0
- package/lib/types/client/CatalogPanel.d.ts +15 -0
- package/lib/types/client/DataAgentWorkbench.d.ts +1 -2
- package/lib/types/client/QueryResultTable.d.ts +13 -0
- package/lib/types/client/catalog-client.d.ts +57 -0
- package/lib/types/client/locales.d.ts +290 -0
- package/lib/types/client/persistence.d.ts +3 -1
- package/lib/types/client/query-export.d.ts +11 -0
- package/lib/types/client-discovery.d.ts +3 -4
- package/lib/types/clients.d.ts +14 -8
- package/lib/types/command.d.ts +16 -5
- package/lib/types/connections.d.ts +42 -4
- package/lib/types/database-types.d.ts +23 -0
- package/lib/types/defaults.d.ts +26 -0
- package/lib/types/ecosystem.d.ts +13 -0
- package/lib/types/index.d.ts +58 -15
- package/lib/types/query.d.ts +13 -11
- package/lib/types/sql.d.ts +9 -1
- package/lib/types/storage.d.ts +11 -1
- package/lib/types/structured-read.d.ts +2 -2
- package/lib/types/structured.d.ts +1 -1
- package/lib/types/tool.d.ts +5 -5
- package/lib/types/tui-connection-form.d.ts +21 -12
- package/package.json +30 -4
- package/preset/data-agent/agent.cordis.yml +13 -2
- package/lib/command-DuCpwVbl.js +0 -875
- package/lib/defaults-DP4RyRh1.js +0 -21
package/lib/ecosystem.js
ADDED
|
@@ -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 {
|
|
2
|
-
import { a as
|
|
3
|
-
import {
|
|
4
|
-
import { n as apply$2 } from "./tool-
|
|
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
|
-
*
|
|
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([
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
321
|
-
|
|
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), {
|