@yejiming/dsh-data-agent 0.0.6 → 0.0.10
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 +142 -119
- package/README.md +138 -118
- package/cordis.patch.yml +8 -9
- package/lib/client.js +42423 -524
- package/lib/client.js.map +1 -1
- package/lib/command-LFgLb6el.js +875 -0
- package/lib/command.js +2 -0
- package/lib/connections-WmjuUrDj.js +1608 -0
- package/lib/defaults-DP4RyRh1.js +21 -0
- package/lib/index.js +265 -68
- package/lib/routes.js +94 -170
- package/lib/tool-Dka6RyEp.js +1128 -0
- package/lib/tool.js +1 -426
- package/lib/types/analysis.d.ts +1071 -0
- package/lib/types/client/AnalysisChart.d.ts +26 -0
- package/lib/types/client/AnalysisDashboard.d.ts +30 -0
- package/lib/types/client/DataAgentWorkbench.d.ts +2 -2
- package/lib/types/client/analysis-charts.d.ts +40 -0
- package/lib/types/client/analysis-view-model.d.ts +44 -0
- package/lib/types/client/index.d.ts +3 -4
- package/lib/types/client/locales.d.ts +66 -0
- package/lib/types/client/persistence.d.ts +6 -1
- package/lib/types/client-discovery.d.ts +45 -0
- package/lib/types/clients.d.ts +17 -10
- package/lib/types/command.d.ts +41 -0
- package/lib/types/connections.d.ts +115 -40
- package/lib/types/defaults.d.ts +2 -0
- package/lib/types/index.d.ts +109 -63
- package/lib/types/routes.d.ts +25 -91
- package/lib/types/sql.d.ts +1 -1
- package/lib/types/storage.d.ts +70 -0
- package/lib/types/structured-read.d.ts +50 -0
- package/lib/types/tool.d.ts +29 -20
- package/lib/types/tui-connection-form.d.ts +98 -0
- package/package.json +65 -4
- package/preset/data-agent/agent.cordis.yml +22 -25
- package/preset/data-agent/preset.yml +1 -1
- package/lib/defaults-Bac6QvNt.js +0 -911
- package/lib/query-CmhTFklw.js +0 -86
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/defaults.ts
|
|
2
|
+
/**
|
|
3
|
+
* Package-wide defaults shared by the server half (`src/index.ts`) and the
|
|
4
|
+
* database tool half (`src/tool.ts`). Loader schemas carry these as their
|
|
5
|
+
* defaults so a deployment may override every one of them in cordis.yml.
|
|
6
|
+
* @module @yejiming/dsh-data-agent/defaults
|
|
7
|
+
*/
|
|
8
|
+
/** Preset directory name installed into `$DSH_HOME/.agent-presets/`. */
|
|
9
|
+
const DEFAULT_PRESET_ID = "data-agent";
|
|
10
|
+
/** End-to-end deadline for one `/connect` connectivity check, milliseconds. */
|
|
11
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
12
|
+
/** End-to-end deadline for one database-tool query, milliseconds. */
|
|
13
|
+
const DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
14
|
+
/** In-memory cap on database-tool captured output (stdout and stderr each). */
|
|
15
|
+
const DEFAULT_MAX_RESULT_CHARS = 2e4;
|
|
16
|
+
/** Cap on one /query SQL text length (abuse guard; the wire body stays small). */
|
|
17
|
+
const DEFAULT_MAX_QUERY_CHARS = 65536;
|
|
18
|
+
/** Grace period for the subprocess terminate escalation. */
|
|
19
|
+
const DEFAULT_GRACE_MS = 5e3;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { DEFAULT_PRESET_ID as a, DEFAULT_MAX_RESULT_CHARS as i, DEFAULT_GRACE_MS as n, DEFAULT_QUERY_TIMEOUT_MS as o, DEFAULT_MAX_QUERY_CHARS as r, DEFAULT_CONNECT_TIMEOUT_MS as t };
|
package/lib/index.js
CHANGED
|
@@ -1,67 +1,143 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { o as clientsSchema, t as createConnectionService } from "./connections-WmjuUrDj.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-LFgLb6el.js";
|
|
4
|
+
import { n as apply$2 } from "./tool-Dka6RyEp.js";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { access, cp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
7
|
import { homedir } from "node:os";
|
|
4
8
|
import { join, resolve } from "node:path";
|
|
5
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import Storage from "@deepseek-ai/dsh-storage";
|
|
11
|
+
import * as storageDomainPlugin from "@deepseek-ai/dsh-storage-domain";
|
|
12
|
+
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
13
|
+
import * as storageJsonPlugin from "@deepseek-ai/dsh-storage-json";
|
|
6
14
|
import z from "schemastery";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
import { z as z$1 } from "zod";
|
|
16
|
+
//#region src/storage.ts
|
|
17
|
+
/**
|
|
18
|
+
* Durable, non-secret connection profiles, session bindings, and form drafts.
|
|
19
|
+
*
|
|
20
|
+
* The domain intentionally excludes passwords, resolved credentials, SQL,
|
|
21
|
+
* table metadata, and client output. Form drafts likewise accept no secret
|
|
22
|
+
* fields. Runtime secrets stay in
|
|
23
|
+
* {@link DataAgentConnectionService}; durable records only retain enough
|
|
24
|
+
* information to rebuild a connection description in another DSH surface.
|
|
25
|
+
* @module @yejiming/dsh-data-agent/storage
|
|
26
|
+
*/
|
|
27
|
+
/** Storage-domain identity. Bump the version only with an explicit migration. */
|
|
28
|
+
const CONNECTION_STORAGE_DOMAIN = "data_agent_connections";
|
|
29
|
+
/** Durable profile schema. There is deliberately no `password` field. */
|
|
30
|
+
const persistedConnectionProfileSchema = z$1.object({
|
|
31
|
+
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
|
+
]),
|
|
40
|
+
host: z$1.string().optional(),
|
|
41
|
+
port: z$1.number().int().min(1).max(65535).optional(),
|
|
42
|
+
user: z$1.string().optional(),
|
|
43
|
+
database: z$1.string().min(1),
|
|
44
|
+
readonly: z$1.boolean().optional(),
|
|
45
|
+
passwordRef: z$1.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional(),
|
|
46
|
+
updatedAt: z$1.string().min(1)
|
|
47
|
+
}).strict();
|
|
48
|
+
/** Durable session-to-profile binding schema. */
|
|
49
|
+
const sessionConnectionBindingSchema = z$1.object({
|
|
50
|
+
profileId: z$1.string().min(1),
|
|
51
|
+
updatedAt: z$1.string().min(1)
|
|
52
|
+
}).strict();
|
|
53
|
+
/** Session form draft schema. Secret-shaped fields are rejected by strict mode. */
|
|
54
|
+
const persistedConnectionFormDraftSchema = z$1.object({
|
|
55
|
+
type: z$1.enum([
|
|
56
|
+
"mysql",
|
|
57
|
+
"postgres",
|
|
58
|
+
"sqlite",
|
|
59
|
+
"oracle",
|
|
60
|
+
"hive",
|
|
61
|
+
"impala"
|
|
62
|
+
]),
|
|
63
|
+
host: z$1.string(),
|
|
64
|
+
port: z$1.string(),
|
|
65
|
+
user: z$1.string(),
|
|
66
|
+
database: z$1.string(),
|
|
67
|
+
readonly: z$1.boolean(),
|
|
68
|
+
updatedAt: z$1.string().min(1)
|
|
69
|
+
}).strict();
|
|
70
|
+
/** Single source of truth for the storage layout and durable validation. */
|
|
71
|
+
const connectionStorageSpec = defineDomain({
|
|
72
|
+
name: CONNECTION_STORAGE_DOMAIN,
|
|
73
|
+
version: 1,
|
|
74
|
+
tables: {
|
|
75
|
+
profiles: domainTable(persistedConnectionProfileSchema),
|
|
76
|
+
bindings: domainTable(sessionConnectionBindingSchema),
|
|
77
|
+
drafts: domainTable(persistedConnectionFormDraftSchema)
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
/** Project a typed DSH domain handle onto the service's persistence seam. */
|
|
81
|
+
function createDomainConnectionPersistence(domain) {
|
|
82
|
+
const profiles = domain.table("profiles");
|
|
83
|
+
const bindings = domain.table("bindings");
|
|
84
|
+
const drafts = domain.table("drafts");
|
|
25
85
|
return {
|
|
26
|
-
|
|
27
|
-
|
|
86
|
+
getProfile(profileId) {
|
|
87
|
+
return profiles.get(profileId);
|
|
88
|
+
},
|
|
89
|
+
putProfile(profileId, profile) {
|
|
90
|
+
return profiles.put(profileId, profile);
|
|
91
|
+
},
|
|
92
|
+
deleteProfile(profileId) {
|
|
93
|
+
return profiles.delete(profileId);
|
|
28
94
|
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return connection === void 0 ? void 0 : summarize(connection);
|
|
95
|
+
getBinding(sessionId) {
|
|
96
|
+
return bindings.get(sessionId);
|
|
32
97
|
},
|
|
33
|
-
|
|
34
|
-
return
|
|
98
|
+
putBinding(sessionId, binding) {
|
|
99
|
+
return bindings.put(sessionId, binding);
|
|
35
100
|
},
|
|
36
|
-
|
|
37
|
-
return
|
|
101
|
+
deleteBinding(sessionId) {
|
|
102
|
+
return bindings.delete(sessionId);
|
|
38
103
|
},
|
|
39
|
-
|
|
40
|
-
|
|
104
|
+
getDraft(sessionId) {
|
|
105
|
+
return drafts.get(sessionId);
|
|
106
|
+
},
|
|
107
|
+
putDraft(sessionId, draft) {
|
|
108
|
+
return drafts.put(sessionId, draft);
|
|
41
109
|
}
|
|
42
110
|
};
|
|
43
111
|
}
|
|
44
112
|
//#endregion
|
|
45
113
|
//#region src/index.ts
|
|
46
114
|
/**
|
|
47
|
-
* Data Agent
|
|
48
|
-
* `dataAgentConnections` service (
|
|
49
|
-
*
|
|
50
|
-
* wildcard default),
|
|
51
|
-
* `$DSH_HOME/.agent-presets
|
|
52
|
-
*
|
|
115
|
+
* Data Agent profile entry. The host row provides the
|
|
116
|
+
* `dataAgentConnections` service (shared non-secret profile/binding storage;
|
|
117
|
+
* temporary passwords stay process-local), seeds config connections (`connections`, `'*'` =
|
|
118
|
+
* wildcard default), installs the `data-agent` agent preset into
|
|
119
|
+
* `$DSH_HOME/.agent-presets/`, and preloads the preset-scoped database tools
|
|
120
|
+
* and command through this profile bundle entry.
|
|
53
121
|
*
|
|
54
122
|
* The HTTP routes live in the separate `./routes` entry
|
|
55
123
|
* (`@yejiming/dsh-data-agent/routes`, cordis row `data-agent-routes`) so
|
|
56
|
-
* this row keeps working in headless profiles without a webserver
|
|
57
|
-
* database
|
|
58
|
-
*
|
|
124
|
+
* this row keeps working in headless profiles without a webserver. The
|
|
125
|
+
* database implementations still have public `./tool` and `./command`
|
|
126
|
+
* exports, but the shipped preset does not dynamically import those package
|
|
127
|
+
* subpaths. Loading them here keeps Desktop on the same profile-startup path
|
|
128
|
+
* as other UI bundles and avoids Electron ASAR package-resolution drift.
|
|
59
129
|
* @module @yejiming/dsh-data-agent
|
|
60
130
|
*/
|
|
61
131
|
/** Cordis plugin name (diagnostics only). */
|
|
62
132
|
const name = "data-agent";
|
|
63
|
-
/** Services required before the
|
|
64
|
-
const inject = [
|
|
133
|
+
/** Services required before the profile entry can mount its preset layer. */
|
|
134
|
+
const inject = [
|
|
135
|
+
"agentPresets",
|
|
136
|
+
"commands",
|
|
137
|
+
"credentials",
|
|
138
|
+
"subprocess",
|
|
139
|
+
"tools"
|
|
140
|
+
];
|
|
65
141
|
/** Loader schema with deployment defaults (no library defaults). */
|
|
66
142
|
const Config = z.object({
|
|
67
143
|
presetId: z.string().default(DEFAULT_PRESET_ID),
|
|
@@ -70,7 +146,10 @@ const Config = z.object({
|
|
|
70
146
|
introspectMaxTables: z.number().step(1).min(1).default(500),
|
|
71
147
|
queryTimeoutMs: z.number().step(1).min(1e3).default(DEFAULT_QUERY_TIMEOUT_MS),
|
|
72
148
|
maxResultChars: z.number().step(1).min(1024).default(DEFAULT_MAX_RESULT_CHARS),
|
|
149
|
+
maxRows: z.number().step(1).min(1).default(100),
|
|
150
|
+
maxQueryChars: z.number().step(1).min(1024).default(DEFAULT_MAX_QUERY_CHARS),
|
|
73
151
|
readonly: z.boolean().default(false),
|
|
152
|
+
persistConnections: z.boolean().default(true),
|
|
74
153
|
clients: clientsSchema,
|
|
75
154
|
connections: z.dict(z.object({
|
|
76
155
|
type: z.union([
|
|
@@ -85,7 +164,9 @@ const Config = z.object({
|
|
|
85
164
|
port: z.natural(),
|
|
86
165
|
user: z.string(),
|
|
87
166
|
database: z.string(),
|
|
88
|
-
readonly: z.boolean()
|
|
167
|
+
readonly: z.boolean(),
|
|
168
|
+
passwordRef: z.string().pattern(/^[A-Za-z_][A-Za-z0-9_]*$/),
|
|
169
|
+
password: z.never().hidden()
|
|
89
170
|
})).default({})
|
|
90
171
|
});
|
|
91
172
|
/**
|
|
@@ -99,35 +180,95 @@ function resolveDshHome(env = process.env) {
|
|
|
99
180
|
}
|
|
100
181
|
/**
|
|
101
182
|
* Install the packaged `preset/data-agent/` directory into
|
|
102
|
-
* `$DSH_HOME/.agent-presets/<presetId>/`. Idempotent: an existing target
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
183
|
+
* `$DSH_HOME/.agent-presets/<presetId>/`. Idempotent: an existing target is
|
|
184
|
+
* normally left untouched. The exact package-owned 0.0.9 composition is
|
|
185
|
+
* migrated once because its two dynamic package rows are incompatible with
|
|
186
|
+
* DSH Desktop's unpacked-ASAR loader; user-edited compositions are never
|
|
187
|
+
* overwritten. `installPreset: false` never calls this. Best-effort — a
|
|
188
|
+
* failure logs a warning with manual install instructions instead of failing
|
|
189
|
+
* the boot.
|
|
106
190
|
*/
|
|
107
191
|
async function installPreset(ctx, presetId) {
|
|
108
192
|
const targetDir = join(resolveDshHome(), ".agent-presets", presetId);
|
|
193
|
+
const sourceDir = fileURLToPath(new URL("../preset/data-agent/", import.meta.url));
|
|
109
194
|
try {
|
|
110
195
|
await access(targetDir);
|
|
111
|
-
|
|
112
|
-
return;
|
|
196
|
+
return await synchronizeExistingPreset(ctx, targetDir, sourceDir, presetId);
|
|
113
197
|
} catch {}
|
|
114
|
-
const sourceDir = fileURLToPath(new URL("../preset/data-agent/", import.meta.url));
|
|
115
198
|
try {
|
|
116
199
|
await mkdir(targetDir, { recursive: true });
|
|
117
200
|
await cp(sourceDir, targetDir, { recursive: true });
|
|
118
201
|
ctx.logger.info("data-agent: installed preset \"%s\" to %s", presetId, targetDir);
|
|
202
|
+
return true;
|
|
119
203
|
} catch (error) {
|
|
120
204
|
ctx.logger.warn("data-agent: failed to install preset \"%s\" to %s (%s); copy preset/data-agent/ manually to enable the 数据模式 preset", presetId, targetDir, error instanceof Error ? error.message : String(error));
|
|
205
|
+
return false;
|
|
121
206
|
}
|
|
122
207
|
}
|
|
208
|
+
/** SHA-256 of the unmodified 0.0.9 composition that imported /tool and /command dynamically. */
|
|
209
|
+
const LEGACY_PRESET_0_0_9_SHA256 = "bae875a90d638ea78715030246b0f8a9f1a2c3359ca61febb6ceb59d0fcd930a";
|
|
210
|
+
/** Public for regression tests of the non-destructive preset migration gate. */
|
|
211
|
+
function isLegacyManagedPreset(source) {
|
|
212
|
+
return createHash("sha256").update(source).digest("hex") === LEGACY_PRESET_0_0_9_SHA256;
|
|
213
|
+
}
|
|
214
|
+
/** Upgrade only the exact package-owned legacy composition; preserve every edited preset. */
|
|
215
|
+
async function synchronizeExistingPreset(ctx, targetDir, sourceDir, presetId) {
|
|
216
|
+
const composition = join(targetDir, "agent.cordis.yml");
|
|
217
|
+
try {
|
|
218
|
+
const current = await readFile(composition, "utf8");
|
|
219
|
+
if (isLegacyManagedPreset(current)) {
|
|
220
|
+
const replacement = await readFile(join(sourceDir, "agent.cordis.yml"), "utf8");
|
|
221
|
+
await writeFile(composition, replacement, "utf8");
|
|
222
|
+
ctx.logger.info("data-agent: migrated preset at %s to profile-preloaded tools (removed dynamic /tool and /command rows)", composition);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
if (current.includes("@yejiming/dsh-data-agent/tool") || current.includes("@yejiming/dsh-data-agent/command")) {
|
|
226
|
+
ctx.logger.warn("data-agent: user-edited preset at %s still imports /tool or /command dynamically; remove those rows so the profile-preloaded preset capabilities can activate in DSH Desktop", composition);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
ctx.logger.info("data-agent: preset \"%s\" already present at %s, skipping install", presetId, targetDir);
|
|
230
|
+
return true;
|
|
231
|
+
} catch (error) {
|
|
232
|
+
ctx.logger.warn("data-agent: could not inspect existing preset %s (%s); it was not overwritten", composition, error instanceof Error ? error.message : String(error));
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** Exact profile-local package installation command used by diagnostics/docs. */
|
|
237
|
+
function profileInstallCommand(profile) {
|
|
238
|
+
return `dsh plugin --profile ${profile} add @yejiming/dsh-data-agent`;
|
|
239
|
+
}
|
|
240
|
+
/** Actionable diagnostic for a roster-visible preset whose profile lacks this package. */
|
|
241
|
+
function missingProfileDependencyMessage(profile) {
|
|
242
|
+
return `data-agent preset is visible, but its profile-preloaded capabilities are absent from profile "${profile}". Run: ${profileInstallCommand(profile)}`;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Register the statically imported database tools and command under the exact
|
|
246
|
+
* standing key owned by the data-agent preset. Selecting the preset performs
|
|
247
|
+
* no package import and only links the agent scope to this key.
|
|
248
|
+
*/
|
|
249
|
+
async function mountPresetCapabilities(ctx, key, scopeTag, config) {
|
|
250
|
+
const scoped = ctx.extend({ [scopeTag]: key });
|
|
251
|
+
apply$2(scoped, config);
|
|
252
|
+
apply$1(scoped);
|
|
253
|
+
}
|
|
254
|
+
/** Read the host-owned scope tag from AgentPresets' already-created standing mount. */
|
|
255
|
+
async function standingScopeTag(ctx, presetId, key) {
|
|
256
|
+
const pending = ctx.agentPresets.standing?.get(presetId);
|
|
257
|
+
if (pending === void 0) throw new Error(`data-agent: preset "${presetId}" has no standing scope after standingKeyFor()`);
|
|
258
|
+
const standing = await pending;
|
|
259
|
+
if (standing.key !== key) throw new Error(`data-agent: preset "${presetId}" standing scope changed during profile preload`);
|
|
260
|
+
const tag = Object.getOwnPropertySymbols(standing.scope.ctx).find((candidate) => Reflect.get(standing.scope.ctx, candidate) === key);
|
|
261
|
+
if (tag === void 0) throw new Error(`data-agent: preset "${presetId}" standing context exposes no scope tag`);
|
|
262
|
+
return tag;
|
|
263
|
+
}
|
|
123
264
|
/**
|
|
124
|
-
* Mount the data-agent
|
|
125
|
-
* connections,
|
|
126
|
-
* `data-agent-routes` row (`./routes`).
|
|
265
|
+
* Mount the data-agent profile row: connection store, config-seeded
|
|
266
|
+
* connections, preset installation, and profile-preloaded preset capabilities.
|
|
267
|
+
* HTTP routes are the sibling `data-agent-routes` row (`./routes`).
|
|
127
268
|
* @param ctx - host cordis context.
|
|
128
269
|
* @param config - validated loader configuration.
|
|
129
270
|
*/
|
|
130
|
-
function apply(ctx, config) {
|
|
271
|
+
async function apply(ctx, config) {
|
|
131
272
|
const resolved = {
|
|
132
273
|
presetId: config.presetId,
|
|
133
274
|
installPreset: config.installPreset,
|
|
@@ -135,24 +276,80 @@ function apply(ctx, config) {
|
|
|
135
276
|
introspectMaxTables: config.introspectMaxTables,
|
|
136
277
|
queryTimeoutMs: config.queryTimeoutMs,
|
|
137
278
|
maxResultChars: config.maxResultChars,
|
|
279
|
+
maxRows: config.maxRows,
|
|
280
|
+
maxQueryChars: config.maxQueryChars,
|
|
138
281
|
readonly: config.readonly,
|
|
282
|
+
persistConnections: config.persistConnections,
|
|
139
283
|
clients: config.clients,
|
|
140
284
|
connections: config.connections
|
|
141
285
|
};
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
286
|
+
const mountService = (scope, persistence) => {
|
|
287
|
+
const store = createConnectionService(scope, {
|
|
288
|
+
connectTimeoutMs: resolved.connectTimeoutMs,
|
|
289
|
+
queryTimeoutMs: resolved.queryTimeoutMs,
|
|
290
|
+
maxResultChars: resolved.maxResultChars,
|
|
291
|
+
maxQueryChars: resolved.maxQueryChars,
|
|
292
|
+
introspectMaxTables: resolved.introspectMaxTables,
|
|
293
|
+
readonly: resolved.readonly,
|
|
294
|
+
clients: resolved.clients
|
|
295
|
+
}, persistence);
|
|
296
|
+
scope.provide("dataAgentConnections", store);
|
|
297
|
+
for (const [sessionId, spec] of Object.entries(resolved.connections)) {
|
|
298
|
+
const connection = {
|
|
299
|
+
type: spec.type,
|
|
300
|
+
database: spec.type === "sqlite" ? resolve(process.cwd(), spec.database) : spec.database,
|
|
301
|
+
...spec.host !== void 0 ? { host: spec.host } : {},
|
|
302
|
+
...spec.port !== void 0 ? { port: spec.port } : {},
|
|
303
|
+
...spec.user !== void 0 ? { user: spec.user } : {},
|
|
304
|
+
...spec.passwordRef !== void 0 ? { passwordRef: spec.passwordRef } : {},
|
|
305
|
+
...spec.readonly !== void 0 ? { readonly: spec.readonly } : {}
|
|
306
|
+
};
|
|
307
|
+
store.set(sessionId, connection);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
const presetReady = resolved.installPreset ? await installPreset(ctx, resolved.presetId) : false;
|
|
311
|
+
if (resolved.persistConnections) {
|
|
312
|
+
const domain = await (await ensureStorageDomain(ctx)).open(connectionStorageSpec);
|
|
313
|
+
ctx.effect(() => () => domain.close(), "data-agent: close connection storage domain");
|
|
314
|
+
mountService(ctx, createDomainConnectionPersistence(domain));
|
|
315
|
+
} else {
|
|
316
|
+
ctx.logger.warn("data-agent: persistConnections=false; connection state is process-local and cannot restore across Web/TUI");
|
|
317
|
+
mountService(ctx);
|
|
318
|
+
}
|
|
319
|
+
if (presetReady) {
|
|
320
|
+
const standingKey = await ctx.agentPresets.standingKeyFor(resolved.presetId);
|
|
321
|
+
await mountPresetCapabilities(ctx, standingKey, await standingScopeTag(ctx, resolved.presetId, standingKey), {
|
|
322
|
+
queryTimeoutMs: resolved.queryTimeoutMs,
|
|
323
|
+
maxResultChars: resolved.maxResultChars,
|
|
324
|
+
maxRows: resolved.maxRows,
|
|
325
|
+
maxQueryChars: resolved.maxQueryChars,
|
|
326
|
+
readonly: resolved.readonly,
|
|
327
|
+
clients: resolved.clients
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Reuse a surface-provided storage stack (Web) or mount the same JSON stack
|
|
333
|
+
* when an interactive profile such as dsh-tui does not ship one.
|
|
334
|
+
*/
|
|
335
|
+
async function ensureStorageDomain(ctx) {
|
|
336
|
+
const existing = ctx.get("storageDomain");
|
|
337
|
+
if (existing !== void 0) return existing;
|
|
338
|
+
ctx.logger.info("data-agent: storageDomain is absent; mounting the JSON storage stack for this profile");
|
|
339
|
+
let storage = ctx.get("storage");
|
|
340
|
+
if (storage === void 0) {
|
|
341
|
+
await ctx.plugin(Storage);
|
|
342
|
+
storage = ctx.get("storage");
|
|
343
|
+
}
|
|
344
|
+
if (storage === void 0) throw new Error("data-agent: failed to mount DSH storage hub");
|
|
345
|
+
if (!storage.backend.names().includes("json")) await ctx.plugin(storageJsonPlugin, { root: join(resolveDshHome(), "storages") });
|
|
346
|
+
let facility = ctx.get("storageDomain");
|
|
347
|
+
if (facility === void 0) {
|
|
348
|
+
await ctx.plugin(storageDomainPlugin, { backend: "json" });
|
|
349
|
+
facility = ctx.get("storageDomain");
|
|
154
350
|
}
|
|
155
|
-
if (
|
|
351
|
+
if (facility === void 0) throw new Error("data-agent: failed to mount DSH storage-domain facility");
|
|
352
|
+
return facility;
|
|
156
353
|
}
|
|
157
354
|
//#endregion
|
|
158
|
-
export { Config, apply, inject, installPreset, name, resolveDshHome };
|
|
355
|
+
export { Config, apply, inject, installPreset, isLegacyManagedPreset, missingProfileDependencyMessage, mountPresetCapabilities, name, profileInstallCommand, resolveDshHome };
|