@duckcodeailabs/dql-cli 1.12.7 → 1.12.9
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/{AgentAnswerCard-BJAnjaLA.js → AgentAnswerCard-DbI0CAsX.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AgentLogPage-J72WR6hk.js → AgentLogPage-BbMhuJps.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildDialog-CHJZfDmp.js → AiBuildDialog-DNgkam6H.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildResult-Da7_Cn12.js → AiBuildResult-BuTao779.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiSidePanel-BY6W_5aC.js → AiSidePanel-D-EhCOEd.js} +1 -1
- package/dist/assets/dql-notebook/assets/AnalyticsHome-BTjkolBn.js +6 -0
- package/dist/assets/dql-notebook/assets/{AppsView-3k7ftDpd.js → AppsView-B3jlWoNm.js} +13 -18
- package/dist/assets/dql-notebook/assets/{BlockStudio-CLX4EUyO.js → BlockStudio-DTFHMR3y.js} +1 -1
- package/dist/assets/dql-notebook/assets/{BusinessArtifactView-3THMNeyO.js → BusinessArtifactView-DGiHB7OS.js} +1 -1
- package/dist/assets/dql-notebook/assets/{DbtFirstModelingPage-BwBPwtdA.js → DbtFirstModelingPage-D-fv_dS2.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GitPage-CvEki0pe.js → GitPage-BWzPKyws.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GlobalAiRail-NXdk9kNy.js → GlobalAiRail-CGvBUci2.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GovernedContextPage-DILbw-Vr.js → GovernedContextPage-CxkrtqyP.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HelpDocsPage-DgCu4bvY.js → HelpDocsPage-C7IX_7xh.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HomePage-CK3fXTQo.js → HomePage-e0CObjOb.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDAG-DW_NO3P2.js → LineageDAG-DV6T_0ha.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDetailView-VT7DLJOB.js → LineageDetailView-ZVJiCqua.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDrawer-DSTm8aXw.js → LineageDrawer-DQdzGIoL.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineagePathBreadcrumb-DRlAYasJ.js → LineagePathBreadcrumb-6gFsnWlN.js} +1 -1
- package/dist/assets/dql-notebook/assets/{MiniLineageGraph-CR_nhwhZ.js → MiniLineageGraph-BlJYiAY0.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewBlockModal-CJvjYiBF.js → NewBlockModal-DaxBAFLu.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewNotebookModal-BEpPH93n.js → NewNotebookModal-B7GPLsYc.js} +1 -1
- package/dist/assets/dql-notebook/assets/NotebookEditor-RuxFQnQC.js +202 -0
- package/dist/assets/dql-notebook/assets/{ReadinessPage-rZiOp2xr.js → ReadinessPage-m8jlEKzQ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SetupOnboarding-BWjhTjPB.js → SetupOnboarding-ClldPV4C.js} +1 -1
- package/dist/assets/dql-notebook/assets/{TrustBadge-RvZ9s6OQ.js → TrustBadge-C0FyL1ZO.js} +1 -1
- package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-Bd1qmRFx.js +83 -0
- package/dist/assets/dql-notebook/assets/{answer-to-notebook-Di227Cd4.js → answer-to-notebook-CgVDBH0o.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-left-BZHDcWHR.js → arrow-left-XTRkwgdW.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-right-DlM1pC3_.js → arrow-right-BUYb7B8y.js} +1 -1
- package/dist/assets/dql-notebook/assets/{book-open-text-DBcXFQAu.js → book-open-text-CQHKAXcN.js} +1 -1
- package/dist/assets/dql-notebook/assets/{bot-BAywbvPm.js → bot-D9ODKCwi.js} +1 -1
- package/dist/assets/dql-notebook/assets/{chevron-up-B_GfKE_F.js → chevron-up-BR57nCns.js} +1 -1
- package/dist/assets/dql-notebook/assets/{circle-x-BcxtZ-ym.js → circle-x-B3MKf4hs.js} +1 -1
- package/dist/assets/dql-notebook/assets/{dagre.esm-Bqs0lsEz.js → dagre.esm-BWViOEog.js} +1 -1
- package/dist/assets/dql-notebook/assets/{external-link-B24Dk9_t.js → external-link-D2rxGhQ_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{grip-vertical-D9g31N3j.js → grip-vertical-Bzs7bIWP.js} +1 -1
- package/dist/assets/dql-notebook/assets/{index-DU7j3Sqk.js → index-CDpukm0G.js} +128 -128
- package/dist/assets/dql-notebook/assets/{link-2-B36oR5Bj.js → link-2-D6nW0_qQ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{list-tree-B9ZzGc4U.js → list-tree-MH5kYamM.js} +1 -1
- package/dist/assets/dql-notebook/assets/{maximize-2-BrvQLPoj.js → maximize-2-C3bEmsdA.js} +1 -1
- package/dist/assets/dql-notebook/assets/{minimize-2-BrpFmsxS.js → minimize-2-cji4jKmJ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{panel-right-open-B1g1qhh5.js → panel-right-open-T-_o8FS9.js} +1 -1
- package/dist/assets/dql-notebook/assets/{pencil-DwywbTqQ.js → pencil-BzJ_vZuL.js} +1 -1
- package/dist/assets/dql-notebook/assets/{rotate-ccw-Db_KVs3J.js → rotate-ccw-CqSfrg2J.js} +1 -1
- package/dist/assets/dql-notebook/assets/{semantic-fields-BIVuvP2l.js → semantic-fields-BXNCrm72.js} +1 -1
- package/dist/assets/dql-notebook/assets/star-C22MG8pV.js +6 -0
- package/dist/assets/dql-notebook/assets/{triangle-alert-Cne6C2aG.js → triangle-alert-BtoT0VL-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{usePersistedAgentThreadId-D6mtrJ-_.js → usePersistedAgentThreadId-BL9P0gFB.js} +1 -1
- package/dist/assets/dql-notebook/assets/{user-round-DkzxN1xD.js → user-round-CpifcAyh.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wand-sparkles-CJUvFtur.js → wand-sparkles-BiOywrQh.js} +1 -1
- package/dist/assets/dql-notebook/assets/{workflow-BOvVTg7R.js → workflow-DDvVMZ2B.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wrench-YeSpzZRt.js → wrench-CoKg2kyO.js} +1 -1
- package/dist/assets/dql-notebook/assets/{x-C6tsHUR7.js → x-CCmr0LQ8.js} +1 -1
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/local-runtime.d.ts +37 -0
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +278 -8
- 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/AnalyticsHome-u-buXgdv.js +0 -6
- package/dist/assets/dql-notebook/assets/NotebookEditor-C7GDFwhF.js +0 -202
- package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-CGPcpY1B.js +0 -83
package/dist/local-runtime.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync, execSync } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
+
import { gzipSync } from "node:zlib";
|
|
3
4
|
import { createServer } from "node:http";
|
|
4
5
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, watch, writeFileSync, } from "node:fs";
|
|
5
6
|
import { createRequire } from "node:module";
|
|
@@ -20,7 +21,7 @@ import { fetchLatestPublishedDqlVersion, resolveDqlRuntimeVersionStatus } from '
|
|
|
20
21
|
import { resolveRetrievalHealthStatus } from './retrieval-health.js';
|
|
21
22
|
import { createDqlAgentProviderRunner, resolveAgentFollowUpContext } from './llm/providers/dql-agent-provider.js';
|
|
22
23
|
import { listRemoteMcpSettings, saveRemoteMcpSettings } from './llm/mcp-config.js';
|
|
23
|
-
import { ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, conversationHistoryFromContext, recallRelevantTurns, renderConversationEnvelopeForPrompt, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildAnalysisQuestionPlan, composeSemanticQueryForQuestion, aggregationIntegrityIssuesForSql, buildLocalContextPack, applyContextPackCompatibility, toAgentRetrievalEvidence, prepareConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureAgentProjectReady, isAgentProjectIndexReady, currentMetadataFingerprint, ensureMetadataCatalogFresh, readIndexedDomainKnowledge, readIndexedKnowledge360, compactSemanticRuntimeFailure, classifyAnalyticalFailure, propose, proposePlan, recordGovernedCorrection, HintStore, defaultHintIndexPath, ensureHintIndexFresh, listHintsFromGit, getHintEvaluationFromGit, getCorrectionTraceFromGit, inspectGovernedHint, editGovernedHintCandidate, reopenGovernedHint, retireHint, supersedeHint, hintsConflict, mineJoinPatterns, reviewGovernedHint, AgentRunEngine, SqliteAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, buildDeterministicDashboardStory, synthesizeAnswer, streamOrGenerate, narrateResult, buildProposePreview, buildFromPrompt, internalRelationIdsInSql, defaultAgentRunStorePath, defaultAgentRunSqlitePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, configuredSkillsPath, skillsDir, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, deleteSkill, deriveGeneratedDraftSlug, deriveAnalyticalRepair, reindexProject, invalidateAgentProjectState, recordAgentRuntimeVersion, resolveDomainContextEnvelope, projectEmbeddingProvider, isHashedEmbeddingProvider, clearProjectEmbeddingCache, upgradeVectorIndexForProject, openMetadataCatalog, defaultKgPath, planAppFromPrompt, KGStore, planResearch, loadSemanticMetrics, cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, routeReasoningEffort, routeForCascadeAnswerTier, clampReasoningEffort, bumpReasoningEffort, resolveThinkingMode, coerceThinkingMode, upsertGeneratedDqlArtifactDraft, loadAgentSemanticLayer, isTrustedConversationTurn, analyticalError, withAnalyticalErrorOrigin, withAnalyticalErrorOriginSync, } from '@duckcodeailabs/dql-agent';
|
|
24
|
+
import { ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, conversationHistoryFromContext, recallRelevantTurns, renderConversationEnvelopeForPrompt, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildAnalysisQuestionPlan, composeSemanticQueryForQuestion, aggregationIntegrityIssuesForSql, buildLocalContextPack, applyContextPackCompatibility, toAgentRetrievalEvidence, prepareConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureAgentProjectReady, isAgentProjectIndexReady, currentMetadataFingerprint, ensureMetadataCatalogFresh, readIndexedDomainKnowledge, readIndexedKnowledge360, compactSemanticRuntimeFailure, classifyAnalyticalFailure, propose, proposePlan, recordGovernedCorrection, HintStore, defaultHintIndexPath, ensureHintIndexFresh, listHintsFromGit, getHintEvaluationFromGit, getCorrectionTraceFromGit, inspectGovernedHint, editGovernedHintCandidate, reopenGovernedHint, retireHint, supersedeHint, hintsConflict, mineJoinPatterns, reviewGovernedHint, AgentRunEngine, SqliteAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, buildDeterministicDashboardStory, synthesizeAnswer, streamOrGenerate, narrateResult, buildProposePreview, buildFromPrompt, internalRelationIdsInSql, defaultAgentRunStorePath, defaultAgentRunSqlitePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, configuredSkillsPath, skillsDir, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, deleteSkill, deriveGeneratedDraftSlug, deriveAnalyticalRepair, reindexProject, invalidateAgentProjectState, recordAgentRuntimeVersion, resolveDomainContextEnvelope, projectEmbeddingProvider, isHashedEmbeddingProvider, clearProjectEmbeddingCache, upgradeVectorIndexForProject, openMetadataCatalog, defaultKgPath, planAppFromPrompt, KGStore, planResearch, loadSemanticMetrics, cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, routeReasoningEffort, routeForCascadeAnswerTier, clampReasoningEffort, bumpReasoningEffort, resolveThinkingMode, coerceThinkingMode, upsertGeneratedDqlArtifactDraft, loadAgentSemanticLayer, isTrustedConversationTurn, resolveInternalRelationIds, analyticalError, withAnalyticalErrorOrigin, withAnalyticalErrorOriginSync, } from '@duckcodeailabs/dql-agent';
|
|
24
25
|
import { addSqlResultFilter, filterableResultColumns, replaceBlockStudioSql } from './sql-result-filter.js';
|
|
25
26
|
import { gatherProposeEnrichment } from './propose-enrich.js';
|
|
26
27
|
import { handleAppsApi, proposeAppAiBuild, recommendVisualization } from './apps-api.js';
|
|
@@ -526,6 +527,73 @@ function recordConversationTurn(store, threadId, run) {
|
|
|
526
527
|
// Conversation persistence is additive; a failed write must not fail the run.
|
|
527
528
|
}
|
|
528
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Presentation projection of a run, for anything that SHIPS it — thread history
|
|
532
|
+
* and the live SSE stream alike.
|
|
533
|
+
*
|
|
534
|
+
* A stored run is dominated by DUPLICATED diagnostics rather than content. On a
|
|
535
|
+
* real 2.4 MB run: `diagnosticReceipt` is held twice — once top-level and once
|
|
536
|
+
* inside `artifacts[0].payload`, byte-identical — and each copy embeds its own
|
|
537
|
+
* full `steps` and `artifacts`, which embed the payload again. The part the UI
|
|
538
|
+
* actually renders (`payload.result`) was 3 KB.
|
|
539
|
+
*
|
|
540
|
+
* Everything the thread view reads is preserved, including
|
|
541
|
+
* `diagnosticReceipt.failure.message`; only the self-referential nesting and the
|
|
542
|
+
* unread `considered` block are dropped. The complete record stays available
|
|
543
|
+
* from the existing `GET /api/agent-runs/:id` run-state route.
|
|
544
|
+
*/
|
|
545
|
+
export function slimAgentRunForTransport(run) {
|
|
546
|
+
const slimReceipt = (receipt) => {
|
|
547
|
+
const record = agentRunRecord(receipt);
|
|
548
|
+
if (!record)
|
|
549
|
+
return receipt;
|
|
550
|
+
// `steps`/`artifacts` inside the receipt duplicate the run's own.
|
|
551
|
+
const { steps: _steps, artifacts: _artifacts, ...rest } = record;
|
|
552
|
+
return rest;
|
|
553
|
+
};
|
|
554
|
+
const artifacts = (run.artifacts ?? []).map((artifact) => {
|
|
555
|
+
const payload = agentRunRecord(artifact.payload);
|
|
556
|
+
if (!payload)
|
|
557
|
+
return artifact;
|
|
558
|
+
const { considered: _considered, ...restPayload } = payload;
|
|
559
|
+
return {
|
|
560
|
+
...artifact,
|
|
561
|
+
payload: {
|
|
562
|
+
...restPayload,
|
|
563
|
+
...(payload.diagnosticReceipt ? { diagnosticReceipt: slimReceipt(payload.diagnosticReceipt) } : {}),
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
});
|
|
567
|
+
// `steps[].artifacts` is a THIRD copy of the run's own artifacts, and one
|
|
568
|
+
// progress event carries a FOURTH as its payload. Both are dropped rather than
|
|
569
|
+
// truncated: the data itself is unchanged and still on `run.artifacts`.
|
|
570
|
+
const MAX_EVENT_PAYLOAD_BYTES = 8 * 1024;
|
|
571
|
+
const steps = (run.steps ?? []).map((step) => {
|
|
572
|
+
const record = agentRunRecord(step);
|
|
573
|
+
if (!record?.artifacts)
|
|
574
|
+
return step;
|
|
575
|
+
const { artifacts: _stepArtifacts, ...rest } = record;
|
|
576
|
+
return rest;
|
|
577
|
+
});
|
|
578
|
+
const events = (run.events ?? []).slice(-20).map((event) => {
|
|
579
|
+
const record = agentRunRecord(event);
|
|
580
|
+
if (!record?.payload)
|
|
581
|
+
return event;
|
|
582
|
+
const encoded = JSON.stringify(record.payload) ?? '';
|
|
583
|
+
if (encoded.length <= MAX_EVENT_PAYLOAD_BYTES)
|
|
584
|
+
return event;
|
|
585
|
+
return { ...record, payload: { omitted: 'oversized', bytes: encoded.length } };
|
|
586
|
+
});
|
|
587
|
+
return {
|
|
588
|
+
...run,
|
|
589
|
+
artifacts,
|
|
590
|
+
steps,
|
|
591
|
+
events,
|
|
592
|
+
...(run.diagnosticReceipt
|
|
593
|
+
? { diagnosticReceipt: slimReceipt(run.diagnosticReceipt) }
|
|
594
|
+
: {}),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
529
597
|
export function conversationTurnInputFromRun(run) {
|
|
530
598
|
const artifact = run.artifacts.find((candidate) => candidate.kind === 'answer')
|
|
531
599
|
?? run.artifacts.find((candidate) => candidate.kind === 'research_run')
|
|
@@ -4702,7 +4770,97 @@ export async function startLocalServer(opts) {
|
|
|
4702
4770
|
response.write(`event: ${event}\n`);
|
|
4703
4771
|
response.write(`data: ${serializeJSON(data)}\n\n`);
|
|
4704
4772
|
};
|
|
4773
|
+
/**
|
|
4774
|
+
* Gzip text responses.
|
|
4775
|
+
*
|
|
4776
|
+
* Nothing was compressed before: the main bundle went out at 592 KB where gzip
|
|
4777
|
+
* makes it 154 KB (73% smaller), and JSON API responses — which compress even
|
|
4778
|
+
* better — were also sent raw. One wrapper here rather than a change at
|
|
4779
|
+
* hundreds of `res.end(...)` call sites.
|
|
4780
|
+
*
|
|
4781
|
+
* STREAMING IS EXEMPT. Buffering an SSE response in order to compress it would
|
|
4782
|
+
* defeat streaming entirely, so any response that calls `res.write()` opts
|
|
4783
|
+
* itself out permanently, as does `text/event-stream`.
|
|
4784
|
+
*/
|
|
4785
|
+
const MIN_COMPRESS_BYTES = 1024;
|
|
4786
|
+
const COMPRESSIBLE = /^(?:text\/(?!event-stream)|application\/(?:json|javascript|xml|wasm)|image\/svg)/i;
|
|
4787
|
+
const withCompression = (request, response) => {
|
|
4788
|
+
if (!/\bgzip\b/i.test(String(request.headers['accept-encoding'] ?? '')))
|
|
4789
|
+
return response;
|
|
4790
|
+
const writeHead = response.writeHead.bind(response);
|
|
4791
|
+
const end = response.end.bind(response);
|
|
4792
|
+
const write = response.write.bind(response);
|
|
4793
|
+
let status = 200;
|
|
4794
|
+
let headers = {};
|
|
4795
|
+
let captured = false;
|
|
4796
|
+
let streaming = false;
|
|
4797
|
+
response.writeHead = ((code, hdrs) => {
|
|
4798
|
+
status = code;
|
|
4799
|
+
headers = hdrs ?? {};
|
|
4800
|
+
captured = true;
|
|
4801
|
+
return response;
|
|
4802
|
+
});
|
|
4803
|
+
response.write = ((...args) => {
|
|
4804
|
+
if (captured && !streaming) {
|
|
4805
|
+
streaming = true;
|
|
4806
|
+
writeHead(status, headers);
|
|
4807
|
+
}
|
|
4808
|
+
return write(...args);
|
|
4809
|
+
});
|
|
4810
|
+
response.end = ((...args) => {
|
|
4811
|
+
const body = args[0];
|
|
4812
|
+
const contentType = String(headers['Content-Type'] ?? '');
|
|
4813
|
+
const encodable = captured
|
|
4814
|
+
&& !streaming
|
|
4815
|
+
&& (typeof body === 'string' || Buffer.isBuffer(body))
|
|
4816
|
+
&& COMPRESSIBLE.test(contentType);
|
|
4817
|
+
if (!encodable) {
|
|
4818
|
+
if (captured && !streaming) {
|
|
4819
|
+
streaming = true;
|
|
4820
|
+
writeHead(status, headers);
|
|
4821
|
+
}
|
|
4822
|
+
return end(...args);
|
|
4823
|
+
}
|
|
4824
|
+
const raw = Buffer.isBuffer(body) ? body : Buffer.from(body, 'utf8');
|
|
4825
|
+
streaming = true;
|
|
4826
|
+
// VALIDATOR CACHING. API responses carried no ETag or Last-Modified, so a
|
|
4827
|
+
// browser could never revalidate: every panel switch re-downloaded every
|
|
4828
|
+
// read in full. An ETag with `Cache-Control: no-cache` means the browser
|
|
4829
|
+
// always ASKS but the server answers 304 with no body when nothing
|
|
4830
|
+
// changed — freshness is unchanged, the bytes are not re-sent.
|
|
4831
|
+
const cacheableRead = request.method === 'GET' && status === 200;
|
|
4832
|
+
const etag = cacheableRead
|
|
4833
|
+
? `W/"${createHash('sha1').update(raw).digest('base64url')}"`
|
|
4834
|
+
: undefined;
|
|
4835
|
+
if (etag && String(request.headers['if-none-match'] ?? '') === etag) {
|
|
4836
|
+
writeHead(304, {
|
|
4837
|
+
ETag: etag,
|
|
4838
|
+
'Cache-Control': headers['Cache-Control'] ?? 'no-cache',
|
|
4839
|
+
Vary: 'Accept-Encoding, Origin',
|
|
4840
|
+
});
|
|
4841
|
+
return end();
|
|
4842
|
+
}
|
|
4843
|
+
const validatorHeaders = etag
|
|
4844
|
+
? { ETag: etag, 'Cache-Control': headers['Cache-Control'] ?? 'no-cache' }
|
|
4845
|
+
: {};
|
|
4846
|
+
if (raw.byteLength < MIN_COMPRESS_BYTES) {
|
|
4847
|
+
writeHead(status, { ...headers, ...validatorHeaders });
|
|
4848
|
+
return end(raw);
|
|
4849
|
+
}
|
|
4850
|
+
const gz = gzipSync(raw);
|
|
4851
|
+
writeHead(status, {
|
|
4852
|
+
...headers,
|
|
4853
|
+
...validatorHeaders,
|
|
4854
|
+
'Content-Encoding': 'gzip',
|
|
4855
|
+
'Content-Length': gz.byteLength,
|
|
4856
|
+
Vary: 'Accept-Encoding, Origin',
|
|
4857
|
+
});
|
|
4858
|
+
return end(gz);
|
|
4859
|
+
});
|
|
4860
|
+
return response;
|
|
4861
|
+
};
|
|
4705
4862
|
const server = createServer((req, res) => {
|
|
4863
|
+
res = withCompression(req, res);
|
|
4706
4864
|
const requestUrl = req.url || '/';
|
|
4707
4865
|
const url = new URL(requestUrl, 'http://127.0.0.1');
|
|
4708
4866
|
const path = url.pathname || '/';
|
|
@@ -5693,7 +5851,12 @@ export async function startLocalServer(opts) {
|
|
|
5693
5851
|
writeAgentRunSse(res, 'agent-run-answer-delta', { runId: parsed.request.runId, delta });
|
|
5694
5852
|
});
|
|
5695
5853
|
recordConversationTurn(conversationStore, parsed.request.threadId, run);
|
|
5696
|
-
|
|
5854
|
+
// The terminal frame carried the whole stored run — 2.4 MB on a real
|
|
5855
|
+
// question, of which the renderable part was ~3 KB. The rest was the
|
|
5856
|
+
// same artifacts repeated four times over. The client renders from
|
|
5857
|
+
// the projection and fetches the full record from the run-state
|
|
5858
|
+
// route only if something needs it.
|
|
5859
|
+
writeAgentRunSse(res, 'agent-run-complete', slimAgentRunForTransport(run));
|
|
5697
5860
|
res.end();
|
|
5698
5861
|
return;
|
|
5699
5862
|
}
|
|
@@ -6865,6 +7028,26 @@ export async function startLocalServer(opts) {
|
|
|
6865
7028
|
res.end(serializeJSON({ ok: true }));
|
|
6866
7029
|
return;
|
|
6867
7030
|
}
|
|
7031
|
+
// Rename / pin. Both are housekeeping on an existing conversation, so
|
|
7032
|
+
// neither moves `updated_at` — see the store methods.
|
|
7033
|
+
if (req.method === 'PATCH' && !action) {
|
|
7034
|
+
const body = await readJSON(req).catch(() => null);
|
|
7035
|
+
const record = agentRunRecord(body) ?? {};
|
|
7036
|
+
const title = agentRunString(record.title);
|
|
7037
|
+
const favorite = typeof record.favorite === 'boolean' ? record.favorite : undefined;
|
|
7038
|
+
if (title === undefined && favorite === undefined) {
|
|
7039
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
7040
|
+
res.end(serializeJSON({ error: 'Provide a title or a favorite flag.' }));
|
|
7041
|
+
return;
|
|
7042
|
+
}
|
|
7043
|
+
if (title !== undefined)
|
|
7044
|
+
store.renameThread(threadId, title);
|
|
7045
|
+
if (favorite !== undefined)
|
|
7046
|
+
store.setThreadFavorite(threadId, favorite);
|
|
7047
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
7048
|
+
res.end(serializeJSON({ thread: store.getThread(threadId) }));
|
|
7049
|
+
return;
|
|
7050
|
+
}
|
|
6868
7051
|
if (req.method === 'DELETE' && !action) {
|
|
6869
7052
|
store.deleteThread(threadId);
|
|
6870
7053
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
@@ -6914,7 +7097,13 @@ export async function startLocalServer(opts) {
|
|
|
6914
7097
|
if (!turn.agentRunId)
|
|
6915
7098
|
return [];
|
|
6916
7099
|
const run = agentRunStore.get(turn.agentRunId);
|
|
6917
|
-
|
|
7100
|
+
// Thread history is a PRESENTATION payload. A stored run averages
|
|
7101
|
+
// ~700 KB and can exceed 2 MB, almost none of it renderable, so
|
|
7102
|
+
// fetching 50 of them meant tens of megabytes to read, parse,
|
|
7103
|
+
// re-serialize and ship before the sidebar could draw — the
|
|
7104
|
+
// reported ~10s load. `GET /api/agent-runs/:id` serves the full
|
|
7105
|
+
// record when the inspector actually needs it.
|
|
7106
|
+
return run ? [slimAgentRunForTransport(run)] : [];
|
|
6918
7107
|
});
|
|
6919
7108
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
6920
7109
|
res.end(serializeJSON({
|
|
@@ -12728,10 +12917,26 @@ export async function startLocalServer(opts) {
|
|
|
12728
12917
|
res.end(serializeJSON({ cellType: cell.type, result: null }));
|
|
12729
12918
|
return;
|
|
12730
12919
|
}
|
|
12731
|
-
|
|
12920
|
+
// DECODE, THEN VALIDATE. A leaked `source::db.schema.table` is an internal
|
|
12921
|
+
// retrieval key whose suffix IS the physical relation, so rejecting the
|
|
12922
|
+
// cell and asking the user (or another AI round trip) to repair it was
|
|
12923
|
+
// busywork for a mechanical substitution. Anything still unresolvable
|
|
12924
|
+
// after this — a BARE `source::orders`, which names no database or
|
|
12925
|
+
// schema — genuinely cannot be decoded and still fails here.
|
|
12926
|
+
let decodedSql = resolveInternalRelationIds(executableSql, EMPTY_SCHEMA_GROUNDING);
|
|
12927
|
+
let bareResolution = null;
|
|
12928
|
+
if (internalDqlRelationIdValidationError(decodedSql, 'x')) {
|
|
12929
|
+
bareResolution = await resolveBareInternalRelationIds(decodedSql, executor, cellConnection);
|
|
12930
|
+
decodedSql = bareResolution.sql;
|
|
12931
|
+
}
|
|
12932
|
+
const internalRelationError = internalDqlRelationIdValidationError(decodedSql, cell.type === 'dql' ? 'DQL query' : 'Notebook query',
|
|
12933
|
+
// A probe that came back empty is PROOF the relation is not visible
|
|
12934
|
+
// to this connection, so the refusal states that instead of sending
|
|
12935
|
+
// the user to an AI repair that cannot conjure a missing table.
|
|
12936
|
+
bareResolution ? { probed: true, ambiguous: bareResolution.ambiguous } : undefined);
|
|
12732
12937
|
if (internalRelationError)
|
|
12733
12938
|
throw new DqlInternalRelationIdError(internalRelationError);
|
|
12734
|
-
const prepared = prepareLocalExecution(
|
|
12939
|
+
const prepared = prepareLocalExecution(decodedSql, cellConnection, projectRoot, projectConfig);
|
|
12735
12940
|
const app = loadRuntimeApp(projectRoot, typeof body.appId === 'string' ? body.appId : activePersonaAppId());
|
|
12736
12941
|
assertAppAccess({ app, domain: resolved.domain ?? app?.domain, level: 'execute' });
|
|
12737
12942
|
const semanticExecution = semanticCompose?.compiled
|
|
@@ -12791,7 +12996,10 @@ export async function startLocalServer(opts) {
|
|
|
12791
12996
|
tests: plan?.tests,
|
|
12792
12997
|
result: normalized,
|
|
12793
12998
|
executionTarget: executionTargetDescriptor(body),
|
|
12794
|
-
|
|
12999
|
+
// Report the statement that ACTUALLY ran. `executableSql` is the
|
|
13000
|
+
// pre-decode text, so showing it would put a `source::` identity in
|
|
13001
|
+
// front of the user that the warehouse never saw.
|
|
13002
|
+
compiledSql: semanticCompose?.compiled?.sql ?? decodedSql,
|
|
12795
13003
|
engine: semanticCompose?.compiled?.engine,
|
|
12796
13004
|
...(semanticExecution ? {
|
|
12797
13005
|
semanticTrace: semanticExecution.semanticTrace,
|
|
@@ -15478,6 +15686,8 @@ const AGENT_PREVIEW_FORBIDDEN_SQL = [
|
|
|
15478
15686
|
function withAnalyticalCompilationOrigin(stage, fn) {
|
|
15479
15687
|
return withAnalyticalErrorOriginSync({ origin: 'dql_compilation', stage }, fn);
|
|
15480
15688
|
}
|
|
15689
|
+
/** No dbt grounding — decoding an internal id needs none for a qualified suffix. */
|
|
15690
|
+
const EMPTY_SCHEMA_GROUNDING = { tables: [], joinKeys: [], byKey: new Map() };
|
|
15481
15691
|
export function clampAnalyticalRowBound(rowLimit) {
|
|
15482
15692
|
return Number.isFinite(rowLimit) ? Math.max(1, Math.min(10_000, Math.floor(rowLimit))) : 200;
|
|
15483
15693
|
}
|
|
@@ -15966,11 +16176,19 @@ function readOnlySqlValidationError(sql, subject, dialect = 'duckdb') {
|
|
|
15966
16176
|
}
|
|
15967
16177
|
return null;
|
|
15968
16178
|
}
|
|
15969
|
-
function internalDqlRelationIdValidationError(sql, subject) {
|
|
16179
|
+
function internalDqlRelationIdValidationError(sql, subject, probe) {
|
|
15970
16180
|
const internalIds = internalRelationIdsInSql(stripSqlStringsAndComments(sql));
|
|
15971
16181
|
if (internalIds.length === 0)
|
|
15972
16182
|
return null;
|
|
15973
|
-
|
|
16183
|
+
const plural = internalIds.length === 1 ? '' : 's';
|
|
16184
|
+
const head = `${subject} contains internal DQL graph relation identifier${plural} ${internalIds.join(', ')}.`;
|
|
16185
|
+
if (probe?.ambiguous.length) {
|
|
16186
|
+
return `${head} ${probe.ambiguous.join(', ')} matched more than one schema in this connection, so the name alone does not identify a relation. Qualify it as database.schema.table.`;
|
|
16187
|
+
}
|
|
16188
|
+
if (probe?.probed) {
|
|
16189
|
+
return `${head} DQL checked this connection and no relation with that name is visible to it. Qualify it as database.schema.table, or connect to the source that holds it.`;
|
|
16190
|
+
}
|
|
16191
|
+
return `${head} Use the physical database.schema.table relation instead, or choose Ask AI to fix from the notebook error.`;
|
|
15974
16192
|
}
|
|
15975
16193
|
function parseHintLessonInput(value) {
|
|
15976
16194
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
@@ -23114,6 +23332,58 @@ ${predicates.map((predicate) => ` ${predicate}`).join(' OR\n')}
|
|
|
23114
23332
|
ORDER BY table_schema, table_name, ordinal_position
|
|
23115
23333
|
LIMIT 400`;
|
|
23116
23334
|
}
|
|
23335
|
+
/**
|
|
23336
|
+
* Resolve a BARE internal graph identity (`source::orders` — a name with no
|
|
23337
|
+
* database or schema) against the warehouse.
|
|
23338
|
+
*
|
|
23339
|
+
* A qualified identity decodes mechanically, but a bare one carries no
|
|
23340
|
+
* location, so the old behaviour was to reject the cell and hand the user an
|
|
23341
|
+
* "Ask AI to fix" button. That was an LLM round trip for a lookup: the name is
|
|
23342
|
+
* unambiguous whenever exactly one relation in the connection answers to it.
|
|
23343
|
+
* Probe for it, and only refuse when the answer is genuinely zero or many —
|
|
23344
|
+
* at which point the refusal can say which it was.
|
|
23345
|
+
*
|
|
23346
|
+
* Runs ONLY after a decode has already failed, so the happy path pays nothing.
|
|
23347
|
+
*/
|
|
23348
|
+
export async function resolveBareInternalRelationIds(sql, executor, connection) {
|
|
23349
|
+
const bare = internalRelationIdsInSql(stripSqlStringsAndComments(sql))
|
|
23350
|
+
.filter((id) => !id.slice(id.indexOf('::') + 2).includes('.'));
|
|
23351
|
+
if (bare.length === 0)
|
|
23352
|
+
return { sql, resolved: [], ambiguous: [] };
|
|
23353
|
+
const names = bare.map((id) => id.slice(id.indexOf('::') + 2));
|
|
23354
|
+
const probeSql = buildNamedRelationProbeSql(names);
|
|
23355
|
+
if (!probeSql)
|
|
23356
|
+
return { sql, resolved: [], ambiguous: [] };
|
|
23357
|
+
let rows = [];
|
|
23358
|
+
try {
|
|
23359
|
+
const probe = await executor.executeQuery(probeSql, [], {}, connection);
|
|
23360
|
+
rows = (probe?.rows ?? []);
|
|
23361
|
+
}
|
|
23362
|
+
catch {
|
|
23363
|
+
// The probe is best-effort. A warehouse that cannot answer it leaves the
|
|
23364
|
+
// identity unresolved, which is the same outcome as not probing at all.
|
|
23365
|
+
return { sql, resolved: [], ambiguous: [] };
|
|
23366
|
+
}
|
|
23367
|
+
const resolved = [];
|
|
23368
|
+
const ambiguous = [];
|
|
23369
|
+
let out = sql;
|
|
23370
|
+
for (const id of bare) {
|
|
23371
|
+
const name = id.slice(id.indexOf('::') + 2).toLowerCase();
|
|
23372
|
+
const matches = new Set(rows
|
|
23373
|
+
.filter((row) => String(row.table_name ?? '').toLowerCase() === name)
|
|
23374
|
+
.map((row) => `${String(row.table_schema ?? '')}.${String(row.table_name ?? '')}`));
|
|
23375
|
+
if (matches.size !== 1) {
|
|
23376
|
+
if (matches.size > 1)
|
|
23377
|
+
ambiguous.push(id);
|
|
23378
|
+
continue;
|
|
23379
|
+
}
|
|
23380
|
+
const target = [...matches][0];
|
|
23381
|
+
const idPattern = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
23382
|
+
out = out.replace(new RegExp(`\\b${idPattern}`, 'g'), target);
|
|
23383
|
+
resolved.push({ from: id, to: target });
|
|
23384
|
+
}
|
|
23385
|
+
return { sql: out, resolved, ambiguous };
|
|
23386
|
+
}
|
|
23117
23387
|
export function buildRuntimeSchemaSearchSql(question) {
|
|
23118
23388
|
const terms = Array.from(agentSchemaTokens(question))
|
|
23119
23389
|
.map((term) => term.toLowerCase().replace(/[^a-z0-9_]/g, ''))
|