@duckcodeailabs/dql-agent 1.11.9 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/agent-run-engine.d.ts.map +1 -1
  2. package/dist/agent-run-engine.js +7 -3
  3. package/dist/agent-run-engine.js.map +1 -1
  4. package/dist/answer-loop.d.ts +25 -1
  5. package/dist/answer-loop.d.ts.map +1 -1
  6. package/dist/answer-loop.js +139 -16
  7. package/dist/answer-loop.js.map +1 -1
  8. package/dist/conversation/rolling-summary.d.ts +23 -0
  9. package/dist/conversation/rolling-summary.d.ts.map +1 -1
  10. package/dist/conversation/rolling-summary.js +64 -0
  11. package/dist/conversation/rolling-summary.js.map +1 -1
  12. package/dist/conversation/session-store.d.ts +18 -2
  13. package/dist/conversation/session-store.d.ts.map +1 -1
  14. package/dist/conversation/session-store.js +65 -8
  15. package/dist/conversation/session-store.js.map +1 -1
  16. package/dist/conversation/snapshot.d.ts +37 -4
  17. package/dist/conversation/snapshot.d.ts.map +1 -1
  18. package/dist/conversation/snapshot.js +115 -6
  19. package/dist/conversation/snapshot.js.map +1 -1
  20. package/dist/hints/correction-eval.d.ts.map +1 -1
  21. package/dist/hints/correction-eval.js +4 -6
  22. package/dist/hints/correction-eval.js.map +1 -1
  23. package/dist/hints/dependencies.d.ts +63 -0
  24. package/dist/hints/dependencies.d.ts.map +1 -0
  25. package/dist/hints/dependencies.js +196 -0
  26. package/dist/hints/dependencies.js.map +1 -0
  27. package/dist/hints/git-store.d.ts +55 -2
  28. package/dist/hints/git-store.d.ts.map +1 -1
  29. package/dist/hints/git-store.js +246 -11
  30. package/dist/hints/git-store.js.map +1 -1
  31. package/dist/hints/graph.d.ts +17 -0
  32. package/dist/hints/graph.d.ts.map +1 -0
  33. package/dist/hints/graph.js +222 -0
  34. package/dist/hints/graph.js.map +1 -0
  35. package/dist/hints/lifecycle.d.ts +101 -0
  36. package/dist/hints/lifecycle.d.ts.map +1 -0
  37. package/dist/hints/lifecycle.js +387 -0
  38. package/dist/hints/lifecycle.js.map +1 -0
  39. package/dist/hints/retrieval.d.ts +13 -1
  40. package/dist/hints/retrieval.d.ts.map +1 -1
  41. package/dist/hints/retrieval.js +74 -11
  42. package/dist/hints/retrieval.js.map +1 -1
  43. package/dist/hints/store.d.ts +9 -2
  44. package/dist/hints/store.d.ts.map +1 -1
  45. package/dist/hints/store.js +155 -15
  46. package/dist/hints/store.js.map +1 -1
  47. package/dist/hints/types.d.ts +63 -3
  48. package/dist/hints/types.d.ts.map +1 -1
  49. package/dist/hints/types.js +1 -1
  50. package/dist/hints/types.js.map +1 -1
  51. package/dist/index.d.ts +16 -10
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +8 -5
  54. package/dist/index.js.map +1 -1
  55. package/dist/intent-controller.d.ts.map +1 -1
  56. package/dist/intent-controller.js +24 -3
  57. package/dist/intent-controller.js.map +1 -1
  58. package/dist/metadata/catalog.d.ts +32 -0
  59. package/dist/metadata/catalog.d.ts.map +1 -1
  60. package/dist/metadata/catalog.js +86 -4
  61. package/dist/metadata/catalog.js.map +1 -1
  62. package/dist/metadata/metric-match.d.ts.map +1 -1
  63. package/dist/metadata/metric-match.js +28 -2
  64. package/dist/metadata/metric-match.js.map +1 -1
  65. package/dist/router.d.ts.map +1 -1
  66. package/dist/router.js +32 -9
  67. package/dist/router.js.map +1 -1
  68. package/dist/skills/loader.js +1 -1
  69. package/dist/skills/loader.js.map +1 -1
  70. package/dist/tools/registry.js +5 -5
  71. package/dist/tools/registry.js.map +1 -1
  72. package/package.json +4 -4
@@ -17,6 +17,8 @@
17
17
  import { createHash } from 'node:crypto';
18
18
  import { describeDialectForPrompt, normalizeMetricCapabilityContract, } from '@duckcodeailabs/dql-core';
19
19
  import { buildSkillBlockHints, buildSkillMetricHints, buildSkillsPrompt, expandQuestionWithSkillVocabulary, selectRelevantSkills } from './skills/loader.js';
20
+ import { conversationTurnContextState, } from './conversation/snapshot.js';
21
+ import { renderStructuredConversationSummary } from './conversation/rolling-summary.js';
20
22
  import { detectResultSetOperation, computeResultSetOperation } from './conversation/result-ops.js';
21
23
  import { classifyGovernedQueryShape } from './semantic-bridge/query-shape.js';
22
24
  import { domainContextSearchDomains } from './domain-context.js';
@@ -2900,6 +2902,7 @@ async function runAnswerLoop(input) {
2900
2902
  ];
2901
2903
  let result;
2902
2904
  let executionError;
2905
+ let warehouseFailure;
2903
2906
  let repairAttempts = 0;
2904
2907
  // The repair turn returns error-recovery prose ("the column X was not recognized…"),
2905
2908
  // NOT a user-facing answer. Keep it for the trace only — never as the answer text.
@@ -2909,6 +2912,7 @@ async function runAnswerLoop(input) {
2909
2912
  }
2910
2913
  if (deepCandidateExecutionError) {
2911
2914
  executionError = deepCandidateExecutionError;
2915
+ warehouseFailure = normalizeWarehouseSqlFailure(deepCandidateExecutionError, input.semanticDriver);
2912
2916
  }
2913
2917
  const executeCurrentSql = () => {
2914
2918
  const requestedLimit = questionPlan.requestedShape.topN?.scope === 'per_group'
@@ -2961,10 +2965,12 @@ async function runAnswerLoop(input) {
2961
2965
  result = await executeCurrentSql();
2962
2966
  }
2963
2967
  catch (err) {
2964
- executionError = err instanceof Error ? err.message : String(err);
2968
+ warehouseFailure = normalizeWarehouseSqlFailure(err, input.semanticDriver);
2969
+ executionError = warehouseFailure.redactedMessage;
2965
2970
  }
2966
2971
  if (executionError && !fanoutContradiction && !authoritativeSemanticBinding) {
2967
- if (isRetryableGeneratedSqlError(executionError)) {
2972
+ warehouseFailure ??= normalizeWarehouseSqlFailure(executionError, input.semanticDriver);
2973
+ if (isRetryableGeneratedSqlError(warehouseFailure)) {
2968
2974
  const localRepairSql = repairGeneratedSqlLocally(parsed.sql, executionError, schemaContext);
2969
2975
  if (localRepairSql && canUseLaneRepair(repairBudgetState, 'execution')) {
2970
2976
  recordLaneRepair(repairBudgetState, 'execution');
@@ -2989,23 +2995,28 @@ async function runAnswerLoop(input) {
2989
2995
  try {
2990
2996
  result = await executeCurrentSql();
2991
2997
  executionError = undefined;
2998
+ warehouseFailure = undefined;
2992
2999
  }
2993
3000
  catch (retryErr) {
2994
- executionError = retryErr instanceof Error ? retryErr.message : String(retryErr);
3001
+ warehouseFailure = normalizeWarehouseSqlFailure(retryErr, input.semanticDriver);
3002
+ executionError = warehouseFailure.redactedMessage;
2995
3003
  }
2996
3004
  }
2997
3005
  else {
2998
3006
  executionError = localRepairValidation.error;
3007
+ warehouseFailure = normalizeWarehouseSqlFailure(localRepairValidation.error, input.semanticDriver);
2999
3008
  }
3000
3009
  }
3001
- if (executionError && canUseLaneRepair(repairBudgetState, 'execution')) {
3010
+ if (executionError
3011
+ && warehouseFailure?.retryDisposition === 'model_repair'
3012
+ && canUseLaneRepair(repairBudgetState, 'execution')) {
3002
3013
  recordLaneRepair(repairBudgetState, 'execution');
3003
3014
  const repairedRaw = await requestSqlRepair({
3004
3015
  provider,
3005
3016
  baseMessages: messages,
3006
3017
  question,
3007
3018
  parsed,
3008
- executionError,
3019
+ failure: warehouseFailure,
3009
3020
  schemaContext,
3010
3021
  signal: input.signal,
3011
3022
  reasoningEffort: input.reasoningEffort,
@@ -3037,13 +3048,16 @@ async function runAnswerLoop(input) {
3037
3048
  try {
3038
3049
  result = await executeCurrentSql();
3039
3050
  executionError = undefined;
3051
+ warehouseFailure = undefined;
3040
3052
  }
3041
3053
  catch (retryErr) {
3042
- executionError = retryErr instanceof Error ? retryErr.message : String(retryErr);
3054
+ warehouseFailure = normalizeWarehouseSqlFailure(retryErr, input.semanticDriver);
3055
+ executionError = warehouseFailure.redactedMessage;
3043
3056
  }
3044
3057
  }
3045
3058
  else {
3046
3059
  executionError = repairedValidation.error;
3060
+ warehouseFailure = normalizeWarehouseSqlFailure(repairedValidation.error, input.semanticDriver);
3047
3061
  }
3048
3062
  }
3049
3063
  }
@@ -3204,6 +3218,7 @@ async function runAnswerLoop(input) {
3204
3218
  sql: parsed.sql,
3205
3219
  result,
3206
3220
  executionError,
3221
+ ...(warehouseFailure ? { warehouseFailure } : {}),
3207
3222
  ...(finalSemanticExecutionTrace ? { semanticExecutionTrace: finalSemanticExecutionTrace } : {}),
3208
3223
  ...(governedMetricExecutionFailure ? { refusalCode: 'grounding_gap' } : {}),
3209
3224
  suggestedViz: parsed.viz ?? 'table',
@@ -4271,7 +4286,7 @@ function renderContextPrompt(blocks, businessContext, others, activeTier, memory
4271
4286
  const hintsSection = appliedHints.length > 0
4272
4287
  ? `\n\n## Applied governed hints (human-approved corrections)\n\nReviewed, scope-matched corrections from your team. Apply them when preparing a SQL preview to avoid known mistakes; they refine the DQL artifact's SQL preview but MUST NOT override a certified artifact that already answers the question.\n${appliedHints
4273
4288
  .slice(0, 6)
4274
- .map((h) => `- \`${h.title}\`: ${h.guidance}${h.correctedSql ? `\n corrected SQL pattern: ${h.correctedSql.replace(/\s+/g, ' ').trim().slice(0, 240)}` : ''}`)
4289
+ .map((h) => `- \`${h.title}\`: ${h.guidance}${h.graphReason ? `\n matched graph context: ${h.graphReason}` : ''}${h.correctedSql ? `\n corrected SQL pattern: ${h.correctedSql.replace(/\s+/g, ' ').trim().slice(0, 240)}` : ''}`)
4275
4290
  .join('\n')}`
4276
4291
  : '';
4277
4292
  const extraSection = extraContext?.trim()
@@ -4347,8 +4362,12 @@ function renderConversationSnapshot(snapshot) {
4347
4362
  else if (snapshot.topicRelation) {
4348
4363
  parts.push(`Working state: topic=${snapshot.topicRelation}`);
4349
4364
  }
4350
- if (snapshot.rollingSummary) {
4351
- parts.push(`Earlier in this conversation (compacted):\n${snapshot.rollingSummary.slice(0, 600)}`);
4365
+ const structuredSummary = renderStructuredConversationSummary(snapshot.structuredSummary);
4366
+ if (structuredSummary) {
4367
+ parts.push(`Earlier in this conversation (source-attributed):\n${structuredSummary}`);
4368
+ }
4369
+ else if (snapshot.rollingSummary) {
4370
+ parts.push(`Earlier in this conversation (legacy compacted view):\n${snapshot.rollingSummary.slice(0, 600)}`);
4352
4371
  }
4353
4372
  if (snapshot.recentTurns.length > 0) {
4354
4373
  const turns = snapshot.recentTurns.slice(-4).map((turn, index) => {
@@ -4356,16 +4375,20 @@ function renderConversationSnapshot(snapshot) {
4356
4375
  });
4357
4376
  parts.push(`Recent turns:\n${turns.join('\n')}`);
4358
4377
  }
4359
- if (snapshot.recalledTurns?.length) {
4360
- parts.push(`Recalled earlier turns (semantic match):\n${snapshot.recalledTurns.slice(0, 3)
4378
+ const recalledTurns = snapshot.recalledTurns
4379
+ ?.filter((turn) => conversationTurnContextState(turn) !== 'blocked')
4380
+ .slice(0, 3);
4381
+ if (recalledTurns?.length) {
4382
+ parts.push(`Recalled earlier turns (semantic match):\n${recalledTurns
4361
4383
  .map((turn) => `- ${renderConversationSnapshotTurnLine(turn, 520)}`)
4362
4384
  .join('\n')}`);
4363
4385
  }
4364
- parts.push('Use this only where the question refers to it. On a new topic, answer fresh.');
4386
+ parts.push('Use this only where the question refers to it. Provisional, unresolved, and blocked turns are not verified facts. On a new topic, answer fresh.');
4365
4387
  return parts.join('\n\n');
4366
4388
  }
4367
4389
  function renderConversationSnapshotTurnLine(turn, max) {
4368
4390
  const line = [
4391
+ `state: ${conversationTurnContextState(turn)}`,
4369
4392
  `Q: ${turn.question}`,
4370
4393
  turn.answerSummary ? `A: ${turn.answerSummary}` : '',
4371
4394
  turn.resultColumns?.length ? `cols: ${turn.resultColumns.slice(0, 6).join(', ')}` : '',
@@ -6221,12 +6244,18 @@ async function requestSqlRepair(input) {
6221
6244
  content: [
6222
6245
  'The SQL preview for the review-required DQL artifact failed during bounded preview execution.',
6223
6246
  `Question: ${input.question}`,
6224
- `Execution error: ${input.executionError}`,
6247
+ `Warehouse dialect: ${input.failure.driver ?? 'unknown'}`,
6248
+ `Failure category: ${input.failure.category}`,
6249
+ `Execution error: ${input.failure.redactedMessage}`,
6250
+ input.failure.vendorCode ? `Vendor code: ${input.failure.vendorCode}` : '',
6251
+ input.failure.sqlState ? `SQL state: ${input.failure.sqlState}` : '',
6252
+ input.failure.line !== undefined ? `Failure line: ${input.failure.line}` : '',
6253
+ input.failure.position !== undefined ? `Failure position: ${input.failure.position}` : '',
6225
6254
  'Qualify every source column independently inside each SELECT/CTE scope. Do not repair an ambiguous column by choosing the first FROM relation; use the runtime schema to identify its unique owner, or leave the query unresolved when no unique owner is proven.',
6226
6255
  'Preserve every requested output measure and dimension while repairing the failing reference.',
6227
6256
  'Return one corrected read-only SQL query using only the runtime schema below, as a single ```json fenced object with summary, sql, viz, outputs, and optional dql metadata fields.',
6228
6257
  schema,
6229
- ].join('\n\n'),
6258
+ ].filter(Boolean).join('\n\n'),
6230
6259
  },
6231
6260
  ], {
6232
6261
  signal: input.signal,
@@ -6499,8 +6528,102 @@ function summarizeEvidencePayload(value, maxLength = 700) {
6499
6528
  return undefined;
6500
6529
  return compact.length <= maxLength ? compact : `${compact.slice(0, maxLength - 3)}...`;
6501
6530
  }
6502
- function isRetryableGeneratedSqlError(error) {
6503
- return !/\b(read-only|readonly|select or with|unsafe|delete|insert|update|drop|alter|create|attach|copy|pragma)\b/i.test(error);
6531
+ export function normalizeWarehouseSqlFailure(error, fallbackDriver) {
6532
+ const record = error && typeof error === 'object'
6533
+ ? error
6534
+ : undefined;
6535
+ const rawMessage = error instanceof Error
6536
+ ? error.message
6537
+ : typeof error === 'string'
6538
+ ? error
6539
+ : typeof record?.message === 'string'
6540
+ ? record.message
6541
+ : String(error);
6542
+ const message = redactWarehouseSqlFailureMessage(rawMessage);
6543
+ const category = classifyWarehouseSqlFailure(message);
6544
+ return {
6545
+ version: 1,
6546
+ category,
6547
+ retryDisposition: warehouseSqlRetryDisposition(category),
6548
+ redactedMessage: message,
6549
+ ...(stringField(record, 'driver') ?? fallbackDriver
6550
+ ? { driver: stringField(record, 'driver') ?? fallbackDriver }
6551
+ : {}),
6552
+ ...(stringField(record, 'vendorCode') ? { vendorCode: stringField(record, 'vendorCode') } : {}),
6553
+ ...(stringField(record, 'sqlState') ? { sqlState: stringField(record, 'sqlState') } : {}),
6554
+ ...(stringField(record, 'queryId') ? { queryId: stringField(record, 'queryId') } : {}),
6555
+ ...(numberField(record, 'line') !== undefined ? { line: numberField(record, 'line') } : {}),
6556
+ ...(numberField(record, 'position') !== undefined ? { position: numberField(record, 'position') } : {}),
6557
+ ...(typeof record?.retryable === 'boolean' ? { retryable: record.retryable } : {}),
6558
+ };
6559
+ }
6560
+ function redactWarehouseSqlFailureMessage(message) {
6561
+ const compact = message.replace(/\s+/g, ' ').trim()
6562
+ .replace(/\b(password|token|secret|private[_ -]?key)\s*[=:]\s*([^\s,;]+)/gi, '$1=[redacted]')
6563
+ .replace(/\b(?:https?|snowflake|postgres(?:ql)?):\/\/[^\s]+/gi, '[redacted connection]');
6564
+ return (compact || 'Warehouse query failed.').slice(0, 700);
6565
+ }
6566
+ function classifyWarehouseSqlFailure(message) {
6567
+ if (/\b(read-only|readonly|select or with|unsafe statement|not permitted sql|delete|insert|update|drop|alter|create|attach|copy|pragma)\b/i.test(message)) {
6568
+ return 'unsafe';
6569
+ }
6570
+ if (/\b(authentication|authenticate|invalid credentials?|incorrect username|login failed|oauth|expired token)\b/i.test(message)) {
6571
+ return 'authentication';
6572
+ }
6573
+ if (/\b(permission denied|insufficient privileges?|not authorized|access denied|authorization failed|does not have privilege)\b/i.test(message)) {
6574
+ return 'permission';
6575
+ }
6576
+ if (/\b(cancelled|canceled|aborted by user)\b/i.test(message))
6577
+ return 'cancelled';
6578
+ if (/\b(timeout|timed out|deadline exceeded|statement timeout)\b/i.test(message))
6579
+ return 'timeout';
6580
+ if (/\bambiguous\s+(?:reference|column(?:\s+name)?)\b/i.test(message))
6581
+ return 'ambiguous_column';
6582
+ if (/\b(column|identifier|field)\b.*\b(not found|does not exist|not recognized|invalid identifier|unknown)\b/i.test(message)
6583
+ || /\bdoes not have a column named\b/i.test(message)
6584
+ || /\binvalid identifier\b/i.test(message)) {
6585
+ return 'unknown_column';
6586
+ }
6587
+ if (/\b(table|relation|view|object)\b.*\b(not found|does not exist|unknown|not exist|not authorized)\b/i.test(message)) {
6588
+ return 'unknown_relation';
6589
+ }
6590
+ if (/\b(function|routine)\b.*\b(not found|does not exist|unknown|unsupported|no matching signature)\b/i.test(message)) {
6591
+ return 'unsupported_function';
6592
+ }
6593
+ if (/\b(type mismatch|cannot cast|conversion error|invalid argument types?|operator does not exist)\b/i.test(message)) {
6594
+ return 'type_mismatch';
6595
+ }
6596
+ if (/\b(parser error|parse error|syntax error|sql compilation error|unexpected token|unexpected keyword)\b/i.test(message)) {
6597
+ return 'syntax';
6598
+ }
6599
+ return 'unknown';
6600
+ }
6601
+ function warehouseSqlRetryDisposition(category) {
6602
+ if (category === 'syntax'
6603
+ || category === 'unknown_column'
6604
+ || category === 'ambiguous_column'
6605
+ || category === 'unsupported_function'
6606
+ || category === 'type_mismatch') {
6607
+ return 'model_repair';
6608
+ }
6609
+ if (category === 'unknown_relation')
6610
+ return 'refresh_metadata';
6611
+ if (category === 'timeout')
6612
+ return 'explicit_retry';
6613
+ if (category === 'permission' || category === 'authentication')
6614
+ return 'change_authorized_access';
6615
+ return 'terminal';
6616
+ }
6617
+ function stringField(record, key) {
6618
+ const value = record?.[key];
6619
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
6620
+ }
6621
+ function numberField(record, key) {
6622
+ const value = record?.[key];
6623
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
6624
+ }
6625
+ function isRetryableGeneratedSqlError(failure) {
6626
+ return failure.retryDisposition === 'model_repair';
6504
6627
  }
6505
6628
  /**
6506
6629
  * Only SQL-shape/binding failures may leave the certified lane. Connectivity,