@duckcodeailabs/dql-cli 1.13.4 → 1.14.0
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 +2 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +5 -0
- package/dist/args.js.map +1 -1
- package/dist/assets/dql-notebook/assets/{AgentLogPage-DYmHO6_i.js → AgentLogPage-Ch7VK20X.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildDialog-BfhaOd1h.js → AiBuildDialog-DBr5TmyM.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildResult-DLKFAe6R.js → AiBuildResult-jLPzQO7O.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiSidePanel-C6Z3URGr.js → AiSidePanel-CdlGsiVC.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AnalyticsHome-uG3xCP_s.js → AnalyticsHome-C0DXbOwY.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AppsView-DfieNjkh.js → AppsView-CcpwjApv.js} +4 -4
- package/dist/assets/dql-notebook/assets/{BlockStudio-90J_wvWL.js → BlockStudio-CFYxafw-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{BusinessArtifactView-_zSMae5q.js → BusinessArtifactView-C0kLYg2p.js} +1 -1
- package/dist/assets/dql-notebook/assets/{DbtFirstModelingPage-DoXiPssu.js → DbtFirstModelingPage-CMwElXD_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GitPage-CUfAhS67.js → GitPage-JjhRDeWY.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GlobalAiRail-ovtL7nQu.js → GlobalAiRail-DakE4NdR.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GovernedContextPage-CaMiKAzA.js → GovernedContextPage-BokDqG6a.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HelpDocsPage-CEGUx28b.js → HelpDocsPage-CjOv6_gz.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HomePage-2v3SRf8V.js → HomePage-nGaNcwdw.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDAG-AaqLEiBN.js → LineageDAG-CO6CFJRg.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDetailView-CFc8pm91.js → LineageDetailView-BnGb7OF7.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDrawer-DOkFjPq-.js → LineageDrawer-C5Y0Ht0b.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineagePathBreadcrumb-BrDwCuMs.js → LineagePathBreadcrumb-Cgh3GR3F.js} +1 -1
- package/dist/assets/dql-notebook/assets/{MiniLineageGraph-CiHtQs3y.js → MiniLineageGraph-Kla9PYuj.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewBlockModal-DAgJZbws.js → NewBlockModal-BR2SnmPT.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewNotebookModal-xjG7nZ3O.js → NewNotebookModal-BaCHMYeB.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NotebookEditor-CXZaKfYT.js → NotebookEditor-CBLqY8cE.js} +1 -1
- package/dist/assets/dql-notebook/assets/{ReadinessPage-MuJBFwwg.js → ReadinessPage-CiN0IWSS.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SetupOnboarding-vKFtKtjh.js → SetupOnboarding-BhiCsYF-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SkillsPage-DCFVOV5f.js → SkillsPage-CCSf8VMm.js} +1 -1
- package/dist/assets/dql-notebook/assets/{TrustBadge-TcwXFFHK.js → TrustBadge-BgQmFe_x.js} +1 -1
- package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-Bw5AXNDB.js +88 -0
- package/dist/assets/dql-notebook/assets/{answer-to-notebook-DCFWlHB7.js → answer-to-notebook-AeDYUDla.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-left-BLnlJAtp.js → arrow-left-C55x2hq_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-right-BmUAb1aW.js → arrow-right-C1cJrhOm.js} +1 -1
- package/dist/assets/dql-notebook/assets/{book-open-text-bdSChSag.js → book-open-text-CQf_sdv2.js} +1 -1
- package/dist/assets/dql-notebook/assets/{circle-x-Cc6dEaf4.js → circle-x-X8-Z2yLY.js} +1 -1
- package/dist/assets/dql-notebook/assets/{dagre.esm-CPebQKQ9.js → dagre.esm-BjjNYKyY.js} +1 -1
- package/dist/assets/dql-notebook/assets/{external-link-rmXvubf3.js → external-link-C2zrz5DH.js} +1 -1
- package/dist/assets/dql-notebook/assets/{grip-vertical-Sg4zd5-Q.js → grip-vertical-qGV_PYGU.js} +1 -1
- package/dist/assets/dql-notebook/assets/{index-mQd_pC-P.js → index-ByTDPDaH.js} +127 -127
- package/dist/assets/dql-notebook/assets/{link-2-BLaPoFxW.js → link-2-VpyOxQXG.js} +1 -1
- package/dist/assets/dql-notebook/assets/{list-tree-j0s4asFK.js → list-tree-CH2Jhwms.js} +1 -1
- package/dist/assets/dql-notebook/assets/{minimize-2-Cl4pef4s.js → minimize-2-B2TZJ8BT.js} +1 -1
- package/dist/assets/dql-notebook/assets/{panel-right-open-Clz-FtWi.js → panel-right-open-BunF88lt.js} +1 -1
- package/dist/assets/dql-notebook/assets/{play-DFdazMg7.js → play-DAFVF4_G.js} +1 -1
- package/dist/assets/dql-notebook/assets/{rotate-ccw-C3tTaR7s.js → rotate-ccw-DGCrrqtY.js} +1 -1
- package/dist/assets/dql-notebook/assets/{semantic-fields-DIAhTlfy.js → semantic-fields-Ci-9QL9F.js} +1 -1
- package/dist/assets/dql-notebook/assets/{sliders-horizontal-BUiu2tfY.js → sliders-horizontal-BOAlXXbn.js} +1 -1
- package/dist/assets/dql-notebook/assets/{star--Yq77aH4.js → star-CkksZXHt.js} +1 -1
- package/dist/assets/dql-notebook/assets/{triangle-alert-D5Ng-xZO.js → triangle-alert-D3mjyJZE.js} +1 -1
- package/dist/assets/dql-notebook/assets/{upload-L8BKQAxP.js → upload-CTNOAVEO.js} +1 -1
- package/dist/assets/dql-notebook/assets/{usePersistedAgentThreadId-DBu97YaM.js → usePersistedAgentThreadId-DiQjc7x-.js} +4 -4
- package/dist/assets/dql-notebook/assets/{user-round-BK5N1cJW.js → user-round-BWd5tQRg.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wand-sparkles-D8XDQD9z.js → wand-sparkles-CqsAv8P-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{workflow-Bl9zwG0q.js → workflow-CSqsj-sC.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wrench-C-zLCMbW.js → wrench-DWqzqlX8.js} +1 -1
- package/dist/assets/dql-notebook/assets/{x-stiJ-sP7.js → x-65M5rLCE.js} +1 -1
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/commands/eval.d.ts +15 -1
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +32 -3
- package/dist/commands/eval.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/llm/answer-loop-tools.d.ts +3 -1
- package/dist/llm/answer-loop-tools.d.ts.map +1 -1
- package/dist/llm/answer-loop-tools.js +31 -23
- package/dist/llm/answer-loop-tools.js.map +1 -1
- package/dist/llm/providers/dql-agent-provider.d.ts +3 -1
- package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
- package/dist/llm/providers/dql-agent-provider.js +315 -22
- package/dist/llm/providers/dql-agent-provider.js.map +1 -1
- package/dist/llm/types.d.ts +29 -2
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/local-runtime.d.ts +200 -5
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +2280 -457
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/dist/providers/oauth/claude-oauth.d.ts +1 -1
- package/dist/providers/oauth/claude-oauth.d.ts.map +1 -1
- package/dist/providers/oauth/claude-oauth.js +18 -11
- package/dist/providers/oauth/claude-oauth.js.map +1 -1
- package/dist/providers/oauth/codex-oauth.d.ts +1 -1
- package/dist/providers/oauth/codex-oauth.d.ts.map +1 -1
- package/dist/providers/oauth/codex-oauth.js +9 -1
- package/dist/providers/oauth/codex-oauth.js.map +1 -1
- package/dist/providers/subscription-cli.d.ts +1 -1
- package/dist/providers/subscription-cli.d.ts.map +1 -1
- package/dist/providers/subscription-cli.js +33 -4
- package/dist/providers/subscription-cli.js.map +1 -1
- package/package.json +10 -10
- package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-Cu-YgIe4.js +0 -88
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { ClaudeProvider, KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, GeminiProvider, loadAgentSemanticLayer, OllamaProvider, OpenAIProvider, answer, buildAnalysisQuestionPlan, buildLocalContextPack, contextRetrievalBudgetForQuestion, ensureAgentProjectReady, isLikelyClarificationReply, isTrustedConversationTurn, } from '@duckcodeailabs/dql-agent';
|
|
1
|
+
import { ClaudeProvider, KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, GeminiProvider, loadAgentSemanticLayer, OllamaProvider, OpenAIProvider, answer, buildAnalysisQuestionPlan, buildLocalContextPack, contextRetrievalBudgetForQuestion, ensureAgentProjectReady, isLikelyClarificationReply, isTrustedConversationTurn, createProviderDispatchEgressReceipt, createProviderEgressReceipt, assertProviderPayloadAllowed, prepareProviderContextForDispatch, prepareProviderWireEnvelopeForDispatch, prepareServerOwnedProviderSchemaContext, } from '@duckcodeailabs/dql-agent';
|
|
2
2
|
import { buildManifest, normalizeDqlArtifactReference, resolveDbtManifestPath } from '@duckcodeailabs/dql-core';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { buildAnswerLoopTools, createGroundingContextExpander } from '../answer-loop-tools.js';
|
|
5
|
-
import { rethrowIfCancelled } from '../cancellation.js';
|
|
6
5
|
import { getSemanticRuntimeStatus } from '../../semantic-runtime.js';
|
|
7
6
|
import { blockProposalDqlMetadata } from '../proposal-metadata.js';
|
|
8
7
|
import { getEffectiveProviderConfig } from '../../settings/provider-settings.js';
|
|
@@ -258,11 +257,126 @@ function emitProposalFromText(text, emit) {
|
|
|
258
257
|
// Ignore malformed proposal text. The visible assistant response still streams as text.
|
|
259
258
|
}
|
|
260
259
|
}
|
|
261
|
-
export function createDqlAgentProviderRunner(id) {
|
|
260
|
+
export function createDqlAgentProviderRunner(id, providerOverride) {
|
|
262
261
|
return {
|
|
263
262
|
async run(req, emit, signal) {
|
|
264
263
|
const spec = SPECS[id];
|
|
265
|
-
const
|
|
264
|
+
const rawProvider = providerOverride ?? spec.create(req.projectRoot);
|
|
265
|
+
const isResearch = req.analysisDepth === 'deep';
|
|
266
|
+
const researchRowsOptIn = isResearch && req.researchResultRowsOptIn === true;
|
|
267
|
+
const sharedDispatchEvidence = req.providerDispatchEvidenceSink;
|
|
268
|
+
let providerRoundTrips = 0;
|
|
269
|
+
let sqlExecutions = 0;
|
|
270
|
+
let pendingResultRowCount = 0;
|
|
271
|
+
let pendingColumnCount = 0;
|
|
272
|
+
let pendingCumulativeResultRowCount = 0;
|
|
273
|
+
let pendingResearchPurpose = 'research_narration';
|
|
274
|
+
const providerEgressReceipts = [];
|
|
275
|
+
const dispatchEvidence = (fallbackReason) => {
|
|
276
|
+
const shared = sharedDispatchEvidence?.snapshot(fallbackReason);
|
|
277
|
+
return shared
|
|
278
|
+
? { ...shared, sqlExecutions, fallbackReason }
|
|
279
|
+
: {
|
|
280
|
+
providerEgressReceipts: [...providerEgressReceipts],
|
|
281
|
+
providerRoundTrips,
|
|
282
|
+
toolCalls: 0,
|
|
283
|
+
sqlExecutions,
|
|
284
|
+
repairs: 0,
|
|
285
|
+
fallbackReason,
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
signal.addEventListener('abort', () => {
|
|
289
|
+
const reason = signal.reason;
|
|
290
|
+
if (reason && typeof reason === 'object') {
|
|
291
|
+
Object.assign(reason, { providerDispatchEvidence: dispatchEvidence('cancelled') });
|
|
292
|
+
}
|
|
293
|
+
}, { once: true });
|
|
294
|
+
const withPhysicalDispatchObserver = (options) => ({
|
|
295
|
+
...(options ?? {}),
|
|
296
|
+
// A tool-calling loop capped at two physical sends can make exactly one
|
|
297
|
+
// tool call before it must answer, which is not enough to look something
|
|
298
|
+
// up and then use it. The run-scoped ledger is the real guardrail.
|
|
299
|
+
maxProviderDispatches: isResearch ? 8 : 4,
|
|
300
|
+
...(sharedDispatchEvidence?.mayStartToolCall
|
|
301
|
+
? { mayStartToolCall: () => sharedDispatchEvidence.mayStartToolCall() }
|
|
302
|
+
: {}),
|
|
303
|
+
onProviderDispatch: (event) => {
|
|
304
|
+
const purpose = isResearch ? pendingResearchPurpose : 'answer_generation';
|
|
305
|
+
if (sharedDispatchEvidence) {
|
|
306
|
+
const envelope = sharedDispatchEvidence.observe(event, {
|
|
307
|
+
purpose,
|
|
308
|
+
dispatchPhase: 'generation',
|
|
309
|
+
optIn: pendingResultRowCount > 0 && researchRowsOptIn,
|
|
310
|
+
serializedResultShape: {
|
|
311
|
+
resultRowCount: pendingResultRowCount,
|
|
312
|
+
columnCount: pendingColumnCount,
|
|
313
|
+
},
|
|
314
|
+
...(pendingCumulativeResultRowCount > 0
|
|
315
|
+
? { cumulativeResultRowCount: pendingCumulativeResultRowCount }
|
|
316
|
+
: {}),
|
|
317
|
+
});
|
|
318
|
+
pendingResultRowCount = 0;
|
|
319
|
+
pendingColumnCount = 0;
|
|
320
|
+
pendingCumulativeResultRowCount = 0;
|
|
321
|
+
pendingResearchPurpose = 'research_narration';
|
|
322
|
+
return envelope;
|
|
323
|
+
}
|
|
324
|
+
// Fallback path only (CLI/MCP-direct runs carry no shared ledger).
|
|
325
|
+
// Kept in step with `maxProviderDispatches` above so the same question
|
|
326
|
+
// does not get a different budget depending on which surface asked it.
|
|
327
|
+
if (!isResearch && providerRoundTrips >= 4) {
|
|
328
|
+
throw Object.assign(new Error('Provider dispatch budget exhausted after four ordinary generation attempts.'), {
|
|
329
|
+
code: 'PROVIDER_DISPATCH_BUDGET_EXHAUSTED',
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
const envelope = prepareProviderWireEnvelopeForDispatch(rawProvider.name, event.envelope);
|
|
333
|
+
assertProviderPayloadAllowed(envelope, {
|
|
334
|
+
allowResultRows: false,
|
|
335
|
+
maxResultRows: 0,
|
|
336
|
+
purpose,
|
|
337
|
+
});
|
|
338
|
+
providerRoundTrips += 1;
|
|
339
|
+
providerEgressReceipts.push(createProviderDispatchEgressReceipt({
|
|
340
|
+
purpose,
|
|
341
|
+
dispatchPhase: 'generation',
|
|
342
|
+
provider: rawProvider.name,
|
|
343
|
+
permittedCategories: pendingResultRowCount > 0
|
|
344
|
+
? ['instructions', 'question', 'schema_metadata', 'governed_context', 'result_rows']
|
|
345
|
+
: ['instructions', 'question', 'schema_metadata', 'governed_context'],
|
|
346
|
+
optIn: pendingResultRowCount > 0 && researchRowsOptIn,
|
|
347
|
+
envelope,
|
|
348
|
+
serializedResultShape: {
|
|
349
|
+
resultRowCount: pendingResultRowCount,
|
|
350
|
+
columnCount: pendingColumnCount,
|
|
351
|
+
},
|
|
352
|
+
...(pendingCumulativeResultRowCount > 0
|
|
353
|
+
? { cumulativeResultRowCount: pendingCumulativeResultRowCount }
|
|
354
|
+
: {}),
|
|
355
|
+
}));
|
|
356
|
+
pendingResultRowCount = 0;
|
|
357
|
+
pendingColumnCount = 0;
|
|
358
|
+
pendingCumulativeResultRowCount = 0;
|
|
359
|
+
pendingResearchPurpose = 'research_narration';
|
|
360
|
+
return envelope;
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
const provider = {
|
|
364
|
+
name: rawProvider.name,
|
|
365
|
+
available: () => rawProvider.available(),
|
|
366
|
+
generate: (...args) => {
|
|
367
|
+
return rawProvider.generate(args[0], withPhysicalDispatchObserver(args[1]));
|
|
368
|
+
},
|
|
369
|
+
...(rawProvider.generateWithTools ? {
|
|
370
|
+
generateWithTools: (...args) => {
|
|
371
|
+
return rawProvider.generateWithTools(args[0], args[1], withPhysicalDispatchObserver(args[2]));
|
|
372
|
+
},
|
|
373
|
+
} : {}),
|
|
374
|
+
...(rawProvider.generateStream ? {
|
|
375
|
+
generateStream: (...args) => {
|
|
376
|
+
return rawProvider.generateStream(args[0], withPhysicalDispatchObserver(args[1]), args[2]);
|
|
377
|
+
},
|
|
378
|
+
} : {}),
|
|
379
|
+
};
|
|
266
380
|
const available = await provider.available().catch(() => false);
|
|
267
381
|
if (!available) {
|
|
268
382
|
emit({ kind: 'error', message: `${spec.label} is not configured or reachable. ${spec.setup}` });
|
|
@@ -362,7 +476,9 @@ export function createDqlAgentProviderRunner(id) {
|
|
|
362
476
|
sourcePath: skill.sourcePath ?? '',
|
|
363
477
|
}));
|
|
364
478
|
const contextDurationMs = Date.now() - contextStartedAt;
|
|
365
|
-
const answerLoopTools = buildAnswerLoopTools(req.projectRoot
|
|
479
|
+
const answerLoopTools = buildAnswerLoopTools(req.projectRoot, {
|
|
480
|
+
researchResultRowsOptIn: researchRowsOptIn,
|
|
481
|
+
});
|
|
366
482
|
const sourceSearchTool = answerLoopTools.find((tool) => tool.name === 'search_project_files');
|
|
367
483
|
const sourceSearchStartedAt = Date.now();
|
|
368
484
|
const earlySourceSearch = sourceSearchTool && shouldSearchProjectFiles(contextPack)
|
|
@@ -404,6 +520,19 @@ export function createDqlAgentProviderRunner(id) {
|
|
|
404
520
|
if (req.projectSnapshot)
|
|
405
521
|
req.assertProjectSnapshot?.(req.projectSnapshot.snapshotId);
|
|
406
522
|
};
|
|
523
|
+
assertProviderPayloadAllowed(prepareProviderContextForDispatch({
|
|
524
|
+
question,
|
|
525
|
+
...(conversationSnapshot ? { conversationSnapshot } : {}),
|
|
526
|
+
...(memoryContext ? { memoryContext } : {}),
|
|
527
|
+
schemaContext: prepareServerOwnedProviderSchemaContext(schemaContext),
|
|
528
|
+
...(contextPack ? { contextPack } : {}),
|
|
529
|
+
skills,
|
|
530
|
+
...(followUp ? { followUp } : {}),
|
|
531
|
+
}), {
|
|
532
|
+
allowResultRows: false,
|
|
533
|
+
maxResultRows: 0,
|
|
534
|
+
purpose: 'answer_generation',
|
|
535
|
+
});
|
|
407
536
|
const result = await answer({
|
|
408
537
|
question,
|
|
409
538
|
...(req.resolvedAnalyticalPlan
|
|
@@ -442,22 +571,55 @@ export function createDqlAgentProviderRunner(id) {
|
|
|
442
571
|
signal,
|
|
443
572
|
reasoningEffort: req.reasoningEffort,
|
|
444
573
|
analysisDepth: contextBudget.analysisDepth,
|
|
574
|
+
allowProviderSemanticMemberSelection: req.allowProviderSemanticMemberSelection === true,
|
|
445
575
|
...(req.semanticDriver ? { semanticDriver: req.semanticDriver } : {}),
|
|
446
576
|
...(req.semanticTableMapping ? { semanticTableMapping: req.semanticTableMapping } : {}),
|
|
447
577
|
...(req.semanticQueryCompiler ? { semanticQueryCompiler: req.semanticQueryCompiler } : {}),
|
|
448
578
|
...(req.preferredEvidenceIds?.length ? { preferredEvidenceIds: req.preferredEvidenceIds } : {}),
|
|
449
579
|
...(req.preferredExecutionId ? { preferredExecutionId: req.preferredExecutionId } : {}),
|
|
450
580
|
executeCertifiedBlock: req.executeCertifiedBlock
|
|
451
|
-
? async (...args) => { guardSnapshot(); return req.executeCertifiedBlock(...args); }
|
|
581
|
+
? async (...args) => { guardSnapshot(); sqlExecutions += 1; return req.executeCertifiedBlock(...args); }
|
|
452
582
|
: undefined,
|
|
453
583
|
executeGeneratedSql: req.executeGeneratedSql
|
|
454
|
-
? async (...args) => { guardSnapshot(); return req.executeGeneratedSql(...args); }
|
|
584
|
+
? async (...args) => { guardSnapshot(); sqlExecutions += 1; return req.executeGeneratedSql(...args); }
|
|
455
585
|
: undefined,
|
|
456
586
|
executeDqlArtifact: req.executeDqlArtifact
|
|
457
|
-
? async (...args) => { guardSnapshot(); return req.executeDqlArtifact(...args); }
|
|
587
|
+
? async (...args) => { guardSnapshot(); sqlExecutions += 1; return req.executeDqlArtifact(...args); }
|
|
458
588
|
: undefined,
|
|
459
589
|
expandGroundingContext: createGroundingContextExpander(req.projectRoot, req.probeNamedRelations),
|
|
460
590
|
answerLoopTools,
|
|
591
|
+
providerPayloadGuard: {
|
|
592
|
+
purpose: 'research_tool',
|
|
593
|
+
allowedResultRowTools: researchRowsOptIn
|
|
594
|
+
? { sample_notebook_dataset: 20, execute_local_analysis: 200 }
|
|
595
|
+
: {},
|
|
596
|
+
resultRowBudgetGroupByTool: {
|
|
597
|
+
sample_notebook_dataset: 'research_sample',
|
|
598
|
+
execute_local_analysis: 'research_local_analysis',
|
|
599
|
+
},
|
|
600
|
+
cumulativeResultRowBudgets: {
|
|
601
|
+
research_sample: 20,
|
|
602
|
+
research_local_analysis: 200,
|
|
603
|
+
},
|
|
604
|
+
onPayload: ({ toolName, output, resultRowCount, columnCount, cumulativeResultRowCount }) => {
|
|
605
|
+
pendingResearchPurpose = researchDispatchPurposeForTool(toolName);
|
|
606
|
+
pendingResultRowCount += resultRowCount;
|
|
607
|
+
pendingColumnCount = Math.max(pendingColumnCount, columnCount);
|
|
608
|
+
pendingCumulativeResultRowCount = Math.max(pendingCumulativeResultRowCount, cumulativeResultRowCount);
|
|
609
|
+
if (resultRowCount === 0)
|
|
610
|
+
return;
|
|
611
|
+
providerEgressReceipts.push(createProviderEgressReceipt({
|
|
612
|
+
purpose: researchDispatchPurposeForTool(toolName),
|
|
613
|
+
provider: provider.name,
|
|
614
|
+
permittedCategories: ['question', 'schema_metadata', 'result_rows'],
|
|
615
|
+
optIn: researchRowsOptIn,
|
|
616
|
+
payload: { toolName, output },
|
|
617
|
+
resultRowCount,
|
|
618
|
+
columnCount,
|
|
619
|
+
cumulativeResultRowCount,
|
|
620
|
+
}));
|
|
621
|
+
},
|
|
622
|
+
},
|
|
461
623
|
// NOTE: no captureGeneratedDraft here — a plain answer/research question must NOT
|
|
462
624
|
// auto-write a draft into the blocks space. A draft is created only when the user
|
|
463
625
|
// explicitly acts (the "Create DQL draft" action → the dql_block_draft route).
|
|
@@ -483,6 +645,27 @@ export function createDqlAgentProviderRunner(id) {
|
|
|
483
645
|
{ phase: 'answer_resolution', durationMs: answerDurationMs, detail: result.route?.tier ?? result.kind },
|
|
484
646
|
{ phase: 'total', durationMs: Date.now() - requestStartedAt },
|
|
485
647
|
];
|
|
648
|
+
const terminalDispatchEvidence = dispatchEvidence('none');
|
|
649
|
+
result.evidence.runtimeCounters = {
|
|
650
|
+
providerRoundTrips: terminalDispatchEvidence.providerRoundTrips,
|
|
651
|
+
toolCalls: result.evidence.toolCalls?.length ?? 0,
|
|
652
|
+
sqlExecutions,
|
|
653
|
+
repairs: result.analysisPlan?.repairAttempts ?? 0,
|
|
654
|
+
};
|
|
655
|
+
if (req.analysisDepth === 'deep' && !providerEgressReceipts.some((receipt) => receipt.purpose === 'research_tool')) {
|
|
656
|
+
providerEgressReceipts.push(createProviderEgressReceipt({
|
|
657
|
+
purpose: 'research_tool',
|
|
658
|
+
provider: provider.name,
|
|
659
|
+
permittedCategories: ['instructions', 'question', 'schema_metadata', 'governed_context'],
|
|
660
|
+
optIn: researchRowsOptIn,
|
|
661
|
+
payload: { resultRows: 0, enabled: researchRowsOptIn },
|
|
662
|
+
resultRowCount: 0,
|
|
663
|
+
columnCount: 0,
|
|
664
|
+
}));
|
|
665
|
+
}
|
|
666
|
+
result.providerEgressReceipts = sharedDispatchEvidence
|
|
667
|
+
? terminalDispatchEvidence.providerEgressReceipts
|
|
668
|
+
: providerEgressReceipts;
|
|
486
669
|
emit({ kind: 'tool_result', id: 'governed_answer', output: result });
|
|
487
670
|
emit({ kind: 'text', text: formatAgentAnswer(result) });
|
|
488
671
|
if (result.proposedSql) {
|
|
@@ -507,10 +690,41 @@ export function createDqlAgentProviderRunner(id) {
|
|
|
507
690
|
// A deadline/cancellation is NOT a provider failure. Rethrow it intact so
|
|
508
691
|
// the engine renders its graceful bounded-deadline message instead of a
|
|
509
692
|
// raw "<provider> failed: The operation was aborted due to timeout".
|
|
510
|
-
|
|
693
|
+
if (signal.aborted) {
|
|
694
|
+
const reason = signal.reason ?? err;
|
|
695
|
+
if (reason && typeof reason === 'object') {
|
|
696
|
+
Object.assign(reason, { providerDispatchEvidence: dispatchEvidence('cancelled') });
|
|
697
|
+
}
|
|
698
|
+
throw reason;
|
|
699
|
+
}
|
|
511
700
|
const message = err instanceof Error ? err.message : String(err);
|
|
701
|
+
// Both codes are DQL's OWN budget refusing to start another dispatch.
|
|
702
|
+
// Only the count-based one was recognized, so a time-based refusal was
|
|
703
|
+
// reported as "<provider> failed: The 30-second soft target elapsed" —
|
|
704
|
+
// blaming the user's AI subscription for DQL's timer and sending them
|
|
705
|
+
// to debug the wrong system entirely.
|
|
706
|
+
const budgetCode = err && typeof err === 'object'
|
|
707
|
+
? String(err.code)
|
|
708
|
+
: '';
|
|
709
|
+
const orchestrationBudgetExhausted = budgetCode === 'PROVIDER_DISPATCH_BUDGET_EXHAUSTED'
|
|
710
|
+
|| budgetCode === 'RUN_SOFT_TARGET_EXCEEDED'
|
|
711
|
+
|| budgetCode === 'RUN_DEADLINE_INSUFFICIENT';
|
|
712
|
+
// The project catalog was rebuilt underneath a run in flight. That is
|
|
713
|
+
// DQL's own concurrency, and telling the user their AI subscription
|
|
714
|
+
// failed sends them to re-authenticate a provider that worked fine.
|
|
715
|
+
const snapshotDrift = budgetCode === 'PROJECT_SNAPSHOT_MISMATCH';
|
|
512
716
|
const setupHint = shouldShowProviderSetupHint(message) ? ` ${spec.setup}` : '';
|
|
513
|
-
emit({
|
|
717
|
+
emit({
|
|
718
|
+
kind: 'error',
|
|
719
|
+
message: orchestrationBudgetExhausted
|
|
720
|
+
? `Ask stopped at its own bounded orchestration budget before a final answer was available — the AI provider did not fail. (${message})`
|
|
721
|
+
: snapshotDrift
|
|
722
|
+
? 'The project snapshot was rebuilt while this run was in flight, so DQL discarded the in-progress plan rather than answer from a stale catalog. The AI provider did not fail — retry the question.'
|
|
723
|
+
: `${spec.label} failed: ${message}.${setupHint}`,
|
|
724
|
+
dispatchEvidence: dispatchEvidence(orchestrationBudgetExhausted ? 'orchestration_budget_exhausted'
|
|
725
|
+
: snapshotDrift ? 'project_snapshot_mismatch'
|
|
726
|
+
: 'provider_error'),
|
|
727
|
+
});
|
|
514
728
|
}
|
|
515
729
|
},
|
|
516
730
|
};
|
|
@@ -663,13 +877,20 @@ export function renderExtraContext(req, followUp) {
|
|
|
663
877
|
// questions. Render it as prose with its trust instruction, and strip it
|
|
664
878
|
// from the JSON below rather than shipping the same content twice.
|
|
665
879
|
try {
|
|
666
|
-
const
|
|
667
|
-
const appContext =
|
|
880
|
+
const rawEnvelope = JSON.parse(upstream);
|
|
881
|
+
const appContext = rawEnvelope.workspaceContext?.appContext;
|
|
668
882
|
const rendered = renderAppContextForPrompt(appContext);
|
|
669
883
|
if (rendered) {
|
|
670
884
|
parts.push(rendered);
|
|
671
|
-
const { appContext: _dropped, ...restWorkspace } =
|
|
672
|
-
|
|
885
|
+
const { appContext: _dropped, ...restWorkspace } = rawEnvelope.workspaceContext ?? {};
|
|
886
|
+
const envelope = prepareProviderContextForDispatch({
|
|
887
|
+
...rawEnvelope,
|
|
888
|
+
workspaceContext: restWorkspace,
|
|
889
|
+
});
|
|
890
|
+
upstream = JSON.stringify(envelope, null, 2);
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
upstream = JSON.stringify(prepareProviderContextForDispatch(rawEnvelope), null, 2);
|
|
673
894
|
}
|
|
674
895
|
}
|
|
675
896
|
catch {
|
|
@@ -885,6 +1106,27 @@ function resolveAgentFollowUpContextRaw(rawContext, question) {
|
|
|
885
1106
|
const priorResultValues = cleanStringRecordArray(activeResult?.dimensionValues) ?? cleanStringRecordArray(context.resultDimensionValues);
|
|
886
1107
|
const priorResultColumns = mergeStrings(arrayValue(activeResult?.columns), context.resultColumns, context.outputColumns);
|
|
887
1108
|
const priorResultRef = priorResultRefFromTurn(activeTurn, activeResult, priorResultColumns);
|
|
1109
|
+
// The prior turn's actual rows, so a follow-up that computes ACROSS what was
|
|
1110
|
+
// already shown ("show only the top 3", "of these, the average") is answered
|
|
1111
|
+
// from those rows instead of a fresh warehouse query.
|
|
1112
|
+
//
|
|
1113
|
+
// `answer-loop.ts` declared `followUp.priorResult` and `tryCrossResultAnswer`
|
|
1114
|
+
// read it, but NOTHING ever populated it — the cross-result path was dead in
|
|
1115
|
+
// the repository. The turn already persists everything it needs: `columns`,
|
|
1116
|
+
// the bounded `rowsSample`, `measureColumns` and the full `rowCount`.
|
|
1117
|
+
const priorSampleRows = Array.isArray(activeResult?.rowsSample)
|
|
1118
|
+
? activeResult.rowsSample.filter((row) => Array.isArray(row))
|
|
1119
|
+
: [];
|
|
1120
|
+
const priorResultRowCount = typeof activeResult?.rowCount === 'number' ? activeResult.rowCount : undefined;
|
|
1121
|
+
const priorMeasureColumns = arrayValue(activeResult?.measureColumns);
|
|
1122
|
+
const priorResult = priorResultColumns?.length && priorSampleRows.length > 0
|
|
1123
|
+
? {
|
|
1124
|
+
columns: priorResultColumns,
|
|
1125
|
+
rows: priorSampleRows,
|
|
1126
|
+
...(priorMeasureColumns?.length ? { measureColumns: priorMeasureColumns } : {}),
|
|
1127
|
+
...(priorResultRowCount !== undefined ? { rowCount: priorResultRowCount } : {}),
|
|
1128
|
+
}
|
|
1129
|
+
: undefined;
|
|
888
1130
|
const priorDqlArtifact = cleanDqlArtifactReference(activeTurn?.dqlArtifact) ?? cleanDqlArtifactReference(context.dqlArtifact);
|
|
889
1131
|
const resolvedReferences = resolveConversationReferences(question, turns, priorResultValues);
|
|
890
1132
|
const focusedPriorResultValues = resolvedReferences.valuesByDimension ?? priorResultValues;
|
|
@@ -928,6 +1170,7 @@ function resolveAgentFollowUpContextRaw(rawContext, question) {
|
|
|
928
1170
|
? mergeStrings(hasFocusedReference ? undefined : activeTurnStringArray(activeTurn, 'requestedDimensions'), hasFocusedReference ? undefined : context.requestedDimensions, extractDrilldownDimensions(question), resolvedReferences.dimensions)
|
|
929
1171
|
: undefined,
|
|
930
1172
|
priorResultColumns: relativeComparison ? undefined : priorResultColumns,
|
|
1173
|
+
...(priorResult && !relativeComparison ? { priorResult } : {}),
|
|
931
1174
|
priorResultValues: focusedPriorResultValues,
|
|
932
1175
|
priorResultRef: relativeComparison ? undefined : priorResultRef,
|
|
933
1176
|
priorDqlArtifact: relativeComparison ? undefined : priorDqlArtifact,
|
|
@@ -943,6 +1186,7 @@ function resolveAgentFollowUpContextRaw(rawContext, question) {
|
|
|
943
1186
|
})),
|
|
944
1187
|
resolvedReferences: resolvedReferences.labels,
|
|
945
1188
|
unresolvedReferences: resolvedReferences.unresolved,
|
|
1189
|
+
deicticChoices: resolvedReferences.deicticChoices,
|
|
946
1190
|
// The prior turn's actual rows, so a follow-up can compute across the shown
|
|
947
1191
|
// results ("of these, the average") without a fresh query. A relative
|
|
948
1192
|
// comparison deliberately drops the prior contract, so skip it there too.
|
|
@@ -1161,7 +1405,18 @@ function resolveConversationReferences(question, turns, activeValues) {
|
|
|
1161
1405
|
];
|
|
1162
1406
|
let filters = resolveDeicticFilters(question, activeValues) ?? [];
|
|
1163
1407
|
const labels = [];
|
|
1164
|
-
|
|
1408
|
+
// An ambiguous singular reference is the single most common follow-up shape
|
|
1409
|
+
// ("what region does this customer belong to?" after a ranked list). Keep the
|
|
1410
|
+
// candidates so the run can offer them instead of dead-ending.
|
|
1411
|
+
const ambiguousSingular = !namedValues
|
|
1412
|
+
? resolveSingularDeicticCandidates(question, activeValues)
|
|
1413
|
+
: undefined;
|
|
1414
|
+
const deicticChoices = ambiguousSingular && ambiguousSingular.values.length > 1
|
|
1415
|
+
? ambiguousSingular
|
|
1416
|
+
: undefined;
|
|
1417
|
+
// Do NOT backfill an ambiguous singular from history: filtering on all of the
|
|
1418
|
+
// candidates answers a different question than the one that was asked.
|
|
1419
|
+
if (!namedValues && !deicticChoices && dimensions.length > 0 && filters.length === 0) {
|
|
1165
1420
|
for (const turn of [...turns].reverse()) {
|
|
1166
1421
|
const values = cleanStringRecordArray(cleanRecord(turn.result)?.dimensionValues);
|
|
1167
1422
|
if (!values)
|
|
@@ -1200,7 +1455,9 @@ function resolveConversationReferences(question, turns, activeValues) {
|
|
|
1200
1455
|
}]
|
|
1201
1456
|
: [];
|
|
1202
1457
|
const unresolved = referencesNeedValues(question) && filters.length === 0
|
|
1203
|
-
? [
|
|
1458
|
+
? [deicticChoices
|
|
1459
|
+
? `"${deicticChoices.dimension}" was referenced in the singular, but the previous result had ${deicticChoices.values.length} of them.`
|
|
1460
|
+
: 'Could not resolve the referenced prior result values from conversation state.']
|
|
1204
1461
|
: undefined;
|
|
1205
1462
|
return {
|
|
1206
1463
|
filters: filters.length > 0 ? Array.from(new Set(filters)).slice(0, 24) : undefined,
|
|
@@ -1209,6 +1466,7 @@ function resolveConversationReferences(question, turns, activeValues) {
|
|
|
1209
1466
|
unresolved,
|
|
1210
1467
|
valuesByDimension: namedValues,
|
|
1211
1468
|
memberBindings: memberBindings.length > 0 ? memberBindings : undefined,
|
|
1469
|
+
deicticChoices,
|
|
1212
1470
|
};
|
|
1213
1471
|
}
|
|
1214
1472
|
/**
|
|
@@ -1301,7 +1559,7 @@ function activeTurnNumber(turn, key) {
|
|
|
1301
1559
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
1302
1560
|
}
|
|
1303
1561
|
function referencesNeedValues(question) {
|
|
1304
|
-
return /\b(?:it|its|they|their|them|this|that|these|those|same|above|previous|prior)\b/i.test(question);
|
|
1562
|
+
return /\b(?:it|its|they|their|them|he|she|him|his|her|hers|this|that|these|those|same|above|previous|prior)\b/i.test(question);
|
|
1305
1563
|
}
|
|
1306
1564
|
function extractCertifiedBlockName(content) {
|
|
1307
1565
|
const fromAnswer = content.match(/Answered by certified block \*\*([^*]+)\*\*/i)?.[1];
|
|
@@ -1353,15 +1611,34 @@ function cleanStringRecordArray(value) {
|
|
|
1353
1611
|
}
|
|
1354
1612
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
1355
1613
|
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Values a singular deictic reference ("this customer") could be pointing at.
|
|
1616
|
+
*
|
|
1617
|
+
* This used to `.slice(0, 1)` — silently picking the FIRST row of the previous
|
|
1618
|
+
* answer and binding it as a required filter. After a ten-row ranking, "this
|
|
1619
|
+
* customer" would quietly become "Melissa Lopez" because she happened to sort
|
|
1620
|
+
* first, and the user was never told. An ambiguous reference must stay
|
|
1621
|
+
* ambiguous so the caller can ask which one was meant.
|
|
1622
|
+
*/
|
|
1623
|
+
function resolveSingularDeicticCandidates(question, priorValues) {
|
|
1624
|
+
if (!priorValues)
|
|
1625
|
+
return undefined;
|
|
1626
|
+
const singular = resolveSingularDeicticDimension(question, priorValues);
|
|
1627
|
+
if (!singular)
|
|
1628
|
+
return undefined;
|
|
1629
|
+
const values = Array.from(new Set(valuesForPriorDimension(priorValues, singular))).slice(0, 24);
|
|
1630
|
+
return values.length > 0 ? { dimension: singular, values } : undefined;
|
|
1631
|
+
}
|
|
1356
1632
|
function resolveDeicticFilters(question, priorValues) {
|
|
1357
1633
|
if (!priorValues)
|
|
1358
1634
|
return undefined;
|
|
1359
1635
|
const hasExplicitPluralReference = /\b(?:these|those|same)\s+(?:customers?|products?|cat(?:egor|agor|ogor)(?:y|ies)|segments?|regions?)\b/i.test(question);
|
|
1360
1636
|
if (!hasExplicitPluralReference) {
|
|
1361
|
-
const singular =
|
|
1637
|
+
const singular = resolveSingularDeicticCandidates(question, priorValues);
|
|
1362
1638
|
if (singular) {
|
|
1363
|
-
|
|
1364
|
-
|
|
1639
|
+
// Exactly one candidate is unambiguous and can be bound as a filter.
|
|
1640
|
+
// Several candidates are a question for the user, not a coin flip.
|
|
1641
|
+
return singular.values.length === 1 ? singular.values : undefined;
|
|
1365
1642
|
}
|
|
1366
1643
|
}
|
|
1367
1644
|
const dims = resolveDeicticDimensions(question, priorValues) ?? [];
|
|
@@ -1401,7 +1678,18 @@ function resolveDeicticDimensions(question, priorValues) {
|
|
|
1401
1678
|
&& /\b(?:they|their|them)\b[^.?!]{0,48}\b(?:buy|bought|purchase|purchased|order|ordered|spend|spent|use|used)\b/.test(lower)
|
|
1402
1679
|
&& valuesForPriorDimension(priorValues, 'customer').length > 0)
|
|
1403
1680
|
dims.push('customer');
|
|
1404
|
-
|
|
1681
|
+
// Singular people pronouns are common in conversational analytical follow-ups
|
|
1682
|
+
// ("what region does he belong to?", "what is her segment?"). Resolve them
|
|
1683
|
+
// against the prior result's typed customer values, preserving the same
|
|
1684
|
+
// ambiguity guard used by "this customer". This is intentionally limited to
|
|
1685
|
+
// a person-like prior dimension; a pronoun must never make us guess a product
|
|
1686
|
+
// or metric member from the first row.
|
|
1687
|
+
if (dims.length === 0
|
|
1688
|
+
&& /\b(?:he|she|him|his|her|hers)\b/.test(lower)
|
|
1689
|
+
&& valuesForPriorDimension(priorValues, 'customer').length > 0) {
|
|
1690
|
+
dims.push('customer');
|
|
1691
|
+
}
|
|
1692
|
+
if (dims.length === 0 && /\b(?:it|its|they|their|them|he|she|him|his|her|hers|this|these|those|that|same|above|previous|prior)\b/.test(lower)) {
|
|
1405
1693
|
const single = singlePriorValueDimension(priorValues);
|
|
1406
1694
|
if (single)
|
|
1407
1695
|
dims.push(single);
|
|
@@ -1413,6 +1701,7 @@ function resolveSingularDeicticDimension(question, priorValues) {
|
|
|
1413
1701
|
const candidates = [
|
|
1414
1702
|
[/\b(?:this|that|same|previous|prior|above)\s+product\b/, 'product'],
|
|
1415
1703
|
[/\b(?:this|that|same|previous|prior|above)\s+customer\b/, 'customer'],
|
|
1704
|
+
[/\b(?:he|she|him|his|her|hers)\b/, 'customer'],
|
|
1416
1705
|
[/\b(?:this|that|same|previous|prior|above)\s+category\b/, 'category'],
|
|
1417
1706
|
[/\b(?:this|that|same|previous|prior|above)\s+segment\b/, 'segment'],
|
|
1418
1707
|
[/\b(?:this|that|same|previous|prior|above)\s+region\b/, 'region'],
|
|
@@ -1499,7 +1788,11 @@ export const __test__ = {
|
|
|
1499
1788
|
shouldLoadSchemaContext,
|
|
1500
1789
|
shouldSearchProjectFiles,
|
|
1501
1790
|
renderProjectSourceSearch,
|
|
1791
|
+
researchDispatchPurposeForTool,
|
|
1502
1792
|
};
|
|
1793
|
+
function researchDispatchPurposeForTool(toolName) {
|
|
1794
|
+
return toolName === 'execute_local_analysis' ? 'research_tool' : 'research_narration';
|
|
1795
|
+
}
|
|
1503
1796
|
const GENERIC_FOLLOW_UP_WORDS = new Set([
|
|
1504
1797
|
'a', 'about', 'again', 'all', 'also', 'and', 'are', 'as', 'be', 'block', 'can', 'could', 'data', 'did', 'do', 'does',
|
|
1505
1798
|
'execute', 'for', 'from', 'get', 'give', 'import', 'it', 'its', 'let', 'lets', 'me', 'metrics',
|
|
@@ -1537,7 +1830,7 @@ function isGenericFollowUp(question) {
|
|
|
1537
1830
|
function isDrilldownFollowUp(question, priorTerms = []) {
|
|
1538
1831
|
const lower = question.toLowerCase();
|
|
1539
1832
|
const deicticDrilldown = /\b(?:this|that|these|those|same|above|previous|prior)\s+(?:amount|value|orders?|results?|rows?|customers?|products?|cat(?:egor|agor|ogor)(?:y|ies)|segments?|regions?)\b/.test(lower)
|
|
1540
|
-
|| /\b(?:they|their|them)\b/.test(lower);
|
|
1833
|
+
|| /\b(?:they|their|them|he|she|him|his|her|hers)\b/.test(lower);
|
|
1541
1834
|
// `break ... down` is split by its object more often than not ("break that
|
|
1542
1835
|
// down", "break the revenue down"), so the verb and particle are matched with
|
|
1543
1836
|
// a short gap between them rather than adjacently.
|