@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/command.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as formatConnectionStatus, c as parseConnectArguments, i as executeDatabaseCommand, l as parseDatabaseAction, n as DATA_AGENT_TOOL_NAMES, o as inject, r as apply, s as name, t as DATABASE_COMMAND_USAGE } from "./command-utC5MHd9.js";
2
- export { DATABASE_COMMAND_USAGE, DATA_AGENT_TOOL_NAMES, apply, executeDatabaseCommand, formatConnectionStatus, inject, name, parseConnectArguments, parseDatabaseAction };
1
+ import { a as executeDatabaseCommand, c as isDshTuiPluginLoaded, d as parseDatabaseAction, i as apply, l as name, n as DATA_AGENT_TOOL_NAMES, o as formatConnectionStatus, r as DSH_TUI_PLUGIN_RUNTIME_NAME, s as inject, t as DATABASE_COMMAND_USAGE, u as parseConnectArguments } from "./command-CzzSPmag.js";
2
+ export { DATABASE_COMMAND_USAGE, DATA_AGENT_TOOL_NAMES, DSH_TUI_PLUGIN_RUNTIME_NAME, apply, executeDatabaseCommand, formatConnectionStatus, inject, isDshTuiPluginLoaded, name, parseConnectArguments, parseDatabaseAction };
@@ -1,10 +1,115 @@
1
- import { c as WORKBENCH_MAX_RESULT_CHARS, d as defaultDatabasePort, f as defaultDatabaseUser, p as isDatabaseType, s as WORKBENCH_MAX_EXPORT_ROWS } from "./defaults-Cngd8Tf8.js";
2
1
  import { readdir } from "node:fs/promises";
3
2
  import { homedir } from "node:os";
4
3
  import { posix, resolve, win32 } from "node:path";
5
4
  import z from "schemastery";
6
5
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
7
6
  import { createClient } from "@clickhouse/client";
7
+ //#region src/database-types.ts
8
+ /**
9
+ * Browser-safe database type descriptors shared by every DSH surface.
10
+ * Keep this module dependency-free: server-only client/process details belong
11
+ * in the database adapters, not in Web or persistence bundles.
12
+ */
13
+ const DATABASE_TYPES = [
14
+ "mysql",
15
+ "postgres",
16
+ "sqlite",
17
+ "oracle",
18
+ "hive",
19
+ "impala",
20
+ "clickhouse",
21
+ "doris",
22
+ "sqlserver"
23
+ ];
24
+ const DATABASE_TYPE_DESCRIPTORS = {
25
+ mysql: {
26
+ type: "mysql",
27
+ label: "MySQL",
28
+ localeKey: "type.mysql",
29
+ defaultPort: 3306,
30
+ defaultUser: "root",
31
+ fileBased: false
32
+ },
33
+ postgres: {
34
+ type: "postgres",
35
+ label: "PostgreSQL",
36
+ localeKey: "type.postgres",
37
+ defaultPort: 5432,
38
+ defaultUser: "postgres",
39
+ fileBased: false
40
+ },
41
+ sqlite: {
42
+ type: "sqlite",
43
+ label: "SQLite",
44
+ localeKey: "type.sqlite",
45
+ defaultPort: 0,
46
+ defaultUser: "",
47
+ fileBased: true
48
+ },
49
+ oracle: {
50
+ type: "oracle",
51
+ label: "Oracle",
52
+ localeKey: "type.oracle",
53
+ defaultPort: 1521,
54
+ defaultUser: "",
55
+ fileBased: false
56
+ },
57
+ hive: {
58
+ type: "hive",
59
+ label: "Hive",
60
+ localeKey: "type.hive",
61
+ defaultPort: 1e4,
62
+ defaultUser: "",
63
+ fileBased: false
64
+ },
65
+ impala: {
66
+ type: "impala",
67
+ label: "Impala",
68
+ localeKey: "type.impala",
69
+ defaultPort: 21050,
70
+ defaultUser: "",
71
+ fileBased: false
72
+ },
73
+ clickhouse: {
74
+ type: "clickhouse",
75
+ label: "ClickHouse",
76
+ localeKey: "type.clickhouse",
77
+ defaultPort: 8123,
78
+ securePort: 8443,
79
+ defaultUser: "default",
80
+ fileBased: false
81
+ },
82
+ doris: {
83
+ type: "doris",
84
+ label: "Apache Doris",
85
+ localeKey: "type.doris",
86
+ defaultPort: 9030,
87
+ defaultUser: "root",
88
+ fileBased: false
89
+ },
90
+ sqlserver: {
91
+ type: "sqlserver",
92
+ label: "SQL Server",
93
+ localeKey: "type.sqlserver",
94
+ defaultPort: 1433,
95
+ defaultUser: "sa",
96
+ fileBased: false
97
+ }
98
+ };
99
+ function isDatabaseType(value) {
100
+ return typeof value === "string" && DATABASE_TYPES.includes(value);
101
+ }
102
+ function defaultDatabasePort(type, secure = false) {
103
+ const descriptor = DATABASE_TYPE_DESCRIPTORS[type];
104
+ return secure && descriptor.securePort !== void 0 ? descriptor.securePort : descriptor.defaultPort;
105
+ }
106
+ function defaultDatabaseUser(type) {
107
+ return DATABASE_TYPE_DESCRIPTORS[type].defaultUser;
108
+ }
109
+ function databaseTypeLabel(type) {
110
+ return DATABASE_TYPE_DESCRIPTORS[type].label;
111
+ }
112
+ //#endregion
8
113
  //#region src/sql.ts
9
114
  /**
10
115
  * Lightweight SQL-text scanning helpers shared by the sql-cmd tool half and
@@ -1145,6 +1250,40 @@ function parseColumns(type, stdout) {
1145
1250
  return columns;
1146
1251
  }
1147
1252
  //#endregion
1253
+ //#region src/defaults.ts
1254
+ /**
1255
+ * Package-wide defaults shared by the server half (`src/index.ts`) and the
1256
+ * database tool half (`src/tool.ts`). Loader schemas carry these as their
1257
+ * defaults so a deployment may override every one of them in cordis.yml.
1258
+ * @module @yejiming/dsh-data-agent/defaults
1259
+ */
1260
+ /** Preset directory name installed into `$DSH_HOME/.agent-presets/`. */
1261
+ const DEFAULT_PRESET_ID = "data-agent";
1262
+ /** End-to-end deadline for one `/connect` connectivity check, milliseconds. */
1263
+ const DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
1264
+ /** End-to-end deadline for one database-tool query, milliseconds. */
1265
+ const DEFAULT_QUERY_TIMEOUT_MS = 3e4;
1266
+ /** In-memory cap on database-tool captured output (stdout and stderr each). */
1267
+ const DEFAULT_MAX_RESULT_CHARS = 2e4;
1268
+ /** Hard row cap for one structured Web workbench result/export. */
1269
+ const WORKBENCH_MAX_EXPORT_ROWS = 5e4;
1270
+ /** Bounded capture size for the larger structured Web workbench result. */
1271
+ const WORKBENCH_MAX_RESULT_CHARS = 33554432;
1272
+ /** Cap on one /query SQL text length (abuse guard; the wire body stays small). */
1273
+ const DEFAULT_MAX_QUERY_CHARS = 65536;
1274
+ /** Catalog metadata query deadline. Kept separate from user SQL execution. */
1275
+ const DEFAULT_CATALOG_QUERY_TIMEOUT_MS = 3e4;
1276
+ /**
1277
+ * Per-stream capture budget for one system-catalog query. Catalog metadata is
1278
+ * intentionally independent from the much smaller model/interactive SQL
1279
+ * result budget because a schema snapshot can contain thousands of objects.
1280
+ */
1281
+ const DEFAULT_CATALOG_MAX_RESULT_CHARS = 33554432;
1282
+ /** Hard bound on technical assets (including columns) staged by one run. */
1283
+ const DEFAULT_CATALOG_MAX_ASSETS = 5e4;
1284
+ /** Maximum normalized length of one database or human-authored text field. */
1285
+ const DEFAULT_CATALOG_MAX_TEXT_CHARS = 4096;
1286
+ //#endregion
1148
1287
  //#region src/client-discovery.ts
1149
1288
  /**
1150
1289
  * Cross-platform database CLI discovery.
@@ -1950,15 +2089,15 @@ function createConnectionService(ctx, options, persistence) {
1950
2089
  tables: copyTables(connection.tables)
1951
2090
  };
1952
2091
  };
1953
- const queryOptions = (mode, connect = false, maxResultChars = resolvedOptions.maxResultChars) => ({
2092
+ const queryOptions = (mode, connect = false, maxResultChars = resolvedOptions.maxResultChars, catalog = false) => ({
1954
2093
  clients: resolvedOptions.clients,
1955
- timeoutMs: connect ? resolvedOptions.connectTimeoutMs : resolvedOptions.queryTimeoutMs,
2094
+ timeoutMs: connect ? resolvedOptions.connectTimeoutMs : catalog ? resolvedOptions.catalogQueryTimeoutMs ?? resolvedOptions.queryTimeoutMs : resolvedOptions.queryTimeoutMs,
1956
2095
  maxResultChars,
1957
2096
  ...mode !== void 0 ? { mode } : {}
1958
2097
  });
1959
- const run = async (connection, sql, signal, introspection = false, connect = false, mode, maxResultChars) => {
2098
+ const run = async (connection, sql, signal, introspection = false, connect = false, mode, maxResultChars, catalog = false) => {
1960
2099
  try {
1961
- return redactQueryResult(await runClientQuery(requireContext(), connection, sql, queryOptions(mode, connect, maxResultChars), signal, introspection), connection);
2100
+ return redactQueryResult(await runClientQuery(requireContext(), connection, sql, queryOptions(mode, connect, maxResultChars, catalog), signal, introspection), connection);
1962
2101
  } catch (error) {
1963
2102
  const message = redactSecretText(error instanceof Error ? error.message : String(error), [connection.password]);
1964
2103
  throw new Error(message, error instanceof Error ? { cause: error } : void 0);
@@ -2021,6 +2160,50 @@ function createConnectionService(ctx, options, persistence) {
2021
2160
  throw error;
2022
2161
  }
2023
2162
  };
2163
+ const matchingProfiles = (connection) => (persistence?.listProfiles?.() ?? []).filter((entry) => profileMatchesConnection(entry.profile, connection, resolvedOptions.cwd));
2164
+ const preferredMatches = (matches) => {
2165
+ const preferred = new Set(resolvedOptions.preferredProfileIds?.() ?? []);
2166
+ return matches.filter((entry) => preferred.has(entry.profileId));
2167
+ };
2168
+ const reusableProfileId = (sessionId, connection) => {
2169
+ if (connection.profileId !== void 0) return connection.profileId;
2170
+ const fallback = `session:${sessionId}`;
2171
+ const matches = matchingProfiles(connection);
2172
+ const binding = persistence?.getBinding(sessionId);
2173
+ const boundMatch = binding === void 0 ? void 0 : matches.find((entry) => entry.profileId === binding.profileId);
2174
+ const preferred = preferredMatches(matches);
2175
+ const stableMatches = matches.filter((entry) => !entry.profileId.startsWith("session:"));
2176
+ if (boundMatch !== void 0 && preferred.some((entry) => entry.profileId === boundMatch.profileId)) return boundMatch.profileId;
2177
+ if (preferred.length === 1) return preferred[0].profileId;
2178
+ if (preferred.length > 1) return fallback;
2179
+ if (boundMatch !== void 0 && !boundMatch.profileId.startsWith("session:")) return boundMatch.profileId;
2180
+ if (stableMatches.length === 1) return stableMatches[0].profileId;
2181
+ if (stableMatches.length > 1) return fallback;
2182
+ if (boundMatch !== void 0) return boundMatch.profileId;
2183
+ return matches.length === 1 ? matches[0].profileId : fallback;
2184
+ };
2185
+ const reconcileStableProfile = async (sessionId, connection) => {
2186
+ if (persistence === void 0 || connection.profileId === void 0) return connection;
2187
+ const matches = matchingProfiles(connection);
2188
+ const preferred = preferredMatches(matches);
2189
+ if (preferred.some((entry) => entry.profileId === connection.profileId)) return connection;
2190
+ const stableMatches = matches.filter((entry) => !entry.profileId.startsWith("session:"));
2191
+ const target = preferred.length === 1 ? preferred[0] : connection.profileId.startsWith("session:") ? stableMatches.length === 1 ? stableMatches[0] : void 0 : void 0;
2192
+ if (target === void 0) return connection;
2193
+ const profileId = target.profileId;
2194
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2195
+ await persistence.putBinding(sessionId, {
2196
+ profileId,
2197
+ updatedAt
2198
+ });
2199
+ const reconciled = {
2200
+ ...connection,
2201
+ profileId,
2202
+ tables: copyTables(connection.tables)
2203
+ };
2204
+ runtime.set(sessionId, reconciled);
2205
+ return reconciled;
2206
+ };
2024
2207
  const credentialSummary = async (connection) => {
2025
2208
  const mode = credentialModeOf(connection);
2026
2209
  if (connection.type === "sqlite" || mode === "none") return void 0;
@@ -2095,14 +2278,14 @@ function createConnectionService(ctx, options, persistence) {
2095
2278
  async status(sessionId) {
2096
2279
  const connection = rawConnection(sessionId);
2097
2280
  if (connection === void 0) return void 0;
2098
- return statusSummary(connection);
2281
+ return statusSummary(await reconcileStableProfile(sessionId, connection));
2099
2282
  },
2100
2283
  async connect(sessionId, input, signal) {
2101
2284
  if (sessionId.length === 0) throw new Error("sessionId 必须是非空字符串");
2102
2285
  const normalized = normalizeConnectionInput(input, resolvedOptions.cwd);
2103
2286
  const execution = await resolveCredential(normalized);
2104
2287
  const tables = await verify(execution, signal, true);
2105
- const profileId = normalized.profileId ?? `session:${sessionId}`;
2288
+ const profileId = reusableProfileId(sessionId, normalized);
2106
2289
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2107
2290
  const draft = formDraftFromConnection(normalized);
2108
2291
  await persistAtomically(sessionId, profileId, profileFromConnection(normalized, updatedAt), draft);
@@ -2141,6 +2324,20 @@ function createConnectionService(ctx, options, persistence) {
2141
2324
  if (connection === void 0) throw new Error("请先在 Web「数据库」标签页连接数据库,或在 TUI 运行 /database connect(未找到当前会话的连接)");
2142
2325
  return resolveCredential(connection);
2143
2326
  },
2327
+ async queryMetadata(sessionId, sql, signal) {
2328
+ if (sql.trim().length === 0) throw new Error("Catalog metadata SQL must not be empty");
2329
+ const maxQueryChars = resolvedOptions.maxQueryChars ?? 65536;
2330
+ if (sql.length > maxQueryChars) throw new Error(`Catalog metadata SQL exceeds ${maxQueryChars} characters`);
2331
+ assertSingleStatement(sql, "Catalog metadata query");
2332
+ const connection = await service.resolveForExecution(sessionId);
2333
+ if (classifyStatement(sql, connection.type) !== "read") throw new Error("Catalog metadata execution accepts read-only system catalog statements only");
2334
+ const result = await run(connection, sql, signal, true, false, void 0, resolvedOptions.catalogMaxResultChars ?? resolvedOptions.maxResultChars, true);
2335
+ if (result.exitCode !== 0) {
2336
+ const detail = result.stderr.trim() !== "" ? result.stderr.trim() : result.stdout.trim();
2337
+ throw new Error(`Catalog metadata query failed (exit ${result.exitCode}): ${detail}`);
2338
+ }
2339
+ return result;
2340
+ },
2144
2341
  async listSchemas(sessionId, signal) {
2145
2342
  const connection = await service.resolveForExecution(sessionId);
2146
2343
  const stdout = await runMetadata(connection, "schemas", signal);
@@ -2182,7 +2379,7 @@ function createConnectionService(ctx, options, persistence) {
2182
2379
  kind: "message",
2183
2380
  ...await run(connection, sql, signal)
2184
2381
  };
2185
- const limitedSql = enforceReadRowLimit(sql, connection.type, WORKBENCH_MAX_EXPORT_ROWS + 1);
2382
+ const limitedSql = enforceReadRowLimit(sql, connection.type, 50001);
2186
2383
  const startedAt = Date.now();
2187
2384
  const result = await run(connection, limitedSql, signal, false, false, "structured", WORKBENCH_MAX_RESULT_CHARS);
2188
2385
  if (result.exitCode !== 0) return {
@@ -2294,6 +2491,23 @@ function profileFromConnection(connection, updatedAt) {
2294
2491
  ...connection.credentialMode !== void 0 ? { credentialMode: connection.credentialMode } : {}
2295
2492
  };
2296
2493
  }
2494
+ /** Match only normalized, non-secret endpoint/principal identity fields. */
2495
+ function profileMatchesConnection(profile, connection, cwd = process.cwd()) {
2496
+ let candidate;
2497
+ try {
2498
+ candidate = normalizeConnectionInput({
2499
+ type: profile.type,
2500
+ database: profile.database,
2501
+ ...profile.host !== void 0 ? { host: profile.host } : {},
2502
+ ...profile.port !== void 0 ? { port: profile.port } : {},
2503
+ ...profile.user !== void 0 ? { user: profile.user } : {},
2504
+ ...profile.secure !== void 0 ? { secure: profile.secure } : {}
2505
+ }, cwd);
2506
+ } catch {
2507
+ return false;
2508
+ }
2509
+ return candidate.type === connection.type && candidate.database === connection.database && candidate.host === connection.host && candidate.port === connection.port && candidate.user === connection.user && (candidate.secure ?? false) === (connection.secure ?? false);
2510
+ }
2297
2511
  /** Infer legacy records while leaving ambiguous secret-less SQL profiles conservative. */
2298
2512
  function credentialModeOf(connection) {
2299
2513
  if (connection.credentialMode !== void 0) return connection.credentialMode;
@@ -2308,4 +2522,4 @@ function requireIdentifier(type, value, label) {
2308
2522
  return value;
2309
2523
  }
2310
2524
  //#endregion
2311
- export { parseStructuredQueryOutput as a, clientsSchema as c, validatePasswordRef as i, enforceReadRowLimit as l, redactQueryResult as n, runClientQuery as o, redactSecretText as r, classifyStatement as s, createConnectionService as t, assertSingleStatement as u };
2525
+ export { isDatabaseType as C, defaultDatabasePort as S, clientsSchema as _, parseStructuredQueryOutput as a, DATABASE_TYPES as b, DEFAULT_CATALOG_MAX_RESULT_CHARS as c, DEFAULT_CONNECT_TIMEOUT_MS as d, DEFAULT_MAX_QUERY_CHARS as f, classifyStatement as g, DEFAULT_QUERY_TIMEOUT_MS as h, validatePasswordRef as i, DEFAULT_CATALOG_MAX_TEXT_CHARS as l, DEFAULT_PRESET_ID as m, redactQueryResult as n, runClientQuery as o, DEFAULT_MAX_RESULT_CHARS as p, redactSecretText as r, DEFAULT_CATALOG_MAX_ASSETS as s, createConnectionService as t, DEFAULT_CATALOG_QUERY_TIMEOUT_MS as u, enforceReadRowLimit as v, databaseTypeLabel as x, assertSingleStatement as y };