@duckcodeailabs/dql-cli 1.12.1 → 1.12.2

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.
@@ -40,7 +40,7 @@ import { observeWarehouseTargetIdentity } from './semantic-execution/connection-
40
40
  import { configuredWarehouseTargetIdentity } from './semantic-execution/connection-identity.js';
41
41
  import { executeTargetBoundSemanticQuery, semanticExecutionFailureCode, semanticExecutionFailureDetails, } from './semantic-execution/execution-gateway.js';
42
42
  import { buildSemanticTargetBinding } from './semantic-execution/target-binding.js';
43
- import { normalizeConnectionMetadataScope, syncWarehouseMetadata, warehouseMetadataStatus, } from './warehouse-metadata.js';
43
+ import { discoverWarehouseMetadataScopes, normalizeConnectionMetadataScope, syncWarehouseMetadata, warehouseMetadataStatus, } from './warehouse-metadata.js';
44
44
  import { NotebookDatasetWorkspace, } from "./notebook-datasets.js";
45
45
  import { prepareBlockInvocation } from './block-invocation.js';
46
46
  const NOTEBOOK_EXECUTE_PREVIEW_ROW_LIMIT = 500;
@@ -204,6 +204,18 @@ export function parseAgentRunRequestBody(body) {
204
204
  if (!question)
205
205
  return { error: 'question is required.' };
206
206
  const selectedObject = parseAgentRunSelectedObject(record.selectedObject);
207
+ const rawExecutionTarget = agentRunRecord(record.executionTarget)
208
+ ?? agentRunRecord(agentRunRecord(record.workspaceContext)?.executionTarget);
209
+ const executionTarget = rawExecutionTarget?.target === 'local'
210
+ ? { target: 'local' }
211
+ : rawExecutionTarget?.target === 'connection'
212
+ ? {
213
+ target: 'connection',
214
+ ...(agentRunString(rawExecutionTarget.connectionName)
215
+ ? { connectionName: agentRunString(rawExecutionTarget.connectionName) }
216
+ : {}),
217
+ }
218
+ : undefined;
207
219
  const workspaceContext = agentRunRecord(record.workspaceContext) ?? agentRunRecord(record.context);
208
220
  const signals = agentRunRecord(record.signals);
209
221
  const requestedMode = parseAgentRunRequestedMode(record.requestedMode) ?? parseAgentRunRequestedMode(record.mode);
@@ -219,6 +231,7 @@ export function parseAgentRunRequestBody(body) {
219
231
  intent: agentRunString(record.intent),
220
232
  signals: signals,
221
233
  selectedObject,
234
+ executionTarget,
222
235
  workspaceContext,
223
236
  conversationContext: agentRunRecord(record.conversationContext),
224
237
  history: parseAgentRunHistory(record.history),
@@ -887,6 +900,7 @@ export async function startLocalServer(opts) {
887
900
  }
888
901
  return requireActiveConnection(isConnectionConfig(body.connection) ? body.connection : connection);
889
902
  };
903
+ const resolveAgentRunExecutionConnection = (request) => resolveExecutionConnection(request.executionTarget ? { executionTarget: request.executionTarget } : {});
890
904
  const executionTargetDescriptor = (body) => {
891
905
  const target = body.executionTarget && typeof body.executionTarget === 'object'
892
906
  ? body.executionTarget
@@ -1312,15 +1326,18 @@ export async function startLocalServer(opts) {
1312
1326
  // Best-effort active warehouse dialect so Lane-2 semantic compiles emit
1313
1327
  // dialect-correct SQL (e.g. DATE_TRUNC / identifier quoting). Absent when no
1314
1328
  // connection is configured — the compiler then uses its default dialect.
1315
- let semanticDriver;
1316
1329
  let semanticConnection;
1317
1330
  try {
1318
- semanticConnection = requireActiveConnection();
1319
- semanticDriver = semanticConnection.driver;
1331
+ semanticConnection = await resolveAgentRunExecutionConnection(request);
1320
1332
  }
1321
- catch {
1322
- semanticDriver = undefined;
1333
+ catch (error) {
1334
+ // An explicit target is a contract and must fail closed. Without one,
1335
+ // conversational/non-data runs retain the existing no-connection path;
1336
+ // any later analytical execution will surface the normal setup action.
1337
+ if (request.executionTarget)
1338
+ throw error;
1323
1339
  }
1340
+ const semanticDriver = semanticConnection?.driver;
1324
1341
  const semanticTableMapping = semanticLayer && semanticConnection
1325
1342
  ? await resolveSemanticTableMapping(executor, semanticConnection, semanticLayer, projectRoot)
1326
1343
  : undefined;
@@ -1510,10 +1527,12 @@ export async function startLocalServer(opts) {
1510
1527
  ? { resolvedAnalyticalPlan: routeDecision.resolvedAnalyticalPlan }
1511
1528
  : {}),
1512
1529
  analyticalReferenceInstant: new Date().toISOString(),
1513
- executeCertifiedBlock: executeCertifiedBlockForAgent,
1514
- executeGeneratedSql: (sql, artifact) => executeGeneratedArtifactForAgent(request.question, sql, artifact),
1515
- executeDqlArtifact: (artifact) => executeArtifactReferenceForAgent(artifact, request.question),
1516
- getSchemaContext: getSchemaContextForAgent,
1530
+ executeCertifiedBlock: (node, invocation) => executeCertifiedBlockForAgent(node, invocation, semanticConnection),
1531
+ executeGeneratedSql: (sql, artifact) => executeGeneratedArtifactForAgent(request.question, sql, artifact, semanticConnection),
1532
+ executeDqlArtifact: (artifact) => executeArtifactReferenceForAgent(artifact, request.question, semanticConnection),
1533
+ getSchemaContext: (question, preparedContextPack) => getSchemaContextForAgent(question, preparedContextPack, semanticConnection, request.executionTarget?.target === 'connection'
1534
+ ? request.executionTarget.connectionName
1535
+ : undefined),
1517
1536
  }, (turn) => {
1518
1537
  if (turn.kind === 'thinking')
1519
1538
  onProgress?.(turn.text);
@@ -1762,7 +1781,7 @@ export async function startLocalServer(opts) {
1762
1781
  }));
1763
1782
  const exploration = await executeExploratoryCandidate(governedAnswer.exploratoryCandidate, explorationSchema, request.question, governedAnswer.contextPack?.questionPlan.requestedShape.topN?.scope === 'overall'
1764
1783
  ? governedAnswer.contextPack.questionPlan.requestedShape.topN.n
1765
- : undefined);
1784
+ : undefined, await resolveAgentRunExecutionConnection(request));
1766
1785
  const probeSummary = exploration.proofs.length > 0
1767
1786
  ? `${exploration.proofs.length} bounded join probe${exploration.proofs.length === 1 ? '' : 's'} completed.`
1768
1787
  : 'No join probe was required for this query.';
@@ -2976,7 +2995,7 @@ export async function startLocalServer(opts) {
2976
2995
  cells: snapshotCells,
2977
2996
  });
2978
2997
  };
2979
- const executeDqlArtifactSourceForAgent = async (source, metadata = {}, invocationInput) => {
2998
+ const executeDqlArtifactSourceForAgent = async (source, metadata = {}, invocationInput, executionConnection) => {
2980
2999
  const invocation = prepareBlockInvocation({
2981
3000
  source,
2982
3001
  parameters: invocationInput?.parameters,
@@ -2989,7 +3008,7 @@ export async function startLocalServer(opts) {
2989
3008
  if (invocation.unresolvedParameters.length > 0) {
2990
3009
  throw new Error(`I need values for: ${invocation.unresolvedParameters.join(', ')}.`);
2991
3010
  }
2992
- const activeConnection = requireActiveConnection();
3011
+ const activeConnection = requireActiveConnection(executionConnection);
2993
3012
  const precompiledSemanticQuery = Boolean(extractBlockStudioSql(source));
2994
3013
  const tableMapping = !precompiledSemanticQuery
2995
3014
  ? await resolveSemanticTableMapping(executor, activeConnection, semanticLayer, projectRoot)
@@ -3061,7 +3080,7 @@ export async function startLocalServer(opts) {
3061
3080
  } : {}),
3062
3081
  };
3063
3082
  };
3064
- const executeCertifiedBlockByNameForAgent = async (blockName, invocationInput, requireCertified = false) => {
3083
+ const executeCertifiedBlockByNameForAgent = async (blockName, invocationInput, requireCertified = false, executionConnection) => {
3065
3084
  const manifest = buildManifest({ projectRoot });
3066
3085
  const block = manifest.blocks[blockName];
3067
3086
  if (!block) {
@@ -3076,7 +3095,7 @@ export async function startLocalServer(opts) {
3076
3095
  path: block.filePath,
3077
3096
  domain: block.domain,
3078
3097
  chartType: block.chartType,
3079
- }, invocationInput);
3098
+ }, invocationInput, executionConnection);
3080
3099
  return {
3081
3100
  ...result,
3082
3101
  dqlArtifact: {
@@ -3095,17 +3114,17 @@ export async function startLocalServer(opts) {
3095
3114
  },
3096
3115
  };
3097
3116
  };
3098
- const executeCertifiedBlockForAgent = async (node, invocationInput) => {
3117
+ const executeCertifiedBlockForAgent = async (node, invocationInput, executionConnection) => {
3099
3118
  if (node.kind !== 'block') {
3100
3119
  throw new Error(`Certified ${node.kind} "${node.name}" is a navigation artifact and cannot be executed as a block.`);
3101
3120
  }
3102
- return executeCertifiedBlockByNameForAgent(node.name || node.nodeId.replace(/^block:/, ''), invocationInput);
3121
+ return executeCertifiedBlockByNameForAgent(node.name || node.nodeId.replace(/^block:/, ''), invocationInput, false, executionConnection);
3103
3122
  };
3104
3123
  // Secondary agent surfaces use this compatibility callback. Delegate to the
3105
3124
  // same artifact-first path as Ask so research/app/notebook execution cannot
3106
3125
  // silently reintroduce a raw-SQL result followed by a different DQL artifact.
3107
3126
  const executeGeneratedSqlForAgent = async (sql, artifact) => executeGeneratedArtifactForAgent(artifact?.name ?? 'AI-generated query', sql, artifact);
3108
- const executeArtifactReferenceForAgent = async (artifact, question) => {
3127
+ const executeArtifactReferenceForAgent = async (artifact, question, executionConnection) => {
3109
3128
  const invocation = prepareBlockInvocation({
3110
3129
  source: artifact.source,
3111
3130
  parameters: artifact.parameterValues,
@@ -3120,7 +3139,7 @@ export async function startLocalServer(opts) {
3120
3139
  question,
3121
3140
  parameters: invocation.values,
3122
3141
  rowLimit: artifact.limit ?? 200,
3123
- });
3142
+ }, executionConnection);
3124
3143
  return {
3125
3144
  ...result,
3126
3145
  dqlArtifact: {
@@ -3132,9 +3151,9 @@ export async function startLocalServer(opts) {
3132
3151
  },
3133
3152
  };
3134
3153
  };
3135
- const executeGeneratedArtifactForAgent = async (question, sql, seed) => {
3154
+ const executeGeneratedArtifactForAgent = async (question, sql, seed, executionConnection) => {
3136
3155
  const artifact = generatedSqlArtifactContract(question, sql, seed);
3137
- return executeArtifactReferenceForAgent({ ...artifact, limit: seed?.limit ?? artifact.limit ?? 200 }, question);
3156
+ return executeArtifactReferenceForAgent({ ...artifact, limit: seed?.limit ?? artifact.limit ?? 200 }, question, executionConnection);
3138
3157
  };
3139
3158
  /**
3140
3159
  * EXP-001: execution host for the deliberately narrow non-governed lane.
@@ -3144,8 +3163,8 @@ export async function startLocalServer(opts) {
3144
3163
  * existing read-only 200-row preview. Any malformed, broad, or unsupported
3145
3164
  * candidate remains review-required and is not executed automatically.
3146
3165
  */
3147
- const executeExploratoryCandidate = async (candidate, schemaContext = [], question = '', requestedTopN) => {
3148
- const activeConnection = requireActiveConnection();
3166
+ const executeExploratoryCandidate = async (candidate, schemaContext = [], question = '', requestedTopN, executionConnection) => {
3167
+ const activeConnection = requireActiveConnection(executionConnection);
3149
3168
  const preflight = repairExploratorySqlBeforeExecution(candidate.sql, schemaContext, question, activeConnection.driver);
3150
3169
  const boundedSql = applyRequestedTopNToExploratorySql(preflight.sql, requestedTopN);
3151
3170
  const repairs = boundedSql === preflight.sql
@@ -3236,26 +3255,27 @@ export async function startLocalServer(opts) {
3236
3255
  return { proofs, sql: boundedSql, repairs, error: contradiction };
3237
3256
  }
3238
3257
  }
3239
- const result = await executeGeneratedArtifactForAgent(question, boundedSql);
3258
+ const result = await executeGeneratedArtifactForAgent(question, boundedSql, undefined, activeConnection);
3240
3259
  return { result, proofs, sql: boundedSql, repairs };
3241
3260
  }
3242
3261
  catch (error) {
3243
3262
  return { proofs, sql: boundedSql, repairs, error: error instanceof Error ? error.message : String(error) };
3244
3263
  }
3245
3264
  };
3246
- const getSchemaContextForAgent = async (question, preparedContextPack) => {
3265
+ const getSchemaContextForAgent = async (question, preparedContextPack, executionConnection, executionConnectionName) => {
3266
+ const schemaConnection = executionConnection ?? connection;
3247
3267
  const scanRuntimeSchema = async () => {
3248
- if (!connection)
3268
+ if (!schemaConnection)
3249
3269
  return { ranked: [], snapshot: [] };
3250
- const result = await executor.executePositional(buildRuntimeSchemaSearchSql(question), [], connection, { maxRows: 600, maxBytes: 2 * 1024 * 1024, batchSize: 200, deadlineMs: 30_000 });
3270
+ const result = await executor.executePositional(buildRuntimeSchemaSearchSql(question), [], schemaConnection, { maxRows: 600, maxBytes: 2 * 1024 * 1024, batchSize: 200, deadlineMs: 30_000 });
3251
3271
  return {
3252
3272
  ranked: buildAgentSchemaContext(question, result.rows),
3253
3273
  snapshot: buildAgentSchemaContext(question, result.rows, { includeUnscored: true, limit: 100 }),
3254
3274
  };
3255
3275
  };
3256
3276
  const catalogContext = await buildAgentSchemaContextFromCatalog(projectRoot, question, preparedContextPack).catch(() => []);
3257
- const activeScope = connection
3258
- ? optionalActiveConnectionMetadataScope(projectRoot, projectConfig, connection, projectConfig.defaultConnectionName ?? 'default')
3277
+ const activeScope = schemaConnection
3278
+ ? optionalActiveConnectionMetadataScope(projectRoot, projectConfig, schemaConnection, executionConnectionName ?? projectConfig.defaultConnectionName ?? 'default')
3259
3279
  : null;
3260
3280
  const activeRuntimeSnapshot = latestRuntimeSchemaSnapshotForProject(projectRoot);
3261
3281
  const hasActivatedMetadataGeneration = Boolean(activeScope
@@ -3263,12 +3283,12 @@ export async function startLocalServer(opts) {
3263
3283
  && activeRuntimeSnapshot.status !== 'stale');
3264
3284
  const valueGrounding = resolveAgentRuntimeValueGrounding(projectConfig);
3265
3285
  if (catalogContext.length > 0) {
3266
- if (!connection || valueGrounding.mode !== 'safe_automatic')
3286
+ if (!schemaConnection || valueGrounding.mode !== 'safe_automatic')
3267
3287
  return catalogContext;
3268
3288
  // The immutable dbt/DQL snapshot already owns schema discovery. A named
3269
3289
  // row value must not turn a warm Ask into an information_schema scan over
3270
3290
  // thousands of enterprise tables; only field-scoped value probes are live.
3271
- const enriched = await enrichAgentSchemaContextWithValueMatches(question, catalogContext, executor, connection, valueGrounding.searchSafeColumns);
3291
+ const enriched = await enrichAgentSchemaContextWithValueMatches(question, catalogContext, executor, schemaConnection, valueGrounding.searchSafeColumns);
3272
3292
  if (!hasActivatedMetadataGeneration) {
3273
3293
  recordAgentRuntimeSchemaSnapshot(projectRoot, catalogContext, 'catalog runtime schema');
3274
3294
  }
@@ -3280,12 +3300,12 @@ export async function startLocalServer(opts) {
3280
3300
  // user may explicitly refresh the selected scope.
3281
3301
  if (hasActivatedMetadataGeneration)
3282
3302
  return [];
3283
- if (!connection)
3303
+ if (!schemaConnection)
3284
3304
  return [];
3285
3305
  try {
3286
3306
  const schemaContext = await scanRuntimeSchema();
3287
3307
  const enriched = valueGrounding.mode === 'safe_automatic'
3288
- ? await enrichAgentSchemaContextWithValueMatches(question, schemaContext.ranked, executor, connection, valueGrounding.searchSafeColumns)
3308
+ ? await enrichAgentSchemaContextWithValueMatches(question, schemaContext.ranked, executor, schemaConnection, valueGrounding.searchSafeColumns)
3289
3309
  : schemaContext.ranked;
3290
3310
  recordAgentRuntimeSchemaSnapshot(projectRoot, schemaContext.snapshot, 'question-scoped information_schema search');
3291
3311
  return enriched;
@@ -6732,6 +6752,7 @@ export async function startLocalServer(opts) {
6732
6752
  hintId,
6733
6753
  title: typeof body.title === 'string' ? body.title : undefined,
6734
6754
  guidance: typeof body.guidance === 'string' ? body.guidance : undefined,
6755
+ lesson: parseHintLessonInput(body.lesson),
6735
6756
  correctedSql: typeof body.correctedSql === 'string' ? body.correctedSql : undefined,
6736
6757
  scope: rawScope ? {
6737
6758
  metric: scopeValue('metric'),
@@ -6911,6 +6932,7 @@ export async function startLocalServer(opts) {
6911
6932
  requiredEvaluation: correctionEvaluation,
6912
6933
  hintTitle: typeof body.title === 'string' && body.title.trim() ? body.title.trim() : undefined,
6913
6934
  hintGuidance: typeof body.guidance === 'string' && body.guidance.trim() ? body.guidance.trim() : undefined,
6935
+ lesson: parseHintLessonInput(body.lesson),
6914
6936
  tags: Array.isArray(body.tags) ? body.tags.map(String) : undefined,
6915
6937
  });
6916
6938
  // `approve` is intentionally ignored: capture never promotes a hint.
@@ -9824,8 +9846,32 @@ export async function startLocalServer(opts) {
9824
9846
  return;
9825
9847
  }
9826
9848
  const metadataScopePreviewMatch = path.match(/^\/api\/connections\/([^/]+)\/metadata-scope\/preview$/);
9849
+ const metadataDiscoveryMatch = path.match(/^\/api\/connections\/([^/]+)\/metadata-scope\/discovery$/);
9827
9850
  const metadataScopeMatch = path.match(/^\/api\/connections\/([^/]+)\/metadata-scope$/);
9828
9851
  const metadataSyncMatch = path.match(/^\/api\/connections\/([^/]+)\/metadata-sync$/);
9852
+ if (metadataDiscoveryMatch && req.method === 'POST') {
9853
+ try {
9854
+ const connectionId = decodeURIComponent(metadataDiscoveryMatch[1]);
9855
+ const cfg = loadProjectConfig(projectRoot);
9856
+ const selectedConnection = projectConnectionById(projectRoot, cfg, connectionId);
9857
+ if (!selectedConnection)
9858
+ throw new Error(`Unknown connection "${connectionId}".`);
9859
+ const defaults = metadataScopeDbtDefaults(projectRoot, cfg);
9860
+ const discovery = await discoverWarehouseMetadataScopes({
9861
+ connectionId,
9862
+ executor,
9863
+ connection: selectedConnection,
9864
+ dbtRelations: defaults.relations,
9865
+ });
9866
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
9867
+ res.end(serializeJSON(discovery));
9868
+ }
9869
+ catch (error) {
9870
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
9871
+ res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
9872
+ }
9873
+ return;
9874
+ }
9829
9875
  if (metadataScopePreviewMatch && req.method === 'POST') {
9830
9876
  try {
9831
9877
  const connectionId = decodeURIComponent(metadataScopePreviewMatch[1]);
@@ -15438,6 +15484,24 @@ function internalDqlRelationIdValidationError(sql, subject) {
15438
15484
  return null;
15439
15485
  return `${subject} contains internal DQL graph relation identifier${internalIds.length === 1 ? '' : 's'} ${internalIds.join(', ')}. Use the physical database.schema.table relation instead, or choose Ask AI to fix from the notebook error.`;
15440
15486
  }
15487
+ function parseHintLessonInput(value) {
15488
+ if (!value || typeof value !== 'object' || Array.isArray(value))
15489
+ return undefined;
15490
+ const raw = value;
15491
+ const stringList = (field) => Array.isArray(raw[field])
15492
+ ? raw[field].filter((item) => typeof item === 'string')
15493
+ : undefined;
15494
+ return {
15495
+ version: 1,
15496
+ category: typeof raw.category === 'string'
15497
+ ? raw.category
15498
+ : undefined,
15499
+ rule: typeof raw.rule === 'string' ? raw.rule : undefined,
15500
+ intentExamples: stringList('intentExamples'),
15501
+ avoid: stringList('avoid'),
15502
+ expectedOutcome: typeof raw.expectedOutcome === 'string' ? raw.expectedOutcome : undefined,
15503
+ };
15504
+ }
15441
15505
  class DqlInternalRelationIdError extends Error {
15442
15506
  name = 'DqlInternalRelationIdError';
15443
15507
  }