@duckcodeailabs/dql-cli 1.12.6 → 1.12.8
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-D1VcQOTJ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AgentLogPage-J72WR6hk.js → AgentLogPage-DKvtW4Fq.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildDialog-CHJZfDmp.js → AiBuildDialog-apJ4HoK3.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildResult-Da7_Cn12.js → AiBuildResult-CvNitmsw.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiSidePanel-BY6W_5aC.js → AiSidePanel-B2cqbPJr.js} +1 -1
- package/dist/assets/dql-notebook/assets/AnalyticsHome-D5I-Okbz.js +6 -0
- package/dist/assets/dql-notebook/assets/{AppsView-3k7ftDpd.js → AppsView-DLtjogVz.js} +13 -18
- package/dist/assets/dql-notebook/assets/{BlockStudio-CLX4EUyO.js → BlockStudio-BmNsIfNK.js} +1 -1
- package/dist/assets/dql-notebook/assets/{BusinessArtifactView-3THMNeyO.js → BusinessArtifactView-Bvn5X8uW.js} +1 -1
- package/dist/assets/dql-notebook/assets/{DbtFirstModelingPage-BwBPwtdA.js → DbtFirstModelingPage-DnpVGZJv.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GitPage-CvEki0pe.js → GitPage-C_5u2aCj.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GlobalAiRail-NXdk9kNy.js → GlobalAiRail-ZOHo4H6j.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GovernedContextPage-DILbw-Vr.js → GovernedContextPage-CUaYIqfd.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HelpDocsPage-DgCu4bvY.js → HelpDocsPage-6SmmBvJh.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HomePage-CK3fXTQo.js → HomePage-LDu8z_nW.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDAG-DW_NO3P2.js → LineageDAG-VFSCvevO.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDetailView-VT7DLJOB.js → LineageDetailView-CuGG_JDv.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDrawer-DSTm8aXw.js → LineageDrawer-B2SVsEw1.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineagePathBreadcrumb-DRlAYasJ.js → LineagePathBreadcrumb-qWhGM3bh.js} +1 -1
- package/dist/assets/dql-notebook/assets/{MiniLineageGraph-CR_nhwhZ.js → MiniLineageGraph-BHxUFWDi.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewBlockModal-CJvjYiBF.js → NewBlockModal-oo3fU5OW.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewNotebookModal-BEpPH93n.js → NewNotebookModal-Bl78Nnjz.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NotebookEditor-C7GDFwhF.js → NotebookEditor-BWMX6Xfi.js} +1 -1
- package/dist/assets/dql-notebook/assets/{ReadinessPage-rZiOp2xr.js → ReadinessPage-CL2EJLKo.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SetupOnboarding-BWjhTjPB.js → SetupOnboarding-Cqw2NuAn.js} +1 -1
- package/dist/assets/dql-notebook/assets/{TrustBadge-RvZ9s6OQ.js → TrustBadge-C4jIUuEE.js} +1 -1
- package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-CZjUHLl5.js +83 -0
- package/dist/assets/dql-notebook/assets/{answer-to-notebook-Di227Cd4.js → answer-to-notebook-mhY-MBsX.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-left-BZHDcWHR.js → arrow-left-CwMW-Dnz.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-right-DlM1pC3_.js → arrow-right-CJ_6d0z7.js} +1 -1
- package/dist/assets/dql-notebook/assets/{book-open-text-DBcXFQAu.js → book-open-text-B2q44mou.js} +1 -1
- package/dist/assets/dql-notebook/assets/{bot-BAywbvPm.js → bot-CbsSLtXR.js} +1 -1
- package/dist/assets/dql-notebook/assets/{chevron-up-B_GfKE_F.js → chevron-up-DSMkEFrK.js} +1 -1
- package/dist/assets/dql-notebook/assets/{circle-x-BcxtZ-ym.js → circle-x-C5hi2hCq.js} +1 -1
- package/dist/assets/dql-notebook/assets/{dagre.esm-Bqs0lsEz.js → dagre.esm-BT65gOyC.js} +1 -1
- package/dist/assets/dql-notebook/assets/{external-link-B24Dk9_t.js → external-link-sDmXu1pG.js} +1 -1
- package/dist/assets/dql-notebook/assets/{grip-vertical-D9g31N3j.js → grip-vertical-Ca_E_fRZ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{index-DU7j3Sqk.js → index-CcZ8Dw84.js} +128 -128
- package/dist/assets/dql-notebook/assets/{link-2-B36oR5Bj.js → link-2-DaVaJfdX.js} +1 -1
- package/dist/assets/dql-notebook/assets/{list-tree-B9ZzGc4U.js → list-tree-DMGqPCsV.js} +1 -1
- package/dist/assets/dql-notebook/assets/{maximize-2-BrvQLPoj.js → maximize-2-CVJP65sF.js} +1 -1
- package/dist/assets/dql-notebook/assets/{minimize-2-BrpFmsxS.js → minimize-2-BnsbPUr5.js} +1 -1
- package/dist/assets/dql-notebook/assets/{panel-right-open-B1g1qhh5.js → panel-right-open-BTTos1w4.js} +1 -1
- package/dist/assets/dql-notebook/assets/{pencil-DwywbTqQ.js → pencil-BDR0kEX-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{rotate-ccw-Db_KVs3J.js → rotate-ccw-DrQw5ZrW.js} +1 -1
- package/dist/assets/dql-notebook/assets/{semantic-fields-BIVuvP2l.js → semantic-fields-CgqeBot0.js} +1 -1
- package/dist/assets/dql-notebook/assets/star-DBVkbHOE.js +6 -0
- package/dist/assets/dql-notebook/assets/{triangle-alert-Cne6C2aG.js → triangle-alert-_FtKJhjm.js} +1 -1
- package/dist/assets/dql-notebook/assets/{usePersistedAgentThreadId-D6mtrJ-_.js → usePersistedAgentThreadId-BG1TZXp_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{user-round-DkzxN1xD.js → user-round-DkUToep7.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wand-sparkles-CJUvFtur.js → wand-sparkles-TgacCrJg.js} +1 -1
- package/dist/assets/dql-notebook/assets/{workflow-BOvVTg7R.js → workflow-z-B8gUF9.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wrench-YeSpzZRt.js → wrench-CgXGBYJ-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{x-C6tsHUR7.js → x-FQc3jbQ8.js} +1 -1
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/local-runtime.d.ts +31 -0
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +298 -12
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/dist/semantic-runtime.d.ts.map +1 -1
- package/dist/semantic-runtime.js +31 -5
- package/dist/semantic-runtime.js.map +1 -1
- package/package.json +10 -10
- package/dist/assets/dql-notebook/assets/AnalyticsHome-u-buXgdv.js +0 -6
- 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";
|
|
@@ -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')
|
|
@@ -1592,6 +1660,37 @@ export async function startLocalServer(opts) {
|
|
|
1592
1660
|
}
|
|
1593
1661
|
return governedAnswer;
|
|
1594
1662
|
}
|
|
1663
|
+
/**
|
|
1664
|
+
* The user-facing line for a terminal analytical failure.
|
|
1665
|
+
*
|
|
1666
|
+
* `message` is a fixed SAFE_FAILURES headline chosen by failure code — that
|
|
1667
|
+
* redaction is the governance contract and stays. But on its own it says
|
|
1668
|
+
* nothing actionable: "The selected route could not compile its immutable
|
|
1669
|
+
* analytical plan" is the DEFAULT for anything unclassified, so a MetricFlow
|
|
1670
|
+
* compile error and a dialect mismatch read identically and neither tells the
|
|
1671
|
+
* user what to change.
|
|
1672
|
+
*
|
|
1673
|
+
* `diagnostic` carries the producer's own words, already stripped of
|
|
1674
|
+
* credentials, connection strings, and quoted literals by
|
|
1675
|
+
* `redactAnalyticalDiagnostic`. Appending it costs nothing and is the
|
|
1676
|
+
* difference between a dead end and a fixable error.
|
|
1677
|
+
*/
|
|
1678
|
+
function analyticalFailureSummary(failure) {
|
|
1679
|
+
if (!failure)
|
|
1680
|
+
return undefined;
|
|
1681
|
+
const headline = failure.message?.trim();
|
|
1682
|
+
const diagnostic = failure.diagnostic?.trim();
|
|
1683
|
+
if (!headline)
|
|
1684
|
+
return diagnostic || undefined;
|
|
1685
|
+
if (!diagnostic)
|
|
1686
|
+
return headline;
|
|
1687
|
+
// Avoid saying the same thing twice when the producer text is already the
|
|
1688
|
+
// headline (or is contained in it).
|
|
1689
|
+
const normalize = (value) => value.toLowerCase().replace(/\s+/g, ' ');
|
|
1690
|
+
if (normalize(headline).includes(normalize(diagnostic)))
|
|
1691
|
+
return headline;
|
|
1692
|
+
return `${headline} ${diagnostic}`;
|
|
1693
|
+
}
|
|
1595
1694
|
function resolvedRunRouteFromAnswer(governedAnswer) {
|
|
1596
1695
|
return routeForCascadeAnswerTier(governedAnswer.route?.tier);
|
|
1597
1696
|
}
|
|
@@ -2125,7 +2224,7 @@ export async function startLocalServer(opts) {
|
|
|
2125
2224
|
answerRefusalCode: governedAnswer.kind === 'no_answer' ? governedAnswer.refusalCode : undefined,
|
|
2126
2225
|
answerTier: governedAnswer.route?.tier,
|
|
2127
2226
|
summary: isTerminalFailure
|
|
2128
|
-
? governedAnswer.analyticalFailure
|
|
2227
|
+
? analyticalFailureSummary(governedAnswer.analyticalFailure)
|
|
2129
2228
|
?? governedAnswer.executionError
|
|
2130
2229
|
?? governedAnswer.answer
|
|
2131
2230
|
?? governedAnswer.text
|
|
@@ -4671,7 +4770,97 @@ export async function startLocalServer(opts) {
|
|
|
4671
4770
|
response.write(`event: ${event}\n`);
|
|
4672
4771
|
response.write(`data: ${serializeJSON(data)}\n\n`);
|
|
4673
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
|
+
};
|
|
4674
4862
|
const server = createServer((req, res) => {
|
|
4863
|
+
res = withCompression(req, res);
|
|
4675
4864
|
const requestUrl = req.url || '/';
|
|
4676
4865
|
const url = new URL(requestUrl, 'http://127.0.0.1');
|
|
4677
4866
|
const path = url.pathname || '/';
|
|
@@ -5662,7 +5851,12 @@ export async function startLocalServer(opts) {
|
|
|
5662
5851
|
writeAgentRunSse(res, 'agent-run-answer-delta', { runId: parsed.request.runId, delta });
|
|
5663
5852
|
});
|
|
5664
5853
|
recordConversationTurn(conversationStore, parsed.request.threadId, run);
|
|
5665
|
-
|
|
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));
|
|
5666
5860
|
res.end();
|
|
5667
5861
|
return;
|
|
5668
5862
|
}
|
|
@@ -6834,6 +7028,26 @@ export async function startLocalServer(opts) {
|
|
|
6834
7028
|
res.end(serializeJSON({ ok: true }));
|
|
6835
7029
|
return;
|
|
6836
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
|
+
}
|
|
6837
7051
|
if (req.method === 'DELETE' && !action) {
|
|
6838
7052
|
store.deleteThread(threadId);
|
|
6839
7053
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
@@ -6883,7 +7097,13 @@ export async function startLocalServer(opts) {
|
|
|
6883
7097
|
if (!turn.agentRunId)
|
|
6884
7098
|
return [];
|
|
6885
7099
|
const run = agentRunStore.get(turn.agentRunId);
|
|
6886
|
-
|
|
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)] : [];
|
|
6887
7107
|
});
|
|
6888
7108
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
6889
7109
|
res.end(serializeJSON({
|
|
@@ -16554,14 +16774,73 @@ export function openBlockStudioDocument(projectRoot, relativePath, semanticLayer
|
|
|
16554
16774
|
validation: validateBlockStudioSource(source, semanticLayer),
|
|
16555
16775
|
};
|
|
16556
16776
|
}
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16777
|
+
/**
|
|
16778
|
+
* Replace the CONTENTS of every string literal with same-length filler, keeping
|
|
16779
|
+
* the quotes and every offset intact.
|
|
16780
|
+
*
|
|
16781
|
+
* Field lookups below scan the whole block source for `key = ...`. A block's
|
|
16782
|
+
* `description` is free text — on an AI-generated block it is literally the
|
|
16783
|
+
* user's question — so a description mentioning `granularity = "day"` or
|
|
16784
|
+
* `dimensions = [...]` was matched as if it were the field itself and silently
|
|
16785
|
+
* replaced the real one. Masking first means a match can only ever land on a
|
|
16786
|
+
* real assignment; reading the value back from the ORIGINAL at the same offsets
|
|
16787
|
+
* keeps it exact.
|
|
16788
|
+
*/
|
|
16789
|
+
export function maskDqlStringContents(source) {
|
|
16790
|
+
const chars = source.split('');
|
|
16791
|
+
let index = 0;
|
|
16792
|
+
while (index < chars.length) {
|
|
16793
|
+
if (source.startsWith('\"\"\"', index)) {
|
|
16794
|
+
const end = source.indexOf('\"\"\"', index + 3);
|
|
16795
|
+
const contentEnd = end === -1 ? chars.length : end;
|
|
16796
|
+
for (let cursor = index + 3; cursor < contentEnd; cursor += 1) {
|
|
16797
|
+
if (chars[cursor] !== '\n')
|
|
16798
|
+
chars[cursor] = 'x';
|
|
16799
|
+
}
|
|
16800
|
+
index = end === -1 ? chars.length : end + 3;
|
|
16801
|
+
continue;
|
|
16802
|
+
}
|
|
16803
|
+
if (chars[index] === '"') {
|
|
16804
|
+
let cursor = index + 1;
|
|
16805
|
+
while (cursor < chars.length && chars[cursor] !== '"') {
|
|
16806
|
+
if (chars[cursor] === '\\') {
|
|
16807
|
+
cursor += 2;
|
|
16808
|
+
continue;
|
|
16809
|
+
}
|
|
16810
|
+
cursor += 1;
|
|
16811
|
+
}
|
|
16812
|
+
for (let inner = index + 1; inner < Math.min(cursor, chars.length); inner += 1) {
|
|
16813
|
+
if (chars[inner] !== '\n')
|
|
16814
|
+
chars[inner] = 'x';
|
|
16815
|
+
}
|
|
16816
|
+
index = cursor + 1;
|
|
16817
|
+
continue;
|
|
16818
|
+
}
|
|
16819
|
+
index += 1;
|
|
16820
|
+
}
|
|
16821
|
+
return chars.join('');
|
|
16822
|
+
}
|
|
16823
|
+
export function parseBlockStudioArrayField(source, key) {
|
|
16824
|
+
const masked = maskDqlStringContents(source);
|
|
16825
|
+
const match = masked.match(new RegExp(`\\b${key}\\s*=\\s*\\[`, 'i'));
|
|
16826
|
+
if (!match || match.index === undefined)
|
|
16560
16827
|
return [];
|
|
16561
|
-
|
|
16828
|
+
const start = match.index + match[0].length;
|
|
16829
|
+
const end = masked.indexOf(']', start);
|
|
16830
|
+
if (end === -1)
|
|
16831
|
+
return [];
|
|
16832
|
+
return (source.slice(start, end).match(/"([^"]*)"/g) ?? [])
|
|
16833
|
+
.map((value) => value.slice(1, -1))
|
|
16834
|
+
.filter(Boolean);
|
|
16562
16835
|
}
|
|
16563
|
-
function parseBlockStudioStringField(source, key) {
|
|
16564
|
-
|
|
16836
|
+
export function parseBlockStudioStringField(source, key) {
|
|
16837
|
+
const masked = maskDqlStringContents(source);
|
|
16838
|
+
const match = masked.match(new RegExp(`\\b${key}\\s*=\\s*"`, 'i'));
|
|
16839
|
+
if (!match || match.index === undefined)
|
|
16840
|
+
return undefined;
|
|
16841
|
+
const start = match.index + match[0].length;
|
|
16842
|
+
const end = masked.indexOf('"', start);
|
|
16843
|
+
return end === -1 ? undefined : source.slice(start, end);
|
|
16565
16844
|
}
|
|
16566
16845
|
function parseSemanticRequestedFilters(source) {
|
|
16567
16846
|
return parseBlockStudioArrayField(source, 'requested_filters')
|
|
@@ -19595,9 +19874,16 @@ function extractBlockStudioChartConfig(source) {
|
|
|
19595
19874
|
};
|
|
19596
19875
|
}
|
|
19597
19876
|
export function extractBlockStudioSql(source) {
|
|
19598
|
-
|
|
19599
|
-
|
|
19600
|
-
|
|
19877
|
+
// Masked lookup for the same reason the field parsers use one: a `description`
|
|
19878
|
+
// is free text and must never be able to present itself as the query.
|
|
19879
|
+
const masked = maskDqlStringContents(source);
|
|
19880
|
+
const tripleQuoteMatch = masked.match(/query\s*=\s*"""/i);
|
|
19881
|
+
if (tripleQuoteMatch && tripleQuoteMatch.index !== undefined) {
|
|
19882
|
+
const start = tripleQuoteMatch.index + tripleQuoteMatch[0].length;
|
|
19883
|
+
const end = masked.indexOf('"""', start);
|
|
19884
|
+
if (end !== -1)
|
|
19885
|
+
return source.slice(start, end).trim() || null;
|
|
19886
|
+
}
|
|
19601
19887
|
const bareTripleMatch = source.match(/"""([\s\S]*?)"""/);
|
|
19602
19888
|
if (bareTripleMatch)
|
|
19603
19889
|
return bareTripleMatch[1].trim() || null;
|