@duckcodeailabs/dql-cli 1.12.1 → 1.12.3

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 (73) hide show
  1. package/dist/assets/dql-notebook/assets/AgentAnswerCard-BEIwKGm4.js +5 -0
  2. package/dist/assets/dql-notebook/assets/AgentLogPage-CsSC2tOR.js +1 -0
  3. package/dist/assets/dql-notebook/assets/AiBuildDialog-CjJ-9bQW.js +1 -0
  4. package/dist/assets/dql-notebook/assets/AiBuildResult-BvTcW9oE.js +33 -0
  5. package/dist/assets/dql-notebook/assets/AiSidePanel-CxFrOfCE.js +6 -0
  6. package/dist/assets/dql-notebook/assets/AnalyticsHome-BtXEdJNR.js +6 -0
  7. package/dist/assets/dql-notebook/assets/AppsView-HjQkbbE6.js +4471 -0
  8. package/dist/assets/dql-notebook/assets/BlockStudio-oQHTJsXC.js +60 -0
  9. package/dist/assets/dql-notebook/assets/BusinessArtifactView-Dpsftjs7.js +1 -0
  10. package/dist/assets/dql-notebook/assets/DbtFirstModelingPage-c6ZteaI6.js +22 -0
  11. package/dist/assets/dql-notebook/assets/GitPage-Cj6mc-Yv.js +7 -0
  12. package/dist/assets/dql-notebook/assets/GlobalAiRail-8G_rEyhK.js +1 -0
  13. package/dist/assets/dql-notebook/assets/GovernedContextPage-BTtzt0Za.js +7 -0
  14. package/dist/assets/dql-notebook/assets/HelpDocsPage-e2CesE73.js +22 -0
  15. package/dist/assets/dql-notebook/assets/HomePage-U3ryMLEF.js +117 -0
  16. package/dist/assets/dql-notebook/assets/LineageDAG-CEmgJzm3.js +1 -0
  17. package/dist/assets/dql-notebook/assets/LineageDetailView-CeV5QMXS.js +1 -0
  18. package/dist/assets/dql-notebook/assets/LineageDrawer-B1oAn-N_.js +1 -0
  19. package/dist/assets/dql-notebook/assets/LineagePathBreadcrumb-fP6RTXut.js +14 -0
  20. package/dist/assets/dql-notebook/assets/MiniLineageGraph-CBYSTmRC.js +1 -0
  21. package/dist/assets/dql-notebook/assets/NewBlockModal-ksEwimjl.js +1 -0
  22. package/dist/assets/dql-notebook/assets/NewNotebookModal-CUx0WmXb.js +46 -0
  23. package/dist/assets/dql-notebook/assets/NotebookEditor-BdxJL3LX.js +202 -0
  24. package/dist/assets/dql-notebook/assets/ReadinessPage-DZ9wDW76.js +1 -0
  25. package/dist/assets/dql-notebook/assets/SetupOnboarding-Bt0AAe0h.js +6 -0
  26. package/dist/assets/dql-notebook/assets/TrustBadge-DNVJK_mP.js +1 -0
  27. package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-BNqE169i.js +83 -0
  28. package/dist/assets/dql-notebook/assets/ai-build-bus-1BR770pY.js +1 -0
  29. package/dist/assets/dql-notebook/assets/answer-to-notebook-B_gE6rsY.js +2 -0
  30. package/dist/assets/dql-notebook/assets/arrow-left-DnKGhzOR.js +6 -0
  31. package/dist/assets/dql-notebook/assets/arrow-right-CQ73DG7_.js +6 -0
  32. package/dist/assets/dql-notebook/assets/book-open-text-CyBV5Nhw.js +6 -0
  33. package/dist/assets/dql-notebook/assets/bot-UOPtBzCj.js +6 -0
  34. package/dist/assets/dql-notebook/assets/chevron-up-CDHDU3N3.js +6 -0
  35. package/dist/assets/dql-notebook/assets/circle-x-DC5nkL_u.js +6 -0
  36. package/dist/assets/dql-notebook/assets/{codemirror-Co0dCEDr.js → codemirror-1_I9gm6Z.js} +1 -1
  37. package/dist/assets/dql-notebook/assets/dagre-BZV40eAE.css +1 -0
  38. package/dist/assets/dql-notebook/assets/dagre.esm-C7ujXipB.js +23 -0
  39. package/dist/assets/dql-notebook/assets/external-link-BiJ_dtTM.js +6 -0
  40. package/dist/assets/dql-notebook/assets/grip-vertical-eXvkUerq.js +6 -0
  41. package/dist/assets/dql-notebook/assets/index-BMVfPaXr.js +644 -0
  42. package/dist/assets/dql-notebook/assets/{index-DvLT6Kls.css → index-DKu4CZt3.css} +1 -1
  43. package/dist/assets/dql-notebook/assets/link-2-DkHVJTuH.js +6 -0
  44. package/dist/assets/dql-notebook/assets/list-tree-8lYW2Itb.js +6 -0
  45. package/dist/assets/dql-notebook/assets/maximize-2-DjSovOdP.js +6 -0
  46. package/dist/assets/dql-notebook/assets/minimize-2-DGlmQLES.js +6 -0
  47. package/dist/assets/dql-notebook/assets/panel-right-open-DPc4gShM.js +6 -0
  48. package/dist/assets/dql-notebook/assets/pencil-nwaGtOdL.js +6 -0
  49. package/dist/assets/dql-notebook/assets/{react-BRY8hXab.js → react-CRB3T2We.js} +2 -2
  50. package/dist/assets/dql-notebook/assets/rotate-ccw-ByjLhAu7.js +6 -0
  51. package/dist/assets/dql-notebook/assets/semantic-fields-eEhwnQhG.js +3 -0
  52. package/dist/assets/dql-notebook/assets/triangle-alert-zws0cKhp.js +6 -0
  53. package/dist/assets/dql-notebook/assets/usePersistedAgentThreadId-hxi1RZDm.js +18 -0
  54. package/dist/assets/dql-notebook/assets/user-round-CM8xNakq.js +11 -0
  55. package/dist/assets/dql-notebook/assets/wand-sparkles-CIKc8r-4.js +6 -0
  56. package/dist/assets/dql-notebook/assets/workflow-6C0ZLbTi.js +6 -0
  57. package/dist/assets/dql-notebook/assets/wrench-CeU7hF12.js +6 -0
  58. package/dist/assets/dql-notebook/assets/x-DLWEzvri.js +6 -0
  59. package/dist/assets/dql-notebook/index.html +3 -4
  60. package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
  61. package/dist/llm/providers/dql-agent-provider.js +3 -1
  62. package/dist/llm/providers/dql-agent-provider.js.map +1 -1
  63. package/dist/local-runtime.d.ts +10 -2
  64. package/dist/local-runtime.d.ts.map +1 -1
  65. package/dist/local-runtime.js +215 -60
  66. package/dist/local-runtime.js.map +1 -1
  67. package/dist/package.json +10 -10
  68. package/dist/warehouse-metadata.d.ts +34 -1
  69. package/dist/warehouse-metadata.d.ts.map +1 -1
  70. package/dist/warehouse-metadata.js +136 -2
  71. package/dist/warehouse-metadata.js.map +1 -1
  72. package/package.json +10 -10
  73. package/dist/assets/dql-notebook/assets/index-CNQTSBBg.js +0 -5943
@@ -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
@@ -954,7 +968,10 @@ export async function startLocalServer(opts) {
954
968
  return result;
955
969
  };
956
970
  await reloadSemanticLayer();
957
- await refreshLocalMetadataCatalog(projectRoot);
971
+ // Normal startup reuses the fingerprinted local catalog when its inputs are
972
+ // unchanged. Explicit refresh, save, and certification paths still request
973
+ // the default forced rebuild below.
974
+ await refreshLocalMetadataCatalog(projectRoot, undefined, undefined, false);
958
975
  const startupDbtManifestPath = resolveDbtManifestPath(projectRoot, projectConfig);
959
976
  if (startupDbtManifestPath && !isAgentProjectIndexReady(projectRoot)) {
960
977
  try {
@@ -1312,17 +1329,26 @@ export async function startLocalServer(opts) {
1312
1329
  // Best-effort active warehouse dialect so Lane-2 semantic compiles emit
1313
1330
  // dialect-correct SQL (e.g. DATE_TRUNC / identifier quoting). Absent when no
1314
1331
  // connection is configured — the compiler then uses its default dialect.
1315
- let semanticDriver;
1316
1332
  let semanticConnection;
1317
1333
  try {
1318
- semanticConnection = requireActiveConnection();
1319
- semanticDriver = semanticConnection.driver;
1334
+ semanticConnection = await resolveAgentRunExecutionConnection(request);
1320
1335
  }
1321
- catch {
1322
- semanticDriver = undefined;
1336
+ catch (error) {
1337
+ // An explicit target is a contract and must fail closed. Without one,
1338
+ // conversational/non-data runs retain the existing no-connection path;
1339
+ // any later analytical execution will surface the normal setup action.
1340
+ if (request.executionTarget)
1341
+ throw error;
1323
1342
  }
1343
+ const semanticDriver = semanticConnection?.driver;
1344
+ const defaultExecutionTarget = executionTargetDescriptor({});
1345
+ const semanticConnectionName = request.executionTarget?.target === 'connection'
1346
+ ? request.executionTarget.connectionName
1347
+ : defaultExecutionTarget.target === 'connection'
1348
+ ? defaultExecutionTarget.connectionName
1349
+ : undefined;
1324
1350
  const semanticTableMapping = semanticLayer && semanticConnection
1325
- ? await resolveSemanticTableMapping(executor, semanticConnection, semanticLayer, projectRoot)
1351
+ ? await resolveSemanticTableMapping(executor, semanticConnection, semanticLayer, projectRoot, semanticConnectionName)
1326
1352
  : undefined;
1327
1353
  const requestedDomain = normalizeAgentRunDomain(agentRunWorkspaceValue(request, 'domain'));
1328
1354
  const requestedPurpose = agentRunWorkspaceValue(request, 'purpose');
@@ -1510,10 +1536,12 @@ export async function startLocalServer(opts) {
1510
1536
  ? { resolvedAnalyticalPlan: routeDecision.resolvedAnalyticalPlan }
1511
1537
  : {}),
1512
1538
  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,
1539
+ executeCertifiedBlock: (node, invocation) => executeCertifiedBlockForAgent(node, invocation, semanticConnection, semanticConnectionName),
1540
+ executeGeneratedSql: (sql, artifact) => executeGeneratedArtifactForAgent(request.question, sql, artifact, semanticConnection, semanticConnectionName),
1541
+ executeDqlArtifact: (artifact) => executeArtifactReferenceForAgent(artifact, request.question, semanticConnection, semanticConnectionName),
1542
+ getSchemaContext: (question, preparedContextPack) => getSchemaContextForAgent(question, preparedContextPack, semanticConnection, request.executionTarget?.target === 'connection'
1543
+ ? request.executionTarget.connectionName
1544
+ : undefined),
1517
1545
  }, (turn) => {
1518
1546
  if (turn.kind === 'thinking')
1519
1547
  onProgress?.(turn.text);
@@ -1762,7 +1790,7 @@ export async function startLocalServer(opts) {
1762
1790
  }));
1763
1791
  const exploration = await executeExploratoryCandidate(governedAnswer.exploratoryCandidate, explorationSchema, request.question, governedAnswer.contextPack?.questionPlan.requestedShape.topN?.scope === 'overall'
1764
1792
  ? governedAnswer.contextPack.questionPlan.requestedShape.topN.n
1765
- : undefined);
1793
+ : undefined, await resolveAgentRunExecutionConnection(request));
1766
1794
  const probeSummary = exploration.proofs.length > 0
1767
1795
  ? `${exploration.proofs.length} bounded join probe${exploration.proofs.length === 1 ? '' : 's'} completed.`
1768
1796
  : 'No join probe was required for this query.';
@@ -2694,6 +2722,18 @@ export async function startLocalServer(opts) {
2694
2722
  const preparedMetadataFingerprint = isAgentProjectIndexReady(projectRoot)
2695
2723
  ? currentMetadataFingerprint(projectRoot)
2696
2724
  : undefined;
2725
+ const targetDescriptor = executionTargetDescriptor(request);
2726
+ const targetConnectionName = targetDescriptor.target === 'connection'
2727
+ ? targetDescriptor.connectionName
2728
+ : undefined;
2729
+ let runtimeSchemaSnapshot;
2730
+ if (targetConnectionName) {
2731
+ const targetConnection = resolveNamedConnection(targetConnectionName);
2732
+ const activeScope = targetConnection
2733
+ ? optionalActiveConnectionMetadataScope(projectRoot, projectConfig, targetConnection, targetConnectionName)
2734
+ : null;
2735
+ runtimeSchemaSnapshot = runtimeSchemaSnapshotForAgentConnection(projectRoot, targetConnectionName, activeScope?.scopeFingerprint);
2736
+ }
2697
2737
  const task = buildLocalContextPack(projectRoot, {
2698
2738
  question: request.question,
2699
2739
  focusObjectKey: request.selectedEvidenceId,
@@ -2705,7 +2745,11 @@ export async function startLocalServer(opts) {
2705
2745
  || topicRelation === 'shift'
2706
2746
  ? topicRelation
2707
2747
  : undefined,
2748
+ // Reuse remains available for fast follow-ups; the context-pack freshness
2749
+ // contract now binds it to this runtime snapshot's connection/generation.
2750
+ reusePolicy: 'seed',
2708
2751
  preparedMetadataFingerprint,
2752
+ ...(runtimeSchemaSnapshot ? { runtimeSchemaSnapshot } : {}),
2709
2753
  surface: 'notebook',
2710
2754
  selectedContext: {
2711
2755
  selectedObject: request.selectedObject,
@@ -2976,7 +3020,7 @@ export async function startLocalServer(opts) {
2976
3020
  cells: snapshotCells,
2977
3021
  });
2978
3022
  };
2979
- const executeDqlArtifactSourceForAgent = async (source, metadata = {}, invocationInput) => {
3023
+ const executeDqlArtifactSourceForAgent = async (source, metadata = {}, invocationInput, executionConnection, executionConnectionName) => {
2980
3024
  const invocation = prepareBlockInvocation({
2981
3025
  source,
2982
3026
  parameters: invocationInput?.parameters,
@@ -2989,10 +3033,10 @@ export async function startLocalServer(opts) {
2989
3033
  if (invocation.unresolvedParameters.length > 0) {
2990
3034
  throw new Error(`I need values for: ${invocation.unresolvedParameters.join(', ')}.`);
2991
3035
  }
2992
- const activeConnection = requireActiveConnection();
3036
+ const activeConnection = requireActiveConnection(executionConnection);
2993
3037
  const precompiledSemanticQuery = Boolean(extractBlockStudioSql(source));
2994
3038
  const tableMapping = !precompiledSemanticQuery
2995
- ? await resolveSemanticTableMapping(executor, activeConnection, semanticLayer, projectRoot)
3039
+ ? await resolveSemanticTableMapping(executor, activeConnection, semanticLayer, projectRoot, executionConnectionName)
2996
3040
  : undefined;
2997
3041
  const semanticCompose = semanticLayer
2998
3042
  ? await composeSemanticBlockSqlForRuntime(source, semanticLayer, {
@@ -3061,7 +3105,7 @@ export async function startLocalServer(opts) {
3061
3105
  } : {}),
3062
3106
  };
3063
3107
  };
3064
- const executeCertifiedBlockByNameForAgent = async (blockName, invocationInput, requireCertified = false) => {
3108
+ const executeCertifiedBlockByNameForAgent = async (blockName, invocationInput, requireCertified = false, executionConnection, executionConnectionName) => {
3065
3109
  const manifest = buildManifest({ projectRoot });
3066
3110
  const block = manifest.blocks[blockName];
3067
3111
  if (!block) {
@@ -3076,7 +3120,7 @@ export async function startLocalServer(opts) {
3076
3120
  path: block.filePath,
3077
3121
  domain: block.domain,
3078
3122
  chartType: block.chartType,
3079
- }, invocationInput);
3123
+ }, invocationInput, executionConnection, executionConnectionName);
3080
3124
  return {
3081
3125
  ...result,
3082
3126
  dqlArtifact: {
@@ -3095,17 +3139,17 @@ export async function startLocalServer(opts) {
3095
3139
  },
3096
3140
  };
3097
3141
  };
3098
- const executeCertifiedBlockForAgent = async (node, invocationInput) => {
3142
+ const executeCertifiedBlockForAgent = async (node, invocationInput, executionConnection, executionConnectionName) => {
3099
3143
  if (node.kind !== 'block') {
3100
3144
  throw new Error(`Certified ${node.kind} "${node.name}" is a navigation artifact and cannot be executed as a block.`);
3101
3145
  }
3102
- return executeCertifiedBlockByNameForAgent(node.name || node.nodeId.replace(/^block:/, ''), invocationInput);
3146
+ return executeCertifiedBlockByNameForAgent(node.name || node.nodeId.replace(/^block:/, ''), invocationInput, false, executionConnection, executionConnectionName);
3103
3147
  };
3104
3148
  // Secondary agent surfaces use this compatibility callback. Delegate to the
3105
3149
  // same artifact-first path as Ask so research/app/notebook execution cannot
3106
3150
  // silently reintroduce a raw-SQL result followed by a different DQL artifact.
3107
3151
  const executeGeneratedSqlForAgent = async (sql, artifact) => executeGeneratedArtifactForAgent(artifact?.name ?? 'AI-generated query', sql, artifact);
3108
- const executeArtifactReferenceForAgent = async (artifact, question) => {
3152
+ const executeArtifactReferenceForAgent = async (artifact, question, executionConnection, executionConnectionName) => {
3109
3153
  const invocation = prepareBlockInvocation({
3110
3154
  source: artifact.source,
3111
3155
  parameters: artifact.parameterValues,
@@ -3120,7 +3164,7 @@ export async function startLocalServer(opts) {
3120
3164
  question,
3121
3165
  parameters: invocation.values,
3122
3166
  rowLimit: artifact.limit ?? 200,
3123
- });
3167
+ }, executionConnection, executionConnectionName);
3124
3168
  return {
3125
3169
  ...result,
3126
3170
  dqlArtifact: {
@@ -3132,9 +3176,9 @@ export async function startLocalServer(opts) {
3132
3176
  },
3133
3177
  };
3134
3178
  };
3135
- const executeGeneratedArtifactForAgent = async (question, sql, seed) => {
3179
+ const executeGeneratedArtifactForAgent = async (question, sql, seed, executionConnection, executionConnectionName) => {
3136
3180
  const artifact = generatedSqlArtifactContract(question, sql, seed);
3137
- return executeArtifactReferenceForAgent({ ...artifact, limit: seed?.limit ?? artifact.limit ?? 200 }, question);
3181
+ return executeArtifactReferenceForAgent({ ...artifact, limit: seed?.limit ?? artifact.limit ?? 200 }, question, executionConnection, executionConnectionName);
3138
3182
  };
3139
3183
  /**
3140
3184
  * EXP-001: execution host for the deliberately narrow non-governed lane.
@@ -3144,8 +3188,8 @@ export async function startLocalServer(opts) {
3144
3188
  * existing read-only 200-row preview. Any malformed, broad, or unsupported
3145
3189
  * candidate remains review-required and is not executed automatically.
3146
3190
  */
3147
- const executeExploratoryCandidate = async (candidate, schemaContext = [], question = '', requestedTopN) => {
3148
- const activeConnection = requireActiveConnection();
3191
+ const executeExploratoryCandidate = async (candidate, schemaContext = [], question = '', requestedTopN, executionConnection) => {
3192
+ const activeConnection = requireActiveConnection(executionConnection);
3149
3193
  const preflight = repairExploratorySqlBeforeExecution(candidate.sql, schemaContext, question, activeConnection.driver);
3150
3194
  const boundedSql = applyRequestedTopNToExploratorySql(preflight.sql, requestedTopN);
3151
3195
  const repairs = boundedSql === preflight.sql
@@ -3236,42 +3280,40 @@ export async function startLocalServer(opts) {
3236
3280
  return { proofs, sql: boundedSql, repairs, error: contradiction };
3237
3281
  }
3238
3282
  }
3239
- const result = await executeGeneratedArtifactForAgent(question, boundedSql);
3283
+ const result = await executeGeneratedArtifactForAgent(question, boundedSql, undefined, activeConnection);
3240
3284
  return { result, proofs, sql: boundedSql, repairs };
3241
3285
  }
3242
3286
  catch (error) {
3243
3287
  return { proofs, sql: boundedSql, repairs, error: error instanceof Error ? error.message : String(error) };
3244
3288
  }
3245
3289
  };
3246
- const getSchemaContextForAgent = async (question, preparedContextPack) => {
3290
+ const getSchemaContextForAgent = async (question, preparedContextPack, executionConnection, executionConnectionName) => {
3291
+ const schemaConnection = executionConnection ?? connection;
3247
3292
  const scanRuntimeSchema = async () => {
3248
- if (!connection)
3293
+ if (!schemaConnection)
3249
3294
  return { ranked: [], snapshot: [] };
3250
- const result = await executor.executePositional(buildRuntimeSchemaSearchSql(question), [], connection, { maxRows: 600, maxBytes: 2 * 1024 * 1024, batchSize: 200, deadlineMs: 30_000 });
3295
+ const result = await executor.executePositional(buildRuntimeSchemaSearchSql(question), [], schemaConnection, { maxRows: 600, maxBytes: 2 * 1024 * 1024, batchSize: 200, deadlineMs: 30_000 });
3251
3296
  return {
3252
3297
  ranked: buildAgentSchemaContext(question, result.rows),
3253
3298
  snapshot: buildAgentSchemaContext(question, result.rows, { includeUnscored: true, limit: 100 }),
3254
3299
  };
3255
3300
  };
3256
3301
  const catalogContext = await buildAgentSchemaContextFromCatalog(projectRoot, question, preparedContextPack).catch(() => []);
3257
- const activeScope = connection
3258
- ? optionalActiveConnectionMetadataScope(projectRoot, projectConfig, connection, projectConfig.defaultConnectionName ?? 'default')
3302
+ const activeScope = schemaConnection
3303
+ ? optionalActiveConnectionMetadataScope(projectRoot, projectConfig, schemaConnection, executionConnectionName ?? projectConfig.defaultConnectionName ?? 'default')
3259
3304
  : null;
3260
- const activeRuntimeSnapshot = latestRuntimeSchemaSnapshotForProject(projectRoot);
3305
+ const activeRuntimeSnapshot = latestRuntimeSchemaSnapshotForProject(projectRoot, executionConnectionName);
3261
3306
  const hasActivatedMetadataGeneration = Boolean(activeScope
3262
3307
  && activeRuntimeSnapshot?.scopeFingerprint === activeScope.scopeFingerprint
3263
3308
  && activeRuntimeSnapshot.status !== 'stale');
3264
3309
  const valueGrounding = resolveAgentRuntimeValueGrounding(projectConfig);
3265
3310
  if (catalogContext.length > 0) {
3266
- if (!connection || valueGrounding.mode !== 'safe_automatic')
3311
+ if (!schemaConnection || valueGrounding.mode !== 'safe_automatic')
3267
3312
  return catalogContext;
3268
3313
  // The immutable dbt/DQL snapshot already owns schema discovery. A named
3269
3314
  // row value must not turn a warm Ask into an information_schema scan over
3270
3315
  // thousands of enterprise tables; only field-scoped value probes are live.
3271
- const enriched = await enrichAgentSchemaContextWithValueMatches(question, catalogContext, executor, connection, valueGrounding.searchSafeColumns);
3272
- if (!hasActivatedMetadataGeneration) {
3273
- recordAgentRuntimeSchemaSnapshot(projectRoot, catalogContext, 'catalog runtime schema');
3274
- }
3316
+ const enriched = await enrichAgentSchemaContextWithValueMatches(question, catalogContext, executor, schemaConnection, valueGrounding.searchSafeColumns);
3275
3317
  return enriched;
3276
3318
  }
3277
3319
  // An activated, scope-qualified warehouse generation is the complete warm
@@ -3280,14 +3322,14 @@ export async function startLocalServer(opts) {
3280
3322
  // user may explicitly refresh the selected scope.
3281
3323
  if (hasActivatedMetadataGeneration)
3282
3324
  return [];
3283
- if (!connection)
3325
+ if (!schemaConnection)
3284
3326
  return [];
3285
3327
  try {
3286
3328
  const schemaContext = await scanRuntimeSchema();
3287
3329
  const enriched = valueGrounding.mode === 'safe_automatic'
3288
- ? await enrichAgentSchemaContextWithValueMatches(question, schemaContext.ranked, executor, connection, valueGrounding.searchSafeColumns)
3330
+ ? await enrichAgentSchemaContextWithValueMatches(question, schemaContext.ranked, executor, schemaConnection, valueGrounding.searchSafeColumns)
3289
3331
  : schemaContext.ranked;
3290
- recordAgentRuntimeSchemaSnapshot(projectRoot, schemaContext.snapshot, 'question-scoped information_schema search');
3332
+ recordAgentRuntimeSchemaSnapshot(projectRoot, schemaContext.snapshot, 'question-scoped information_schema search', executionConnectionName);
3291
3333
  return enriched;
3292
3334
  }
3293
3335
  catch {
@@ -6499,6 +6541,12 @@ export async function startLocalServer(opts) {
6499
6541
  res.end(serializeJSON({ ok: true }));
6500
6542
  return;
6501
6543
  }
6544
+ if (req.method === 'DELETE' && !action) {
6545
+ store.deleteThread(threadId);
6546
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
6547
+ res.end(serializeJSON({ ok: true }));
6548
+ return;
6549
+ }
6502
6550
  // Explicit promotion — the ONLY path from conversation into durable
6503
6551
  // memory ("remember this"). Deliberate user act, tagged for audit.
6504
6552
  if (req.method === 'POST' && action === 'promote') {
@@ -6732,6 +6780,7 @@ export async function startLocalServer(opts) {
6732
6780
  hintId,
6733
6781
  title: typeof body.title === 'string' ? body.title : undefined,
6734
6782
  guidance: typeof body.guidance === 'string' ? body.guidance : undefined,
6783
+ lesson: parseHintLessonInput(body.lesson),
6735
6784
  correctedSql: typeof body.correctedSql === 'string' ? body.correctedSql : undefined,
6736
6785
  scope: rawScope ? {
6737
6786
  metric: scopeValue('metric'),
@@ -6911,6 +6960,7 @@ export async function startLocalServer(opts) {
6911
6960
  requiredEvaluation: correctionEvaluation,
6912
6961
  hintTitle: typeof body.title === 'string' && body.title.trim() ? body.title.trim() : undefined,
6913
6962
  hintGuidance: typeof body.guidance === 'string' && body.guidance.trim() ? body.guidance.trim() : undefined,
6963
+ lesson: parseHintLessonInput(body.lesson),
6914
6964
  tags: Array.isArray(body.tags) ? body.tags.map(String) : undefined,
6915
6965
  });
6916
6966
  // `approve` is intentionally ignored: capture never promotes a hint.
@@ -9824,8 +9874,32 @@ export async function startLocalServer(opts) {
9824
9874
  return;
9825
9875
  }
9826
9876
  const metadataScopePreviewMatch = path.match(/^\/api\/connections\/([^/]+)\/metadata-scope\/preview$/);
9877
+ const metadataDiscoveryMatch = path.match(/^\/api\/connections\/([^/]+)\/metadata-scope\/discovery$/);
9827
9878
  const metadataScopeMatch = path.match(/^\/api\/connections\/([^/]+)\/metadata-scope$/);
9828
9879
  const metadataSyncMatch = path.match(/^\/api\/connections\/([^/]+)\/metadata-sync$/);
9880
+ if (metadataDiscoveryMatch && req.method === 'POST') {
9881
+ try {
9882
+ const connectionId = decodeURIComponent(metadataDiscoveryMatch[1]);
9883
+ const cfg = loadProjectConfig(projectRoot);
9884
+ const selectedConnection = projectConnectionById(projectRoot, cfg, connectionId);
9885
+ if (!selectedConnection)
9886
+ throw new Error(`Unknown connection "${connectionId}".`);
9887
+ const defaults = metadataScopeDbtDefaults(projectRoot, cfg);
9888
+ const discovery = await discoverWarehouseMetadataScopes({
9889
+ connectionId,
9890
+ executor,
9891
+ connection: selectedConnection,
9892
+ dbtRelations: defaults.relations,
9893
+ });
9894
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
9895
+ res.end(serializeJSON(discovery));
9896
+ }
9897
+ catch (error) {
9898
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
9899
+ res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
9900
+ }
9901
+ return;
9902
+ }
9829
9903
  if (metadataScopePreviewMatch && req.method === 'POST') {
9830
9904
  try {
9831
9905
  const connectionId = decodeURIComponent(metadataScopePreviewMatch[1]);
@@ -9838,7 +9912,7 @@ export async function startLocalServer(opts) {
9838
9912
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
9839
9913
  res.end(serializeJSON({
9840
9914
  scope,
9841
- status: warehouseMetadataStatus(projectRoot, scope.scopeFingerprint),
9915
+ status: warehouseMetadataStatus(projectRoot, scope.scopeFingerprint, connectionId),
9842
9916
  }));
9843
9917
  }
9844
9918
  catch (error) {
@@ -9858,7 +9932,7 @@ export async function startLocalServer(opts) {
9858
9932
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
9859
9933
  res.end(serializeJSON({
9860
9934
  scope,
9861
- status: warehouseMetadataStatus(projectRoot, scope.scopeFingerprint),
9935
+ status: warehouseMetadataStatus(projectRoot, scope.scopeFingerprint, connectionId),
9862
9936
  }));
9863
9937
  }
9864
9938
  catch (error) {
@@ -9887,7 +9961,7 @@ export async function startLocalServer(opts) {
9887
9961
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
9888
9962
  res.end(serializeJSON({
9889
9963
  scope: synced.scope,
9890
- status: warehouseMetadataStatus(projectRoot, synced.scope.scopeFingerprint),
9964
+ status: warehouseMetadataStatus(projectRoot, synced.scope.scopeFingerprint, connectionId),
9891
9965
  }));
9892
9966
  }
9893
9967
  catch (error) {
@@ -9913,7 +9987,7 @@ export async function startLocalServer(opts) {
9913
9987
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
9914
9988
  res.end(serializeJSON({
9915
9989
  scope: synced.scope,
9916
- status: warehouseMetadataStatus(projectRoot, synced.scope.scopeFingerprint),
9990
+ status: warehouseMetadataStatus(projectRoot, synced.scope.scopeFingerprint, connectionId),
9917
9991
  }));
9918
9992
  }
9919
9993
  catch (error) {
@@ -9950,7 +10024,7 @@ export async function startLocalServer(opts) {
9950
10024
  connectorStatus,
9951
10025
  metadataScope,
9952
10026
  metadataStatus: metadataScope
9953
- ? warehouseMetadataStatus(projectRoot, metadataScope.scopeFingerprint)
10027
+ ? warehouseMetadataStatus(projectRoot, metadataScope.scopeFingerprint, defaultKey)
9954
10028
  : warehouseMetadataStatus(projectRoot),
9955
10029
  }));
9956
10030
  return;
@@ -12478,7 +12552,11 @@ table: ${table}${tagList}
12478
12552
  return;
12479
12553
  }
12480
12554
  const content = readFileSync(filePath);
12481
- res.writeHead(200, { 'Content-Type': contentTypeFor(filePath) });
12555
+ res.writeHead(200, {
12556
+ 'Content-Type': contentTypeFor(filePath),
12557
+ 'Cache-Control': staticResponseCacheControl(filePath),
12558
+ 'Content-Length': content.byteLength,
12559
+ });
12482
12560
  res.end(content);
12483
12561
  })().catch((error) => {
12484
12562
  // API-007/E2E-014: no rejected request promise may terminate the local
@@ -13434,10 +13512,10 @@ export function compileBlockStudioManifest(projectRoot, projectConfig = loadProj
13434
13512
  _lineageCache.delete(projectRoot);
13435
13513
  return manifest;
13436
13514
  }
13437
- async function refreshLocalMetadataCatalog(projectRoot, manifest, semanticLayer) {
13515
+ async function refreshLocalMetadataCatalog(projectRoot, manifest, semanticLayer, force = true) {
13438
13516
  try {
13439
13517
  await ensureMetadataCatalogFresh(projectRoot, {
13440
- force: true,
13518
+ force,
13441
13519
  ...(manifest ? { manifest } : {}),
13442
13520
  ...(semanticLayer ? { semanticLayer } : {}),
13443
13521
  });
@@ -15438,6 +15516,24 @@ function internalDqlRelationIdValidationError(sql, subject) {
15438
15516
  return null;
15439
15517
  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
15518
  }
15519
+ function parseHintLessonInput(value) {
15520
+ if (!value || typeof value !== 'object' || Array.isArray(value))
15521
+ return undefined;
15522
+ const raw = value;
15523
+ const stringList = (field) => Array.isArray(raw[field])
15524
+ ? raw[field].filter((item) => typeof item === 'string')
15525
+ : undefined;
15526
+ return {
15527
+ version: 1,
15528
+ category: typeof raw.category === 'string'
15529
+ ? raw.category
15530
+ : undefined,
15531
+ rule: typeof raw.rule === 'string' ? raw.rule : undefined,
15532
+ intentExamples: stringList('intentExamples'),
15533
+ avoid: stringList('avoid'),
15534
+ expectedOutcome: typeof raw.expectedOutcome === 'string' ? raw.expectedOutcome : undefined,
15535
+ };
15536
+ }
15441
15537
  class DqlInternalRelationIdError extends Error {
15442
15538
  name = 'DqlInternalRelationIdError';
15443
15539
  }
@@ -15627,6 +15723,16 @@ function contentTypeFor(filePath) {
15627
15723
  return 'text/plain; charset=utf-8';
15628
15724
  }
15629
15725
  }
15726
+ export function staticResponseCacheControl(filePath) {
15727
+ // Vite fingerprints every generated asset filename, so a new DQL build gets
15728
+ // a new URL and the previous file is safe to retain indefinitely. Keep the
15729
+ // HTML shell revalidated so it can point at the current asset hashes.
15730
+ if (extname(filePath).toLowerCase() === '.html')
15731
+ return 'no-cache';
15732
+ return filePath.split(sep).includes('assets')
15733
+ ? 'public, max-age=31536000, immutable'
15734
+ : 'no-cache';
15735
+ }
15630
15736
  function listProjectFiles(projectRoot) {
15631
15737
  const allowed = new Set(['.dql', '.sql', '.md', '.json', '.csv', '.yaml', '.yml', '.dqlnb']);
15632
15738
  const files = [];
@@ -16068,11 +16174,11 @@ function parseSemanticBlockConfig(source) {
16068
16174
  limit: limitMatch ? Number.parseInt(limitMatch[1], 10) : undefined,
16069
16175
  };
16070
16176
  }
16071
- export async function resolveSemanticTableMapping(executor, connection, semanticLayer, projectRoot) {
16177
+ export async function resolveSemanticTableMapping(executor, connection, semanticLayer, projectRoot, connectionId) {
16072
16178
  if (!semanticLayer)
16073
16179
  return undefined;
16074
16180
  if (projectRoot) {
16075
- const snapshot = latestRuntimeSchemaSnapshotForProject(projectRoot);
16181
+ const snapshot = latestRuntimeSchemaSnapshotForProject(projectRoot, connectionId);
16076
16182
  if (snapshot?.scopeFingerprint && snapshot.tables.length) {
16077
16183
  return buildSemanticTableMapping(semanticLayer, snapshot.tables.map((table) => ({
16078
16184
  table_catalog: table.catalogOrDatabase,
@@ -16094,16 +16200,30 @@ export async function resolveSemanticTableMapping(executor, connection, semantic
16094
16200
  }
16095
16201
  }
16096
16202
  export function buildSemanticTableMapping(semanticLayer, rows) {
16097
- const dbTableNames = new Set();
16098
- const schemaQualified = new Map();
16203
+ const normalizeIdentifierPart = (value) => value
16204
+ .trim()
16205
+ .replace(/^["'`\[]|["'`\]]$/g, '')
16206
+ .toLowerCase();
16207
+ const normalizeRelation = (value) => value
16208
+ .split('.')
16209
+ .map(normalizeIdentifierPart)
16210
+ .filter(Boolean)
16211
+ .join('.');
16212
+ const candidates = [];
16099
16213
  for (const row of rows) {
16214
+ const catalog = String(row['table_catalog'] ?? '');
16100
16215
  const schema = String(row['table_schema'] ?? '');
16101
16216
  const name = String(row['table_name'] ?? '');
16102
- const relation = typeof row['table_relation'] === 'string' ? row['table_relation'] : undefined;
16217
+ const relation = typeof row['table_relation'] === 'string' && row['table_relation'].trim()
16218
+ ? row['table_relation']
16219
+ : [catalog, schema, name].filter(Boolean).join('.');
16103
16220
  if (!name)
16104
16221
  continue;
16105
- dbTableNames.add(name);
16106
- schemaQualified.set(name, relation ?? (schema ? `${schema}.${name}` : name));
16222
+ candidates.push({
16223
+ name: normalizeIdentifierPart(name),
16224
+ relation,
16225
+ normalizedRelation: normalizeRelation(relation),
16226
+ });
16107
16227
  }
16108
16228
  const tableMapping = {};
16109
16229
  const allSemanticTables = new Set();
@@ -16118,9 +16238,19 @@ export function buildSemanticTableMapping(semanticLayer, rows) {
16118
16238
  for (const semTable of allSemanticTables) {
16119
16239
  if (!semTable)
16120
16240
  continue;
16121
- if (dbTableNames.has(semTable) && schemaQualified.has(semTable)) {
16122
- tableMapping[semTable] = schemaQualified.get(semTable);
16123
- }
16241
+ const normalizedSemantic = normalizeRelation(semTable);
16242
+ const semanticLeaf = normalizedSemantic.split('.').at(-1);
16243
+ if (!semanticLeaf)
16244
+ continue;
16245
+ const qualifiedMatches = candidates.filter((candidate) => candidate.normalizedRelation === normalizedSemantic
16246
+ || candidate.normalizedRelation.endsWith(`.${normalizedSemantic}`));
16247
+ const leafMatches = candidates.filter((candidate) => candidate.name === semanticLeaf);
16248
+ const matches = qualifiedMatches.length > 0 ? qualifiedMatches : leafMatches;
16249
+ // Never guess when the same semantic leaf exists in several schemas. A
16250
+ // qualified semantic relation can disambiguate; otherwise the semantic
16251
+ // runtime must surface the modeling gap instead of executing the wrong one.
16252
+ if (matches.length === 1)
16253
+ tableMapping[semTable] = matches[0].relation;
16124
16254
  }
16125
16255
  return Object.keys(tableMapping).length > 0 ? tableMapping : undefined;
16126
16256
  }
@@ -21369,11 +21499,36 @@ export function runtimeSnapshotStale(projectRoot, maxAgeMs = RUNTIME_SNAPSHOT_MA
21369
21499
  return false;
21370
21500
  }
21371
21501
  }
21372
- function recordAgentRuntimeSchemaSnapshot(projectRoot, schemaContext, source) {
21502
+ /**
21503
+ * Resolve only metadata captured for the connection executing this Ask run.
21504
+ * Returning an explicit empty snapshot is a correctness boundary: callers pass
21505
+ * it into the context builder so it cannot silently fall back to another
21506
+ * connection's project-global runtime FTS rows.
21507
+ */
21508
+ export function runtimeSchemaSnapshotForAgentConnection(projectRoot, connectionId, expectedScopeFingerprint) {
21509
+ const snapshot = latestRuntimeSchemaSnapshotForProject(projectRoot, connectionId);
21510
+ const current = Boolean(snapshot
21511
+ && snapshot.status !== 'stale'
21512
+ && (!expectedScopeFingerprint
21513
+ || !snapshot.scopeFingerprint
21514
+ || snapshot.scopeFingerprint === expectedScopeFingerprint));
21515
+ return current
21516
+ ? snapshot
21517
+ : {
21518
+ version: 1,
21519
+ connectionId,
21520
+ status: 'partial',
21521
+ source: 'selected connection has no current runtime metadata',
21522
+ tables: [],
21523
+ };
21524
+ }
21525
+ function recordAgentRuntimeSchemaSnapshot(projectRoot, schemaContext, source, connectionId) {
21373
21526
  if (schemaContext.length === 0)
21374
21527
  return;
21375
21528
  try {
21376
21529
  recordRuntimeSchemaSnapshot(projectRoot, {
21530
+ ...(connectionId ? { connectionId } : {}),
21531
+ status: 'partial',
21377
21532
  source,
21378
21533
  tables: schemaContext.map((table) => ({
21379
21534
  relation: table.relation,