@yejiming/dsh-data-agent 0.0.6 → 0.0.9

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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Data Agent server half for the dsh web GUI. The host row provides the
3
- * `dataAgentConnections` service (session-scoped in-memory store; passwords
4
- * never leave memory), seeds config connections (`connections`, `'*'` =
3
+ * `dataAgentConnections` service (shared non-secret profile/binding storage;
4
+ * temporary passwords stay process-local), seeds config connections (`connections`, `'*'` =
5
5
  * wildcard default), and installs the `data-agent` agent preset into
6
6
  * `$DSH_HOME/.agent-presets/` (idempotent, never overwrites a user-edited
7
7
  * directory).
@@ -20,6 +20,7 @@ declare module '@deepseek-ai/cordis' {
20
20
  dataAgentConnections: DataAgentConnections;
21
21
  }
22
22
  }
23
+ import z from 'schemastery';
23
24
  import { type DataAgentConnections, type DatabaseType } from './connections.ts';
24
25
  import { type ClientConfig } from './clients.ts';
25
26
  /** Cordis plugin name (diagnostics only). */
@@ -27,11 +28,7 @@ export declare const name = "data-agent";
27
28
  /** Services required before the store can serve. */
28
29
  export declare const inject: string[];
29
30
  /** Deployment overrides for one database type's CLI client. */
30
- export interface ClientsConfig {
31
- mysql?: ClientConfig;
32
- postgres?: ClientConfig;
33
- sqlite?: ClientConfig;
34
- }
31
+ export type ClientsConfig = Partial<Record<DatabaseType, ClientConfig>>;
35
32
  /**
36
33
  * One config-seeded connection. Deliberately password-free: passwords are a
37
34
  * memory-only / connect-time value, so only the /connect route may carry one.
@@ -46,6 +43,9 @@ export interface SeededConnectionConfig {
46
43
  database: string;
47
44
  /** Optional per-seed read-only guard. */
48
45
  readonly?: boolean;
46
+ /** Safe credential reference. Real passwords are rejected by the schema. */
47
+ passwordRef?: string;
48
+ password?: never;
49
49
  }
50
50
  /** Required plugin configuration (loader schema with deployment defaults). */
51
51
  export interface Config {
@@ -61,73 +61,89 @@ export interface Config {
61
61
  queryTimeoutMs: number;
62
62
  /** In-memory cap on database-tool captured output. */
63
63
  maxResultChars: number;
64
+ /** Maximum SQL text accepted by the shared Web query adapter. */
65
+ maxQueryChars: number;
64
66
  /** Default read-only guard: true rejects write statements in database tools and /query. */
65
67
  readonly: boolean;
68
+ /** Persist non-secret profiles/bindings through DSH storage-domain. */
69
+ persistConnections: boolean;
66
70
  /** CLI client overrides keyed by database type. */
67
71
  clients: ClientsConfig;
68
72
  /** Config-seeded connections keyed by session id (`'*'` = wildcard default). */
69
73
  connections: Record<string, SeededConnectionConfig>;
70
74
  }
71
75
  /** Loader schema with deployment defaults (no library defaults). */
72
- export declare const Config: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
73
- presetId: import("@deepseek-ai/schemastery").default<string, string>;
74
- installPreset: import("@deepseek-ai/schemastery").default<boolean, boolean>;
75
- connectTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
76
- introspectMaxTables: import("@deepseek-ai/schemastery").default<number, number>;
77
- queryTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
78
- maxResultChars: import("@deepseek-ai/schemastery").default<number, number>;
79
- readonly: import("@deepseek-ai/schemastery").default<boolean, boolean>;
80
- clients: import("@deepseek-ai/schemastery").default<import("@deepseek-ai/cosmokit").Dict<{
76
+ export declare const Config: z<Schemastery.ObjectS<{
77
+ presetId: z<string, string>;
78
+ installPreset: z<boolean, boolean>;
79
+ connectTimeoutMs: z<number, number>;
80
+ introspectMaxTables: z<number, number>;
81
+ queryTimeoutMs: z<number, number>;
82
+ maxResultChars: z<number, number>;
83
+ maxQueryChars: z<number, number>;
84
+ readonly: z<boolean, boolean>;
85
+ persistConnections: z<boolean, boolean>;
86
+ clients: z<import("cosmokit").Dict<{
81
87
  command?: string | null | undefined;
82
88
  args?: string[] | null | undefined;
83
- } & import("cosmokit").Dict, string>, import("@deepseek-ai/cosmokit").Dict<Schemastery.ObjectT<{
84
- command: import("@deepseek-ai/schemastery").default<string, string>;
85
- args: import("@deepseek-ai/schemastery").default<string[], string[]>;
89
+ } & import("@deepseek-ai/cosmokit").Dict, string>, import("cosmokit").Dict<Schemastery.ObjectT<{
90
+ command: z<string, string>;
91
+ args: z<string[], string[]>;
86
92
  }>, string>>;
87
- connections: import("@deepseek-ai/schemastery").default<import("@deepseek-ai/cosmokit").Dict<{
93
+ connections: z<import("cosmokit").Dict<{
88
94
  type?: "mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala" | null | undefined;
89
95
  host?: string | null | undefined;
90
96
  port?: number | null | undefined;
91
97
  user?: string | null | undefined;
92
98
  database?: string | null | undefined;
93
99
  readonly?: boolean | null | undefined;
94
- } & import("cosmokit").Dict, string>, import("@deepseek-ai/cosmokit").Dict<Schemastery.ObjectT<{
95
- type: import("@deepseek-ai/schemastery").default<"mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala", "mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala">;
96
- host: import("@deepseek-ai/schemastery").default<string, string>;
97
- port: import("@deepseek-ai/schemastery").default<number, number>;
98
- user: import("@deepseek-ai/schemastery").default<string, string>;
99
- database: import("@deepseek-ai/schemastery").default<string, string>;
100
- readonly: import("@deepseek-ai/schemastery").default<boolean, boolean>;
100
+ passwordRef?: string | null | undefined;
101
+ password?: null | undefined;
102
+ } & import("@deepseek-ai/cosmokit").Dict, string>, import("cosmokit").Dict<Schemastery.ObjectT<{
103
+ type: z<"mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala", "mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala">;
104
+ host: z<string, string>;
105
+ port: z<number, number>;
106
+ user: z<string, string>;
107
+ database: z<string, string>;
108
+ readonly: z<boolean, boolean>;
109
+ passwordRef: z<string, string>;
110
+ password: z<never, never>;
101
111
  }>, string>>;
102
112
  }>, Schemastery.ObjectT<{
103
- presetId: import("@deepseek-ai/schemastery").default<string, string>;
104
- installPreset: import("@deepseek-ai/schemastery").default<boolean, boolean>;
105
- connectTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
106
- introspectMaxTables: import("@deepseek-ai/schemastery").default<number, number>;
107
- queryTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
108
- maxResultChars: import("@deepseek-ai/schemastery").default<number, number>;
109
- readonly: import("@deepseek-ai/schemastery").default<boolean, boolean>;
110
- clients: import("@deepseek-ai/schemastery").default<import("@deepseek-ai/cosmokit").Dict<{
113
+ presetId: z<string, string>;
114
+ installPreset: z<boolean, boolean>;
115
+ connectTimeoutMs: z<number, number>;
116
+ introspectMaxTables: z<number, number>;
117
+ queryTimeoutMs: z<number, number>;
118
+ maxResultChars: z<number, number>;
119
+ maxQueryChars: z<number, number>;
120
+ readonly: z<boolean, boolean>;
121
+ persistConnections: z<boolean, boolean>;
122
+ clients: z<import("cosmokit").Dict<{
111
123
  command?: string | null | undefined;
112
124
  args?: string[] | null | undefined;
113
- } & import("cosmokit").Dict, string>, import("@deepseek-ai/cosmokit").Dict<Schemastery.ObjectT<{
114
- command: import("@deepseek-ai/schemastery").default<string, string>;
115
- args: import("@deepseek-ai/schemastery").default<string[], string[]>;
125
+ } & import("@deepseek-ai/cosmokit").Dict, string>, import("cosmokit").Dict<Schemastery.ObjectT<{
126
+ command: z<string, string>;
127
+ args: z<string[], string[]>;
116
128
  }>, string>>;
117
- connections: import("@deepseek-ai/schemastery").default<import("@deepseek-ai/cosmokit").Dict<{
129
+ connections: z<import("cosmokit").Dict<{
118
130
  type?: "mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala" | null | undefined;
119
131
  host?: string | null | undefined;
120
132
  port?: number | null | undefined;
121
133
  user?: string | null | undefined;
122
134
  database?: string | null | undefined;
123
135
  readonly?: boolean | null | undefined;
124
- } & import("cosmokit").Dict, string>, import("@deepseek-ai/cosmokit").Dict<Schemastery.ObjectT<{
125
- type: import("@deepseek-ai/schemastery").default<"mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala", "mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala">;
126
- host: import("@deepseek-ai/schemastery").default<string, string>;
127
- port: import("@deepseek-ai/schemastery").default<number, number>;
128
- user: import("@deepseek-ai/schemastery").default<string, string>;
129
- database: import("@deepseek-ai/schemastery").default<string, string>;
130
- readonly: import("@deepseek-ai/schemastery").default<boolean, boolean>;
136
+ passwordRef?: string | null | undefined;
137
+ password?: null | undefined;
138
+ } & import("@deepseek-ai/cosmokit").Dict, string>, import("cosmokit").Dict<Schemastery.ObjectT<{
139
+ type: z<"mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala", "mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala">;
140
+ host: z<string, string>;
141
+ port: z<number, number>;
142
+ user: z<string, string>;
143
+ database: z<string, string>;
144
+ readonly: z<boolean, boolean>;
145
+ passwordRef: z<string, string>;
146
+ password: z<never, never>;
131
147
  }>, string>>;
132
148
  }>>;
133
149
  /**
@@ -143,6 +159,10 @@ export declare function resolveDshHome(env?: Record<string, string | undefined>)
143
159
  * install instructions instead of failing the boot.
144
160
  */
145
161
  export declare function installPreset(ctx: Context, presetId: string): Promise<void>;
162
+ /** Exact profile-local package installation command used by diagnostics/docs. */
163
+ export declare function profileInstallCommand(profile: string): string;
164
+ /** Actionable diagnostic for a roster-visible preset whose profile lacks this package. */
165
+ export declare function missingProfileDependencyMessage(profile: string): string;
146
166
  /**
147
167
  * Mount the data-agent host row: connection store, config-seeded
148
168
  * connections, and preset self-install. HTTP routes are the sibling
@@ -150,4 +170,4 @@ export declare function installPreset(ctx: Context, presetId: string): Promise<v
150
170
  * @param ctx - host cordis context.
151
171
  * @param config - validated loader configuration.
152
172
  */
153
- export declare function apply(ctx: Context, config: Config): void;
173
+ export declare function apply(ctx: Context, config: Config): Promise<void>;
@@ -1,32 +1,15 @@
1
1
  /**
2
- * Data Agent routes half (`@yejiming/dsh-data-agent/routes`): the
3
- * `/plugins/data-agent` HTTP surface. A separate row from the main `data-agent`
4
- * row so the plugin keeps working in headless profiles (no webserver): the
5
- * connection store, preset self-install, and config-seeded connections all
6
- * live on the main row, and this row simply never activates where
7
- * `webServer` is absent.
2
+ * Web adapter for the shared data-agent connection service.
8
3
  *
9
- * Routes:
10
- * - `POST /plugins/data-agent/connect` — validate and store one session's
11
- * database connection, verify connectivity by listing all tables, and
12
- * return `{ ok, tables }` (or `{ ok: false, error }` without saving).
13
- * - `POST /plugins/data-agent/disconnect` — drop one session's connection.
14
- * - `GET /plugins/data-agent/status` — the current connection's
15
- * password-stripped summary plus the table list.
16
- * - `GET /plugins/data-agent/schemas` — schema/database list.
17
- * - `GET /plugins/data-agent/tables` — table list of one schema.
18
- * - `GET /plugins/data-agent/describe` — column structure of one table.
19
- * - `POST /plugins/data-agent/query` — run one SQL text (the workbench
20
- * command box; non-agent channel, same trust as sqlcmd).
4
+ * This entry owns only HTTP parsing/serialization. Connection validation,
5
+ * credentials, persistence, metadata, query safety, and error semantics live
6
+ * in `DataAgentConnections`, which is also consumed by TUI commands/tools.
21
7
  * @module @yejiming/dsh-data-agent/routes
22
8
  */
23
9
  import type { IncomingMessage, ServerResponse } from 'node:http';
24
10
  import type { Context } from '@deepseek-ai/cordis';
25
- /**
26
- * Minimal face of the host webserver service used by this row.
27
- * The service was renamed from `httpServer` to `webServer` in
28
- * dsh 0.1.0-rc.6; the nested inject below waits on `webServer`.
29
- */
11
+ import z from 'schemastery';
12
+ import type { DatabaseConnectionInput } from './connections.ts';
30
13
  interface WebServerLike {
31
14
  register(route: {
32
15
  kind: 'exact' | 'prefix';
@@ -39,74 +22,39 @@ declare module '@deepseek-ai/cordis' {
39
22
  webServer: WebServerLike;
40
23
  }
41
24
  }
42
- import type { DatabaseType } from './connections.ts';
43
- /** Cordis plugin name (diagnostics only). */
44
25
  export declare const name = "data-agent-routes";
45
- /**
46
- * No top-level `inject` export: the row must ACTIVATE even in headless
47
- * profiles where `webServer` never exists (a permanently pending entry
48
- * breaks one-shot runs). The routes register through a nested inject fiber
49
- * the moment the webserver and the connection store are both available.
50
- */
26
+ /** Headless profiles activate this row without waiting forever for webServer. */
51
27
  export declare const inject: string[];
52
- /** Route prefix owned by this plugin (the browser half calls under it). */
53
28
  export declare const DATA_AGENT_PATH = "/plugins/data-agent";
54
- /** Routes-half configuration (defaults mirror the main row). */
29
+ /** Retained loader surface for backward compatibility; domain options live on the host row. */
55
30
  export interface Config {
56
- /** Deadline for one /connect connectivity check, milliseconds. */
57
31
  connectTimeoutMs: number;
58
- /** Cap on metadata lists returned by /connect /status /schemas /tables. */
59
32
  introspectMaxTables: number;
60
- /** In-memory cap on captured output. */
61
33
  maxResultChars: number;
62
- /** Deadline for one /query or metadata query, milliseconds. */
63
34
  queryTimeoutMs: number;
64
- /** Cap on one /query SQL text length. */
65
35
  maxQueryChars: number;
66
- /** Read-only guard: true rejects write statements in /query. */
67
36
  readonly: boolean;
68
37
  }
69
- /** Loader schema with deployment defaults (no library defaults). */
70
- export declare const Config: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
71
- connectTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
72
- introspectMaxTables: import("@deepseek-ai/schemastery").default<number, number>;
73
- maxResultChars: import("@deepseek-ai/schemastery").default<number, number>;
74
- queryTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
75
- maxQueryChars: import("@deepseek-ai/schemastery").default<number, number>;
76
- readonly: import("@deepseek-ai/schemastery").default<boolean, boolean>;
38
+ export declare const Config: z<Schemastery.ObjectS<{
39
+ connectTimeoutMs: z<number, number>;
40
+ introspectMaxTables: z<number, number>;
41
+ maxResultChars: z<number, number>;
42
+ queryTimeoutMs: z<number, number>;
43
+ maxQueryChars: z<number, number>;
44
+ readonly: z<boolean, boolean>;
77
45
  }>, Schemastery.ObjectT<{
78
- connectTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
79
- introspectMaxTables: import("@deepseek-ai/schemastery").default<number, number>;
80
- maxResultChars: import("@deepseek-ai/schemastery").default<number, number>;
81
- queryTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
82
- maxQueryChars: import("@deepseek-ai/schemastery").default<number, number>;
83
- readonly: import("@deepseek-ai/schemastery").default<boolean, boolean>;
46
+ connectTimeoutMs: z<number, number>;
47
+ introspectMaxTables: z<number, number>;
48
+ maxResultChars: z<number, number>;
49
+ queryTimeoutMs: z<number, number>;
50
+ maxQueryChars: z<number, number>;
51
+ readonly: z<boolean, boolean>;
84
52
  }>>;
85
- /** The connection request wire body (validated in the /connect handler). */
86
- export interface ConnectRequestBody {
53
+ export interface ConnectRequestBody extends DatabaseConnectionInput {
87
54
  sessionId: string;
88
- type: DatabaseType;
89
- host?: string;
90
- port?: number;
91
- user?: string;
92
- database: string;
93
- password?: string;
94
- readonly?: boolean;
95
55
  }
96
- /**
97
- * Validate an untrusted /connect body; sqlite paths resolve to absolute
98
- * (the client resolves the path relative to its own cwd, so the server pins
99
- * it at connect time). Oracle/Hive/Impala follow the mysql/postgres shape:
100
- * host/port/user/database (Oracle database = service name/SID, Hive/Impala
101
- * database = default schema).
102
- */
56
+ /** Validate the Web wire shape while retaining temporary-password compatibility. */
103
57
  export declare function validateConnectBody(value: unknown, cwd?: string): ConnectRequestBody;
104
- /**
105
- * Mount the data-agent routes against the host webserver, when one exists.
106
- * The registration rides a nested inject fiber so this row activates in every
107
- * profile; headless profiles simply never get routes.
108
- * @param ctx - host cordis context.
109
- * @param config - validated loader configuration.
110
- */
111
- export declare function apply(ctx: Context, config: Config): void;
58
+ /** Register Web routes only when both the webserver and shared service exist. */
59
+ export declare function apply(ctx: Context, _config: Config): void;
112
60
  export {};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Lightweight SQL-text scanning helpers shared by the sqlcmd tool half and
2
+ * Lightweight SQL-text scanning helpers shared by the sql-cmd tool half and
3
3
  * the /query route. This is intentionally NOT a SQL parser: the scanner only
4
4
  * understands lexical boundaries (strings, quoted identifiers, comments and
5
5
  * parenthesis depth) well enough to make the two agent-loop guarantees from
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Durable, non-secret connection profiles, session bindings, and form drafts.
3
+ *
4
+ * The domain intentionally excludes passwords, resolved credentials, SQL,
5
+ * table metadata, and client output. Form drafts likewise accept no secret
6
+ * fields. Runtime secrets stay in
7
+ * {@link DataAgentConnectionService}; durable records only retain enough
8
+ * information to rebuild a connection description in another DSH surface.
9
+ * @module @yejiming/dsh-data-agent/storage
10
+ */
11
+ import { type Domain } from '@deepseek-ai/dsh-storage-domain';
12
+ import { z } from 'zod';
13
+ import type { ConnectionPersistence, PersistedConnectionFormDraft, PersistedConnectionProfile, SessionConnectionBinding } from './connections.ts';
14
+ /** Storage-domain identity. Bump the version only with an explicit migration. */
15
+ export declare const CONNECTION_STORAGE_DOMAIN = "data_agent_connections";
16
+ export declare const CONNECTION_STORAGE_VERSION = 1;
17
+ /** Durable profile schema. There is deliberately no `password` field. */
18
+ export declare const persistedConnectionProfileSchema: z.ZodObject<{
19
+ name: z.ZodOptional<z.ZodString>;
20
+ type: z.ZodEnum<{
21
+ mysql: "mysql";
22
+ postgres: "postgres";
23
+ sqlite: "sqlite";
24
+ oracle: "oracle";
25
+ hive: "hive";
26
+ impala: "impala";
27
+ }>;
28
+ host: z.ZodOptional<z.ZodString>;
29
+ port: z.ZodOptional<z.ZodNumber>;
30
+ user: z.ZodOptional<z.ZodString>;
31
+ database: z.ZodString;
32
+ readonly: z.ZodOptional<z.ZodBoolean>;
33
+ passwordRef: z.ZodOptional<z.ZodString>;
34
+ updatedAt: z.ZodString;
35
+ }, z.core.$strict>;
36
+ /** Durable session-to-profile binding schema. */
37
+ export declare const sessionConnectionBindingSchema: z.ZodObject<{
38
+ profileId: z.ZodString;
39
+ updatedAt: z.ZodString;
40
+ }, z.core.$strict>;
41
+ /** Session form draft schema. Secret-shaped fields are rejected by strict mode. */
42
+ export declare const persistedConnectionFormDraftSchema: z.ZodObject<{
43
+ type: z.ZodEnum<{
44
+ mysql: "mysql";
45
+ postgres: "postgres";
46
+ sqlite: "sqlite";
47
+ oracle: "oracle";
48
+ hive: "hive";
49
+ impala: "impala";
50
+ }>;
51
+ host: z.ZodString;
52
+ port: z.ZodString;
53
+ user: z.ZodString;
54
+ database: z.ZodString;
55
+ readonly: z.ZodBoolean;
56
+ updatedAt: z.ZodString;
57
+ }, z.core.$strict>;
58
+ /** Single source of truth for the storage layout and durable validation. */
59
+ export declare const connectionStorageSpec: {
60
+ name: string;
61
+ version: number;
62
+ tables: {
63
+ profiles: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, PersistedConnectionProfile>;
64
+ bindings: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, SessionConnectionBinding>;
65
+ drafts: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, PersistedConnectionFormDraft>;
66
+ };
67
+ };
68
+ export type ConnectionStorageDomain = Domain<typeof connectionStorageSpec>;
69
+ /** Project a typed DSH domain handle onto the service's persistence seam. */
70
+ export declare function createDomainConnectionPersistence(domain: ConnectionStorageDomain): ConnectionPersistence;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Shared structured read-query execution extracted from the sql-query tool
3
+ * (task 2.1). Both sql-query and render-analysis run every read dataset
4
+ * through this helper, so connection resolution, single-statement assertion,
5
+ * read classification, LIMIT/maxRows enforcement, client execution, secret
6
+ * redaction, timeout/cancellation, non-zero-exit surfacing and structured
7
+ * parsing share one code path. Existing sql-query behavior and messages stay
8
+ * unchanged.
9
+ * @module @yejiming/dsh-data-agent/structured-read
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import { type ClientConfig } from './clients.ts';
13
+ import { type DatabaseConnection } from './connections.ts';
14
+ import { type QueryOptions, type QueryResult } from './query.ts';
15
+ /** Tool-run context face used by the helpers. */
16
+ export interface ToolExecLike {
17
+ agent?: {
18
+ id: string;
19
+ };
20
+ signal: AbortSignal;
21
+ }
22
+ /** Resolved runner options shared by the database tools. */
23
+ export interface ResolvedRunnerConfig {
24
+ queryTimeoutMs: number;
25
+ maxResultChars: number;
26
+ maxRows: number;
27
+ maxQueryChars: number;
28
+ /** Read-only guard: true rejects write statements. */
29
+ readonly: boolean;
30
+ clients: Readonly<Partial<Record<string, ClientConfig>>>;
31
+ }
32
+ /** Canonical structured read result (elapsed/truncation metadata included). */
33
+ export interface StructuredReadResult {
34
+ columns: string[];
35
+ rows: Record<string, string | null>[];
36
+ elapsedMs: number;
37
+ truncated: boolean;
38
+ }
39
+ /** Look up the session connection, failing with the same message for every tool. */
40
+ export declare function requireToolConnection(ctx: Context, exec: ToolExecLike, toolName: string): Promise<DatabaseConnection>;
41
+ /** Run and redact a client result/error before it reaches tool/session output. */
42
+ export declare function runRedactedClientQuery(ctx: Context, connection: DatabaseConnection, sql: string, options: QueryOptions, signal: AbortSignal): Promise<QueryResult>;
43
+ /** Query runner options with the deployment overrides applied. */
44
+ export declare function runnerOptions(resolved: Pick<ResolvedRunnerConfig, 'queryTimeoutMs' | 'maxResultChars' | 'clients'>, mode?: QueryOptions['mode']): QueryOptions;
45
+ /**
46
+ * Execute one read-only SQL through the structured client template and parse
47
+ * it into the canonical { columns, rows } shape, with maxRows enforced at both
48
+ * the SQL level (LIMIT injection) and the parse level.
49
+ */
50
+ export declare function runStructuredReadQuery(ctx: Context, connection: DatabaseConnection, sql: string, resolved: ResolvedRunnerConfig, toolName: string, signal: AbortSignal): Promise<StructuredReadResult>;
@@ -8,7 +8,7 @@
8
8
  * Tool surface:
9
9
  * - `sql-query`: read-only statements, structured `{ columns, rows, ... }`;
10
10
  * - `sql-write`: one write/management statement per call, explicit autocommit;
11
- * - `sqlcmd`: the original raw-terminal tool (kept for compatibility).
11
+ * - `sql-cmd`: the raw-terminal compatibility tool.
12
12
  *
13
13
  * Execution model (see `src/query.ts`): the SQL text travels on the client's
14
14
  * stdin, argv carries flags only, credentials go through environment entries
@@ -18,6 +18,7 @@
18
18
  * @module @yejiming/dsh-data-agent/tool
19
19
  */
20
20
  import type { Context } from '@deepseek-ai/cordis';
21
+ import z from 'schemastery';
21
22
  import { type ClientConfig } from './clients.ts';
22
23
  /** Cordis plugin name (diagnostics only). */
23
24
  export declare const name = "data-agent-tool";
@@ -25,46 +26,50 @@ export declare const name = "data-agent-tool";
25
26
  export declare const inject: string[];
26
27
  /** Tool-half configuration (loader schema with the same defaults as the host). */
27
28
  export interface Config {
28
- /** Deadline for one sqlcmd / sql-query / sql-write query, milliseconds. */
29
+ /** Deadline for one sql-cmd / sql-query / sql-write query, milliseconds. */
29
30
  queryTimeoutMs: number;
30
31
  /** In-memory cap on captured output. */
31
32
  maxResultChars: number;
32
33
  /** Enforced read-query row cap (LIMIT injection + structured truncation). */
33
34
  maxRows: number;
35
+ /** Maximum SQL text length accepted per dataset statement. */
36
+ maxQueryChars: number;
34
37
  /** Read-only guard: true rejects write statements. */
35
38
  readonly: boolean;
36
39
  /** CLI client overrides keyed by database type. */
37
40
  clients: Partial<Record<string, ClientConfig>>;
38
41
  }
39
42
  /** Loader schema with deployment defaults (no library defaults). */
40
- export declare const Config: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
41
- queryTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
42
- maxResultChars: import("@deepseek-ai/schemastery").default<number, number>;
43
- maxRows: import("@deepseek-ai/schemastery").default<number, number>;
44
- readonly: import("@deepseek-ai/schemastery").default<boolean, boolean>;
45
- clients: import("@deepseek-ai/schemastery").default<import("@deepseek-ai/cosmokit").Dict<{
43
+ export declare const Config: z<Schemastery.ObjectS<{
44
+ queryTimeoutMs: z<number, number>;
45
+ maxResultChars: z<number, number>;
46
+ maxRows: z<number, number>;
47
+ maxQueryChars: z<number, number>;
48
+ readonly: z<boolean, boolean>;
49
+ clients: z<import("cosmokit").Dict<{
46
50
  command?: string | null | undefined;
47
51
  args?: string[] | null | undefined;
48
- } & import("cosmokit").Dict, string>, import("@deepseek-ai/cosmokit").Dict<Schemastery.ObjectT<{
49
- command: import("@deepseek-ai/schemastery").default<string, string>;
50
- args: import("@deepseek-ai/schemastery").default<string[], string[]>;
52
+ } & import("@deepseek-ai/cosmokit").Dict, string>, import("cosmokit").Dict<Schemastery.ObjectT<{
53
+ command: z<string, string>;
54
+ args: z<string[], string[]>;
51
55
  }>, string>>;
52
56
  }>, Schemastery.ObjectT<{
53
- queryTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
54
- maxResultChars: import("@deepseek-ai/schemastery").default<number, number>;
55
- maxRows: import("@deepseek-ai/schemastery").default<number, number>;
56
- readonly: import("@deepseek-ai/schemastery").default<boolean, boolean>;
57
- clients: import("@deepseek-ai/schemastery").default<import("@deepseek-ai/cosmokit").Dict<{
57
+ queryTimeoutMs: z<number, number>;
58
+ maxResultChars: z<number, number>;
59
+ maxRows: z<number, number>;
60
+ maxQueryChars: z<number, number>;
61
+ readonly: z<boolean, boolean>;
62
+ clients: z<import("cosmokit").Dict<{
58
63
  command?: string | null | undefined;
59
64
  args?: string[] | null | undefined;
60
- } & import("cosmokit").Dict, string>, import("@deepseek-ai/cosmokit").Dict<Schemastery.ObjectT<{
61
- command: import("@deepseek-ai/schemastery").default<string, string>;
62
- args: import("@deepseek-ai/schemastery").default<string[], string[]>;
65
+ } & import("@deepseek-ai/cosmokit").Dict, string>, import("cosmokit").Dict<Schemastery.ObjectT<{
66
+ command: z<string, string>;
67
+ args: z<string[], string[]>;
63
68
  }>, string>>;
64
69
  }>>;
65
70
  /**
66
71
  * Mount the data-agent database tools: `sql-query` (structured read-only),
67
- * `sql-write` (explicit write semantics), and `sqlcmd` (raw compatibility).
72
+ * `sql-write` (explicit write semantics), and `sql-cmd` (raw compatibility).
68
73
  * @param ctx - the preset-scoped agent context.
69
74
  * @param config - validated loader configuration.
70
75
  */
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Short-lived ANSI connection form used by `/database connect` in dsh-tui.
3
+ *
4
+ * dsh-tui 0.6.x exposes commands but no public custom-form/sensitive-input
5
+ * slot. This adapter therefore owns a small terminal form and only activates
6
+ * for an interactive `dsh-tui` profile. It snapshots the host's `readable`
7
+ * listeners, consumes input for the lifetime of the form, then restores the
8
+ * listeners exactly. It never imports dsh-tui, React, or Ink.
9
+ * @module @yejiming/dsh-data-agent/tui-connection-form
10
+ */
11
+ import type { ConnectionFormDraft, DatabaseConnectionInput, DatabaseType } from './connections.ts';
12
+ export declare const TUI_DATABASE_TYPES: readonly ["mysql", "postgres", "sqlite", "oracle", "hive", "impala"];
13
+ export type TuiConnectionField = 'type' | 'host' | 'port' | 'user' | 'database' | 'password' | 'readonly' | 'confirm' | 'cancel';
14
+ export interface TuiConnectionFormState {
15
+ type: DatabaseType;
16
+ host: string;
17
+ port: string;
18
+ user: string;
19
+ database: string;
20
+ password: string;
21
+ readonly: boolean;
22
+ focus: TuiConnectionField;
23
+ cursor: number;
24
+ selector?: {
25
+ field: 'type' | 'readonly';
26
+ index: number;
27
+ };
28
+ error?: string;
29
+ }
30
+ export type TuiFormKey = {
31
+ name: 'text';
32
+ text: string;
33
+ } | {
34
+ name: 'tab' | 'backtab' | 'enter' | 'escape' | 'backspace' | 'delete';
35
+ } | {
36
+ name: 'left' | 'right' | 'up' | 'down' | 'home' | 'end' | 'space';
37
+ };
38
+ export type TuiFormTransition = {
39
+ kind: 'editing';
40
+ state: TuiConnectionFormState;
41
+ } | {
42
+ kind: 'submitted';
43
+ state: TuiConnectionFormState;
44
+ input: DatabaseConnectionInput;
45
+ } | {
46
+ kind: 'cancelled';
47
+ state: TuiConnectionFormState;
48
+ };
49
+ type StreamListener = (...args: unknown[]) => void;
50
+ export interface TuiFormInput {
51
+ isTTY?: boolean;
52
+ isRaw?: boolean;
53
+ read(): unknown;
54
+ listeners(event: string): Function[];
55
+ on(event: string, listener: StreamListener): unknown;
56
+ emit?(event: string): unknown;
57
+ push?(value: string): unknown;
58
+ removeListener(event: string, listener: StreamListener): unknown;
59
+ setRawMode?(mode: boolean): unknown;
60
+ ref?(): unknown;
61
+ }
62
+ export interface TuiFormOutput {
63
+ isTTY?: boolean;
64
+ columns?: number;
65
+ write(value: string): unknown;
66
+ on?(event: string, listener: StreamListener): unknown;
67
+ removeListener?(event: string, listener: StreamListener): unknown;
68
+ emit?(event: string): unknown;
69
+ }
70
+ export interface RunTuiConnectionFormOptions {
71
+ input?: TuiFormInput;
72
+ output?: TuiFormOutput;
73
+ signal?: AbortSignal;
74
+ initialDraft?: ConnectionFormDraft;
75
+ persistDraft?: (draft: ConnectionFormDraft) => void | Promise<void>;
76
+ }
77
+ /** Initial form intentionally leaves host/port empty so placeholders are real defaults. */
78
+ export declare function createTuiConnectionFormState(initialDraft?: ConnectionFormDraft): TuiConnectionFormState;
79
+ /** Project form state onto the only values allowed to cross the durable seam. */
80
+ export declare function connectionFormDraft(state: TuiConnectionFormState): ConnectionFormDraft;
81
+ /** Relevant focus order for the selected database kind. */
82
+ export declare function tuiConnectionFields(type: DatabaseType): readonly TuiConnectionField[];
83
+ /** Default network port shown as a placeholder and applied only at submit time. */
84
+ export declare function defaultDatabasePort(type: Exclude<DatabaseType, 'sqlite'>): number;
85
+ /** Detect the supported host without coupling to dsh-tui modules. */
86
+ export declare function isDshTuiTerminal(argv?: readonly string[], input?: Pick<TuiFormInput, 'isTTY'>, output?: Pick<TuiFormOutput, 'isTTY'>): boolean;
87
+ /** Pure keyboard reducer, kept separate from terminal ownership for regression tests. */
88
+ export declare function updateTuiConnectionForm(current: TuiConnectionFormState, key: TuiFormKey): TuiFormTransition;
89
+ /** Rendered value is masked before it reaches the ANSI string. */
90
+ export declare function renderTuiConnectionForm(state: TuiConnectionFormState, columns?: number): string;
91
+ /**
92
+ * Own the terminal only for the form lifetime. `undefined` means user cancel.
93
+ * The returned password has never crossed stdout, argv, env, or a DSH event.
94
+ */
95
+ export declare function runTuiConnectionForm(options?: RunTuiConnectionFormOptions): Promise<DatabaseConnectionInput | undefined>;
96
+ /** Decode the keyboard subset owned by the form; unknown terminal reports are ignored. */
97
+ export declare function decodeTuiFormInput(value: string): TuiFormKey[];
98
+ export {};