@duckcodeailabs/dql-cli 1.14.1 → 1.14.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/{AgentLogPage-BPz-UWFh.js → AgentLogPage-DKbGpRQS.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildDialog-BEl53WA_.js → AiBuildDialog-DPSu0Mly.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildResult-B4yfGTTZ.js → AiBuildResult-1uaGnpi1.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiSidePanel-CSZAAvuD.js → AiSidePanel-BwMREwa7.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AnalyticsHome-D5P6Ujwi.js → AnalyticsHome-BGfey_ve.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AppsView-DQOwU9Cg.js → AppsView-CM1tPywy.js} +4 -4
- package/dist/assets/dql-notebook/assets/{BlockStudio-B4ap0GdY.js → BlockStudio--S6WFVO4.js} +1 -1
- package/dist/assets/dql-notebook/assets/{BusinessArtifactView-BIfNI-0S.js → BusinessArtifactView-BEAJ-yNW.js} +1 -1
- package/dist/assets/dql-notebook/assets/{DbtFirstModelingPage-D72byb2g.js → DbtFirstModelingPage-CNyU5MBX.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GitPage-lQb1uXH2.js → GitPage-IcydAai3.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GlobalAiRail-CVECf6Xj.js → GlobalAiRail-CSKW-5eD.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GovernedContextPage-trOyMCY6.js → GovernedContextPage-CFen0eFT.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HelpDocsPage-D8hLS5lE.js → HelpDocsPage-D0D9UCIz.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HomePage-eIkfBIep.js → HomePage-CmSxapR3.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDAG-CSqcbDrE.js → LineageDAG-BGUcIt1B.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDetailView-DJZZjZu-.js → LineageDetailView-Dx48Zfzb.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDrawer-BcIipIc3.js → LineageDrawer-zaBfSLZO.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineagePathBreadcrumb-CviIf8PN.js → LineagePathBreadcrumb-CTZJp4_r.js} +1 -1
- package/dist/assets/dql-notebook/assets/{MiniLineageGraph-vH_MY_Ju.js → MiniLineageGraph-CdivNR1S.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewBlockModal-DbCQg-pj.js → NewBlockModal-DMBFB7nE.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewNotebookModal-5Vp6XiuK.js → NewNotebookModal-DsC9CWlW.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NotebookEditor-DjqJS44s.js → NotebookEditor-hs-kw8v9.js} +1 -1
- package/dist/assets/dql-notebook/assets/{ReadinessPage-BHGrC5ho.js → ReadinessPage-DJ83cMik.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SetupOnboarding-BfS9Tdx-.js → SetupOnboarding-B1Pu_Bvv.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SkillsPage-BFH21wSj.js → SkillsPage-BHKDay8n.js} +1 -1
- package/dist/assets/dql-notebook/assets/{TrustBadge-zm6g_SxZ.js → TrustBadge-BkyGgob2.js} +1 -1
- package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-BYdrTaEW.js +89 -0
- package/dist/assets/dql-notebook/assets/{answer-to-notebook-DPhxIEzF.js → answer-to-notebook-DmNiuLQA.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-left-DNEb86Xc.js → arrow-left--1rsrxm8.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-right-DpPWbwaD.js → arrow-right-D5TdqY1G.js} +1 -1
- package/dist/assets/dql-notebook/assets/{book-open-text-D7s5jo4X.js → book-open-text-Bw7nHbzg.js} +1 -1
- package/dist/assets/dql-notebook/assets/{circle-x-Db3dXKg7.js → circle-x-DLe6NNM4.js} +1 -1
- package/dist/assets/dql-notebook/assets/{dagre.esm-C7pppQ1a.js → dagre.esm-CW5QZdBt.js} +1 -1
- package/dist/assets/dql-notebook/assets/{external-link-BxwXitO_.js → external-link-C9Q97sA3.js} +1 -1
- package/dist/assets/dql-notebook/assets/{grip-vertical-Dt4lkWRi.js → grip-vertical-CztvkIgo.js} +1 -1
- package/dist/assets/dql-notebook/assets/{index-DKo-bwNw.js → index-zHHzDn6l.js} +127 -127
- package/dist/assets/dql-notebook/assets/{link-2-Dfo2P6wi.js → link-2-CiKAvumL.js} +1 -1
- package/dist/assets/dql-notebook/assets/{list-tree-DiTmIWAL.js → list-tree-BtnP2nQ5.js} +1 -1
- package/dist/assets/dql-notebook/assets/{minimize-2-CMTAkPzL.js → minimize-2-TSFGxcCP.js} +1 -1
- package/dist/assets/dql-notebook/assets/{panel-right-open-DwYr7FW4.js → panel-right-open-BfXIUWy0.js} +1 -1
- package/dist/assets/dql-notebook/assets/{play-BXhHYQ4x.js → play-DVbSFJHD.js} +1 -1
- package/dist/assets/dql-notebook/assets/{rotate-ccw-BNi6F8pl.js → rotate-ccw-D_cesDcX.js} +1 -1
- package/dist/assets/dql-notebook/assets/{semantic-fields-CNOGysAy.js → semantic-fields-CoVStdYB.js} +1 -1
- package/dist/assets/dql-notebook/assets/{sliders-horizontal-Ec5MUMUW.js → sliders-horizontal-l7xV9K5A.js} +1 -1
- package/dist/assets/dql-notebook/assets/{star-B9leDkp_.js → star-CSBS0H3b.js} +1 -1
- package/dist/assets/dql-notebook/assets/{triangle-alert-BefTYCzx.js → triangle-alert-BTrnyY4q.js} +1 -1
- package/dist/assets/dql-notebook/assets/{upload-SPiOM2tQ.js → upload-sySLq9zb.js} +1 -1
- package/dist/assets/dql-notebook/assets/{usePersistedAgentThreadId-C4foXeiQ.js → usePersistedAgentThreadId-CzwgGdus.js} +1 -1
- package/dist/assets/dql-notebook/assets/{user-round-comGmyw-.js → user-round-ChlgXi9j.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wand-sparkles-BffR4dF8.js → wand-sparkles-CGw0ytyT.js} +1 -1
- package/dist/assets/dql-notebook/assets/{workflow-ChmPTEzH.js → workflow-C_RltiK5.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wrench-DovaG_ze.js → wrench-D-wLfeu0.js} +1 -1
- package/dist/assets/dql-notebook/assets/{x-nRx91AgW.js → x-B28hIJIC.js} +1 -1
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/commands/agent-eval-cassette.d.ts +97 -6
- package/dist/commands/agent-eval-cassette.d.ts.map +1 -1
- package/dist/commands/agent-eval-cassette.js +165 -22
- package/dist/commands/agent-eval-cassette.js.map +1 -1
- package/dist/commands/agent-eval-runtime.d.ts +14 -2
- package/dist/commands/agent-eval-runtime.d.ts.map +1 -1
- package/dist/commands/agent-eval-runtime.js +12 -2
- package/dist/commands/agent-eval-runtime.js.map +1 -1
- package/dist/commands/agent.d.ts +9 -3
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +126 -49
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/compile.d.ts +13 -1
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +36 -4
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +11 -3
- package/dist/commands/sync.js.map +1 -1
- package/dist/llm/providers/dql-agent-provider.d.ts +28 -1
- package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
- package/dist/llm/providers/dql-agent-provider.js +272 -22
- package/dist/llm/providers/dql-agent-provider.js.map +1 -1
- package/dist/llm/types.d.ts +29 -1
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/local-runtime.d.ts +16 -1
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +824 -66
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/package.json +10 -10
- package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel--oxmjlgr.js +0 -88
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { deadlineScale, planAnalystTurn, rerankCandidates } from '@duckcodeailabs/dql-agent';
|
|
1
|
+
import { classifyProviderFailure, deadlineScale, planAnalystTurn, rerankCandidates, } from '@duckcodeailabs/dql-agent';
|
|
2
2
|
import { buildAnalystLoopTools } from '../analyst-loop-tools.js';
|
|
3
|
-
import { ClaudeProvider, KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, GeminiProvider, loadAgentSemanticLayer, OllamaProvider, OpenAIProvider, answer, buildAnalysisQuestionPlan, buildLocalContextPack, contextRetrievalBudgetForQuestion, ensureAgentProjectReady, isLikelyClarificationReply, isTrustedConversationTurn, createProviderDispatchEgressReceipt, createProviderEgressReceipt, assertProviderPayloadAllowed, prepareProviderContextForDispatch, prepareProviderWireEnvelopeForDispatch, prepareServerOwnedProviderSchemaContext, projectEmbeddingProvider, answerAgentic, qualifyAuthorizationReferences, createAnalystLaneHandler, parseProposal, renderContextValidationRefusalForUser, validateSqlAgainstLocalContext, resolveOrchestratorPolicy, } from '@duckcodeailabs/dql-agent';
|
|
4
|
-
import { CassetteStore, resolveCassetteModeFromEnv, withCassette } from '../../commands/agent-eval-cassette.js';
|
|
3
|
+
import { ClaudeProvider, KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, GeminiProvider, loadAgentSemanticLayer, OllamaProvider, OpenAIProvider, answer, buildAnalysisQuestionPlan, buildLocalContextPack, contextRetrievalBudgetForQuestion, ensureAgentProjectReady, isLikelyClarificationReply, isTrustedConversationTurn, createProviderDispatchEgressReceipt, createProviderEgressReceipt, assertProviderPayloadAllowed, prepareProviderContextForDispatch, prepareProviderWireEnvelopeForDispatch, prepareServerOwnedProviderSchemaContext, projectEmbeddingProvider, answerAgentic, qualifyAuthorizationReferences, scopeContextPackToExploratoryCandidateClosure, createAnalystLaneHandler, parseProposal, renderContextValidationRefusalForUser, validateSqlAgainstLocalContext, resolveOrchestratorPolicy, } from '@duckcodeailabs/dql-agent';
|
|
4
|
+
import { CassetteStore, evalCassetteCanonicalizationV2, resolveCassetteModeFromEnv, withCassette, } from '../../commands/agent-eval-cassette.js';
|
|
5
5
|
import { buildManifest, normalizeDqlArtifactReference, resolveDbtManifestPath } from '@duckcodeailabs/dql-core';
|
|
6
|
-
import {
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
7
8
|
import { join } from 'node:path';
|
|
8
9
|
import { buildAnswerLoopTools, createGroundingContextExpander } from '../answer-loop-tools.js';
|
|
9
10
|
import { getSemanticRuntimeStatus } from '../../semantic-runtime.js';
|
|
@@ -75,6 +76,39 @@ const SPECS = {
|
|
|
75
76
|
},
|
|
76
77
|
},
|
|
77
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* Capture a content-free provider failure at the closest boundary. Local
|
|
81
|
+
* runtime may later choose a user-facing headline, but must not need raw
|
|
82
|
+
* provider errors or URLs to reconstruct the cause.
|
|
83
|
+
*/
|
|
84
|
+
function providerBoundaryDiagnostic(input) {
|
|
85
|
+
const config = getEffectiveProviderConfig(input.projectRoot, input.providerId);
|
|
86
|
+
const message = input.error instanceof Error ? input.error.message : String(input.error ?? 'provider readiness failed');
|
|
87
|
+
const code = input.code
|
|
88
|
+
?? (input.error && typeof input.error === 'object' ? String(input.error.code ?? '') : '');
|
|
89
|
+
const fingerprint = (value) => value?.trim()
|
|
90
|
+
? `sha256:${createHash('sha256').update(value.trim()).digest('hex')}`
|
|
91
|
+
: undefined;
|
|
92
|
+
let origin;
|
|
93
|
+
if (config.baseUrl) {
|
|
94
|
+
try {
|
|
95
|
+
origin = new URL(config.baseUrl).origin;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// The full malformed URL never leaves this function; its fingerprint is
|
|
99
|
+
// still a useful support correlation key without disclosing content.
|
|
100
|
+
origin = config.baseUrl;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return classifyProviderFailure({
|
|
104
|
+
code,
|
|
105
|
+
message,
|
|
106
|
+
phase: input.phase,
|
|
107
|
+
providerFingerprint: fingerprint(input.providerId),
|
|
108
|
+
modelFingerprint: fingerprint(config.model),
|
|
109
|
+
baseOriginFingerprint: fingerprint(origin),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
78
112
|
function createCertifiedFitConfirmation(provider, signal) {
|
|
79
113
|
return async ({ question, questionPlan, block, fit }) => {
|
|
80
114
|
const response = await provider.generate([
|
|
@@ -228,6 +262,40 @@ function stringArray(value) {
|
|
|
228
262
|
function stringValue(value) {
|
|
229
263
|
return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
|
|
230
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* A precomputed exploratory pack is an optimization/witness only. The runner
|
|
267
|
+
* independently derives the pack from the immutable router-selected IDs and
|
|
268
|
+
* accepts the witness only when its snapshot, source fingerprint, physical
|
|
269
|
+
* relations, and metadata object set are exactly the same. This prevents a
|
|
270
|
+
* caller from replacing a safe same-snapshot closure with a broader one.
|
|
271
|
+
*/
|
|
272
|
+
function exploratoryClosureMatches(derived, witness) {
|
|
273
|
+
if (!derived)
|
|
274
|
+
return false;
|
|
275
|
+
if (derived.knowledgeLens.snapshotId !== witness.knowledgeLens.snapshotId)
|
|
276
|
+
return false;
|
|
277
|
+
if (derived.freshness.fingerprint !== witness.freshness.fingerprint)
|
|
278
|
+
return false;
|
|
279
|
+
const normalize = (value) => value
|
|
280
|
+
.trim()
|
|
281
|
+
.split('.')
|
|
282
|
+
.map((part) => part.trim().replace(/^["`\[]|["`\]]$/g, '').toLowerCase())
|
|
283
|
+
.filter(Boolean)
|
|
284
|
+
.join('.');
|
|
285
|
+
const sameSet = (left, right) => {
|
|
286
|
+
if (left.length !== right.length)
|
|
287
|
+
return false;
|
|
288
|
+
const rightSet = new Set(right);
|
|
289
|
+
return left.every((value) => rightSet.has(value));
|
|
290
|
+
};
|
|
291
|
+
const derivedRelations = [...new Set(derived.allowedSqlContext.relations.map((relation) => normalize(relation.relation)))].sort();
|
|
292
|
+
const witnessRelations = [...new Set(witness.allowedSqlContext.relations.map((relation) => normalize(relation.relation)))].sort();
|
|
293
|
+
if (!sameSet(derivedRelations, witnessRelations))
|
|
294
|
+
return false;
|
|
295
|
+
const derivedObjects = [...new Set(derived.objects.map((object) => object.objectKey))].sort();
|
|
296
|
+
const witnessObjects = [...new Set(witness.objects.map((object) => object.objectKey))].sort();
|
|
297
|
+
return sameSet(derivedObjects, witnessObjects);
|
|
298
|
+
}
|
|
231
299
|
function truncateForFitPrompt(value, max) {
|
|
232
300
|
if (!value)
|
|
233
301
|
return undefined;
|
|
@@ -279,16 +347,27 @@ function emitProposalFromText(text, emit) {
|
|
|
279
347
|
* this one (local-runtime.ts:164), so reaching back would close an import cycle
|
|
280
348
|
* through a 34k-line file. A few lines of JSON reading is the cheaper trade.
|
|
281
349
|
*
|
|
282
|
-
* Cached
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* route real questions onto an
|
|
350
|
+
* Cached by a lightweight file fingerprint, not forever. Settings writes must
|
|
351
|
+
* take effect for the next Ask without requiring a notebook-server restart.
|
|
352
|
+
* A malformed file resolves to `null`, which `resolveOrchestratorPolicy` turns
|
|
353
|
+
* into `legacy` — a broken config must not route real questions onto an
|
|
354
|
+
* unproven path.
|
|
286
355
|
*/
|
|
287
356
|
const agentConfigCache = new Map();
|
|
357
|
+
function agentConfigFingerprint(projectRoot) {
|
|
358
|
+
try {
|
|
359
|
+
const stats = statSync(join(projectRoot, 'dql.config.json'));
|
|
360
|
+
return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}`;
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return 'missing';
|
|
364
|
+
}
|
|
365
|
+
}
|
|
288
366
|
function readAgentConfig(projectRoot) {
|
|
367
|
+
const fingerprint = agentConfigFingerprint(projectRoot);
|
|
289
368
|
const cached = agentConfigCache.get(projectRoot);
|
|
290
|
-
if (cached
|
|
291
|
-
return cached;
|
|
369
|
+
if (cached && cached.fingerprint === fingerprint)
|
|
370
|
+
return cached.value;
|
|
292
371
|
let resolved = null;
|
|
293
372
|
try {
|
|
294
373
|
const raw = readFileSync(join(projectRoot, 'dql.config.json'), 'utf-8');
|
|
@@ -300,7 +379,7 @@ function readAgentConfig(projectRoot) {
|
|
|
300
379
|
catch {
|
|
301
380
|
resolved = null;
|
|
302
381
|
}
|
|
303
|
-
agentConfigCache.set(projectRoot, resolved);
|
|
382
|
+
agentConfigCache.set(projectRoot, { fingerprint, value: resolved });
|
|
304
383
|
return resolved;
|
|
305
384
|
}
|
|
306
385
|
function readOrchestratorConfig(projectRoot) {
|
|
@@ -355,11 +434,43 @@ function valueLookupEnabled(projectRoot) {
|
|
|
355
434
|
function agenticLaneForRequest(req) {
|
|
356
435
|
return req.orchestrationMode === 'research' ? 'research' : 'generated';
|
|
357
436
|
}
|
|
358
|
-
export function applyEvalCassette(provider) {
|
|
437
|
+
export function applyEvalCassette(provider, projectRoot) {
|
|
359
438
|
const dir = process.env.DQL_EVAL_CASSETTE_DIR;
|
|
360
439
|
if (!dir)
|
|
361
440
|
return provider;
|
|
362
|
-
return withCassette(provider, new CassetteStore(dir), resolveCassetteModeFromEnv(process.env));
|
|
441
|
+
return withCassette(provider, new CassetteStore(dir), resolveCassetteModeFromEnv(process.env), evalCassetteCanonicalizationV2(projectRoot));
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Create a replay-only provider when the runtime is launched for an offline
|
|
445
|
+
* evaluation. This is intentionally unavailable outside explicit cassette
|
|
446
|
+
* replay: recording and live modes still require a configured real provider.
|
|
447
|
+
*
|
|
448
|
+
* The cassette's recorded provider identity is part of its key. Recover it
|
|
449
|
+
* from a single-provider cassette directory instead of borrowing a user's
|
|
450
|
+
* active provider or guessing from an API setting. The base provider cannot
|
|
451
|
+
* make a network call; replay misses remain CassetteMissError failures.
|
|
452
|
+
*/
|
|
453
|
+
export function createEvalCassetteReplayProvider(projectRoot) {
|
|
454
|
+
const dir = process.env.DQL_EVAL_CASSETTE_DIR;
|
|
455
|
+
if (!dir || resolveCassetteModeFromEnv(process.env) !== 'replay')
|
|
456
|
+
return undefined;
|
|
457
|
+
const store = new CassetteStore(dir);
|
|
458
|
+
const providerNames = store.providerNames();
|
|
459
|
+
const providerName = providerNames.length === 1 ? asAgentProviderName(providerNames[0]) : undefined;
|
|
460
|
+
if (!providerName)
|
|
461
|
+
return undefined;
|
|
462
|
+
return withCassette({
|
|
463
|
+
name: providerName,
|
|
464
|
+
available: async () => true,
|
|
465
|
+
generate: async () => {
|
|
466
|
+
throw new Error('Eval cassette replay miss: no live provider is available.');
|
|
467
|
+
},
|
|
468
|
+
}, store, 'replay', evalCassetteCanonicalizationV2(projectRoot));
|
|
469
|
+
}
|
|
470
|
+
function asAgentProviderName(value) {
|
|
471
|
+
return value === 'claude' || value === 'openai' || value === 'gemini' || value === 'ollama'
|
|
472
|
+
? value
|
|
473
|
+
: undefined;
|
|
363
474
|
}
|
|
364
475
|
/**
|
|
365
476
|
* A raw text provider for planning calls that are not the answer itself.
|
|
@@ -373,7 +484,7 @@ export function createGovernedTextProvider(id, projectRoot) {
|
|
|
373
484
|
if (!spec)
|
|
374
485
|
return undefined;
|
|
375
486
|
try {
|
|
376
|
-
return applyEvalCassette(spec.create(projectRoot));
|
|
487
|
+
return applyEvalCassette(spec.create(projectRoot), projectRoot);
|
|
377
488
|
}
|
|
378
489
|
catch {
|
|
379
490
|
return undefined;
|
|
@@ -383,7 +494,7 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
383
494
|
return {
|
|
384
495
|
async run(req, emit, signal) {
|
|
385
496
|
const spec = SPECS[id];
|
|
386
|
-
const rawProvider = applyEvalCassette(providerOverride ?? spec.create(req.projectRoot));
|
|
497
|
+
const rawProvider = applyEvalCassette(providerOverride ?? spec.create(req.projectRoot), req.projectRoot);
|
|
387
498
|
const isResearch = req.orchestrationMode === 'research';
|
|
388
499
|
const maxProviderDispatches = isResearch ? 8 : 4;
|
|
389
500
|
const researchRowsOptIn = isResearch && req.researchResultRowsOptIn === true;
|
|
@@ -483,15 +594,41 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
483
594
|
return envelope;
|
|
484
595
|
},
|
|
485
596
|
});
|
|
597
|
+
// One retry only, on the SAME configured provider and only for failures
|
|
598
|
+
// that are normally transient. The physical-dispatch observer remains in
|
|
599
|
+
// the path for both attempts, so a retry cannot exceed the run-wide
|
|
600
|
+
// dispatch budget or silently fail over to another provider.
|
|
601
|
+
let transientRetryUsed = false;
|
|
602
|
+
const mayRetrySameProvider = (error) => {
|
|
603
|
+
if (signal.aborted || transientRetryUsed)
|
|
604
|
+
return false;
|
|
605
|
+
const code = error && typeof error === 'object' ? String(error.code ?? '') : '';
|
|
606
|
+
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
607
|
+
const diagnostic = `${code} ${message}`;
|
|
608
|
+
return !/PROVIDER_DISPATCH_BUDGET|RUN_SOFT_TARGET|RUN_DEADLINE|PROJECT_SNAPSHOT|abort|cancel/i.test(diagnostic)
|
|
609
|
+
&& /(?:429|rate limit|too many requests|502|503|504|gateway|econn|network|fetch failed|connection refused|timeout|timed out)/i.test(diagnostic);
|
|
610
|
+
};
|
|
611
|
+
const retrySameProviderOnce = async (operation) => {
|
|
612
|
+
try {
|
|
613
|
+
return await operation();
|
|
614
|
+
}
|
|
615
|
+
catch (error) {
|
|
616
|
+
if (!mayRetrySameProvider(error))
|
|
617
|
+
throw error;
|
|
618
|
+
transientRetryUsed = true;
|
|
619
|
+
emit({ kind: 'thinking', text: 'The configured AI provider had a transient error; retrying it once within this run budget.' });
|
|
620
|
+
return operation();
|
|
621
|
+
}
|
|
622
|
+
};
|
|
486
623
|
const provider = {
|
|
487
624
|
name: rawProvider.name,
|
|
488
625
|
available: () => rawProvider.available(),
|
|
489
626
|
generate: (...args) => {
|
|
490
|
-
return rawProvider.generate(args[0], withPhysicalDispatchObserver(args[1]));
|
|
627
|
+
return retrySameProviderOnce(() => rawProvider.generate(args[0], withPhysicalDispatchObserver(args[1])));
|
|
491
628
|
},
|
|
492
629
|
...(rawProvider.generateWithTools ? {
|
|
493
630
|
generateWithTools: (...args) => {
|
|
494
|
-
return rawProvider.generateWithTools(args[0], args[1], withPhysicalDispatchObserver(args[2]));
|
|
631
|
+
return retrySameProviderOnce(() => rawProvider.generateWithTools(args[0], args[1], withPhysicalDispatchObserver(args[2])));
|
|
495
632
|
},
|
|
496
633
|
} : {}),
|
|
497
634
|
...(rawProvider.generateStream ? {
|
|
@@ -502,7 +639,20 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
502
639
|
};
|
|
503
640
|
const available = await provider.available().catch(() => false);
|
|
504
641
|
if (!available) {
|
|
505
|
-
|
|
642
|
+
const message = `${spec.label} is not configured or reachable. ${spec.setup}`;
|
|
643
|
+
emit({
|
|
644
|
+
kind: 'error',
|
|
645
|
+
message,
|
|
646
|
+
providerDiagnostic: providerBoundaryDiagnostic({
|
|
647
|
+
providerId: id,
|
|
648
|
+
projectRoot: req.projectRoot,
|
|
649
|
+
phase: 'preflight',
|
|
650
|
+
error: message,
|
|
651
|
+
// Local Ollama readiness is a reachability concern; configured
|
|
652
|
+
// remote providers missing credentials are authentication issues.
|
|
653
|
+
code: id === 'ollama' ? 'NETWORK_FAILURE' : 'AUTHENTICATION_FAILED',
|
|
654
|
+
}),
|
|
655
|
+
});
|
|
506
656
|
return;
|
|
507
657
|
}
|
|
508
658
|
try {
|
|
@@ -633,6 +783,63 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
633
783
|
const schemaContext = req.getSchemaContext && shouldLoadSchemaContext(contextPack, Boolean(semanticLayer))
|
|
634
784
|
? await req.getSchemaContext(question, contextPack).catch(() => [])
|
|
635
785
|
: [];
|
|
786
|
+
// The router's exploratory candidate IDs are server-owned execution
|
|
787
|
+
// authority. Once that tier is selected, provider prompt/schema
|
|
788
|
+
// context must be the candidate closure rather than the broad
|
|
789
|
+
// retrieval pack. The latter remains available to the host for
|
|
790
|
+
// receipts only and cannot be used to introduce another relation.
|
|
791
|
+
const forcedExploratoryTier = req.selectedCascadeTier === 'exploratory_sql';
|
|
792
|
+
// Re-derive the closure from the broad immutable pack and the
|
|
793
|
+
// router-selected IDs. `preparedExploratoryContextPack` is only a
|
|
794
|
+
// server-side consistency witness; it cannot override or widen the
|
|
795
|
+
// derivation even if a future caller constructs an AgentRunner
|
|
796
|
+
// request directly.
|
|
797
|
+
const derivedExploratoryContextPack = forcedExploratoryTier
|
|
798
|
+
? scopeContextPackToExploratoryCandidateClosure(contextPack, req.exploratoryCandidateIds)
|
|
799
|
+
: undefined;
|
|
800
|
+
const closureWitnessMatches = !req.preparedExploratoryContextPack
|
|
801
|
+
|| exploratoryClosureMatches(derivedExploratoryContextPack, req.preparedExploratoryContextPack);
|
|
802
|
+
const exploratoryContextPack = closureWitnessMatches
|
|
803
|
+
? derivedExploratoryContextPack
|
|
804
|
+
: undefined;
|
|
805
|
+
if (forcedExploratoryTier && (!exploratoryContextPack || !req.exploratoryCandidateIds?.length)) {
|
|
806
|
+
const text = 'The router-selected exploratory path no longer has a complete same-snapshot physical closure, so DQL did not send SQL generation or execute a query.';
|
|
807
|
+
emit({
|
|
808
|
+
kind: 'tool_result',
|
|
809
|
+
id: 'governed_answer',
|
|
810
|
+
output: {
|
|
811
|
+
kind: 'no_answer',
|
|
812
|
+
sourceTier: 'no_answer',
|
|
813
|
+
certification: 'analyst_review_required',
|
|
814
|
+
reviewStatus: 'none',
|
|
815
|
+
confidence: 0,
|
|
816
|
+
text,
|
|
817
|
+
answer: text,
|
|
818
|
+
refusalCode: 'grounding_gap',
|
|
819
|
+
refusalDetails: {
|
|
820
|
+
code: closureWitnessMatches ? 'EXPLORATORY_CLOSURE_UNAVAILABLE' : 'EXPLORATORY_CLOSURE_MISMATCH',
|
|
821
|
+
message: text,
|
|
822
|
+
},
|
|
823
|
+
contextPack,
|
|
824
|
+
providerUsed: provider.name,
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const answerContextPack = forcedExploratoryTier
|
|
830
|
+
? exploratoryContextPack ?? contextPack
|
|
831
|
+
: contextPack;
|
|
832
|
+
const normalizeQualifiedRelation = (value) => value
|
|
833
|
+
.trim()
|
|
834
|
+
.split('.')
|
|
835
|
+
.map((part) => part.trim().replace(/^["`\[]|["`\]]$/g, '').toLowerCase())
|
|
836
|
+
.filter(Boolean)
|
|
837
|
+
.join('.');
|
|
838
|
+
const closureRelations = new Set((exploratoryContextPack?.allowedSqlContext.relations ?? [])
|
|
839
|
+
.map((relation) => normalizeQualifiedRelation(relation.relation)));
|
|
840
|
+
const answerSchemaContext = forcedExploratoryTier && closureRelations.size > 0
|
|
841
|
+
? schemaContext.filter((table) => closureRelations.has(normalizeQualifiedRelation(table.relation)))
|
|
842
|
+
: schemaContext;
|
|
636
843
|
const schemaDurationMs = Date.now() - schemaStartedAt;
|
|
637
844
|
const selectedBlockHints = shouldUseSelectedBlockHint(req, question, followUp)
|
|
638
845
|
? extractSelectedBlockHints(req)
|
|
@@ -655,8 +862,8 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
655
862
|
question,
|
|
656
863
|
...(conversationSnapshot ? { conversationSnapshot } : {}),
|
|
657
864
|
...(memoryContext ? { memoryContext } : {}),
|
|
658
|
-
schemaContext: prepareServerOwnedProviderSchemaContext(
|
|
659
|
-
...(
|
|
865
|
+
schemaContext: prepareServerOwnedProviderSchemaContext(answerSchemaContext),
|
|
866
|
+
...(answerContextPack ? { contextPack: answerContextPack } : {}),
|
|
660
867
|
skills,
|
|
661
868
|
...(followUp ? { followUp } : {}),
|
|
662
869
|
}), {
|
|
@@ -672,6 +879,12 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
672
879
|
...(req.resolvedAnalyticalPlan
|
|
673
880
|
? { resolvedAnalyticalPlan: req.resolvedAnalyticalPlan }
|
|
674
881
|
: {}),
|
|
882
|
+
...(req.selectedCascadeTier
|
|
883
|
+
? { selectedCascadeTier: req.selectedCascadeTier }
|
|
884
|
+
: {}),
|
|
885
|
+
...(req.exploratoryCandidateIds?.length
|
|
886
|
+
? { exploratoryCandidateIds: [...req.exploratoryCandidateIds] }
|
|
887
|
+
: {}),
|
|
675
888
|
...(req.generatedProposalTargetFingerprint
|
|
676
889
|
? { generatedProposalTargetFingerprint: req.generatedProposalTargetFingerprint }
|
|
677
890
|
: {}),
|
|
@@ -692,7 +905,7 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
692
905
|
followUp,
|
|
693
906
|
conversationSnapshot,
|
|
694
907
|
memoryContext,
|
|
695
|
-
schemaContext,
|
|
908
|
+
schemaContext: answerSchemaContext,
|
|
696
909
|
semanticLayer,
|
|
697
910
|
// Runtime-aware executability for metric SELECTION: with a full
|
|
698
911
|
// semantic runtime active (dbt Cloud / MetricFlow CLI) every
|
|
@@ -704,7 +917,7 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
704
917
|
canExecuteSemanticMetric: (metricName) => semanticRuntimeActive !== 'native' || semanticLayer.canComposeMetric(metricName),
|
|
705
918
|
}
|
|
706
919
|
: {}),
|
|
707
|
-
contextPack,
|
|
920
|
+
contextPack: answerContextPack,
|
|
708
921
|
// The project's configured embedder (dql.config.json ai.embeddings).
|
|
709
922
|
// Without it, matchSemanticMetric falls back to the offline hashed
|
|
710
923
|
// provider, whose vectors can never ground a match on similarity
|
|
@@ -726,6 +939,28 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
726
939
|
executeGeneratedSql: req.executeGeneratedSql
|
|
727
940
|
? async (...args) => { guardSnapshot(); sqlExecutions += 1; return req.executeGeneratedSql(...args); }
|
|
728
941
|
: undefined,
|
|
942
|
+
prepareExploratorySqlExecution: req.prepareExploratorySqlExecution
|
|
943
|
+
? async (sql, ...args) => {
|
|
944
|
+
guardSnapshot();
|
|
945
|
+
// The execution host repeats this validation immediately
|
|
946
|
+
// before capability minting. Keep the same exact-qualified
|
|
947
|
+
// closure check here as well, before the provider runner can
|
|
948
|
+
// even invoke that host boundary. A provider response cannot
|
|
949
|
+
// use another same-snapshot relation merely because it was
|
|
950
|
+
// present in broad retrieval diagnostics.
|
|
951
|
+
if (forcedExploratoryTier && exploratoryContextPack) {
|
|
952
|
+
const proposalValidation = validateSqlAgainstLocalContext(sql, exploratoryContextPack, {
|
|
953
|
+
runtimeSchema: answerSchemaContext,
|
|
954
|
+
});
|
|
955
|
+
const outsideClosure = !proposalValidation.ok
|
|
956
|
+
|| proposalValidation.referencedRelations.some((relation) => !closureRelations.has(normalizeQualifiedRelation(relation)));
|
|
957
|
+
if (outsideClosure) {
|
|
958
|
+
throw Object.assign(new Error('The generated SQL references a relation outside the router-selected physical closure, so it was not executed.'), { code: 'UNAUTHORIZED_SQL' });
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return req.prepareExploratorySqlExecution(sql, ...args);
|
|
962
|
+
}
|
|
963
|
+
: undefined,
|
|
729
964
|
executeAgenticGeneratedSql: req.executeAgenticGeneratedSql
|
|
730
965
|
? async (capability, sql, artifact) => {
|
|
731
966
|
guardSnapshot();
|
|
@@ -985,6 +1220,19 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
985
1220
|
dispatchEvidence: dispatchEvidence(orchestrationBudgetExhausted ? 'orchestration_budget_exhausted'
|
|
986
1221
|
: snapshotDrift ? 'project_snapshot_mismatch'
|
|
987
1222
|
: 'provider_error'),
|
|
1223
|
+
providerDiagnostic: providerBoundaryDiagnostic({
|
|
1224
|
+
providerId: id,
|
|
1225
|
+
projectRoot: req.projectRoot,
|
|
1226
|
+
phase: orchestrationBudgetExhausted
|
|
1227
|
+
? 'planning'
|
|
1228
|
+
: 'generation',
|
|
1229
|
+
error: err,
|
|
1230
|
+
code: orchestrationBudgetExhausted
|
|
1231
|
+
? 'PROVIDER_DISPATCH_BUDGET'
|
|
1232
|
+
: snapshotDrift
|
|
1233
|
+
? 'ADMISSION_DENIED'
|
|
1234
|
+
: budgetCode,
|
|
1235
|
+
}),
|
|
988
1236
|
});
|
|
989
1237
|
}
|
|
990
1238
|
},
|
|
@@ -2088,6 +2336,8 @@ export const __test__ = {
|
|
|
2088
2336
|
shouldSearchProjectFiles,
|
|
2089
2337
|
renderProjectSourceSearch,
|
|
2090
2338
|
researchDispatchPurposeForTool,
|
|
2339
|
+
readAgentConfig,
|
|
2340
|
+
providerBoundaryDiagnostic,
|
|
2091
2341
|
};
|
|
2092
2342
|
function researchDispatchPurposeForTool(toolName) {
|
|
2093
2343
|
return toolName === 'execute_local_analysis' ? 'research_tool' : 'research_narration';
|