@duckcodeailabs/dql-cli 1.14.0 → 1.14.1
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/args.d.ts +15 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +25 -0
- package/dist/args.js.map +1 -1
- package/dist/assets/dql-notebook/assets/{AgentLogPage-Ch7VK20X.js → AgentLogPage-BPz-UWFh.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildDialog-DBr5TmyM.js → AiBuildDialog-BEl53WA_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildResult-jLPzQO7O.js → AiBuildResult-B4yfGTTZ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiSidePanel-CdlGsiVC.js → AiSidePanel-CSZAAvuD.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AnalyticsHome-C0DXbOwY.js → AnalyticsHome-D5P6Ujwi.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AppsView-CcpwjApv.js → AppsView-DQOwU9Cg.js} +4 -4
- package/dist/assets/dql-notebook/assets/{BlockStudio-CFYxafw-.js → BlockStudio-B4ap0GdY.js} +1 -1
- package/dist/assets/dql-notebook/assets/{BusinessArtifactView-C0kLYg2p.js → BusinessArtifactView-BIfNI-0S.js} +1 -1
- package/dist/assets/dql-notebook/assets/{DbtFirstModelingPage-CMwElXD_.js → DbtFirstModelingPage-D72byb2g.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GitPage-JjhRDeWY.js → GitPage-lQb1uXH2.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GlobalAiRail-DakE4NdR.js → GlobalAiRail-CVECf6Xj.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GovernedContextPage-BokDqG6a.js → GovernedContextPage-trOyMCY6.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HelpDocsPage-CjOv6_gz.js → HelpDocsPage-D8hLS5lE.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HomePage-nGaNcwdw.js → HomePage-eIkfBIep.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDAG-CO6CFJRg.js → LineageDAG-CSqcbDrE.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDetailView-BnGb7OF7.js → LineageDetailView-DJZZjZu-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDrawer-C5Y0Ht0b.js → LineageDrawer-BcIipIc3.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineagePathBreadcrumb-Cgh3GR3F.js → LineagePathBreadcrumb-CviIf8PN.js} +1 -1
- package/dist/assets/dql-notebook/assets/{MiniLineageGraph-Kla9PYuj.js → MiniLineageGraph-vH_MY_Ju.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewBlockModal-BR2SnmPT.js → NewBlockModal-DbCQg-pj.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewNotebookModal-BaCHMYeB.js → NewNotebookModal-5Vp6XiuK.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NotebookEditor-CBLqY8cE.js → NotebookEditor-DjqJS44s.js} +1 -1
- package/dist/assets/dql-notebook/assets/{ReadinessPage-CiN0IWSS.js → ReadinessPage-BHGrC5ho.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SetupOnboarding-BhiCsYF-.js → SetupOnboarding-BfS9Tdx-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SkillsPage-CCSf8VMm.js → SkillsPage-BFH21wSj.js} +1 -1
- package/dist/assets/dql-notebook/assets/{TrustBadge-BgQmFe_x.js → TrustBadge-zm6g_SxZ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{UnifiedAgentRunPanel-Bw5AXNDB.js → UnifiedAgentRunPanel--oxmjlgr.js} +21 -21
- package/dist/assets/dql-notebook/assets/{answer-to-notebook-AeDYUDla.js → answer-to-notebook-DPhxIEzF.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-left-C55x2hq_.js → arrow-left-DNEb86Xc.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-right-C1cJrhOm.js → arrow-right-DpPWbwaD.js} +1 -1
- package/dist/assets/dql-notebook/assets/{book-open-text-CQf_sdv2.js → book-open-text-D7s5jo4X.js} +1 -1
- package/dist/assets/dql-notebook/assets/{circle-x-X8-Z2yLY.js → circle-x-Db3dXKg7.js} +1 -1
- package/dist/assets/dql-notebook/assets/{dagre.esm-BjjNYKyY.js → dagre.esm-C7pppQ1a.js} +1 -1
- package/dist/assets/dql-notebook/assets/{external-link-C2zrz5DH.js → external-link-BxwXitO_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{grip-vertical-qGV_PYGU.js → grip-vertical-Dt4lkWRi.js} +1 -1
- package/dist/assets/dql-notebook/assets/{index-ByTDPDaH.js → index-DKo-bwNw.js} +2 -2
- package/dist/assets/dql-notebook/assets/{link-2-VpyOxQXG.js → link-2-Dfo2P6wi.js} +1 -1
- package/dist/assets/dql-notebook/assets/{list-tree-CH2Jhwms.js → list-tree-DiTmIWAL.js} +1 -1
- package/dist/assets/dql-notebook/assets/{minimize-2-B2TZJ8BT.js → minimize-2-CMTAkPzL.js} +1 -1
- package/dist/assets/dql-notebook/assets/{panel-right-open-BunF88lt.js → panel-right-open-DwYr7FW4.js} +1 -1
- package/dist/assets/dql-notebook/assets/{play-DAFVF4_G.js → play-BXhHYQ4x.js} +1 -1
- package/dist/assets/dql-notebook/assets/{rotate-ccw-DGCrrqtY.js → rotate-ccw-BNi6F8pl.js} +1 -1
- package/dist/assets/dql-notebook/assets/{semantic-fields-Ci-9QL9F.js → semantic-fields-CNOGysAy.js} +1 -1
- package/dist/assets/dql-notebook/assets/{sliders-horizontal-BOAlXXbn.js → sliders-horizontal-Ec5MUMUW.js} +1 -1
- package/dist/assets/dql-notebook/assets/{star-CkksZXHt.js → star-B9leDkp_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{triangle-alert-D3mjyJZE.js → triangle-alert-BefTYCzx.js} +1 -1
- package/dist/assets/dql-notebook/assets/{upload-CTNOAVEO.js → upload-SPiOM2tQ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{usePersistedAgentThreadId-DiQjc7x-.js → usePersistedAgentThreadId-C4foXeiQ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{user-round-BWd5tQRg.js → user-round-comGmyw-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wand-sparkles-CqsAv8P-.js → wand-sparkles-BffR4dF8.js} +1 -1
- package/dist/assets/dql-notebook/assets/{workflow-CSqsj-sC.js → workflow-ChmPTEzH.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wrench-DWqzqlX8.js → wrench-DovaG_ze.js} +1 -1
- package/dist/assets/dql-notebook/assets/{x-65M5rLCE.js → x-nRx91AgW.js} +1 -1
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/commands/agent-eval-cassette.d.ts +73 -0
- package/dist/commands/agent-eval-cassette.d.ts.map +1 -0
- package/dist/commands/agent-eval-cassette.js +170 -0
- package/dist/commands/agent-eval-cassette.js.map +1 -0
- package/dist/commands/agent-eval-runtime.d.ts +97 -0
- package/dist/commands/agent-eval-runtime.d.ts.map +1 -0
- package/dist/commands/agent-eval-runtime.js +155 -0
- package/dist/commands/agent-eval-runtime.js.map +1 -0
- package/dist/commands/agent.d.ts +115 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +289 -62
- package/dist/commands/agent.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/analyst-loop-tools.d.ts +19 -0
- package/dist/llm/analyst-loop-tools.d.ts.map +1 -0
- package/dist/llm/analyst-loop-tools.js +56 -0
- package/dist/llm/analyst-loop-tools.js.map +1 -0
- package/dist/llm/providers/dql-agent-provider.d.ts +24 -1
- package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
- package/dist/llm/providers/dql-agent-provider.js +306 -7
- package/dist/llm/providers/dql-agent-provider.js.map +1 -1
- package/dist/llm/types.d.ts +19 -1
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/local-runtime.d.ts +108 -1
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +626 -110
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/package.json +10 -10
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deadlineScale, planAnalystTurn, rerankCandidates } from '@duckcodeailabs/dql-agent';
|
|
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';
|
|
2
5
|
import { buildManifest, normalizeDqlArtifactReference, resolveDbtManifestPath } from '@duckcodeailabs/dql-core';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
4
8
|
import { buildAnswerLoopTools, createGroundingContextExpander } from '../answer-loop-tools.js';
|
|
5
9
|
import { getSemanticRuntimeStatus } from '../../semantic-runtime.js';
|
|
6
10
|
import { blockProposalDqlMetadata } from '../proposal-metadata.js';
|
|
@@ -257,12 +261,131 @@ function emitProposalFromText(text, emit) {
|
|
|
257
261
|
// Ignore malformed proposal text. The visible assistant response still streams as text.
|
|
258
262
|
}
|
|
259
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Route the runtime's own provider through an eval cassette when the host asks.
|
|
266
|
+
*
|
|
267
|
+
* The client-side cassette in `dql agent eval` only covers `--via loop`: with
|
|
268
|
+
* `--via runtime` the SERVER owns the provider, so without this hook the one
|
|
269
|
+
* driver that actually exercises routing and gates could never be made
|
|
270
|
+
* deterministic — and a suite that cannot be deterministic cannot gate a PR.
|
|
271
|
+
*
|
|
272
|
+
* Opt-in through the environment, never through a request field: a caller must
|
|
273
|
+
* not be able to redirect a production run onto recorded responses.
|
|
274
|
+
*/
|
|
275
|
+
/**
|
|
276
|
+
* Read `agent.orchestrator` straight from dql.config.json.
|
|
277
|
+
*
|
|
278
|
+
* Deliberately NOT `loadProjectConfig` from local-runtime: that module imports
|
|
279
|
+
* this one (local-runtime.ts:164), so reaching back would close an import cycle
|
|
280
|
+
* through a 34k-line file. A few lines of JSON reading is the cheaper trade.
|
|
281
|
+
*
|
|
282
|
+
* Cached per project root because this runs on every turn and the answer is a
|
|
283
|
+
* migration setting, not live state. A malformed file resolves to `null`, which
|
|
284
|
+
* `resolveOrchestratorPolicy` turns into `legacy` — a broken config must not
|
|
285
|
+
* route real questions onto an unproven path.
|
|
286
|
+
*/
|
|
287
|
+
const agentConfigCache = new Map();
|
|
288
|
+
function readAgentConfig(projectRoot) {
|
|
289
|
+
const cached = agentConfigCache.get(projectRoot);
|
|
290
|
+
if (cached !== undefined)
|
|
291
|
+
return cached;
|
|
292
|
+
let resolved = null;
|
|
293
|
+
try {
|
|
294
|
+
const raw = readFileSync(join(projectRoot, 'dql.config.json'), 'utf-8');
|
|
295
|
+
const parsed = JSON.parse(raw);
|
|
296
|
+
resolved = parsed?.agent && typeof parsed.agent === 'object'
|
|
297
|
+
? parsed.agent
|
|
298
|
+
: null;
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
resolved = null;
|
|
302
|
+
}
|
|
303
|
+
agentConfigCache.set(projectRoot, resolved);
|
|
304
|
+
return resolved;
|
|
305
|
+
}
|
|
306
|
+
function readOrchestratorConfig(projectRoot) {
|
|
307
|
+
const orchestrator = readAgentConfig(projectRoot)?.orchestrator;
|
|
308
|
+
return orchestrator && typeof orchestrator === 'object' ? orchestrator : null;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Is on-demand value lookup permitted for this project?
|
|
312
|
+
*
|
|
313
|
+
* Mirrors `resolveAgentRuntimeValueGrounding`, read locally rather than imported
|
|
314
|
+
* — local-runtime imports this module, so reaching back would close a cycle
|
|
315
|
+
* through a 34k-line file. Anything other than an explicit `safe_automatic`
|
|
316
|
+
* resolves to disabled: value lookup touches warehouse cell values, so a
|
|
317
|
+
* malformed setting must fail closed.
|
|
318
|
+
*/
|
|
319
|
+
/**
|
|
320
|
+
* Structured turn planning is OFF by default.
|
|
321
|
+
*
|
|
322
|
+
* It costs one provider dispatch before the loop makes any tool call, and on a
|
|
323
|
+
* slow provider that dispatch can consume enough of the discovery window that
|
|
324
|
+
* the loop is then refused admission and falls back to the legacy path —
|
|
325
|
+
* measured on ollama, where all three agentic dispatches planned successfully
|
|
326
|
+
* and then died at "soft target elapsed before this provider dispatch could
|
|
327
|
+
* start". Its payoff is a legible trace, and `onStep` is not wired to SSE yet,
|
|
328
|
+
* so today it buys nothing a user can see. Opt in with
|
|
329
|
+
* `agent.orchestrator.turnPlanning: true` once streaming lands.
|
|
330
|
+
*/
|
|
331
|
+
function turnPlanningEnabled(projectRoot) {
|
|
332
|
+
const orchestrator = readAgentConfig(projectRoot)?.orchestrator;
|
|
333
|
+
if (!orchestrator || typeof orchestrator !== 'object')
|
|
334
|
+
return false;
|
|
335
|
+
return orchestrator.turnPlanning === true;
|
|
336
|
+
}
|
|
337
|
+
function valueLookupEnabled(projectRoot) {
|
|
338
|
+
const grounding = readAgentConfig(projectRoot)?.runtimeValueGrounding;
|
|
339
|
+
if (!grounding || typeof grounding !== 'object')
|
|
340
|
+
return false;
|
|
341
|
+
return grounding.mode === 'safe_automatic';
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Which migration lane this turn belongs to.
|
|
345
|
+
*
|
|
346
|
+
* Research is identified by the server-resolved `orchestrationMode`, not by
|
|
347
|
+
* `analysisDepth`. A reader can ask Ask AI to think deeply without opting into
|
|
348
|
+
* the Research workflow, its wider dispatch budget, or any row-bearing tools.
|
|
349
|
+
*
|
|
350
|
+
* Deliberately coarse: the seam only needs to know which bucket a turn falls in
|
|
351
|
+
* so a lane can be enabled independently. The fine-grained triage between
|
|
352
|
+
* certified, semantic, and generated stays inside the answer path, where the
|
|
353
|
+
* retrieval evidence lives.
|
|
354
|
+
*/
|
|
355
|
+
function agenticLaneForRequest(req) {
|
|
356
|
+
return req.orchestrationMode === 'research' ? 'research' : 'generated';
|
|
357
|
+
}
|
|
358
|
+
export function applyEvalCassette(provider) {
|
|
359
|
+
const dir = process.env.DQL_EVAL_CASSETTE_DIR;
|
|
360
|
+
if (!dir)
|
|
361
|
+
return provider;
|
|
362
|
+
return withCassette(provider, new CassetteStore(dir), resolveCassetteModeFromEnv(process.env));
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* A raw text provider for planning calls that are not the answer itself.
|
|
366
|
+
*
|
|
367
|
+
* Research hypothesis planning needs `generate`, not the full agent runner —
|
|
368
|
+
* and the runner cannot be reused for it, because the runner IS the governed
|
|
369
|
+
* answer path. Cassettes apply, so a recorded run stays hermetic.
|
|
370
|
+
*/
|
|
371
|
+
export function createGovernedTextProvider(id, projectRoot) {
|
|
372
|
+
const spec = SPECS[id];
|
|
373
|
+
if (!spec)
|
|
374
|
+
return undefined;
|
|
375
|
+
try {
|
|
376
|
+
return applyEvalCassette(spec.create(projectRoot));
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
260
382
|
export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
261
383
|
return {
|
|
262
384
|
async run(req, emit, signal) {
|
|
263
385
|
const spec = SPECS[id];
|
|
264
|
-
const rawProvider = providerOverride ?? spec.create(req.projectRoot);
|
|
265
|
-
const isResearch = req.
|
|
386
|
+
const rawProvider = applyEvalCassette(providerOverride ?? spec.create(req.projectRoot));
|
|
387
|
+
const isResearch = req.orchestrationMode === 'research';
|
|
388
|
+
const maxProviderDispatches = isResearch ? 8 : 4;
|
|
266
389
|
const researchRowsOptIn = isResearch && req.researchResultRowsOptIn === true;
|
|
267
390
|
const sharedDispatchEvidence = req.providerDispatchEvidenceSink;
|
|
268
391
|
let providerRoundTrips = 0;
|
|
@@ -296,7 +419,7 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
296
419
|
// A tool-calling loop capped at two physical sends can make exactly one
|
|
297
420
|
// tool call before it must answer, which is not enough to look something
|
|
298
421
|
// up and then use it. The run-scoped ledger is the real guardrail.
|
|
299
|
-
maxProviderDispatches
|
|
422
|
+
maxProviderDispatches,
|
|
300
423
|
...(sharedDispatchEvidence?.mayStartToolCall
|
|
301
424
|
? { mayStartToolCall: () => sharedDispatchEvidence.mayStartToolCall() }
|
|
302
425
|
: {}),
|
|
@@ -439,6 +562,14 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
439
562
|
strictness: contextBudget.strictness,
|
|
440
563
|
limit: contextBudget.limit,
|
|
441
564
|
confirmCertifiedFit: createCertifiedFitConfirmation(provider, signal),
|
|
565
|
+
// A cross-encoder pass over the fused candidates. Advisory: it may
|
|
566
|
+
// only reorder what retrieval returned, and a failure or timeout
|
|
567
|
+
// leaves retrieval's ordering untouched — so it can improve the
|
|
568
|
+
// pack and cannot break it.
|
|
569
|
+
rerankCandidates: (rerankQuestion, candidates) => rerankCandidates(provider, rerankQuestion, candidates, {
|
|
570
|
+
...(signal ? { signal } : {}),
|
|
571
|
+
timeoutMs: Math.round(2_500 * deadlineScale()),
|
|
572
|
+
}),
|
|
442
573
|
// Conversation-aware reuse: same-topic follow-ups seed (or, for
|
|
443
574
|
// filter-only refinements, re-stamp) the prior turn's context pack.
|
|
444
575
|
priorContextPackId: priorContextPackIdFromSnapshot(conversationSnapshot),
|
|
@@ -533,11 +664,17 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
533
664
|
maxResultRows: 0,
|
|
534
665
|
purpose: 'answer_generation',
|
|
535
666
|
});
|
|
536
|
-
|
|
667
|
+
// The strangler seam. `answerAgentic` and `answer` are interchangeable
|
|
668
|
+
// here; which runs is a per-lane config decision that defaults to
|
|
669
|
+
// legacy, so this is a no-op until a lane is explicitly enabled.
|
|
670
|
+
const answerLoopInput = {
|
|
537
671
|
question,
|
|
538
672
|
...(req.resolvedAnalyticalPlan
|
|
539
673
|
? { resolvedAnalyticalPlan: req.resolvedAnalyticalPlan }
|
|
540
674
|
: {}),
|
|
675
|
+
...(req.generatedProposalTargetFingerprint
|
|
676
|
+
? { generatedProposalTargetFingerprint: req.generatedProposalTargetFingerprint }
|
|
677
|
+
: {}),
|
|
541
678
|
...(req.analyticalReferenceInstant
|
|
542
679
|
? { analyticalReferenceInstant: req.analyticalReferenceInstant }
|
|
543
680
|
: {}),
|
|
@@ -568,6 +705,12 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
568
705
|
}
|
|
569
706
|
: {}),
|
|
570
707
|
contextPack,
|
|
708
|
+
// The project's configured embedder (dql.config.json ai.embeddings).
|
|
709
|
+
// Without it, matchSemanticMetric falls back to the offline hashed
|
|
710
|
+
// provider, whose vectors can never ground a match on similarity
|
|
711
|
+
// alone — so a metric named only by synonym or acronym is invisible
|
|
712
|
+
// and the router dead-ends on a bare-ranking clarification.
|
|
713
|
+
embeddingProvider: projectEmbeddingProvider(req.projectRoot),
|
|
571
714
|
signal,
|
|
572
715
|
reasoningEffort: req.reasoningEffort,
|
|
573
716
|
analysisDepth: contextBudget.analysisDepth,
|
|
@@ -583,6 +726,19 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
583
726
|
executeGeneratedSql: req.executeGeneratedSql
|
|
584
727
|
? async (...args) => { guardSnapshot(); sqlExecutions += 1; return req.executeGeneratedSql(...args); }
|
|
585
728
|
: undefined,
|
|
729
|
+
executeAgenticGeneratedSql: req.executeAgenticGeneratedSql
|
|
730
|
+
? async (capability, sql, artifact) => {
|
|
731
|
+
guardSnapshot();
|
|
732
|
+
sqlExecutions += 1;
|
|
733
|
+
return req.executeAgenticGeneratedSql(capability, sql, artifact);
|
|
734
|
+
}
|
|
735
|
+
: undefined,
|
|
736
|
+
agenticExecutionScope: {
|
|
737
|
+
runId: req.agentRunId,
|
|
738
|
+
snapshotId: req.projectSnapshot?.snapshotId,
|
|
739
|
+
planId: req.resolvedAnalyticalPlan?.planId,
|
|
740
|
+
targetFingerprint: req.generatedProposalTargetFingerprint,
|
|
741
|
+
},
|
|
586
742
|
executeDqlArtifact: req.executeDqlArtifact
|
|
587
743
|
? async (...args) => { guardSnapshot(); sqlExecutions += 1; return req.executeDqlArtifact(...args); }
|
|
588
744
|
: undefined,
|
|
@@ -623,6 +779,111 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
623
779
|
// NOTE: no captureGeneratedDraft here — a plain answer/research question must NOT
|
|
624
780
|
// auto-write a draft into the blocks space. A draft is created only when the user
|
|
625
781
|
// explicitly acts (the "Create DQL draft" action → the dql_block_draft route).
|
|
782
|
+
};
|
|
783
|
+
const result = await answerAgentic(answerLoopInput, {
|
|
784
|
+
policy: resolveOrchestratorPolicy({
|
|
785
|
+
config: readOrchestratorConfig(req.projectRoot),
|
|
786
|
+
}),
|
|
787
|
+
lane: agenticLaneForRequest(req),
|
|
788
|
+
legacy: answer,
|
|
789
|
+
// The generated lane runs the analyst loop: it verifies every
|
|
790
|
+
// identifier against a tool observation before the SQL is executed.
|
|
791
|
+
// Certified and semantic lanes are deliberately NOT registered —
|
|
792
|
+
// their answers already come from a governed contract, so a
|
|
793
|
+
// verification pass would add latency and no safety.
|
|
794
|
+
handlers: {
|
|
795
|
+
generated: createAnalystLaneHandler({
|
|
796
|
+
legacy: answer,
|
|
797
|
+
buildDeps: (loopInput) => {
|
|
798
|
+
const execute = loopInput.executeGeneratedSql;
|
|
799
|
+
const valuesEnabled = valueLookupEnabled(req.projectRoot);
|
|
800
|
+
const tools = buildAnalystLoopTools(loopInput, { valuesEnabled });
|
|
801
|
+
if (process.env.DQL_ORCHESTRATOR_TRACE) {
|
|
802
|
+
console.warn(`[dql] analyst loop deps: tools=${tools.length} preview=${Boolean(execute)} values=${valuesEnabled}`);
|
|
803
|
+
}
|
|
804
|
+
if (tools.length === 0)
|
|
805
|
+
return undefined;
|
|
806
|
+
return {
|
|
807
|
+
tools,
|
|
808
|
+
maxIterations: resolveOrchestratorPolicy({
|
|
809
|
+
config: readOrchestratorConfig(req.projectRoot),
|
|
810
|
+
}).maxIterations,
|
|
811
|
+
// Match the physical wrapper cap and reserve the final
|
|
812
|
+
// composition dispatch: an ordinary cap of four permits
|
|
813
|
+
// at most three text-protocol tool observations.
|
|
814
|
+
maxProviderDispatches,
|
|
815
|
+
// Scaled by the same knob as every other agent deadline, so
|
|
816
|
+
// a local model that needs seconds per call is not planned
|
|
817
|
+
// out of existence by a budget calibrated for a hosted one.
|
|
818
|
+
...(turnPlanningEnabled(req.projectRoot) ? {
|
|
819
|
+
planTurn: async (question, toolNames) => {
|
|
820
|
+
const plan = await planAnalystTurn(loopInput.provider, question, toolNames, {
|
|
821
|
+
timeoutMs: Math.round(2_500 * deadlineScale()),
|
|
822
|
+
...(loopInput.signal ? { signal: loopInput.signal } : {}),
|
|
823
|
+
});
|
|
824
|
+
if (process.env.DQL_ORCHESTRATOR_TRACE) {
|
|
825
|
+
console.warn(plan
|
|
826
|
+
? `[dql] analyst turn plan: "${plan.restatement}" establish=${plan.mustEstablish.length} opening=${plan.openingTool ?? 'unset'}`
|
|
827
|
+
: '[dql] analyst turn plan: unavailable (loop proceeds unplanned)');
|
|
828
|
+
}
|
|
829
|
+
return plan;
|
|
830
|
+
},
|
|
831
|
+
} : {}),
|
|
832
|
+
// The loop's trace, on the channel the provider already uses
|
|
833
|
+
// for progress. `thinking` turns become `onProgress`, which
|
|
834
|
+
// the run engine emits as `executor.started` over SSE — so
|
|
835
|
+
// this needs no new event type, no contract change, and no
|
|
836
|
+
// UI work. Without it the loop does real work (tool calls,
|
|
837
|
+
// identifier checks, a bounded repair) behind a silent
|
|
838
|
+
// spinner, which reads as a hang rather than as an analyst
|
|
839
|
+
// establishing facts.
|
|
840
|
+
onStep: (step) => {
|
|
841
|
+
emit({
|
|
842
|
+
kind: 'thinking',
|
|
843
|
+
text: step.detail ? `${step.label} — ${step.detail}` : step.label,
|
|
844
|
+
});
|
|
845
|
+
},
|
|
846
|
+
// Reuse the legacy parser and validator rather than forking
|
|
847
|
+
// a second SQL front end that would drift from the first.
|
|
848
|
+
parseSql: (raw) => parseProposal(raw).sql,
|
|
849
|
+
extractReferences: (sql) => {
|
|
850
|
+
const validation = validateSqlAgainstLocalContext(sql, loopInput.contextPack);
|
|
851
|
+
const qualified = qualifyAuthorizationReferences(sql, {
|
|
852
|
+
relations: validation.referencedRelations ?? [],
|
|
853
|
+
columns: validation.referencedColumns ?? [],
|
|
854
|
+
});
|
|
855
|
+
return {
|
|
856
|
+
relations: validation.referencedRelations ?? [],
|
|
857
|
+
columns: qualified.filter((reference) => !validation.referencedRelations?.includes(reference)),
|
|
858
|
+
};
|
|
859
|
+
},
|
|
860
|
+
// The safety verifiers keep their logic; only what happens on
|
|
861
|
+
// failure changes. Instead of ending the turn, the specific
|
|
862
|
+
// check that fired comes back as something the model can act
|
|
863
|
+
// on — "joining those tables multiplies rows" rather than
|
|
864
|
+
// "nothing was executed".
|
|
865
|
+
verifySql: (sql) => {
|
|
866
|
+
const validation = validateSqlAgainstLocalContext(sql, loopInput.contextPack);
|
|
867
|
+
if (validation.ok)
|
|
868
|
+
return undefined;
|
|
869
|
+
return renderContextValidationRefusalForUser(validation.code, validation.error, loopInput.followUp?.memberBindings, validation.aggregationSafetyProof?.issueCodes);
|
|
870
|
+
},
|
|
871
|
+
};
|
|
872
|
+
},
|
|
873
|
+
}),
|
|
874
|
+
},
|
|
875
|
+
onDiagnostic: (event) => {
|
|
876
|
+
if (event.kind === 'fallback') {
|
|
877
|
+
console.warn(`[dql] agentic orchestrator fell back on ${event.lane}: ${event.reason}`);
|
|
878
|
+
}
|
|
879
|
+
else if (process.env.DQL_ORCHESTRATOR_TRACE) {
|
|
880
|
+
// Opt-in dispatch trace. A migration where the new path silently
|
|
881
|
+
// never runs looks identical to one where it runs and agrees,
|
|
882
|
+
// and that ambiguity costs more to debug than the log costs to
|
|
883
|
+
// carry.
|
|
884
|
+
console.warn(`[dql] agentic orchestrator dispatched lane=${event.lane} mode=${event.mode}`);
|
|
885
|
+
}
|
|
886
|
+
},
|
|
626
887
|
});
|
|
627
888
|
const answerDurationMs = Date.now() - answerStartedAt;
|
|
628
889
|
// CTX-002: an answer built from one snapshot must never be published
|
|
@@ -652,7 +913,7 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
|
652
913
|
sqlExecutions,
|
|
653
914
|
repairs: result.analysisPlan?.repairAttempts ?? 0,
|
|
654
915
|
};
|
|
655
|
-
if (
|
|
916
|
+
if (isResearch && !providerEgressReceipts.some((receipt) => receipt.purpose === 'research_tool')) {
|
|
656
917
|
providerEgressReceipts.push(createProviderEgressReceipt({
|
|
657
918
|
purpose: 'research_tool',
|
|
658
919
|
provider: provider.name,
|
|
@@ -1097,6 +1358,28 @@ function resolveAgentFollowUpContextRaw(rawContext, question) {
|
|
|
1097
1358
|
const context = rawContext;
|
|
1098
1359
|
if (!context)
|
|
1099
1360
|
return undefined;
|
|
1361
|
+
const taskDependency = analyticalTaskDependencyBindingFromContext(context);
|
|
1362
|
+
// A compound child receives exactly one server-computed parent value plus
|
|
1363
|
+
// result/row proofs. Do not replay parent prose, SQL, or result rows into the
|
|
1364
|
+
// child; its normal governed retrieval and immutable-plan checks still run.
|
|
1365
|
+
if (taskDependency) {
|
|
1366
|
+
return {
|
|
1367
|
+
kind: 'drilldown',
|
|
1368
|
+
sourceTurnId: `task:${taskDependency.sourceTaskId}`,
|
|
1369
|
+
filters: [taskDependency.value],
|
|
1370
|
+
dimensions: [taskDependency.canonicalColumn],
|
|
1371
|
+
priorResultColumns: [taskDependency.canonicalColumn],
|
|
1372
|
+
priorResultValues: { [taskDependency.canonicalColumn]: [taskDependency.value] },
|
|
1373
|
+
memberBindings: [{
|
|
1374
|
+
dimension: taskDependency.canonicalColumn,
|
|
1375
|
+
values: [taskDependency.value],
|
|
1376
|
+
source: 'prior_result',
|
|
1377
|
+
confidence: 'exact',
|
|
1378
|
+
sourceTurnId: `task:${taskDependency.sourceTaskId}`,
|
|
1379
|
+
}],
|
|
1380
|
+
resolvedReferences: [`${taskDependency.canonicalColumn}: ${taskDependency.value}`],
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1100
1383
|
const turns = conversationTurnsFromContext(context);
|
|
1101
1384
|
const activeTurn = activeConversationTurn(context, turns, question);
|
|
1102
1385
|
const activeResult = activeTurn?.result && typeof activeTurn.result === 'object' && !Array.isArray(activeTurn.result)
|
|
@@ -1193,6 +1476,21 @@ function resolveAgentFollowUpContextRaw(rawContext, question) {
|
|
|
1193
1476
|
priorResult: relativeComparison ? undefined : priorResultDataFromTurn(activeResult, priorResultColumns),
|
|
1194
1477
|
};
|
|
1195
1478
|
}
|
|
1479
|
+
function analyticalTaskDependencyBindingFromContext(context) {
|
|
1480
|
+
const raw = cleanRecord(context.analyticalTaskDependencyBinding);
|
|
1481
|
+
if (!raw || raw.version !== 1)
|
|
1482
|
+
return undefined;
|
|
1483
|
+
const sourceTaskId = cleanOptionalString(raw.sourceTaskId);
|
|
1484
|
+
const sourceResultFingerprint = cleanOptionalString(raw.sourceResultFingerprint)?.toLowerCase();
|
|
1485
|
+
const canonicalColumn = cleanOptionalString(raw.canonicalColumn);
|
|
1486
|
+
const value = cleanOptionalString(raw.value);
|
|
1487
|
+
const rowFingerprint = cleanOptionalString(raw.rowFingerprint)?.toLowerCase();
|
|
1488
|
+
if (!sourceTaskId || !canonicalColumn || !value
|
|
1489
|
+
|| !/^[a-f0-9]{64}$/.test(sourceResultFingerprint ?? '')
|
|
1490
|
+
|| !/^[a-f0-9]{64}$/.test(rowFingerprint ?? ''))
|
|
1491
|
+
return undefined;
|
|
1492
|
+
return { sourceTaskId, sourceResultFingerprint: sourceResultFingerprint, canonicalColumn, value, rowFingerprint: rowFingerprint };
|
|
1493
|
+
}
|
|
1196
1494
|
/** Build the bounded prior-result rows (aligned to columns) for cross-result
|
|
1197
1495
|
* follow-up computation, from a turn's persisted result sample. */
|
|
1198
1496
|
function priorResultDataFromTurn(activeResult, columns) {
|
|
@@ -1776,6 +2074,7 @@ function normalizePriorValueDimension(value) {
|
|
|
1776
2074
|
return lower.replace(/[_\s-]+name$/, '').replace(/[^a-z0-9_ -]+/g, '').trim();
|
|
1777
2075
|
}
|
|
1778
2076
|
export const __test__ = {
|
|
2077
|
+
agenticLaneForRequest,
|
|
1779
2078
|
applyTopicShiftGuard,
|
|
1780
2079
|
isDrilldownFollowUp,
|
|
1781
2080
|
buildAnswerLoopTools,
|