@duckcodeailabs/dql-cli 1.8.0 → 1.8.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.
- package/dist/assets/dql-notebook/assets/{index-siDRLswd.js → index-DGbXmDU6.js} +325 -324
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
- package/dist/llm/providers/dql-agent-provider.js +33 -7
- package/dist/llm/providers/dql-agent-provider.js.map +1 -1
- package/dist/local-runtime.d.ts +3 -1
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +184 -76
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/package.json +10 -10
package/dist/local-runtime.js
CHANGED
|
@@ -16,7 +16,7 @@ import { listBlockTemplates } from './block-templates.js';
|
|
|
16
16
|
import { getRunner as getLLMRunner } from './llm/index.js';
|
|
17
17
|
import { createDqlAgentProviderRunner, resolveAgentFollowUpContext } from './llm/providers/dql-agent-provider.js';
|
|
18
18
|
import { listRemoteMcpSettings, saveRemoteMcpSettings } from './llm/mcp-config.js';
|
|
19
|
-
import { ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, recallRelevantTurns, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildAnalysisQuestionPlan, buildLocalContextPack, toAgentRetrievalEvidence, defaultConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureAgentProjectReady, isAgentProjectIndexReady, currentMetadataFingerprint, ensureMetadataCatalogFresh, readIndexedDomainKnowledge, readIndexedKnowledge360, propose, proposePlan, recordCorrectionTrace, emitCorrectionEvalCase, mineJoinPatterns, reviewHint, AgentRunEngine, FileAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, buildDeterministicDashboardStory, synthesizeAnswer, streamOrGenerate, narrateResult, buildProposePreview, buildFromPrompt, defaultAgentRunStorePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, configuredSkillsPath, skillsDir, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, deleteSkill, deriveGeneratedDraftSlug, reindexProject, invalidateAgentProjectState, resolveDomainContextEnvelope, defaultKgPath, planAppFromPrompt, KGStore, planResearch, loadSemanticMetrics, cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, routeReasoningEffort, routeForCascadeAnswerTier, clampReasoningEffort, bumpReasoningEffort, resolveThinkingMode, coerceThinkingMode, upsertGeneratedDqlArtifactDraft, } from '@duckcodeailabs/dql-agent';
|
|
19
|
+
import { ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, recallRelevantTurns, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildAnalysisQuestionPlan, buildLocalContextPack, toAgentRetrievalEvidence, defaultConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureAgentProjectReady, isAgentProjectIndexReady, currentMetadataFingerprint, ensureMetadataCatalogFresh, readIndexedDomainKnowledge, readIndexedKnowledge360, propose, proposePlan, recordCorrectionTrace, emitCorrectionEvalCase, mineJoinPatterns, reviewHint, AgentRunEngine, FileAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, buildDeterministicDashboardStory, synthesizeAnswer, streamOrGenerate, narrateResult, buildProposePreview, buildFromPrompt, defaultAgentRunStorePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, configuredSkillsPath, skillsDir, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, deleteSkill, deriveGeneratedDraftSlug, reindexProject, invalidateAgentProjectState, recordAgentRuntimeVersion, resolveDomainContextEnvelope, defaultKgPath, planAppFromPrompt, KGStore, planResearch, loadSemanticMetrics, cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, routeReasoningEffort, routeForCascadeAnswerTier, clampReasoningEffort, bumpReasoningEffort, resolveThinkingMode, coerceThinkingMode, upsertGeneratedDqlArtifactDraft, } from '@duckcodeailabs/dql-agent';
|
|
20
20
|
import { gatherProposeEnrichment } from './propose-enrich.js';
|
|
21
21
|
import { handleAppsApi, proposeAppAiBuild, recommendVisualization } from './apps-api.js';
|
|
22
22
|
import { getActiveProvider, getEffectiveProviderConfig, isProviderSettingsId, listProviderSettings, saveProviderSettings, } from './settings/provider-settings.js';
|
|
@@ -36,18 +36,26 @@ const NOTEBOOK_FAVICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0
|
|
|
36
36
|
export function resolveProjectSemanticConfig(projectConfig, projectRoot) {
|
|
37
37
|
const configured = projectConfig.semanticLayer;
|
|
38
38
|
const dbtProjectDir = projectConfig.dbt?.projectDir;
|
|
39
|
+
const dbtManifestPath = projectConfig.dbt?.manifestPath;
|
|
40
|
+
if (configured?.provider === 'dbt') {
|
|
41
|
+
return {
|
|
42
|
+
...configured,
|
|
43
|
+
projectPath: configured.projectPath ?? dbtProjectDir,
|
|
44
|
+
manifestPath: configured.manifestPath ?? dbtManifestPath,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
39
47
|
if (dbtProjectDir
|
|
40
48
|
&& (!configured || configured.provider === 'dql')
|
|
41
|
-
&& hasDbtSemanticArtifacts(projectRoot, dbtProjectDir)) {
|
|
42
|
-
return { provider: 'dbt', projectPath: dbtProjectDir };
|
|
49
|
+
&& hasDbtSemanticArtifacts(projectRoot, dbtProjectDir, dbtManifestPath)) {
|
|
50
|
+
return { provider: 'dbt', projectPath: dbtProjectDir, manifestPath: dbtManifestPath };
|
|
43
51
|
}
|
|
44
52
|
return configured;
|
|
45
53
|
}
|
|
46
|
-
function hasDbtSemanticArtifacts(projectRoot, dbtProjectDir) {
|
|
54
|
+
function hasDbtSemanticArtifacts(projectRoot, dbtProjectDir, configuredManifestPath) {
|
|
47
55
|
const dbtRoot = resolve(projectRoot, dbtProjectDir);
|
|
48
|
-
|
|
56
|
+
const manifestPath = resolve(dbtRoot, configuredManifestPath ?? 'target/manifest.json');
|
|
57
|
+
if (existsSync(join(dirname(manifestPath), 'semantic_manifest.json')))
|
|
49
58
|
return true;
|
|
50
|
-
const manifestPath = join(dbtRoot, 'target', 'manifest.json');
|
|
51
59
|
if (!existsSync(manifestPath))
|
|
52
60
|
return false;
|
|
53
61
|
try {
|
|
@@ -137,6 +145,7 @@ export function parseAgentRunRequestBody(body) {
|
|
|
137
145
|
return {
|
|
138
146
|
request: {
|
|
139
147
|
question,
|
|
148
|
+
selectedEvidenceId: agentRunString(record.selectedEvidenceId),
|
|
140
149
|
requestedMode,
|
|
141
150
|
audience,
|
|
142
151
|
intent: agentRunString(record.intent),
|
|
@@ -189,6 +198,10 @@ export function shouldSynthesizeAgentRunAnswer(governedAnswer) {
|
|
|
189
198
|
return false;
|
|
190
199
|
return true;
|
|
191
200
|
}
|
|
201
|
+
export function agentAnswerHasExecutionFailure(governedAnswer) {
|
|
202
|
+
return typeof governedAnswer.executionError === 'string'
|
|
203
|
+
&& governedAnswer.executionError.trim().length > 0;
|
|
204
|
+
}
|
|
192
205
|
function businessNarrativeGaps(warnings) {
|
|
193
206
|
const businessRelevant = (warnings ?? []).filter((warning) => !(/missing requested output column/i.test(warning)
|
|
194
207
|
|| /\bquery plan\b/i.test(warning)
|
|
@@ -272,16 +285,20 @@ function recordConversationTurn(store, threadId, run) {
|
|
|
272
285
|
// Conversation persistence is additive; a failed write must not fail the run.
|
|
273
286
|
}
|
|
274
287
|
}
|
|
275
|
-
function conversationTurnInputFromRun(run) {
|
|
288
|
+
export function conversationTurnInputFromRun(run) {
|
|
276
289
|
const artifact = run.artifacts.find((candidate) => candidate.kind === 'answer')
|
|
277
290
|
?? run.artifacts.find((candidate) => candidate.kind === 'research_run')
|
|
278
291
|
?? run.artifacts[0];
|
|
279
292
|
const payload = agentRunRecord(artifact?.payload);
|
|
280
293
|
const result = agentRunRecord(payload?.result);
|
|
281
294
|
const columns = conversationResultColumns(result?.columns);
|
|
282
|
-
|
|
283
|
-
|
|
295
|
+
// The visual preview stays tiny, but member resolution needs a wider bounded
|
|
296
|
+
// value window. Deriving dimensions from only the eight preview rows caused a
|
|
297
|
+
// valid row 9/10 member to disappear before the next turn (AGT-012/E2E-010).
|
|
298
|
+
const memberRows = Array.isArray(result?.rows)
|
|
299
|
+
? result.rows.filter((row) => Boolean(row && typeof row === 'object' && !Array.isArray(row))).slice(0, 24)
|
|
284
300
|
: [];
|
|
301
|
+
const rows = memberRows.slice(0, 8);
|
|
285
302
|
const rowsSample = rows.map((row) => columns.map((column) => row[column]));
|
|
286
303
|
const contextPack = agentRunRecord(payload?.contextPack);
|
|
287
304
|
const questionPlan = agentRunRecord(contextPack?.questionPlan);
|
|
@@ -305,7 +322,7 @@ function conversationTurnInputFromRun(run) {
|
|
|
305
322
|
? {
|
|
306
323
|
columns,
|
|
307
324
|
rowsSample,
|
|
308
|
-
dimensionValues: conversationDimensionValues(columns,
|
|
325
|
+
dimensionValues: conversationDimensionValues(columns, memberRows),
|
|
309
326
|
measureColumns,
|
|
310
327
|
rowCount: typeof rowCountRaw === 'number' ? rowCountRaw : rows.length || undefined,
|
|
311
328
|
}
|
|
@@ -421,6 +438,7 @@ export async function startLocalServer(opts) {
|
|
|
421
438
|
if (gitRoot)
|
|
422
439
|
ensureLocalRuntimeGitignore(projectRoot);
|
|
423
440
|
let projectConfig = loadProjectConfig(projectRoot);
|
|
441
|
+
recordAgentRuntimeVersion(projectRoot, runtimeVersion);
|
|
424
442
|
const configuredConnection = rawConnection
|
|
425
443
|
? normalizeProjectConnection(rawConnection, projectRoot)
|
|
426
444
|
: projectConfig.defaultConnection
|
|
@@ -499,6 +517,8 @@ export async function startLocalServer(opts) {
|
|
|
499
517
|
const prepared = await ensureAgentProjectReady(projectRoot, {
|
|
500
518
|
kgPath: defaultKgPath(projectRoot),
|
|
501
519
|
manifest: input.manifest,
|
|
520
|
+
forceKgIndex: input.forceRebuild,
|
|
521
|
+
forceMetadataCatalog: input.forceRebuild,
|
|
502
522
|
});
|
|
503
523
|
const current = onboardingJobs.get(id);
|
|
504
524
|
if (!current || current.status === 'cancelled')
|
|
@@ -660,18 +680,25 @@ export async function startLocalServer(opts) {
|
|
|
660
680
|
const semanticLayerDir = join(projectRoot, 'semantic-layer');
|
|
661
681
|
let semanticImportManifest = loadSemanticImportManifest(projectRoot);
|
|
662
682
|
const userPrefsPath = join(projectRoot, '.dql-user-prefs.json');
|
|
663
|
-
|
|
683
|
+
let semanticConfig = resolveProjectSemanticConfig(projectConfig, projectRoot);
|
|
664
684
|
let semanticLastSyncTime = null;
|
|
665
|
-
{
|
|
685
|
+
const reloadSemanticLayer = async () => {
|
|
686
|
+
semanticConfig = resolveProjectSemanticConfig(projectConfig, projectRoot);
|
|
666
687
|
const semanticConnection = connection;
|
|
667
688
|
const executeQuery = semanticConfig?.provider === 'snowflake' && semanticConnection
|
|
668
689
|
? async (sql) => { const r = await executor.executeQuery(sql, [], {}, semanticConnection); return { rows: r.rows }; }
|
|
669
690
|
: undefined;
|
|
670
691
|
const result = await resolveSemanticLayerAsync(semanticConfig, projectRoot, executeQuery);
|
|
671
|
-
semanticLayer = result.layer;
|
|
672
692
|
semanticLayerErrors = result.errors;
|
|
673
693
|
semanticDetectedProvider = result.detectedProvider;
|
|
674
|
-
|
|
694
|
+
if (result.layer) {
|
|
695
|
+
semanticLayer = result.layer;
|
|
696
|
+
semanticLastSyncTime = new Date().toISOString();
|
|
697
|
+
}
|
|
698
|
+
else if (result.errors.length === 0) {
|
|
699
|
+
semanticLayer = undefined;
|
|
700
|
+
semanticLastSyncTime = null;
|
|
701
|
+
}
|
|
675
702
|
semanticImportManifest = loadSemanticImportManifest(projectRoot);
|
|
676
703
|
// Legacy fallback if provider system returned nothing and no errors
|
|
677
704
|
if (!semanticLayer && semanticLayerErrors.length === 0 && existsSync(semanticLayerDir)) {
|
|
@@ -681,8 +708,28 @@ export async function startLocalServer(opts) {
|
|
|
681
708
|
}
|
|
682
709
|
catch { /* continue without */ }
|
|
683
710
|
}
|
|
684
|
-
|
|
711
|
+
return result;
|
|
712
|
+
};
|
|
713
|
+
await reloadSemanticLayer();
|
|
685
714
|
await refreshLocalMetadataCatalog(projectRoot);
|
|
715
|
+
const startupDbtManifestPath = resolveDbtManifestPath(projectRoot, projectConfig);
|
|
716
|
+
if (startupDbtManifestPath && !isAgentProjectIndexReady(projectRoot)) {
|
|
717
|
+
try {
|
|
718
|
+
const startupSnapshot = projectSnapshot();
|
|
719
|
+
startDbtPreparationJob({
|
|
720
|
+
kind: 'dbt_prepare',
|
|
721
|
+
snapshotId: startupSnapshot.snapshotId,
|
|
722
|
+
manifest: startupSnapshot.manifest,
|
|
723
|
+
validationDurationMs: 0,
|
|
724
|
+
compileDurationMs: 0,
|
|
725
|
+
forceRebuild: true,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
catch {
|
|
729
|
+
// Startup remains available with the last valid cache; Setup exposes the
|
|
730
|
+
// compile error and the next explicit refresh retries preparation.
|
|
731
|
+
}
|
|
732
|
+
}
|
|
686
733
|
const recordDatasetMetadataSnapshot = (datasets = datasetWorkspace.list()) => {
|
|
687
734
|
if (datasets.length === 0)
|
|
688
735
|
return;
|
|
@@ -1407,6 +1454,7 @@ export async function startLocalServer(opts) {
|
|
|
1407
1454
|
|| governedAnswer.sourceCertifiedBlock
|
|
1408
1455
|
|| governedAnswer.dqlArtifact?.kind === 'certified_block'));
|
|
1409
1456
|
const isExploratory = Boolean(governedAnswer.exploratoryCandidate);
|
|
1457
|
+
const isExecutionFailure = agentAnswerHasExecutionFailure(governedAnswer);
|
|
1410
1458
|
const isGroundingGap = governedAnswer.kind === 'no_answer'
|
|
1411
1459
|
&& governedAnswer.refusalCode === 'grounding_gap'
|
|
1412
1460
|
&& !isExploratory;
|
|
@@ -1427,7 +1475,7 @@ export async function startLocalServer(opts) {
|
|
|
1427
1475
|
const needsClarification = governedAnswer.kind === 'no_answer'
|
|
1428
1476
|
&& !isGroundingGap && !isProviderError && !isModelDeclined && !isPolicyBlocked;
|
|
1429
1477
|
const sql = governedAnswer.proposedSql ?? governedAnswer.sql;
|
|
1430
|
-
const runnableSql = governedAnswer.kind === 'no_answer' || (isExploratory && !governedAnswer.result)
|
|
1478
|
+
const runnableSql = governedAnswer.kind === 'no_answer' || isExecutionFailure || (isExploratory && !governedAnswer.result)
|
|
1431
1479
|
? undefined
|
|
1432
1480
|
: sql;
|
|
1433
1481
|
// Render executed rows deterministically for ordinary lookups. A second LLM
|
|
@@ -1466,14 +1514,14 @@ export async function startLocalServer(opts) {
|
|
|
1466
1514
|
synthesizedAnswer = undefined;
|
|
1467
1515
|
}
|
|
1468
1516
|
}
|
|
1469
|
-
const status = isProviderError
|
|
1517
|
+
const status = isProviderError || isExecutionFailure
|
|
1470
1518
|
? 'blocked'
|
|
1471
1519
|
: needsClarification
|
|
1472
1520
|
? 'needs_clarification'
|
|
1473
1521
|
: isCertified || isSemantic
|
|
1474
1522
|
? 'completed'
|
|
1475
1523
|
: 'needs_review';
|
|
1476
|
-
const trustState = isProviderError
|
|
1524
|
+
const trustState = isProviderError || isExecutionFailure
|
|
1477
1525
|
? 'blocked'
|
|
1478
1526
|
: needsClarification
|
|
1479
1527
|
? 'not_applicable'
|
|
@@ -1482,7 +1530,7 @@ export async function startLocalServer(opts) {
|
|
|
1482
1530
|
: isSemantic
|
|
1483
1531
|
? 'governed'
|
|
1484
1532
|
: 'review_required';
|
|
1485
|
-
const stopReason = isProviderError
|
|
1533
|
+
const stopReason = isProviderError || isExecutionFailure
|
|
1486
1534
|
? 'blocked'
|
|
1487
1535
|
: needsClarification
|
|
1488
1536
|
? 'needs_clarification'
|
|
@@ -1493,33 +1541,41 @@ export async function startLocalServer(opts) {
|
|
|
1493
1541
|
: 'human_review_required';
|
|
1494
1542
|
const nextActions = needsClarification
|
|
1495
1543
|
? [{ id: 'clarify', label: 'Clarify question', route: 'generated_answer' }]
|
|
1496
|
-
:
|
|
1497
|
-
{ id: '
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
label: governedAnswer.dqlArtifact ? '
|
|
1502
|
-
route: '
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1544
|
+
: isExecutionFailure
|
|
1545
|
+
? [{ id: 'retry-after-connection', label: 'Retry after fixing the database connection', route: 'generated_answer' }]
|
|
1546
|
+
: isGroundingGap
|
|
1547
|
+
? [{ id: 'research-gap', label: 'Research missing metadata coverage', route: 'research', artifactKind: 'research_run' }]
|
|
1548
|
+
: [
|
|
1549
|
+
{ id: 'create-block', label: governedAnswer.dqlArtifact ? 'Review DQL draft' : 'Create DQL draft', route: 'dql_block_draft', artifactKind: 'dql_block_draft' },
|
|
1550
|
+
{ id: 'research-gap', label: 'Research deeper', route: 'research' },
|
|
1551
|
+
...(runnableSql ? [{
|
|
1552
|
+
id: 'insert-sql',
|
|
1553
|
+
label: governedAnswer.dqlArtifact ? 'Insert as DQL cell' : 'Insert SQL preview',
|
|
1554
|
+
route: 'sql_cell',
|
|
1555
|
+
artifactKind: 'sql_cell',
|
|
1556
|
+
}] : []),
|
|
1557
|
+
];
|
|
1506
1558
|
return {
|
|
1507
1559
|
resolvedRoute,
|
|
1508
1560
|
answerRefusalCode: governedAnswer.kind === 'no_answer' ? governedAnswer.refusalCode : undefined,
|
|
1509
1561
|
answerTier: governedAnswer.route?.tier,
|
|
1510
|
-
summary:
|
|
1562
|
+
summary: isExecutionFailure
|
|
1563
|
+
? 'The governed query could not be executed.'
|
|
1564
|
+
: governedAnswer.route?.label ?? (isCertified ? 'Answered from certified DQL context.' : isExploratory ? 'Exploratory DBT-grounded analysis requires review.' : 'Answered with review-required generated analysis.'),
|
|
1511
1565
|
answer: synthesizedAnswer ?? governedAnswer.answer ?? governedAnswer.text,
|
|
1512
1566
|
status,
|
|
1513
1567
|
trustState,
|
|
1514
1568
|
stopReason,
|
|
1515
|
-
artifacts:
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1569
|
+
artifacts: isExecutionFailure
|
|
1570
|
+
? []
|
|
1571
|
+
: governedAnswer.kind === 'no_answer'
|
|
1572
|
+
// A refusal still keeps the DQL draft the answer loop produced (when any),
|
|
1573
|
+
// so the "Review DQL draft" next-action isn't a dead link and the user can
|
|
1574
|
+
// see the SQL that was about to run. Provider outages carry no draft.
|
|
1575
|
+
? (governedAnswer.dqlArtifact && !isProviderError && !isGroundingGap && !isModelDeclined && !isPolicyBlocked
|
|
1576
|
+
? [agentRunArtifact('dql_block_draft', 'DQL draft (review required)', governedAnswer.dqlArtifact, undefined, 'review_required')]
|
|
1577
|
+
: [])
|
|
1578
|
+
: [agentRunArtifact('answer', isCertified ? 'Certified answer' : isSemantic ? 'Governed semantic answer' : isExploratory ? 'Exploratory DBT-grounded answer' : 'Review-required answer', governedAnswer, governedAnswer.sourceCertifiedBlock ?? governedAnswer.block?.name, isCertified ? 'certified' : isSemantic ? 'governed' : 'review_required')],
|
|
1523
1579
|
evaluations: [
|
|
1524
1580
|
agentRunEvaluation('route-decision', 'Route decision', true, 'info', routeDecision?.reason ?? 'Routed request to governed answer.', {
|
|
1525
1581
|
plannedRoute: route,
|
|
@@ -1548,6 +1604,7 @@ export async function startLocalServer(opts) {
|
|
|
1548
1604
|
: isExploratory
|
|
1549
1605
|
? 'The answer used bounded DBT/schema evidence because no certified modeled relationship path covered the request. It is review-required and was not certified.'
|
|
1550
1606
|
: 'The answer is generated or semantic-layer backed and remains review-required.', governedAnswer.route),
|
|
1607
|
+
...(isExecutionFailure ? [agentRunEvaluation('query-execution', 'Query execution', false, 'blocking', `The governed query failed before it produced a result: ${governedAnswer.executionError}`)] : []),
|
|
1551
1608
|
...(isGroundingGap ? [
|
|
1552
1609
|
{
|
|
1553
1610
|
...agentRunEvaluation('grounding-gap', 'Metadata grounding', false, 'warning', 'The bounded lookup could not prove the required metadata grounding. No automatic retry or Research escalation was started.', {
|
|
@@ -2133,6 +2190,7 @@ export async function startLocalServer(opts) {
|
|
|
2133
2190
|
: undefined;
|
|
2134
2191
|
const task = buildLocalContextPack(projectRoot, {
|
|
2135
2192
|
question: request.question,
|
|
2193
|
+
focusObjectKey: request.selectedEvidenceId,
|
|
2136
2194
|
followUp,
|
|
2137
2195
|
priorContextPackId: agentRunString(request.conversationContext?.contextPackId),
|
|
2138
2196
|
conversationTopicRelation: topicRelation === 'continuation'
|
|
@@ -2208,9 +2266,9 @@ export async function startLocalServer(opts) {
|
|
|
2208
2266
|
};
|
|
2209
2267
|
}
|
|
2210
2268
|
if ((candidate.kind === 'semantic_metric' || candidate.kind === 'semantic_member')
|
|
2211
|
-
&& semanticEvidence.has(candidate.id)
|
|
2212
|
-
&&
|
|
2213
|
-
|
|
2269
|
+
&& (semanticEvidence.has(candidate.id) || request.selectedEvidenceId === candidate.id)
|
|
2270
|
+
&& (request.selectedEvidenceId === candidate.id
|
|
2271
|
+
|| (pack.routeDecision.route !== 'clarify' && pack.routeDecision.route !== 'conflict'))) {
|
|
2214
2272
|
const requestedDimensions = pack.questionPlan.requestedShape.dimensions.map((dimension) => dimension.toLowerCase());
|
|
2215
2273
|
const availableDimensions = (candidate.dimensions ?? []).map((dimension) => dimension.toLowerCase());
|
|
2216
2274
|
const dimensionsFit = requestedDimensions.length === 0 || requestedDimensions.every((requested) => availableDimensions.some((available) => available === requested || available.endsWith(`.${requested}`)));
|
|
@@ -3293,20 +3351,7 @@ export async function startLocalServer(opts) {
|
|
|
3293
3351
|
}
|
|
3294
3352
|
// Hot-reload semantic layer on change and notify frontend
|
|
3295
3353
|
if (dir === 'semantic-layer') {
|
|
3296
|
-
|
|
3297
|
-
const executeQuery = semanticConfig?.provider === 'snowflake' && semanticConnection
|
|
3298
|
-
? async (sql) => { const r = await executor.executeQuery(sql, [], {}, semanticConnection); return { rows: r.rows }; }
|
|
3299
|
-
: undefined;
|
|
3300
|
-
resolveSemanticLayerAsync(semanticConfig, projectRoot, executeQuery).then((refreshed) => {
|
|
3301
|
-
if (refreshed.layer) {
|
|
3302
|
-
semanticLayer = refreshed.layer;
|
|
3303
|
-
semanticLayerErrors = refreshed.errors;
|
|
3304
|
-
semanticLastSyncTime = new Date().toISOString();
|
|
3305
|
-
semanticImportManifest = loadSemanticImportManifest(projectRoot);
|
|
3306
|
-
}
|
|
3307
|
-
else if (refreshed.errors.length > 0) {
|
|
3308
|
-
semanticLayerErrors = refreshed.errors;
|
|
3309
|
-
}
|
|
3354
|
+
reloadSemanticLayer().then(() => {
|
|
3310
3355
|
// Notify all connected notebook clients to re-fetch the semantic layer
|
|
3311
3356
|
const reloadPayload = JSON.stringify({ type: 'semantic-reload' });
|
|
3312
3357
|
for (const client of sseClients) {
|
|
@@ -3323,7 +3368,7 @@ export async function startLocalServer(opts) {
|
|
|
3323
3368
|
}
|
|
3324
3369
|
catch { /* dir not watchable */ }
|
|
3325
3370
|
}
|
|
3326
|
-
const configuredDbtManifest = resolveDbtManifestPath(projectRoot);
|
|
3371
|
+
const configuredDbtManifest = resolveDbtManifestPath(projectRoot, projectConfig);
|
|
3327
3372
|
if (configuredDbtManifest && existsSync(dirname(configuredDbtManifest))) {
|
|
3328
3373
|
try {
|
|
3329
3374
|
watch(dirname(configuredDbtManifest), { persistent: false }, (_eventType, filename) => {
|
|
@@ -3344,6 +3389,17 @@ export async function startLocalServer(opts) {
|
|
|
3344
3389
|
sseClients.delete(client);
|
|
3345
3390
|
}
|
|
3346
3391
|
}
|
|
3392
|
+
void reloadSemanticLayer().then(() => {
|
|
3393
|
+
const reloadPayload = JSON.stringify({ type: 'semantic-reload' });
|
|
3394
|
+
for (const client of sseClients) {
|
|
3395
|
+
try {
|
|
3396
|
+
client.write(`event: change\ndata: ${reloadPayload}\n\n`);
|
|
3397
|
+
}
|
|
3398
|
+
catch {
|
|
3399
|
+
sseClients.delete(client);
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
}).catch(() => { });
|
|
3347
3403
|
});
|
|
3348
3404
|
}
|
|
3349
3405
|
catch { /* dbt artifact directory not watchable */ }
|
|
@@ -3866,6 +3922,10 @@ export async function startLocalServer(opts) {
|
|
|
3866
3922
|
let snapshotManifest;
|
|
3867
3923
|
const compileStartedAt = Date.now();
|
|
3868
3924
|
try {
|
|
3925
|
+
const semanticReload = await reloadSemanticLayer();
|
|
3926
|
+
if (semanticReload.errors.length > 0) {
|
|
3927
|
+
throw new Error(`dbt semantic artifacts could not be loaded: ${semanticReload.errors.join('; ')}`);
|
|
3928
|
+
}
|
|
3869
3929
|
const snapshot = projectSnapshot();
|
|
3870
3930
|
snapshotId = snapshot.snapshotId;
|
|
3871
3931
|
snapshotManifest = snapshot.manifest;
|
|
@@ -3878,6 +3938,7 @@ export async function startLocalServer(opts) {
|
|
|
3878
3938
|
connection = previousConnection;
|
|
3879
3939
|
projectSnapshots.invalidate();
|
|
3880
3940
|
invalidateAgentProjectState(projectRoot);
|
|
3941
|
+
await reloadSemanticLayer();
|
|
3881
3942
|
throw Object.assign(new Error(`dbt-first configuration did not compile and was rolled back: ${error instanceof Error ? error.message : String(error)}`), { code: 'SNAPSHOT_BUILD_FAILED' });
|
|
3882
3943
|
}
|
|
3883
3944
|
const preparation = startDbtPreparationJob({
|
|
@@ -3918,6 +3979,7 @@ export async function startLocalServer(opts) {
|
|
|
3918
3979
|
const validationDurationMs = Date.now() - refreshStartedAt;
|
|
3919
3980
|
const compileStartedAt = Date.now();
|
|
3920
3981
|
projectSnapshots.invalidate();
|
|
3982
|
+
await reloadSemanticLayer();
|
|
3921
3983
|
const snapshot = projectSnapshot();
|
|
3922
3984
|
invalidateAgentProjectState(projectRoot);
|
|
3923
3985
|
const job = startDbtPreparationJob({
|
|
@@ -8143,16 +8205,7 @@ export async function startLocalServer(opts) {
|
|
|
8143
8205
|
}
|
|
8144
8206
|
if (req.method === 'POST' && path === '/api/semantic-layer/reload') {
|
|
8145
8207
|
try {
|
|
8146
|
-
|
|
8147
|
-
const executeQuery = semanticConfig?.provider === 'snowflake' && semanticConnection
|
|
8148
|
-
? async (sql) => { const r = await executor.executeQuery(sql, [], {}, semanticConnection); return { rows: r.rows }; }
|
|
8149
|
-
: undefined;
|
|
8150
|
-
const refreshed = await resolveSemanticLayerAsync(semanticConfig, projectRoot, executeQuery);
|
|
8151
|
-
semanticLayer = refreshed.layer;
|
|
8152
|
-
semanticLayerErrors = refreshed.errors;
|
|
8153
|
-
semanticDetectedProvider = refreshed.detectedProvider;
|
|
8154
|
-
semanticLastSyncTime = refreshed.layer ? new Date().toISOString() : null;
|
|
8155
|
-
semanticImportManifest = loadSemanticImportManifest(projectRoot);
|
|
8208
|
+
await reloadSemanticLayer();
|
|
8156
8209
|
const diagnostics = buildSemanticLayerDiagnostics(projectRoot, projectConfig, {
|
|
8157
8210
|
semanticLayer,
|
|
8158
8211
|
semanticErrors: semanticLayerErrors,
|
|
@@ -8162,7 +8215,7 @@ export async function startLocalServer(opts) {
|
|
|
8162
8215
|
});
|
|
8163
8216
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
8164
8217
|
res.end(serializeJSON({
|
|
8165
|
-
ok: Boolean(
|
|
8218
|
+
ok: Boolean(semanticLayer),
|
|
8166
8219
|
...diagnostics,
|
|
8167
8220
|
}));
|
|
8168
8221
|
}
|
|
@@ -8689,6 +8742,12 @@ export async function startLocalServer(opts) {
|
|
|
8689
8742
|
}));
|
|
8690
8743
|
return;
|
|
8691
8744
|
}
|
|
8745
|
+
const provider = semanticConfig?.provider ?? semanticDetectedProvider ?? 'dql';
|
|
8746
|
+
const dbtManifestReady = provider === 'dbt'
|
|
8747
|
+
? hasDbtSemanticManifest(projectRoot, semanticConfig?.projectPath)
|
|
8748
|
+
: false;
|
|
8749
|
+
const metricFlowReady = provider === 'dbt' ? hasMetricFlowCli() : false;
|
|
8750
|
+
const dbtExecutionReady = dbtManifestReady && metricFlowReady;
|
|
8692
8751
|
const metrics = semanticLayer.listMetrics().map((m) => ({
|
|
8693
8752
|
name: m.name,
|
|
8694
8753
|
label: m.label,
|
|
@@ -8703,6 +8762,7 @@ export async function startLocalServer(opts) {
|
|
|
8703
8762
|
typeParams: m.typeParams ?? null,
|
|
8704
8763
|
filter: m.filter ?? null,
|
|
8705
8764
|
source: m.source ?? null,
|
|
8765
|
+
execution: semanticMetricExecutionCapability(m.name, semanticLayer, provider, metricFlowReady, connection?.driver),
|
|
8706
8766
|
}));
|
|
8707
8767
|
const measures = semanticLayer.listMeasures().map((m) => ({
|
|
8708
8768
|
name: m.name,
|
|
@@ -8800,12 +8860,6 @@ export async function startLocalServer(opts) {
|
|
|
8800
8860
|
owner: q.owner ?? null,
|
|
8801
8861
|
source: q.source ?? null,
|
|
8802
8862
|
}));
|
|
8803
|
-
const provider = semanticConfig?.provider ?? semanticDetectedProvider ?? 'dql';
|
|
8804
|
-
const dbtManifestReady = provider === 'dbt'
|
|
8805
|
-
? hasDbtSemanticManifest(projectRoot, semanticConfig?.projectPath)
|
|
8806
|
-
: false;
|
|
8807
|
-
const metricFlowReady = provider === 'dbt' ? hasMetricFlowCli() : false;
|
|
8808
|
-
const dbtExecutionReady = dbtManifestReady && metricFlowReady;
|
|
8809
8863
|
const dbtExecutionSetup = dbtExecutionReady
|
|
8810
8864
|
? null
|
|
8811
8865
|
: !dbtManifestReady && !metricFlowReady
|
|
@@ -9720,8 +9774,20 @@ export async function startLocalServer(opts) {
|
|
|
9720
9774
|
tableMapping,
|
|
9721
9775
|
});
|
|
9722
9776
|
if (!composed) {
|
|
9777
|
+
const provider = semanticConfig?.provider ?? semanticDetectedProvider ?? 'dql';
|
|
9778
|
+
const metricFlowReady = provider === 'dbt' && hasMetricFlowCli();
|
|
9779
|
+
const blocked = metrics.map((metricName) => ({
|
|
9780
|
+
metric: metricName,
|
|
9781
|
+
...semanticMetricExecutionCapability(metricName, semanticLayer, provider, metricFlowReady, driver),
|
|
9782
|
+
})).filter((capability) => capability.status !== 'ready');
|
|
9723
9783
|
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
9724
|
-
res.end(serializeJSON({
|
|
9784
|
+
res.end(serializeJSON({
|
|
9785
|
+
error: blocked.length > 0
|
|
9786
|
+
? blocked.map((capability) => `${capability.metric}: ${capability.reason}`).join(' ')
|
|
9787
|
+
: 'The selected dimensions do not share a governed join path with every selected metric.',
|
|
9788
|
+
code: blocked.length > 0 ? 'SEMANTIC_RUNTIME_REQUIRED' : 'SEMANTIC_FIELDS_INCOMPATIBLE',
|
|
9789
|
+
details: { metrics, dimensions, blocked },
|
|
9790
|
+
}));
|
|
9725
9791
|
return;
|
|
9726
9792
|
}
|
|
9727
9793
|
// Execute the composed SQL against the resolved connection
|
|
@@ -9806,8 +9872,20 @@ export async function startLocalServer(opts) {
|
|
|
9806
9872
|
tableMapping,
|
|
9807
9873
|
});
|
|
9808
9874
|
if (!composed) {
|
|
9875
|
+
const provider = semanticConfig?.provider ?? semanticDetectedProvider ?? 'dql';
|
|
9876
|
+
const metricFlowReady = provider === 'dbt' && hasMetricFlowCli();
|
|
9877
|
+
const blocked = metrics.map((metricName) => ({
|
|
9878
|
+
metric: metricName,
|
|
9879
|
+
...semanticMetricExecutionCapability(metricName, semanticLayer, provider, metricFlowReady, driver),
|
|
9880
|
+
})).filter((capability) => capability.status !== 'ready');
|
|
9809
9881
|
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
9810
|
-
res.end(serializeJSON({
|
|
9882
|
+
res.end(serializeJSON({
|
|
9883
|
+
error: blocked.length > 0
|
|
9884
|
+
? blocked.map((capability) => `${capability.metric}: ${capability.reason}`).join(' ')
|
|
9885
|
+
: 'The selected dimensions do not share a governed join path with every selected metric.',
|
|
9886
|
+
code: blocked.length > 0 ? 'SEMANTIC_RUNTIME_REQUIRED' : 'SEMANTIC_FIELDS_INCOMPATIBLE',
|
|
9887
|
+
details: { metrics, dimensions, blocked },
|
|
9888
|
+
}));
|
|
9811
9889
|
return;
|
|
9812
9890
|
}
|
|
9813
9891
|
const prepared = prepareLocalExecution(composed.sql, targetConnection, projectRoot, projectConfig);
|
|
@@ -13638,6 +13716,28 @@ export function openBlockStudioDocument(projectRoot, relativePath, semanticLayer
|
|
|
13638
13716
|
validation: validateBlockStudioSource(source, semanticLayer),
|
|
13639
13717
|
};
|
|
13640
13718
|
}
|
|
13719
|
+
function semanticMetricExecutionCapability(metricName, semanticLayer, provider, metricFlowReady, driver) {
|
|
13720
|
+
if (provider === 'dbt' && metricFlowReady) {
|
|
13721
|
+
return { status: 'ready', engine: 'metricflow', reason: null };
|
|
13722
|
+
}
|
|
13723
|
+
const native = semanticLayer.composeQuery({ metrics: [metricName], dimensions: [], driver });
|
|
13724
|
+
if (native)
|
|
13725
|
+
return { status: 'ready', engine: 'native', reason: null };
|
|
13726
|
+
const metric = semanticLayer.getMetric(metricName);
|
|
13727
|
+
if (provider === 'dbt') {
|
|
13728
|
+
const kind = metric?.metricType || metric?.aggregation || metric?.type || 'metric';
|
|
13729
|
+
return {
|
|
13730
|
+
status: 'requires_setup',
|
|
13731
|
+
engine: null,
|
|
13732
|
+
reason: `${kind} metric requires MetricFlow execution. Install/configure the MetricFlow CLI, then refresh the semantic layer.`,
|
|
13733
|
+
};
|
|
13734
|
+
}
|
|
13735
|
+
return {
|
|
13736
|
+
status: 'unsupported',
|
|
13737
|
+
engine: null,
|
|
13738
|
+
reason: 'The metric does not have enough composable measure and relation metadata.',
|
|
13739
|
+
};
|
|
13740
|
+
}
|
|
13641
13741
|
function parseBlockStudioArrayField(source, key) {
|
|
13642
13742
|
const match = source.match(new RegExp(`\\b${key}\\s*=\\s*\\[([\\s\\S]*?)\\]`, 'i'));
|
|
13643
13743
|
if (!match)
|
|
@@ -13872,10 +13972,18 @@ function composeSemanticBlockSql(source, semanticLayer, options) {
|
|
|
13872
13972
|
return { sql: null, diagnostics, semanticRefs };
|
|
13873
13973
|
}
|
|
13874
13974
|
if (!composed) {
|
|
13975
|
+
const provider = options?.projectConfig && isDbtSemanticRuntime(options.projectConfig, options.detectedProvider, semanticLayer) ? 'dbt' : (options?.detectedProvider ?? 'dql');
|
|
13976
|
+
const metricFlowReady = provider === 'dbt' && hasMetricFlowCli();
|
|
13977
|
+
const reasons = metrics.map((metricName) => {
|
|
13978
|
+
const capability = semanticMetricExecutionCapability(metricName, semanticLayer, provider, metricFlowReady, options?.driver);
|
|
13979
|
+
return capability.status === 'ready' ? null : `${metricName}: ${capability.reason}`;
|
|
13980
|
+
}).filter((reason) => Boolean(reason));
|
|
13875
13981
|
diagnostics.push({
|
|
13876
13982
|
severity: 'error',
|
|
13877
13983
|
code: 'semantic_compose_failed',
|
|
13878
|
-
message:
|
|
13984
|
+
message: reasons.length > 0
|
|
13985
|
+
? `Could not compose SQL for semantic block metrics. ${reasons.join(' ')}`
|
|
13986
|
+
: `Could not compose SQL for semantic block metrics: [${metrics.join(', ')}]. Check that the selected dimensions share a governed join path.`,
|
|
13879
13987
|
});
|
|
13880
13988
|
return { sql: null, diagnostics, semanticRefs };
|
|
13881
13989
|
}
|