@duckcodeailabs/dql-cli 1.14.2 → 1.14.3-rc.1

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