@duckcodeailabs/dql-cli 1.14.2 → 1.14.4

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.
Files changed (147) hide show
  1. package/dist/args.d.ts +6 -0
  2. package/dist/args.d.ts.map +1 -1
  3. package/dist/args.js +28 -0
  4. package/dist/args.js.map +1 -1
  5. package/dist/ask-runtime/analytical-planner.d.ts +14 -0
  6. package/dist/ask-runtime/analytical-planner.d.ts.map +1 -0
  7. package/dist/ask-runtime/analytical-planner.js +197 -0
  8. package/dist/ask-runtime/analytical-planner.js.map +1 -0
  9. package/dist/ask-runtime/readiness.d.ts +36 -0
  10. package/dist/ask-runtime/readiness.d.ts.map +1 -0
  11. package/dist/ask-runtime/readiness.js +65 -0
  12. package/dist/ask-runtime/readiness.js.map +1 -0
  13. package/dist/assets/dql-notebook/assets/{AgentLogPage-DKbGpRQS.js → AgentLogPage-XonDlOVv.js} +1 -1
  14. package/dist/assets/dql-notebook/assets/{AiBuildDialog-DPSu0Mly.js → AiBuildDialog-DwHVVFIS.js} +1 -1
  15. package/dist/assets/dql-notebook/assets/{AiBuildResult-1uaGnpi1.js → AiBuildResult-BImB4Wej.js} +1 -1
  16. package/dist/assets/dql-notebook/assets/{AiSidePanel-BwMREwa7.js → AiSidePanel-D7fFBhgc.js} +1 -1
  17. package/dist/assets/dql-notebook/assets/AnalyticsHome-CHvGWG92.js +6 -0
  18. package/dist/assets/dql-notebook/assets/{AppsView-CM1tPywy.js → AppsView-DNhs0MyO.js} +30 -35
  19. package/dist/assets/dql-notebook/assets/AskObservabilityPage-1WYjAf8-.js +1 -0
  20. package/dist/assets/dql-notebook/assets/AskTracePage-D6_RunIf.js +8 -0
  21. package/dist/assets/dql-notebook/assets/{BlockStudio--S6WFVO4.js → BlockStudio-DWMOCHy5.js} +9 -14
  22. package/dist/assets/dql-notebook/assets/BusinessArtifactView-guqlfQ0j.js +1 -0
  23. package/dist/assets/dql-notebook/assets/{DbtFirstModelingPage-CNyU5MBX.js → DbtFirstModelingPage-DE7ewjMK.js} +3 -3
  24. package/dist/assets/dql-notebook/assets/{GitPage-IcydAai3.js → GitPage-CM3cgHtf.js} +1 -1
  25. package/dist/assets/dql-notebook/assets/{GlobalAiRail-CSKW-5eD.js → GlobalAiRail-BWfeK8lb.js} +1 -1
  26. package/dist/assets/dql-notebook/assets/{GovernedContextPage-CFen0eFT.js → GovernedContextPage-2Ld4yIgE.js} +3 -3
  27. package/dist/assets/dql-notebook/assets/{HelpDocsPage-D0D9UCIz.js → HelpDocsPage-BxBzoxBO.js} +1 -1
  28. package/dist/assets/dql-notebook/assets/{HomePage-CmSxapR3.js → HomePage-D4ZjCFfB.js} +1 -1
  29. package/dist/assets/dql-notebook/assets/LineageDAG-DLdogZMo.js +1 -0
  30. package/dist/assets/dql-notebook/assets/LineageDetailView-CqPB_tFP.js +1 -0
  31. package/dist/assets/dql-notebook/assets/LineageDrawer-LZyR3zHf.js +1 -0
  32. package/dist/assets/dql-notebook/assets/{LineagePathBreadcrumb-CTZJp4_r.js → LineagePathBreadcrumb-BZ7EcIxp.js} +1 -1
  33. package/dist/assets/dql-notebook/assets/MiniLineageGraph-Be_kqIq2.js +1 -0
  34. package/dist/assets/dql-notebook/assets/{NewBlockModal-DMBFB7nE.js → NewBlockModal-C9MKCa6R.js} +1 -1
  35. package/dist/assets/dql-notebook/assets/{NewNotebookModal-DsC9CWlW.js → NewNotebookModal-ZxHbTU9u.js} +1 -1
  36. package/dist/assets/dql-notebook/assets/{NotebookEditor-hs-kw8v9.js → NotebookEditor-Bh39Vsg0.js} +25 -30
  37. package/dist/assets/dql-notebook/assets/{ReadinessPage-DJ83cMik.js → ReadinessPage-jKOBKkED.js} +1 -1
  38. package/dist/assets/dql-notebook/assets/{SetupOnboarding-B1Pu_Bvv.js → SetupOnboarding-BRut9nLO.js} +1 -1
  39. package/dist/assets/dql-notebook/assets/{SkillsPage-BHKDay8n.js → SkillsPage-D9msUOxY.js} +1 -1
  40. package/dist/assets/dql-notebook/assets/{TrustBadge-BkyGgob2.js → TrustBadge-D1CGSj0Y.js} +1 -1
  41. package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-BexNjqJd.js +88 -0
  42. package/dist/assets/dql-notebook/assets/{answer-to-notebook-DmNiuLQA.js → answer-to-notebook-zHFOdR6E.js} +1 -1
  43. package/dist/assets/dql-notebook/assets/{arrow-left--1rsrxm8.js → arrow-left-C1tZBhgf.js} +1 -1
  44. package/dist/assets/dql-notebook/assets/{arrow-right-D5TdqY1G.js → arrow-right-BP5vPPNo.js} +1 -1
  45. package/dist/assets/dql-notebook/assets/{book-open-text-Bw7nHbzg.js → book-open-text-BD3-ATYI.js} +1 -1
  46. package/dist/assets/dql-notebook/assets/chevron-left-3opVgscY.js +6 -0
  47. package/dist/assets/dql-notebook/assets/{circle-x-DLe6NNM4.js → circle-x-Bg9-70KN.js} +1 -1
  48. package/dist/assets/dql-notebook/assets/clock-3-DLV5puGW.js +6 -0
  49. package/dist/assets/dql-notebook/assets/dagre.esm-B6nvU4OB.js +1 -0
  50. package/dist/assets/dql-notebook/assets/{external-link-C9Q97sA3.js → external-link-BjD8w9QU.js} +1 -1
  51. package/dist/assets/dql-notebook/assets/{grip-vertical-CztvkIgo.js → grip-vertical-DqIc09oX.js} +1 -1
  52. package/dist/assets/dql-notebook/assets/index-B_kaoARS.css +1 -0
  53. package/dist/assets/dql-notebook/assets/{index-zHHzDn6l.js → index-C9KfoqCR.js} +133 -128
  54. package/dist/assets/dql-notebook/assets/{link-2-CiKAvumL.js → link-2-5s0hUu6w.js} +1 -1
  55. package/dist/assets/dql-notebook/assets/{list-tree-BtnP2nQ5.js → list-tree-67yz_uCY.js} +1 -1
  56. package/dist/assets/dql-notebook/assets/{minimize-2-TSFGxcCP.js → minimize-2-BJw5tlPC.js} +1 -1
  57. package/dist/assets/dql-notebook/assets/{panel-right-open-BfXIUWy0.js → panel-right-open-CyhTG-tw.js} +1 -1
  58. package/dist/assets/dql-notebook/assets/{play-DVbSFJHD.js → play-CsY8D5M2.js} +1 -1
  59. package/dist/assets/dql-notebook/assets/{rotate-ccw-D_cesDcX.js → rotate-ccw-RvwtBcw9.js} +1 -1
  60. package/dist/assets/dql-notebook/assets/{semantic-fields-CoVStdYB.js → semantic-fields-EorgHwAj.js} +1 -1
  61. package/dist/assets/dql-notebook/assets/{sliders-horizontal-l7xV9K5A.js → sliders-horizontal-jQ_fUiYf.js} +1 -1
  62. package/dist/assets/dql-notebook/assets/{star-CSBS0H3b.js → star-N_C8YvbB.js} +1 -1
  63. package/dist/assets/dql-notebook/assets/style-D6AlWDHX.js +23 -0
  64. package/dist/assets/dql-notebook/assets/{triangle-alert-BTrnyY4q.js → triangle-alert-D1WefIrN.js} +1 -1
  65. package/dist/assets/dql-notebook/assets/{upload-sySLq9zb.js → upload-CFJjpPL4.js} +1 -1
  66. package/dist/assets/dql-notebook/assets/{usePersistedAgentThreadId-CzwgGdus.js → usePersistedAgentThreadId-B5pTglEr.js} +1 -1
  67. package/dist/assets/dql-notebook/assets/{user-round-ChlgXi9j.js → user-round-DL6DLw0m.js} +1 -1
  68. package/dist/assets/dql-notebook/assets/{wand-sparkles-CGw0ytyT.js → wand-sparkles-CNUjLDqT.js} +1 -1
  69. package/dist/assets/dql-notebook/assets/{workflow-C_RltiK5.js → workflow-0NUwpoQ4.js} +1 -1
  70. package/dist/assets/dql-notebook/assets/{wrench-D-wLfeu0.js → wrench-QNiznShi.js} +1 -1
  71. package/dist/assets/dql-notebook/assets/{x-B28hIJIC.js → x-BhTl5gZs.js} +1 -1
  72. package/dist/assets/dql-notebook/index.html +2 -2
  73. package/dist/commands/agent-eval-cassette.d.ts.map +1 -1
  74. package/dist/commands/agent-eval-cassette.js +15 -0
  75. package/dist/commands/agent-eval-cassette.js.map +1 -1
  76. package/dist/commands/agent-eval-runtime.d.ts +9 -0
  77. package/dist/commands/agent-eval-runtime.d.ts.map +1 -1
  78. package/dist/commands/agent-eval-runtime.js +7 -0
  79. package/dist/commands/agent-eval-runtime.js.map +1 -1
  80. package/dist/commands/agent-shadow-report.d.ts +17 -0
  81. package/dist/commands/agent-shadow-report.d.ts.map +1 -0
  82. package/dist/commands/agent-shadow-report.js +149 -0
  83. package/dist/commands/agent-shadow-report.js.map +1 -0
  84. package/dist/commands/agent-trace.d.ts +3 -0
  85. package/dist/commands/agent-trace.d.ts.map +1 -0
  86. package/dist/commands/agent-trace.js +169 -0
  87. package/dist/commands/agent-trace.js.map +1 -0
  88. package/dist/commands/agent.d.ts +28 -2
  89. package/dist/commands/agent.d.ts.map +1 -1
  90. package/dist/commands/agent.js +413 -36
  91. package/dist/commands/agent.js.map +1 -1
  92. package/dist/commands/notebook.d.ts +4 -1
  93. package/dist/commands/notebook.d.ts.map +1 -1
  94. package/dist/commands/notebook.js +12 -1
  95. package/dist/commands/notebook.js.map +1 -1
  96. package/dist/index.js +5 -0
  97. package/dist/index.js.map +1 -1
  98. package/dist/llm/providers/ask-v2-analyst-prompt.d.ts +32 -0
  99. package/dist/llm/providers/ask-v2-analyst-prompt.d.ts.map +1 -0
  100. package/dist/llm/providers/ask-v2-analyst-prompt.js +136 -0
  101. package/dist/llm/providers/ask-v2-analyst-prompt.js.map +1 -0
  102. package/dist/llm/providers/dql-agent-provider.d.ts +78 -2
  103. package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
  104. package/dist/llm/providers/dql-agent-provider.js +5438 -330
  105. package/dist/llm/providers/dql-agent-provider.js.map +1 -1
  106. package/dist/llm/types.d.ts +144 -3
  107. package/dist/llm/types.d.ts.map +1 -1
  108. package/dist/local-runtime.d.ts +607 -35
  109. package/dist/local-runtime.d.ts.map +1 -1
  110. package/dist/local-runtime.js +7734 -702
  111. package/dist/local-runtime.js.map +1 -1
  112. package/dist/metricflow.d.ts.map +1 -1
  113. package/dist/metricflow.js +80 -11
  114. package/dist/metricflow.js.map +1 -1
  115. package/dist/package.json +10 -10
  116. package/dist/providers/oauth/claude-oauth.d.ts +14 -0
  117. package/dist/providers/oauth/claude-oauth.d.ts.map +1 -1
  118. package/dist/providers/oauth/claude-oauth.js +84 -23
  119. package/dist/providers/oauth/claude-oauth.js.map +1 -1
  120. package/dist/providers/oauth/codex-oauth.d.ts.map +1 -1
  121. package/dist/providers/oauth/codex-oauth.js +72 -44
  122. package/dist/providers/oauth/codex-oauth.js.map +1 -1
  123. package/dist/providers/subscription-cli.d.ts +20 -0
  124. package/dist/providers/subscription-cli.d.ts.map +1 -1
  125. package/dist/providers/subscription-cli.js +69 -11
  126. package/dist/providers/subscription-cli.js.map +1 -1
  127. package/dist/research-lineage-program.d.ts +29 -0
  128. package/dist/research-lineage-program.d.ts.map +1 -0
  129. package/dist/research-lineage-program.js +389 -0
  130. package/dist/research-lineage-program.js.map +1 -0
  131. package/dist/retrieval-health.d.ts.map +1 -1
  132. package/dist/retrieval-health.js +5 -2
  133. package/dist/retrieval-health.js.map +1 -1
  134. package/dist/semantic-runtime.d.ts.map +1 -1
  135. package/dist/semantic-runtime.js +73 -2
  136. package/dist/semantic-runtime.js.map +1 -1
  137. package/package.json +10 -10
  138. package/dist/assets/dql-notebook/assets/AnalyticsHome-BGfey_ve.js +0 -6
  139. package/dist/assets/dql-notebook/assets/BusinessArtifactView-BEAJ-yNW.js +0 -1
  140. package/dist/assets/dql-notebook/assets/LineageDAG-BGUcIt1B.js +0 -1
  141. package/dist/assets/dql-notebook/assets/LineageDetailView-Dx48Zfzb.js +0 -1
  142. package/dist/assets/dql-notebook/assets/LineageDrawer-zaBfSLZO.js +0 -1
  143. package/dist/assets/dql-notebook/assets/MiniLineageGraph-CdivNR1S.js +0 -1
  144. package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-BYdrTaEW.js +0 -89
  145. package/dist/assets/dql-notebook/assets/dagre.esm-CW5QZdBt.js +0 -23
  146. package/dist/assets/dql-notebook/assets/index-B3shyZsg.css +0 -1
  147. /package/dist/assets/dql-notebook/assets/{dagre-BZV40eAE.css → style-BZV40eAE.css} +0 -0
@@ -23,15 +23,18 @@
23
23
  */
24
24
  import { answerFromRuntimeRun, driveViaRuntime, projectRuntimeRun, } from './agent-eval-runtime.js';
25
25
  import { CassetteStore, cassetteEvidenceSummary, cassetteDirFor, evalCassetteCanonicalizationV2, withCassette, } from './agent-eval-cassette.js';
26
+ import { runAgentShadowReport } from './agent-shadow-report.js';
26
27
  import { existsSync, readFileSync } from 'node:fs';
27
28
  import { join, resolve } from 'node:path';
28
29
  import { load as loadYaml } from 'js-yaml';
29
- import { KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, reindexProject, loadSkills, pickProvider, answer, resolveDomainContextEnvelope, buildAnalysisQuestionPlan, buildLocalContextPack, coerceReasoningEffort, contextRetrievalBudgetForQuestion, deriveGeneratedDraftSlug, loadAgentSemanticLayer, recordQueryRun, recordRuntimeSchemaSnapshot, upsertGeneratedDqlArtifactDraft, upsertGeneratedDraft, validateSqlAgainstLocalContext, } from '@duckcodeailabs/dql-agent';
30
+ import { AskTraceSqliteStoreV1, KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, reindexProject, loadSkills, pickProvider, answer, resolveDomainContextEnvelope, buildAnalysisQuestionPlan, buildLocalContextPack, classifyProviderFailure, coerceReasoningEffort, contextRetrievalBudgetForQuestion, deriveGeneratedDraftSlug, loadAgentSemanticLayer, recordQueryRun, recordRuntimeSchemaSnapshot, upsertGeneratedDqlArtifactDraft, upsertGeneratedDraft, validateSqlAgainstLocalContext, createAskTraceObserverV1, defaultAskTraceSqlitePath, } from '@duckcodeailabs/dql-agent';
31
+ import { createHash, randomUUID } from 'node:crypto';
30
32
  import { buildManifest, resolveDbtManifestPath } from '@duckcodeailabs/dql-core';
31
33
  import { findProjectRoot } from '../local-runtime.js';
32
34
  import { buildAnswerLoopTools, createGroundingContextExpander } from '../llm/answer-loop-tools.js';
33
35
  import { judgeAnswer } from './eval-judge.js';
34
36
  import { startProjectRuntime } from './notebook.js';
37
+ import { runAgentTrace } from './agent-trace.js';
35
38
  /**
36
39
  * Resolve the runtime the agent posts certified blocks / generated SQL to.
37
40
  *
@@ -55,7 +58,56 @@ async function resolveAgentRuntime(projectRoot, flags) {
55
58
  return { runtimeBase: base, close: async () => { } };
56
59
  }
57
60
  const handle = await startProjectRuntime(projectRoot, { preferredPort: 0 });
58
- return { runtimeBase: handle.url, close: handle.close };
61
+ return { runtimeBase: handle.url, close: handle.close, askTraceCapability: handle.askTraceCapability };
62
+ }
63
+ function isLoopbackRuntimeUrl(runtimeBase) {
64
+ try {
65
+ const hostname = new URL(runtimeBase).hostname;
66
+ return hostname === '127.0.0.1' || hostname === 'localhost' || hostname === '::1' || hostname === '[::1]';
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ /**
73
+ * An already-running local Notebook runtime mints a one-shot, loopback-only
74
+ * capability for this CLI request. Remote runtimes deliberately receive no
75
+ * capability and therefore cannot be relabelled as CLI by arbitrary text.
76
+ */
77
+ export async function requestLoopbackCliAskTraceCapability(runtimeBase) {
78
+ if (!isLoopbackRuntimeUrl(runtimeBase))
79
+ return undefined;
80
+ try {
81
+ const response = await fetch(`${runtimeBase.replace(/\/$/, '')}/api/ask-traces/cli-capability`, {
82
+ headers: { Accept: 'application/json' },
83
+ });
84
+ if (!response.ok)
85
+ return undefined;
86
+ const payload = await response.json();
87
+ return typeof payload.capability === 'string'
88
+ && typeof payload.expiresAt === 'string'
89
+ && payload.scope === 'agent-runs'
90
+ ? payload.capability
91
+ : undefined;
92
+ }
93
+ catch {
94
+ // A pre-observability runtime remains usable. Its request stays browser
95
+ // attributed rather than fabricating a client-controlled CLI surface.
96
+ return undefined;
97
+ }
98
+ }
99
+ function runtimeProviderForCliFlag(value) {
100
+ if (typeof value !== 'string' || !value.trim())
101
+ return undefined;
102
+ switch (value.trim().toLowerCase()) {
103
+ case 'claude': return 'anthropic';
104
+ case 'openai': return 'openai';
105
+ case 'gemini': return 'gemini';
106
+ case 'ollama': return 'ollama';
107
+ // Preserve an invalid explicit value through the host request so the
108
+ // canonical preflight can return a typed `model_not_found` diagnostic.
109
+ default: return value.trim().toLowerCase();
110
+ }
59
111
  }
60
112
  async function fetchRuntimeSchemaContext(runtimeBase) {
61
113
  try {
@@ -142,6 +194,137 @@ function cliAnalysisDepth(flags) {
142
194
  const value = flags.analysisDepth?.trim().toLowerCase();
143
195
  return value === 'quick' || value === 'deep' ? value : undefined;
144
196
  }
197
+ /**
198
+ * Compatibility test adapter for a standalone provider boundary. Production
199
+ * `dql agent ask` no longer calls this: it uses the runtime AgentRun engine so
200
+ * the router, cascade, freeze, tools, SQL, and provider spans share one
201
+ * canonical trace. Keep this adapter truthful for lower-level provider tests
202
+ * without making it an alternate orchestration authority.
203
+ */
204
+ export function createDirectCliAskTraceProvider(provider, trace) {
205
+ let attemptIndex = 0;
206
+ let lastFailedSpanId;
207
+ const pending = new Map();
208
+ const keyFor = (event) => `${event.provider}:${event.operation}:${event.attemptIndex}`;
209
+ const attempt = (event, retryOfSpanId) => ({
210
+ version: 1,
211
+ phase: 'generation',
212
+ physicalAttemptIndex: ++attemptIndex,
213
+ providerFingerprint: `sha256:${createHash('sha256').update(event.provider).digest('hex')}`,
214
+ ...(event.model ? { modelFingerprint: `sha256:${createHash('sha256').update(event.model).digest('hex')}` } : {}),
215
+ ...(retryOfSpanId ? { retryOfSpanId } : {}),
216
+ admission: 'admitted',
217
+ provenance: 'live',
218
+ });
219
+ const finish = (entry, outcome, error) => {
220
+ if (!entry.spanId)
221
+ return;
222
+ const diagnostic = outcome === 'ok' ? undefined : classifyProviderFailure({
223
+ phase: 'generation',
224
+ code: error && typeof error === 'object' ? String(error.code ?? '') : undefined,
225
+ message: error instanceof Error ? error.message : String(error ?? ''),
226
+ providerFingerprint: entry.attempt.providerFingerprint,
227
+ modelFingerprint: entry.attempt.modelFingerprint,
228
+ });
229
+ const finalAttempt = outcome === 'ok'
230
+ ? entry.attempt
231
+ : {
232
+ ...entry.attempt,
233
+ ...(diagnostic?.httpStatusClass ? { httpStatusClass: diagnostic.httpStatusClass } : {}),
234
+ ...(diagnostic ? { retryable: diagnostic.retryable, safeAction: diagnostic.safeAction } : {}),
235
+ cause: outcome === 'cancelled' ? 'cancelled' : diagnostic?.cause ?? 'unknown',
236
+ };
237
+ trace.finishSpan(entry.spanId, {
238
+ outcome: outcome === 'ok' ? 'ok' : outcome === 'cancelled' ? 'cancelled' : 'error',
239
+ reasonCode: outcome === 'ok' ? 'completed' : outcome === 'cancelled' ? 'cancelled' : 'provider_failure',
240
+ payload: { kind: 'provider', attempt: finalAttempt },
241
+ });
242
+ if (outcome !== 'ok')
243
+ lastFailedSpanId = entry.spanId;
244
+ };
245
+ const observedOptions = (options = {}) => ({
246
+ ...options,
247
+ onProviderDispatch: (event) => {
248
+ const tracedAttempt = attempt(event, lastFailedSpanId);
249
+ const spanId = trace.startSpan({
250
+ name: 'provider.attempt',
251
+ stage: 'provider',
252
+ reasonCode: 'started',
253
+ payload: { kind: 'provider', attempt: tracedAttempt },
254
+ });
255
+ const key = keyFor(event);
256
+ pending.set(key, [...(pending.get(key) ?? []), { spanId, attempt: tracedAttempt }]);
257
+ return options.onProviderDispatch?.(event) ?? event.envelope;
258
+ },
259
+ onProviderDispatchComplete: (event) => {
260
+ const key = keyFor(event);
261
+ const entries = pending.get(key) ?? [];
262
+ const entry = entries[0];
263
+ if (entry && event.outcome === 'ok' && (event.settlement === 'transport' || event.settlement === 'process')) {
264
+ entry.attempt = {
265
+ ...entry.attempt,
266
+ ...(event.settlement === 'transport' ? { transportOutcome: 'ok' } : { processOutcome: 'ok' }),
267
+ };
268
+ }
269
+ else {
270
+ const closed = entries.shift();
271
+ if (entries.length > 0)
272
+ pending.set(key, entries);
273
+ else
274
+ pending.delete(key);
275
+ if (closed)
276
+ finish(closed, event.outcome, event.error ?? (typeof event.httpStatus === 'number' ? Object.assign(new Error(`HTTP ${event.httpStatus}`), { code: `HTTP_${event.httpStatus}` }) : undefined));
277
+ }
278
+ options.onProviderDispatchComplete?.(event);
279
+ },
280
+ onProviderDispatchRejected: (event) => {
281
+ const denied = trace.startSpan({
282
+ name: 'provider.attempt',
283
+ stage: 'provider',
284
+ reasonCode: 'provider_failure',
285
+ payload: {
286
+ kind: 'provider',
287
+ attempt: {
288
+ ...attempt(event, lastFailedSpanId),
289
+ admission: 'denied',
290
+ },
291
+ },
292
+ });
293
+ trace.finishSpan(denied, { outcome: 'denied', reasonCode: 'provider_failure' });
294
+ lastFailedSpanId = denied;
295
+ options.onProviderDispatchRejected?.(event);
296
+ },
297
+ });
298
+ const invoke = async (options, call) => {
299
+ try {
300
+ const result = await call(observedOptions(options));
301
+ for (const entries of pending.values())
302
+ for (const entry of entries)
303
+ finish(entry, 'ok');
304
+ pending.clear();
305
+ return result;
306
+ }
307
+ catch (error) {
308
+ const outcome = options?.signal?.aborted ? 'cancelled' : 'error';
309
+ for (const entries of pending.values())
310
+ for (const entry of entries)
311
+ finish(entry, outcome, error);
312
+ pending.clear();
313
+ throw error;
314
+ }
315
+ };
316
+ return {
317
+ name: provider.name,
318
+ available: () => provider.available(),
319
+ generate: (messages, options) => invoke(options, (observed) => provider.generate(messages, observed)),
320
+ ...(provider.generateWithTools ? {
321
+ generateWithTools: (messages, tools, options) => invoke(options, (observed) => provider.generateWithTools(messages, tools, observed)),
322
+ } : {}),
323
+ ...(provider.generateStream ? {
324
+ generateStream: (messages, options, onDelta) => invoke(options, (observed) => provider.generateStream(messages, observed, onDelta)),
325
+ } : {}),
326
+ };
327
+ }
145
328
  /** A DQL runtime answers `/api/connections` with a connector/connection payload. */
146
329
  async function isDqlRuntime(base) {
147
330
  try {
@@ -161,25 +344,53 @@ export async function runAgent(sub, rest, flags) {
161
344
  return runAsk(rest, flags);
162
345
  case 'threads':
163
346
  return runThreads(flags);
347
+ case 'trace':
348
+ return runAgentTrace(rest, flags);
164
349
  case 'reindex':
165
350
  return runReindex(rest, flags);
166
351
  case 'feedback':
167
352
  return runFeedback(rest, flags);
168
353
  case 'eval':
169
354
  return runEval(rest, flags);
355
+ case 'shadow-report':
356
+ return runAgentShadowReport(rest, flags);
170
357
  default:
171
- throw new Error('Usage: dql agent <ask|threads|reindex|feedback|eval> [args]\n' +
358
+ throw new Error('Usage: dql agent <ask|threads|trace|reindex|feedback|eval|shadow-report> [args]\n' +
172
359
  ' dql agent ask "<question>" [--provider claude|openai|gemini|ollama] [--user <id>] [--domain <d>] [--purpose <approved-purpose>] [--thread <id>]\n' +
173
360
  ' dql agent threads [--runtime-url <url>]\n' +
361
+ ' dql agent trace list|show|export|validate|replay|compare\n' +
174
362
  ' dql agent reindex [path]\n' +
175
363
  ' dql agent feedback up|down --block <id> --question "..."\n' +
176
- ' dql agent eval agent-evals.yml [--provider claude|openai|gemini|ollama] [--execute] [--save]');
364
+ ' dql agent eval agent-evals.yml [--provider claude|openai|gemini|ollama] [--execute] [--save]\n' +
365
+ ' dql agent shadow-report [path] [--json] [--limit <n>] Compare what V1 answered with what V2 would have done');
177
366
  }
178
367
  }
368
+ /**
369
+ * Canonical CLI Ask entrypoint. Direct and threaded CLI questions use the
370
+ * exact local AgentRun engine as the browser, so trace evidence follows the
371
+ * router's candidate/cascade/freeze/execution authority rather than a legacy
372
+ * answer-loop approximation.
373
+ */
179
374
  async function runAsk(rest, flags) {
180
375
  const question = rest.join(' ').trim();
181
376
  if (!question)
182
377
  throw new Error('Usage: dql agent ask "<question>"');
378
+ const threadId = flags.thread;
379
+ return threadId
380
+ ? runThreadAsk(question, threadId, flags)
381
+ : runCanonicalCliAsk(question, undefined, flags);
382
+ }
383
+ /**
384
+ * Compatibility entrypoint retained for downstream imports during the CLI
385
+ * transition. It deliberately delegates before allocating any legacy state,
386
+ * so even an old internal caller gets the canonical AgentRun trace rather than
387
+ * an incomplete synthetic receipt.
388
+ */
389
+ async function runLegacyDirectAsk(rest, flags) {
390
+ const question = rest.join(' ').trim();
391
+ if (!question)
392
+ throw new Error('Usage: dql agent ask "<question>"');
393
+ return runCanonicalCliAsk(question, flags.thread, flags);
183
394
  // Thread-scoped ask: hand the question to the runtime's agent-run engine with
184
395
  // the thread id, so the SERVER injects prior turns and persists this run as a
185
396
  // new turn (the same conversation store the notebook UI uses).
@@ -187,8 +398,31 @@ async function runAsk(rest, flags) {
187
398
  if (threadId)
188
399
  return runThreadAsk(question, threadId, flags);
189
400
  const projectRoot = findProjectRoot(process.cwd());
401
+ const traceStore = new AskTraceSqliteStoreV1({ path: defaultAskTraceSqlitePath(projectRoot) });
402
+ const trace = createAskTraceObserverV1({
403
+ store: traceStore,
404
+ runId: `cli-${randomUUID()}`,
405
+ surface: 'cli',
406
+ mode: 'ask',
407
+ questionFingerprint: `sha256:${createHash('sha256').update(question).digest('hex')}`,
408
+ });
409
+ const classifySpan = trace.startSpan({
410
+ name: 'request.classify',
411
+ stage: 'request',
412
+ reasonCode: 'started',
413
+ payload: { kind: 'stage', route: 'direct_cli_legacy' },
414
+ });
190
415
  const kgPath = defaultKgPath(projectRoot);
191
- await reindexProject(projectRoot, { kgPath });
416
+ try {
417
+ await reindexProject(projectRoot, { kgPath });
418
+ trace.finishSpan(classifySpan, { outcome: 'ok', reasonCode: 'completed' });
419
+ }
420
+ catch (error) {
421
+ trace.finishSpan(classifySpan, { outcome: 'error', reasonCode: 'unknown' });
422
+ trace.finalize({ status: 'failed' });
423
+ traceStore.close();
424
+ throw error;
425
+ }
192
426
  const providerName = flags.provider;
193
427
  const userId = flags.user;
194
428
  const domain = flags.domain;
@@ -196,12 +430,68 @@ async function runAsk(rest, flags) {
196
430
  const format = flags.format;
197
431
  const reasoningEffort = cliReasoningEffort(flags);
198
432
  const requestedDepth = cliAnalysisDepth(flags);
199
- const provider = await pickProvider(providerName);
433
+ let provider;
434
+ try {
435
+ provider = await pickProvider(providerName);
436
+ }
437
+ catch (error) {
438
+ trace.finalize({ status: 'failed' });
439
+ traceStore.close();
440
+ throw error;
441
+ }
200
442
  const kg = new KGStore(kgPath);
201
443
  const memory = new MemoryStore(defaultMemoryPath(projectRoot));
202
444
  const { skills } = loadSkills(projectRoot);
203
445
  let closeRuntime;
204
446
  try {
447
+ const preflight = trace.startSpan({
448
+ name: 'provider.preflight',
449
+ stage: 'provider',
450
+ reasonCode: 'started',
451
+ payload: {
452
+ kind: 'provider',
453
+ attempt: {
454
+ version: 1,
455
+ phase: 'preflight',
456
+ physicalAttemptIndex: 0,
457
+ providerFingerprint: `sha256:${createHash('sha256').update(provider.name).digest('hex')}`,
458
+ readiness: 'unknown',
459
+ admission: 'unknown',
460
+ provenance: 'live',
461
+ },
462
+ },
463
+ });
464
+ let providerReady = false;
465
+ try {
466
+ providerReady = await provider.available();
467
+ }
468
+ catch {
469
+ providerReady = false;
470
+ }
471
+ trace.finishSpan(preflight, {
472
+ outcome: providerReady ? 'ok' : 'unavailable',
473
+ reasonCode: providerReady ? 'completed' : 'provider_preflight',
474
+ payload: {
475
+ kind: 'provider',
476
+ attempt: {
477
+ version: 1,
478
+ phase: 'preflight',
479
+ physicalAttemptIndex: 0,
480
+ providerFingerprint: `sha256:${createHash('sha256').update(provider.name).digest('hex')}`,
481
+ readiness: providerReady ? 'ready' : 'unavailable',
482
+ admission: 'unknown',
483
+ cause: providerReady ? undefined : 'authentication',
484
+ safeAction: providerReady ? undefined : 'fix_provider_configuration',
485
+ provenance: 'live',
486
+ },
487
+ },
488
+ });
489
+ if (!providerReady) {
490
+ throw Object.assign(new Error('The selected AI provider is not ready. Configure or sign in to the provider and retry.'), {
491
+ code: 'AUTHENTICATION_FAILED',
492
+ });
493
+ }
494
+ const tracedProvider = createDirectCliAskTraceProvider(provider, trace);
205
495
  const memoryContext = memory.search({
206
496
  query: question,
207
497
  scopes: ['project', 'user', 'artifact'],
@@ -220,6 +510,12 @@ async function runAsk(rest, flags) {
220
510
  closeRuntime = close;
221
511
  const schemaContext = await fetchRuntimeSchemaContext(runtimeBase);
222
512
  recordCliRuntimeSchemaSnapshot(projectRoot, schemaContext, 'direct CLI runtime schema');
513
+ const retrievalSpan = trace.startSpan({
514
+ name: 'retrieval',
515
+ stage: 'retrieval',
516
+ reasonCode: 'started',
517
+ payload: { kind: 'retrieval', candidateCount: 0 },
518
+ });
223
519
  const contextPack = await buildLocalContextPack(projectRoot, {
224
520
  question,
225
521
  surface: 'cli',
@@ -233,10 +529,15 @@ async function runAsk(rest, flags) {
233
529
  }
234
530
  : undefined,
235
531
  }).catch(() => undefined);
532
+ trace.finishSpan(retrievalSpan, {
533
+ outcome: 'ok',
534
+ reasonCode: 'completed',
535
+ payload: { kind: 'retrieval', candidateCount: contextPack?.objects.length ?? 0 },
536
+ });
236
537
  const answerLoopTools = buildAnswerLoopTools(projectRoot);
237
538
  const result = await answer({
238
539
  question,
239
- provider,
540
+ provider: tracedProvider,
240
541
  kg,
241
542
  manifest,
242
543
  skills,
@@ -365,6 +666,7 @@ async function runAsk(rest, flags) {
365
666
  });
366
667
  },
367
668
  });
669
+ trace.finalize({ status: 'completed' });
368
670
  if (format === 'json') {
369
671
  console.log(JSON.stringify(result, null, 2));
370
672
  return;
@@ -379,17 +681,25 @@ async function runAsk(rest, flags) {
379
681
  : '';
380
682
  const footer = result.provenanceFooter ? `\n\n— ${result.provenanceFooter}` : '';
381
683
  console.log(`${badge}\n\n${result.text}${footer}${cite}`);
382
- if (result.result) {
383
- console.log(`\nRows: ${result.result.rowCount}`);
384
- console.log(JSON.stringify(result.result.rows.slice(0, 5), null, 2));
684
+ const resultPayload = result.result;
685
+ if (resultPayload) {
686
+ console.log(`\nRows: ${resultPayload.rowCount}`);
687
+ console.log(JSON.stringify(resultPayload.rows.slice(0, 5), null, 2));
385
688
  }
386
689
  printDqlArtifactPreview(result);
387
690
  }
691
+ catch (error) {
692
+ const cancelled = error instanceof Error && error.name === 'AbortError';
693
+ trace.finalize({ status: cancelled ? 'cancelled' : 'failed' });
694
+ throw error;
695
+ }
388
696
  finally {
389
697
  kg.close();
390
698
  memory.close();
391
- if (closeRuntime)
392
- await closeRuntime();
699
+ const close = closeRuntime;
700
+ if (close)
701
+ await close();
702
+ traceStore.close();
393
703
  }
394
704
  }
395
705
  function printDqlArtifactPreview(result) {
@@ -410,56 +720,103 @@ function printDqlArtifactPreview(result) {
410
720
  console.log(`Promote: ${result.promoteCommand}`);
411
721
  }
412
722
  }
723
+ function printCanonicalCliAskRun(run, threadId) {
724
+ const badge = run.trustState === 'certified'
725
+ ? '✓ Certified'
726
+ : run.trustState === 'grounded'
727
+ ? '✓ Verified (grounded)'
728
+ : run.trustState === 'review_required'
729
+ ? '! AI-generated · review required'
730
+ : run.trustState === 'blocked'
731
+ ? '✕ Blocked'
732
+ : '· Reply';
733
+ console.log(`${badge}\n\n${(run.answer ?? run.summary ?? '').trim()}`);
734
+ if (threadId)
735
+ console.log(`\nThread: ${threadId}`);
736
+ const trace = run.traceReference;
737
+ if (trace?.traceId) {
738
+ console.log(`\nTrace: ${trace.traceId} (${trace.recordingStatus ?? 'recording'})`);
739
+ }
740
+ else {
741
+ console.log('\nTrace: unavailable');
742
+ }
743
+ }
413
744
  /**
414
- * `dql agent ask --thread <id>` POST the question to the runtime's
415
- * `/api/agent-runs` with the threadId in the body. The server injects the
416
- * thread's prior turns into the conversation context and records the completed
417
- * run as the next turn, so follow-ups resolve "those"/"that product" correctly
418
- * across CLI invocations (and across the notebook UI, which shares the store).
745
+ * Submit every user-visible CLI Ask through the runtime AgentRun endpoint.
746
+ * The runtime owns run IDs, conversation hydration, candidate provenance,
747
+ * cascade/freeze authority, physical tool/SQL execution, and final trace
748
+ * reference. The CLI only supplies user intent and an optional host-minted
749
+ * surface capability.
419
750
  */
420
- async function runThreadAsk(question, threadId, flags) {
751
+ export async function runCanonicalCliAsk(question, threadId, flags) {
421
752
  const projectRoot = findProjectRoot(process.cwd());
422
753
  const format = flags.format;
423
- const { runtimeBase, close } = await resolveAgentRuntime(projectRoot, flags);
754
+ const { runtimeBase, close, askTraceCapability: embeddedCapability } = await resolveAgentRuntime(projectRoot, flags);
424
755
  try {
756
+ const askTraceCapability = embeddedCapability ?? await requestLoopbackCliAskTraceCapability(runtimeBase);
425
757
  const reasoningEffort = cliReasoningEffort(flags);
426
758
  const analysisDepth = cliAnalysisDepth(flags);
759
+ const domain = flags.domain;
760
+ const purpose = flags.purpose || undefined;
761
+ const userId = flags.user;
762
+ const provider = runtimeProviderForCliFlag(flags.provider);
763
+ // `surface` is deliberately absent from public JSON. The runtime assigns
764
+ // trace surface only after consuming its own loopback capability; this
765
+ // context carries user intent, never attribution authority.
766
+ const workspaceContext = {
767
+ ...(domain ? { domain } : {}),
768
+ ...(purpose ? { purpose } : {}),
769
+ ...(userId ? { userId } : {}),
770
+ ...(provider ? { provider } : {}),
771
+ };
772
+ const hasWorkspaceContext = Object.keys(workspaceContext).length > 0;
427
773
  const response = await fetch(`${runtimeBase.replace(/\/$/, '')}/api/agent-runs`, {
428
774
  method: 'POST',
429
- headers: { 'Content-Type': 'application/json' },
775
+ headers: {
776
+ 'Content-Type': 'application/json',
777
+ ...(askTraceCapability ? { 'X-DQL-Ask-Trace-Capability': askTraceCapability } : {}),
778
+ },
430
779
  body: JSON.stringify({
431
780
  question,
432
- threadId,
781
+ ...(threadId ? { threadId } : {}),
433
782
  ...(reasoningEffort ? { reasoningEffort } : {}),
434
783
  ...(analysisDepth ? { analysisDepth } : {}),
784
+ ...(hasWorkspaceContext ? { workspaceContext } : {}),
435
785
  }),
436
786
  });
437
787
  if (!response.ok)
438
788
  throw new Error(`Runtime returned ${response.status}: ${await response.text()}`);
439
789
  const payload = (await response.json());
440
- if (!payload.run)
441
- throw new Error('Runtime did not return an agent run.');
790
+ if (!payload.run?.id)
791
+ throw new Error('Runtime did not return a canonical agent run.');
792
+ const run = payload.run;
442
793
  if (format === 'json') {
443
- console.log(JSON.stringify(payload.run, null, 2));
794
+ // Preserve the established top-level run shape while adding compact,
795
+ // content-free trace discoverability for scripts and support bundles.
796
+ console.log(JSON.stringify({
797
+ ...run,
798
+ runId: run.id,
799
+ ...(run.traceReference?.traceId ? { traceId: run.traceReference.traceId } : {}),
800
+ traceRecordingStatus: run.traceReference?.recordingStatus ?? 'unavailable',
801
+ }, null, 2));
444
802
  return;
445
803
  }
446
- const run = payload.run;
447
- const badge = run.trustState === 'certified'
448
- ? '✓ Certified'
449
- : run.trustState === 'grounded'
450
- ? '✓ Verified (grounded)'
451
- : run.trustState === 'review_required'
452
- ? '! AI-generated · review required'
453
- : run.trustState === 'blocked'
454
- ? '✕ Blocked'
455
- : '· Reply';
456
- console.log(`${badge}\n\n${(run.answer ?? run.summary ?? '').trim()}`);
457
- console.log(`\nThread: ${threadId}`);
804
+ printCanonicalCliAskRun(run, threadId);
458
805
  }
459
806
  finally {
460
807
  await close();
461
808
  }
462
809
  }
810
+ /**
811
+ * `dql agent ask --thread <id>` — POST the question to the runtime's
812
+ * `/api/agent-runs` with the threadId in the body. The server injects the
813
+ * thread's prior turns into the conversation context and records the completed
814
+ * run as the next turn, so follow-ups resolve "those"/"that product" correctly
815
+ * across CLI invocations (and across the notebook UI, which shares the store).
816
+ */
817
+ async function runThreadAsk(question, threadId, flags) {
818
+ return runCanonicalCliAsk(question, threadId, flags);
819
+ }
463
820
  /** `dql agent threads` — list server-persisted conversation threads. */
464
821
  async function runThreads(flags) {
465
822
  const projectRoot = findProjectRoot(process.cwd());
@@ -757,6 +1114,7 @@ async function runEval(rest, flags) {
757
1114
  && Boolean(runtimeRun.answer?.trim()),
758
1115
  meaningResolved: Boolean(runtimeRun.routeDecision?.meaningResolution),
759
1116
  } : {}),
1117
+ ...(runtimeProjection?.observability ? { observability: runtimeProjection.observability } : {}),
760
1118
  intent: runtimeRun?.routeDecision?.category ?? result.contextPack?.routeDecision.intent,
761
1119
  reviewStatus: result.reviewStatus,
762
1120
  contextObjects: runtimeProjection?.retrievalCandidateCount ?? result.contextPack?.objects.length,
@@ -947,6 +1305,9 @@ function evaluateCase(testCase, result, runtime) {
947
1305
  if (expected.terminalOutcomeKind && runtime?.terminalOutcome?.kind !== expected.terminalOutcomeKind) {
948
1306
  failures.push(`terminal outcome expected ${expected.terminalOutcomeKind}, got ${runtime?.terminalOutcome?.kind ?? 'none'}`);
949
1307
  }
1308
+ if (expected.traceRecordingStatus && runtime?.observability?.recordingStatus !== expected.traceRecordingStatus) {
1309
+ failures.push(`trace recording status expected ${expected.traceRecordingStatus}, got ${runtime?.observability?.recordingStatus ?? 'unavailable'}`);
1310
+ }
950
1311
  for (const token of stringList(expected.sqlContains)) {
951
1312
  if (!result.proposedSql?.toLowerCase().includes(token.toLowerCase()))
952
1313
  failures.push(`SQL did not contain "${token}"`);
@@ -1338,6 +1699,22 @@ function buildEvalTrace(input) {
1338
1699
  promoteCommand: result.promoteCommand,
1339
1700
  },
1340
1701
  },
1702
+ {
1703
+ stage: 'observability',
1704
+ status: runtime?.observability?.recordingStatus === 'complete'
1705
+ ? 'passed'
1706
+ : runtime?.observability ? 'info' : 'not_run',
1707
+ message: runtime?.observability
1708
+ ? `Local Ask trace recording ${runtime.observability.recordingStatus}.`
1709
+ : 'No runtime trace receipt was attached to this evaluation run.',
1710
+ payload: runtime?.observability
1711
+ ? {
1712
+ recordingStatus: runtime.observability.recordingStatus,
1713
+ storeSchemaVersion: runtime.observability.storeSchemaVersion,
1714
+ ...(runtime.observability.traceFingerprint ? { traceFingerprint: runtime.observability.traceFingerprint } : {}),
1715
+ }
1716
+ : undefined,
1717
+ },
1341
1718
  {
1342
1719
  stage: 'scoring',
1343
1720
  status: evaluation.failures.length === 0 ? 'passed' : 'failed',