@duckcodeailabs/dql-agent 1.6.20 → 1.6.22

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 (160) hide show
  1. package/dist/agent-run-engine.d.ts +287 -0
  2. package/dist/agent-run-engine.d.ts.map +1 -0
  3. package/dist/agent-run-engine.js +916 -0
  4. package/dist/agent-run-engine.js.map +1 -0
  5. package/dist/agent-run-gates.d.ts +26 -0
  6. package/dist/agent-run-gates.d.ts.map +1 -0
  7. package/dist/agent-run-gates.js +170 -0
  8. package/dist/agent-run-gates.js.map +1 -0
  9. package/dist/agent-run-planner.d.ts +32 -0
  10. package/dist/agent-run-planner.d.ts.map +1 -0
  11. package/dist/agent-run-planner.js +280 -0
  12. package/dist/agent-run-planner.js.map +1 -0
  13. package/dist/answer-loop.d.ts +49 -0
  14. package/dist/answer-loop.d.ts.map +1 -1
  15. package/dist/answer-loop.js +292 -17
  16. package/dist/answer-loop.js.map +1 -1
  17. package/dist/app-builder.d.ts +44 -2
  18. package/dist/app-builder.d.ts.map +1 -1
  19. package/dist/app-builder.js +558 -123
  20. package/dist/app-builder.js.map +1 -1
  21. package/dist/app-planner.d.ts +78 -0
  22. package/dist/app-planner.d.ts.map +1 -0
  23. package/dist/app-planner.js +145 -0
  24. package/dist/app-planner.js.map +1 -0
  25. package/dist/embeddings/provider.d.ts +66 -0
  26. package/dist/embeddings/provider.d.ts.map +1 -0
  27. package/dist/embeddings/provider.js +105 -0
  28. package/dist/embeddings/provider.js.map +1 -0
  29. package/dist/hints/git-store.d.ts +69 -0
  30. package/dist/hints/git-store.d.ts.map +1 -0
  31. package/dist/hints/git-store.js +244 -0
  32. package/dist/hints/git-store.js.map +1 -0
  33. package/dist/hints/retrieval.d.ts +49 -0
  34. package/dist/hints/retrieval.d.ts.map +1 -0
  35. package/dist/hints/retrieval.js +60 -0
  36. package/dist/hints/retrieval.js.map +1 -0
  37. package/dist/hints/store.d.ts +39 -0
  38. package/dist/hints/store.d.ts.map +1 -0
  39. package/dist/hints/store.js +279 -0
  40. package/dist/hints/store.js.map +1 -0
  41. package/dist/hints/types.d.ts +147 -0
  42. package/dist/hints/types.d.ts.map +1 -0
  43. package/dist/hints/types.js +92 -0
  44. package/dist/hints/types.js.map +1 -0
  45. package/dist/index.d.ts +43 -6
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +35 -3
  48. package/dist/index.js.map +1 -1
  49. package/dist/intent-controller.d.ts +63 -0
  50. package/dist/intent-controller.d.ts.map +1 -0
  51. package/dist/intent-controller.js +150 -0
  52. package/dist/intent-controller.js.map +1 -0
  53. package/dist/kg/build.d.ts.map +1 -1
  54. package/dist/kg/build.js +2 -0
  55. package/dist/kg/build.js.map +1 -1
  56. package/dist/kg/sqlite-fts.js +4 -0
  57. package/dist/kg/sqlite-fts.js.map +1 -1
  58. package/dist/kg/types.d.ts +10 -0
  59. package/dist/kg/types.d.ts.map +1 -1
  60. package/dist/metadata/catalog.d.ts +81 -4
  61. package/dist/metadata/catalog.d.ts.map +1 -1
  62. package/dist/metadata/catalog.js +208 -1
  63. package/dist/metadata/catalog.js.map +1 -1
  64. package/dist/metadata/drafts.d.ts +22 -0
  65. package/dist/metadata/drafts.d.ts.map +1 -1
  66. package/dist/metadata/drafts.js +126 -2
  67. package/dist/metadata/drafts.js.map +1 -1
  68. package/dist/metadata/grain-gate.d.ts +61 -0
  69. package/dist/metadata/grain-gate.d.ts.map +1 -0
  70. package/dist/metadata/grain-gate.js +265 -0
  71. package/dist/metadata/grain-gate.js.map +1 -0
  72. package/dist/metadata/identity.d.ts +47 -0
  73. package/dist/metadata/identity.d.ts.map +1 -0
  74. package/dist/metadata/identity.js +147 -0
  75. package/dist/metadata/identity.js.map +1 -0
  76. package/dist/metadata/metric-match.d.ts +93 -0
  77. package/dist/metadata/metric-match.d.ts.map +1 -0
  78. package/dist/metadata/metric-match.js +233 -0
  79. package/dist/metadata/metric-match.js.map +1 -0
  80. package/dist/metadata/sql-grounding.d.ts +149 -0
  81. package/dist/metadata/sql-grounding.d.ts.map +1 -0
  82. package/dist/metadata/sql-grounding.js +397 -0
  83. package/dist/metadata/sql-grounding.js.map +1 -0
  84. package/dist/metadata/sql-retrieval.d.ts +29 -0
  85. package/dist/metadata/sql-retrieval.d.ts.map +1 -0
  86. package/dist/metadata/sql-retrieval.js +79 -0
  87. package/dist/metadata/sql-retrieval.js.map +1 -0
  88. package/dist/narrate.d.ts +52 -0
  89. package/dist/narrate.d.ts.map +1 -0
  90. package/dist/narrate.js +263 -0
  91. package/dist/narrate.js.map +1 -0
  92. package/dist/propose/build-from-prompt.d.ts +160 -0
  93. package/dist/propose/build-from-prompt.d.ts.map +1 -0
  94. package/dist/propose/build-from-prompt.js +815 -0
  95. package/dist/propose/build-from-prompt.js.map +1 -0
  96. package/dist/propose/classify.d.ts +55 -0
  97. package/dist/propose/classify.d.ts.map +1 -0
  98. package/dist/propose/classify.js +167 -0
  99. package/dist/propose/classify.js.map +1 -0
  100. package/dist/propose/config.d.ts +39 -0
  101. package/dist/propose/config.d.ts.map +1 -0
  102. package/dist/propose/config.js +52 -0
  103. package/dist/propose/config.js.map +1 -0
  104. package/dist/propose/dbt-artifacts.d.ts +134 -0
  105. package/dist/propose/dbt-artifacts.d.ts.map +1 -0
  106. package/dist/propose/dbt-artifacts.js +320 -0
  107. package/dist/propose/dbt-artifacts.js.map +1 -0
  108. package/dist/propose/edit-block.d.ts +74 -0
  109. package/dist/propose/edit-block.d.ts.map +1 -0
  110. package/dist/propose/edit-block.js +133 -0
  111. package/dist/propose/edit-block.js.map +1 -0
  112. package/dist/propose/enrich.d.ts +48 -0
  113. package/dist/propose/enrich.d.ts.map +1 -0
  114. package/dist/propose/enrich.js +125 -0
  115. package/dist/propose/enrich.js.map +1 -0
  116. package/dist/propose/generate-sql.d.ts +61 -0
  117. package/dist/propose/generate-sql.d.ts.map +1 -0
  118. package/dist/propose/generate-sql.js +180 -0
  119. package/dist/propose/generate-sql.js.map +1 -0
  120. package/dist/propose/index.d.ts +24 -0
  121. package/dist/propose/index.d.ts.map +1 -0
  122. package/dist/propose/index.js +15 -0
  123. package/dist/propose/index.js.map +1 -0
  124. package/dist/propose/propose.d.ts +226 -0
  125. package/dist/propose/propose.d.ts.map +1 -0
  126. package/dist/propose/propose.js +668 -0
  127. package/dist/propose/propose.js.map +1 -0
  128. package/dist/propose/reflect-block.d.ts +99 -0
  129. package/dist/propose/reflect-block.d.ts.map +1 -0
  130. package/dist/propose/reflect-block.js +220 -0
  131. package/dist/propose/reflect-block.js.map +1 -0
  132. package/dist/propose/write-draft.d.ts +90 -0
  133. package/dist/propose/write-draft.d.ts.map +1 -0
  134. package/dist/propose/write-draft.js +185 -0
  135. package/dist/propose/write-draft.js.map +1 -0
  136. package/dist/providers/claude.d.ts +13 -1
  137. package/dist/providers/claude.d.ts.map +1 -1
  138. package/dist/providers/claude.js +20 -2
  139. package/dist/providers/claude.js.map +1 -1
  140. package/dist/providers/gemini.d.ts +13 -1
  141. package/dist/providers/gemini.d.ts.map +1 -1
  142. package/dist/providers/gemini.js +24 -3
  143. package/dist/providers/gemini.js.map +1 -1
  144. package/dist/providers/index.d.ts +3 -3
  145. package/dist/providers/index.d.ts.map +1 -1
  146. package/dist/providers/index.js +3 -3
  147. package/dist/providers/index.js.map +1 -1
  148. package/dist/research-loop.d.ts +62 -0
  149. package/dist/research-loop.d.ts.map +1 -0
  150. package/dist/research-loop.js +210 -0
  151. package/dist/research-loop.js.map +1 -0
  152. package/dist/skills/defaults.d.ts +37 -0
  153. package/dist/skills/defaults.d.ts.map +1 -0
  154. package/dist/skills/defaults.js +152 -0
  155. package/dist/skills/defaults.js.map +1 -0
  156. package/dist/skills/loader.d.ts +60 -0
  157. package/dist/skills/loader.d.ts.map +1 -1
  158. package/dist/skills/loader.js +164 -2
  159. package/dist/skills/loader.js.map +1 -1
  160. package/package.json +7 -5
@@ -0,0 +1,916 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { decideAgentAction, looksLikeComposeApp, } from "./intent-controller.js";
5
+ /** Routes a stakeholder may never land on (analyst authoring lives in the Notebook). */
6
+ const ANALYST_ONLY_ROUTES = new Set(["sql_cell", "dql_block_draft"]);
7
+ const DEFAULT_MAX_REPAIR_ATTEMPTS = 2;
8
+ const DEFAULT_MAX_STEPS = 4;
9
+ const CERTIFIED_MATCH_THRESHOLD = 0.5;
10
+ /**
11
+ * Routes whose gate failure is better answered by switching routes than by
12
+ * re-running the same executor (repair can't add what the route can't produce).
13
+ */
14
+ export const AGENT_RUN_ESCALATION_MAP = {
15
+ certified_answer: "research",
16
+ generated_answer: "research",
17
+ app_build: "dql_block_draft",
18
+ };
19
+ /** Resolve the request's audience, defaulting to analyst (Notebook) for back-compat. */
20
+ export function resolveAudience(request) {
21
+ return request.audience ?? "analyst";
22
+ }
23
+ /** A stakeholder may never land on an authoring route — collapse it to a governed answer. */
24
+ export function constrainRouteForAudience(route, audience) {
25
+ if (audience === "stakeholder" && ANALYST_ONLY_ROUTES.has(route))
26
+ return "generated_answer";
27
+ return route;
28
+ }
29
+ /**
30
+ * "Answer anyway, labeled" — a stakeholder should get a best-effort governed answer
31
+ * rather than a dead-end clarify, UNLESS the catalog explicitly flagged missing
32
+ * context. The answer loop does its own grounding/retrieval and can still return a
33
+ * needs-clarification result if it genuinely can't proceed.
34
+ */
35
+ export function answerAnywayRoute(route, request, audience) {
36
+ if (audience !== "stakeholder" || route !== "clarify")
37
+ return route;
38
+ const explicitMissing = (request.signals?.missingContext?.length ?? 0) > 0;
39
+ return explicitMissing ? "clarify" : "generated_answer";
40
+ }
41
+ /** Audience-aware escalation target: stakeholders never escalate into authoring. */
42
+ export function escalationRouteFor(route, audience) {
43
+ const target = AGENT_RUN_ESCALATION_MAP[route];
44
+ if (!target)
45
+ return undefined;
46
+ if (audience === "stakeholder" && ANALYST_ONLY_ROUTES.has(target))
47
+ return "research";
48
+ return target;
49
+ }
50
+ /** The handoff action shown on a stakeholder's review-required output. */
51
+ function requestCertificationAction() {
52
+ return { id: "request-certification", label: "Request certification" };
53
+ }
54
+ /**
55
+ * For stakeholders, strip analyst-authoring next actions and, on review-required
56
+ * output, offer the certification handoff. Analysts keep their actions untouched.
57
+ */
58
+ function applyAudienceToNextActions(actions, audience, status) {
59
+ if (audience !== "stakeholder")
60
+ return actions;
61
+ const consumption = actions.filter((action) => !ANALYST_ONLY_ROUTES.has(action.route ?? "clarify")
62
+ && action.artifactKind !== "sql_cell"
63
+ && action.artifactKind !== "dql_block_draft"
64
+ && !/insert-sql|create-block|promote|open-review|draft/i.test(action.id));
65
+ if (status === "needs_review" && !consumption.some((action) => action.id === "request-certification")) {
66
+ consumption.push(requestCertificationAction());
67
+ }
68
+ return consumption;
69
+ }
70
+ export class InMemoryAgentRunStore {
71
+ runs = new Map();
72
+ save(run) {
73
+ this.runs.set(run.id, run);
74
+ }
75
+ get(id) {
76
+ return this.runs.get(id);
77
+ }
78
+ list() {
79
+ return [...this.runs.values()];
80
+ }
81
+ }
82
+ export class FileAgentRunStore {
83
+ path;
84
+ maxRuns;
85
+ constructor(options) {
86
+ this.path = options.path;
87
+ this.maxRuns = options.maxRuns ?? 500;
88
+ }
89
+ save(run) {
90
+ const runs = this.list();
91
+ const index = runs.findIndex((candidate) => candidate.id === run.id);
92
+ if (index >= 0) {
93
+ runs[index] = run;
94
+ }
95
+ else {
96
+ runs.push(run);
97
+ }
98
+ const pruned = runs
99
+ .sort((a, b) => b.startedAt.localeCompare(a.startedAt))
100
+ .slice(0, this.maxRuns);
101
+ this.write(pruned);
102
+ }
103
+ get(id) {
104
+ return this.list().find((run) => run.id === id);
105
+ }
106
+ list() {
107
+ if (!existsSync(this.path))
108
+ return [];
109
+ try {
110
+ const parsed = JSON.parse(readFileSync(this.path, "utf-8"));
111
+ if (!Array.isArray(parsed.runs))
112
+ return [];
113
+ return parsed.runs.flatMap((run) => isAgentRunRecord(run) ? [run] : []);
114
+ }
115
+ catch {
116
+ return [];
117
+ }
118
+ }
119
+ write(runs) {
120
+ mkdirSync(dirname(this.path), { recursive: true });
121
+ const tmpPath = `${this.path}.tmp`;
122
+ writeFileSync(tmpPath, JSON.stringify({ version: 1, runs }, null, 2) + "\n", "utf-8");
123
+ renameSync(tmpPath, this.path);
124
+ }
125
+ }
126
+ export function defaultAgentRunStorePath(projectRoot) {
127
+ return join(projectRoot, ".dql", "local", "agent-runs.json");
128
+ }
129
+ function isAgentRunRecord(value) {
130
+ if (!value || typeof value !== "object" || Array.isArray(value))
131
+ return false;
132
+ const record = value;
133
+ return typeof record.id === "string"
134
+ && typeof record.question === "string"
135
+ && typeof record.route === "string"
136
+ && Array.isArray(record.events)
137
+ && Array.isArray(record.artifacts)
138
+ && Array.isArray(record.evaluations);
139
+ }
140
+ export class AgentRunEngine {
141
+ executors;
142
+ gates;
143
+ planner;
144
+ store;
145
+ idGenerator;
146
+ now;
147
+ maxRepairAttempts;
148
+ maxSteps;
149
+ constructor(options = {}) {
150
+ this.executors = options.executors ?? {};
151
+ this.gates = options.gates ?? {};
152
+ this.planner = options.planner ?? createDeterministicAgentRunPlanner();
153
+ this.store = options.store;
154
+ this.idGenerator = options.idGenerator ?? randomUUID;
155
+ this.now = options.now ?? (() => new Date());
156
+ this.maxRepairAttempts = options.maxRepairAttempts ?? DEFAULT_MAX_REPAIR_ATTEMPTS;
157
+ this.maxSteps = Math.max(1, options.maxSteps ?? DEFAULT_MAX_STEPS);
158
+ }
159
+ async run(request, onEvent) {
160
+ const runId = request.runId ?? this.idGenerator();
161
+ const startedAt = this.timestamp();
162
+ const requestedMode = request.requestedMode ?? "auto";
163
+ const events = [];
164
+ const emit = (event) => {
165
+ const full = {
166
+ id: `${runId}:event:${events.length + 1}`,
167
+ runId,
168
+ at: this.timestamp(),
169
+ ...event,
170
+ };
171
+ events.push(full);
172
+ onEvent?.(full);
173
+ };
174
+ emit({
175
+ type: "run.started",
176
+ message: "Started governed agent run.",
177
+ payload: {
178
+ question: request.question,
179
+ requestedMode,
180
+ selectedObject: request.selectedObject,
181
+ },
182
+ });
183
+ const audience = resolveAudience(request);
184
+ const routeDecision = buildIntentDecision(request);
185
+ const defaultRoute = answerAnywayRoute(constrainRouteForAudience(selectRoute(request, routeDecision), audience), request, audience);
186
+ try {
187
+ const plan = await this.planner.plan({
188
+ request,
189
+ routeDecision,
190
+ defaultRoute,
191
+ maxSteps: this.maxSteps,
192
+ audience,
193
+ });
194
+ emit({
195
+ type: "plan.created",
196
+ message: plan.rationale,
197
+ route: plan.steps[0]?.route,
198
+ payload: plan,
199
+ });
200
+ const executedSteps = [];
201
+ // Normalize planned routes to the audience (works for LLM and deterministic
202
+ // plans alike): stakeholders never author and never dead-end on clarify
203
+ // without explicit missing context.
204
+ const queue = plan.steps.map((step) => ({
205
+ ...step,
206
+ route: answerAnywayRoute(constrainRouteForAudience(step.route, audience), request, audience),
207
+ }));
208
+ let repairAttemptsTotal = 0;
209
+ let stepCount = 0;
210
+ let finalStep;
211
+ let finalResult;
212
+ let finalOutcome;
213
+ let clarifyOutcome;
214
+ while (queue.length > 0 && stepCount < this.maxSteps) {
215
+ const planned = queue.shift();
216
+ stepCount += 1;
217
+ const route = planned.route;
218
+ const stepId = `${runId}:step:${stepCount}`;
219
+ emit({
220
+ type: "step.started",
221
+ message: `Step ${stepCount}: ${planned.goal}`,
222
+ route,
223
+ payload: { stepId, index: stepCount, goal: planned.goal, successCriteria: planned.successCriteria },
224
+ });
225
+ emit({
226
+ type: "route.decided",
227
+ message: stepCount === 1
228
+ ? routeDecision.reason
229
+ : `Routed step ${stepCount} to ${route.replaceAll("_", " ")}.`,
230
+ route,
231
+ payload: stepCount === 1 ? routeDecision : { route, goal: planned.goal },
232
+ });
233
+ let attempt = 0;
234
+ let repairHint;
235
+ let priorEvaluations;
236
+ let result = {};
237
+ let evaluations = [];
238
+ let escalation;
239
+ let stepStatus = "needs_review";
240
+ let clarifyQuestion;
241
+ let isClarify = false;
242
+ // Build → evaluate → modify loop for this step.
243
+ for (;;) {
244
+ emit({
245
+ type: "executor.started",
246
+ message: attempt === 0
247
+ ? `Running ${route.replaceAll("_", " ")} executor.`
248
+ : `Re-running ${route.replaceAll("_", " ")} executor (repair attempt ${attempt}).`,
249
+ route,
250
+ });
251
+ result = await this.executeRoute({
252
+ runId,
253
+ request,
254
+ route,
255
+ routeDecision,
256
+ maxRepairAttempts: this.maxRepairAttempts,
257
+ attempt,
258
+ stepGoal: planned.goal,
259
+ priorEvaluations,
260
+ repairHint,
261
+ emit,
262
+ });
263
+ evaluations = this.evaluate({ route, request, routeDecision, result, attempt });
264
+ for (const evaluation of evaluations) {
265
+ emit({
266
+ type: "evaluation.recorded",
267
+ message: evaluation.message,
268
+ route,
269
+ payload: evaluation,
270
+ });
271
+ }
272
+ // An executor that explicitly self-declares blocked is terminal (infra blocker).
273
+ if (result.status === "blocked") {
274
+ stepStatus = "blocked";
275
+ break;
276
+ }
277
+ const failing = evaluations.find((evaluation) => !evaluation.passed && evaluation.suggestedRepair);
278
+ if (!failing) {
279
+ stepStatus = attempt > 0 ? "repaired" : "passed";
280
+ break;
281
+ }
282
+ // Out of modify budget — accept the best result we have.
283
+ if (repairAttemptsTotal >= this.maxRepairAttempts) {
284
+ stepStatus = "needs_review";
285
+ break;
286
+ }
287
+ const currentStep = {
288
+ id: stepId,
289
+ index: stepCount,
290
+ route,
291
+ goal: planned.goal,
292
+ successCriteria: planned.successCriteria,
293
+ status: "needs_review",
294
+ attempts: attempt + 1,
295
+ summary: result.summary,
296
+ evaluations,
297
+ artifacts: result.artifacts ?? [],
298
+ };
299
+ const decision = await this.planner.replan({
300
+ request,
301
+ plan,
302
+ currentStep,
303
+ remainingSteps: queue,
304
+ attemptsUsed: attempt,
305
+ repairAttemptsUsed: repairAttemptsTotal,
306
+ maxRepairAttempts: this.maxRepairAttempts,
307
+ });
308
+ emit({
309
+ type: "replan.decided",
310
+ message: describeReplan(decision),
311
+ route,
312
+ payload: decision,
313
+ });
314
+ if (decision.decision === "repair") {
315
+ repairAttemptsTotal += 1;
316
+ attempt += 1;
317
+ repairHint = decision.repairHint || failing.suggestedRepair;
318
+ priorEvaluations = evaluations;
319
+ emit({
320
+ type: "repair.attempted",
321
+ message: `Repairing ${route.replaceAll("_", " ")}: ${repairHint}`,
322
+ route,
323
+ payload: { attempt, repairHint },
324
+ });
325
+ continue;
326
+ }
327
+ if (decision.decision === "escalate") {
328
+ repairAttemptsTotal += 1;
329
+ escalation = { route: decision.route, goal: decision.goal, hint: decision.repairHint || failing.suggestedRepair };
330
+ stepStatus = "escalated";
331
+ emit({
332
+ type: "escalated",
333
+ message: `Escalating ${route.replaceAll("_", " ")} → ${decision.route.replaceAll("_", " ")}.`,
334
+ route,
335
+ payload: decision,
336
+ });
337
+ break;
338
+ }
339
+ if (decision.decision === "clarify") {
340
+ isClarify = true;
341
+ clarifyQuestion = decision.question;
342
+ stepStatus = "clarify";
343
+ break;
344
+ }
345
+ // "accept"
346
+ stepStatus = "needs_review";
347
+ break;
348
+ }
349
+ // Escalated steps are recorded in the trace but their output is superseded.
350
+ if (escalation) {
351
+ executedSteps.push({
352
+ id: stepId,
353
+ index: stepCount,
354
+ route,
355
+ goal: planned.goal,
356
+ successCriteria: planned.successCriteria,
357
+ status: "escalated",
358
+ attempts: attempt + 1,
359
+ summary: result.summary,
360
+ evaluations,
361
+ artifacts: [],
362
+ });
363
+ emit({
364
+ type: "step.completed",
365
+ message: `Step ${stepCount} escalated to ${escalation.route.replaceAll("_", " ")}.`,
366
+ route,
367
+ payload: { stepId, status: "escalated" },
368
+ });
369
+ queue.unshift({
370
+ id: `${stepId}:escalation`,
371
+ route: escalation.route,
372
+ goal: escalation.goal ?? `Escalated from ${route} to ${escalation.route}.`,
373
+ successCriteria: [],
374
+ });
375
+ continue;
376
+ }
377
+ const outcome = computeStepOutcome(route, result, evaluations, request, isClarify, clarifyQuestion);
378
+ const step = {
379
+ id: stepId,
380
+ index: stepCount,
381
+ route,
382
+ goal: planned.goal,
383
+ successCriteria: planned.successCriteria,
384
+ status: outcome.status === "blocked" ? "blocked" : stepStatus,
385
+ attempts: attempt + 1,
386
+ summary: outcome.summary,
387
+ evaluations,
388
+ artifacts: outcome.artifacts,
389
+ };
390
+ executedSteps.push(step);
391
+ for (const artifact of outcome.artifacts) {
392
+ emit({
393
+ type: "artifact.created",
394
+ message: `Created ${artifact.kind.replaceAll("_", " ")} artifact.`,
395
+ route,
396
+ trustState: artifact.trustState,
397
+ payload: artifact,
398
+ });
399
+ }
400
+ emit({
401
+ type: "step.completed",
402
+ message: `Step ${stepCount} ${step.status}.`,
403
+ route,
404
+ status: outcome.status,
405
+ trustState: outcome.trustState,
406
+ payload: { stepId, status: step.status },
407
+ });
408
+ if (isClarify) {
409
+ clarifyOutcome = { step, question: clarifyQuestion };
410
+ finalStep = step;
411
+ finalResult = result;
412
+ finalOutcome = outcome;
413
+ break;
414
+ }
415
+ finalStep = step;
416
+ finalResult = result;
417
+ finalOutcome = outcome;
418
+ if (outcome.status === "blocked")
419
+ break;
420
+ if (isTerminalSuccess(route, outcome))
421
+ break;
422
+ // Otherwise continue to the next planned step (if any remain).
423
+ }
424
+ const run = this.finalizeRun({
425
+ runId,
426
+ request,
427
+ requestedMode,
428
+ startedAt,
429
+ routeDecision,
430
+ plan,
431
+ steps: executedSteps,
432
+ finalStep,
433
+ finalResult,
434
+ finalOutcome,
435
+ clarifyOutcome,
436
+ repairAttemptsTotal,
437
+ events,
438
+ });
439
+ emit({
440
+ type: "run.completed",
441
+ message: `Agent run completed with status ${run.status}.`,
442
+ route: run.route,
443
+ status: run.status,
444
+ trustState: run.trustState,
445
+ });
446
+ run.completedAt = this.timestamp();
447
+ await this.store?.save(run);
448
+ return run;
449
+ }
450
+ catch (err) {
451
+ const message = err instanceof Error ? err.message : String(err);
452
+ emit({
453
+ type: "run.failed",
454
+ message,
455
+ route: "blocked",
456
+ status: "blocked",
457
+ trustState: "blocked",
458
+ });
459
+ const run = {
460
+ id: runId,
461
+ question: request.question,
462
+ requestedMode,
463
+ route: "blocked",
464
+ status: "blocked",
465
+ trustState: "blocked",
466
+ stopReason: "blocked",
467
+ startedAt,
468
+ completedAt: this.timestamp(),
469
+ selectedObject: request.selectedObject,
470
+ routeDecision,
471
+ steps: [],
472
+ summary: message,
473
+ artifacts: [],
474
+ evaluations: [{
475
+ id: "executor-error",
476
+ label: "Executor error",
477
+ passed: false,
478
+ severity: "blocking",
479
+ message,
480
+ }],
481
+ events,
482
+ nextActions: [],
483
+ repairAttempts: 0,
484
+ };
485
+ await this.store?.save(run);
486
+ return run;
487
+ }
488
+ }
489
+ finalizeRun(input) {
490
+ const { finalStep, finalResult, finalOutcome } = input;
491
+ const completedAt = this.timestamp();
492
+ if (!finalStep || !finalResult || !finalOutcome) {
493
+ // No step produced a usable result (e.g. an empty plan). Treat as blocked.
494
+ return {
495
+ id: input.runId,
496
+ question: input.request.question,
497
+ requestedMode: input.requestedMode,
498
+ route: "blocked",
499
+ status: "blocked",
500
+ trustState: "blocked",
501
+ stopReason: "blocked",
502
+ startedAt: input.startedAt,
503
+ completedAt,
504
+ selectedObject: input.request.selectedObject,
505
+ routeDecision: input.routeDecision,
506
+ plan: input.plan,
507
+ steps: input.steps,
508
+ summary: "The agent run produced no executable step.",
509
+ artifacts: [],
510
+ evaluations: [{
511
+ id: "empty-plan",
512
+ label: "Empty plan",
513
+ passed: false,
514
+ severity: "blocking",
515
+ message: "The planner returned no runnable steps.",
516
+ }],
517
+ events: input.events,
518
+ nextActions: [],
519
+ repairAttempts: input.repairAttemptsTotal,
520
+ };
521
+ }
522
+ const route = finalStep.route;
523
+ // Aggregate artifacts across every accepted step so a multi-step plan
524
+ // (e.g. research → block draft) surfaces all of its durable work, while the
525
+ // status/trust/answer reflect the final step.
526
+ const artifacts = input.steps.flatMap((step) => step.artifacts);
527
+ return {
528
+ id: input.runId,
529
+ question: input.request.question,
530
+ requestedMode: input.requestedMode,
531
+ route,
532
+ status: finalOutcome.status,
533
+ trustState: finalOutcome.trustState,
534
+ stopReason: finalOutcome.stopReason,
535
+ startedAt: input.startedAt,
536
+ completedAt,
537
+ selectedObject: input.request.selectedObject,
538
+ routeDecision: input.routeDecision,
539
+ plan: input.plan,
540
+ steps: input.steps,
541
+ summary: finalOutcome.summary,
542
+ answer: input.clarifyOutcome?.question ?? finalResult.answer,
543
+ artifacts,
544
+ evaluations: finalStep.evaluations,
545
+ events: input.events,
546
+ nextActions: applyAudienceToNextActions(finalResult.nextActions ?? defaultNextActions(route, finalOutcome.status), resolveAudience(input.request), finalOutcome.status),
547
+ repairAttempts: finalResult.repairAttempts ?? input.repairAttemptsTotal,
548
+ };
549
+ }
550
+ evaluate(context) {
551
+ const gate = this.gates[context.route];
552
+ if (gate)
553
+ return gate(context);
554
+ return context.result.evaluations
555
+ ?? defaultEvaluations(context.route, context.request, context.routeDecision);
556
+ }
557
+ async executeRoute(context) {
558
+ const executor = this.executors[context.route];
559
+ if (executor)
560
+ return executor(context);
561
+ return defaultExecutorResult(context.route, context.request, context.routeDecision);
562
+ }
563
+ timestamp() {
564
+ return this.now().toISOString();
565
+ }
566
+ }
567
+ function computeStepOutcome(route, result, evaluations, request, isClarify, clarifyQuestion) {
568
+ const fallback = defaultOutcome(route);
569
+ if (isClarify) {
570
+ return {
571
+ status: "needs_clarification",
572
+ trustState: "not_applicable",
573
+ artifacts: [],
574
+ stopReason: "needs_clarification",
575
+ summary: result.summary ?? clarifyQuestion ?? fallback.summary,
576
+ };
577
+ }
578
+ const status = result.status ?? statusFromEvaluations(route, evaluations, fallback.status);
579
+ const trustState = result.trustState ?? trustStateFromEvaluations(route, evaluations, fallback.trustState);
580
+ const artifacts = status === "blocked"
581
+ ? []
582
+ : result.artifacts ?? defaultArtifacts(route, result, request);
583
+ const stopReason = result.stopReason ?? stopReasonFor(route, status, trustState, artifacts);
584
+ return {
585
+ status,
586
+ trustState,
587
+ artifacts,
588
+ stopReason,
589
+ summary: result.summary ?? fallback.summary,
590
+ };
591
+ }
592
+ function isTerminalSuccess(route, outcome) {
593
+ // A completed certified answer is the terminal success — no further steps add trust.
594
+ // Every other accepted step falls through so a multi-step plan can keep going; the
595
+ // run loop ends naturally when the planned queue empties or maxSteps is hit.
596
+ return route === "certified_answer" && outcome.status === "completed";
597
+ }
598
+ function describeReplan(decision) {
599
+ switch (decision.decision) {
600
+ case "repair":
601
+ return `Repairing the current route: ${decision.repairHint}`;
602
+ case "escalate":
603
+ return `Escalating to ${decision.route.replaceAll("_", " ")}.`;
604
+ case "clarify":
605
+ return decision.question ?? "Needs a clarifying question before continuing.";
606
+ default:
607
+ return "Accepting the current result.";
608
+ }
609
+ }
610
+ /**
611
+ * The default, fully deterministic planner. Produces a single-step plan from the
612
+ * existing route selection and drives repair/escalation from failing evaluations.
613
+ * Used when no LLM planner is injected — keeps the engine offline + testable.
614
+ */
615
+ export function createDeterministicAgentRunPlanner() {
616
+ return {
617
+ plan({ request, routeDecision, defaultRoute }) {
618
+ return {
619
+ source: "deterministic",
620
+ rationale: routeDecision.reason,
621
+ steps: [{
622
+ id: "step-1",
623
+ route: defaultRoute,
624
+ goal: request.question,
625
+ successCriteria: defaultSuccessCriteria(defaultRoute),
626
+ }],
627
+ };
628
+ },
629
+ replan({ request, currentStep, attemptsUsed, maxRepairAttempts }) {
630
+ const failing = currentStep.evaluations.find((evaluation) => !evaluation.passed && evaluation.suggestedRepair);
631
+ if (!failing)
632
+ return { decision: "accept" };
633
+ const audience = resolveAudience(request);
634
+ const action = failing.repairAction;
635
+ const requested = action?.kind === "escalate" ? action.route : undefined;
636
+ // Honor an explicit escalate target, else the route's default — then clamp for the audience.
637
+ const rawEscalation = requested ?? AGENT_RUN_ESCALATION_MAP[currentStep.route];
638
+ const escalationRoute = rawEscalation
639
+ ? (audience === "stakeholder" && ANALYST_ONLY_ROUTES.has(rawEscalation) ? "research" : rawEscalation)
640
+ : undefined;
641
+ const hint = action?.hint ?? failing.suggestedRepair ?? "Revise and retry.";
642
+ if (action?.kind === "escalate" && escalationRoute) {
643
+ return { decision: "escalate", route: escalationRoute, goal: hint, repairHint: hint };
644
+ }
645
+ if (attemptsUsed < maxRepairAttempts) {
646
+ return { decision: "repair", repairHint: hint };
647
+ }
648
+ if (escalationRoute) {
649
+ return { decision: "escalate", route: escalationRoute, goal: hint, repairHint: hint };
650
+ }
651
+ return { decision: "accept" };
652
+ },
653
+ };
654
+ }
655
+ export function defaultSuccessCriteria(route) {
656
+ switch (route) {
657
+ case "certified_answer":
658
+ return ["Answer is backed by a certified DQL block or governed metric."];
659
+ case "generated_answer":
660
+ return ["Answer is grounded in governed context and marked review-required."];
661
+ case "research":
662
+ return ["Research dossier is grounded in catalog or context-pack evidence."];
663
+ case "sql_cell":
664
+ return ["Generated SQL executes against the preview without errors."];
665
+ case "dql_block_draft":
666
+ return ["Draft passes the certifier with no blockers (still human-reviewed)."];
667
+ case "app_build":
668
+ return ["App tiles are backed by certified blocks."];
669
+ case "clarify":
670
+ return ["A single sharp clarifying question is returned."];
671
+ default:
672
+ return [];
673
+ }
674
+ }
675
+ function buildIntentDecision(request) {
676
+ const forcedAction = requestedModeToAction(request.requestedMode);
677
+ if (forcedAction) {
678
+ return {
679
+ action: forcedAction,
680
+ confidence: 1,
681
+ reason: `User selected ${request.requestedMode} mode.`,
682
+ followsUp: Boolean(request.history?.length),
683
+ };
684
+ }
685
+ return decideAgentAction({
686
+ question: request.question,
687
+ intent: request.intent ?? "ad_hoc_ranking",
688
+ signals: request.signals,
689
+ history: request.history,
690
+ });
691
+ }
692
+ function requestedModeToAction(mode) {
693
+ if (!mode || mode === "auto")
694
+ return undefined;
695
+ if (mode === "app")
696
+ return "compose_app";
697
+ if (mode === "research")
698
+ return "investigate";
699
+ if (mode === "ask" || mode === "sql" || mode === "block")
700
+ return "answer";
701
+ return undefined;
702
+ }
703
+ export function selectRoute(request, decision) {
704
+ const mode = request.requestedMode ?? "auto";
705
+ if (mode === "research")
706
+ return "research";
707
+ if (mode === "sql")
708
+ return "sql_cell";
709
+ if (mode === "block")
710
+ return "dql_block_draft";
711
+ if (mode === "app")
712
+ return "app_build";
713
+ const question = request.question;
714
+ if (looksLikeDqlBlockRequest(question))
715
+ return "dql_block_draft";
716
+ if (looksLikeSqlCellRequest(question))
717
+ return "sql_cell";
718
+ if (looksLikeComposeApp(question))
719
+ return "app_build";
720
+ if (decision.action === "compose_app")
721
+ return "app_build";
722
+ if (decision.action === "investigate")
723
+ return "research";
724
+ if (decision.action === "clarify")
725
+ return "clarify";
726
+ const certifiedScore = request.signals?.certifiedScore ?? 0;
727
+ if (certifiedScore >= CERTIFIED_MATCH_THRESHOLD)
728
+ return "certified_answer";
729
+ return "generated_answer";
730
+ }
731
+ function looksLikeSqlCellRequest(question) {
732
+ return /\b(sql|query|notebook cell|cell draft|write a select|generate a query)\b/i.test(question);
733
+ }
734
+ function looksLikeDqlBlockRequest(question) {
735
+ return /\b(dql block|block draft|draft block|create.*block|turn .* into .*block|promote .* block)\b/i.test(question);
736
+ }
737
+ function defaultExecutorResult(route, request, decision) {
738
+ const fallback = defaultOutcome(route);
739
+ return {
740
+ summary: fallback.summary,
741
+ answer: route === "clarify" ? decision?.clarifyingQuestion : undefined,
742
+ evaluations: defaultEvaluations(route, request, decision),
743
+ artifacts: defaultArtifacts(route, {}, request),
744
+ };
745
+ }
746
+ function defaultOutcome(route) {
747
+ switch (route) {
748
+ case "certified_answer":
749
+ return {
750
+ status: "completed",
751
+ trustState: "certified",
752
+ summary: "Answered from certified DQL context.",
753
+ };
754
+ case "clarify":
755
+ return {
756
+ status: "needs_clarification",
757
+ trustState: "not_applicable",
758
+ summary: "Needs clarification before a governed answer can be produced.",
759
+ };
760
+ case "blocked":
761
+ return {
762
+ status: "blocked",
763
+ trustState: "blocked",
764
+ summary: "Agent run is blocked.",
765
+ };
766
+ default:
767
+ return {
768
+ status: "needs_review",
769
+ trustState: "review_required",
770
+ summary: "Created review-required agent output.",
771
+ };
772
+ }
773
+ }
774
+ function defaultEvaluations(route, request, decision) {
775
+ const base = [{
776
+ id: "route-decision",
777
+ label: "Route decision",
778
+ passed: true,
779
+ severity: "info",
780
+ message: decision?.reason ?? `Routed request to ${route.replaceAll("_", " ")}.`,
781
+ }];
782
+ if (route === "certified_answer") {
783
+ base.push({
784
+ id: "certified-context",
785
+ label: "Certified context",
786
+ passed: (request.signals?.certifiedScore ?? 0) >= CERTIFIED_MATCH_THRESHOLD,
787
+ severity: "blocking",
788
+ message: "A certified DQL block or governed artifact must cover the answer.",
789
+ });
790
+ }
791
+ if (route === "generated_answer" || route === "sql_cell") {
792
+ base.push({
793
+ id: "review-boundary",
794
+ label: "Review boundary",
795
+ passed: true,
796
+ severity: "warning",
797
+ message: "Generated SQL or metadata-derived analysis remains review-required.",
798
+ });
799
+ }
800
+ if (route === "dql_block_draft") {
801
+ base.push({
802
+ id: "certification-boundary",
803
+ label: "Certification boundary",
804
+ passed: true,
805
+ severity: "warning",
806
+ message: "AI may create a draft block, but certification stays gated by review.",
807
+ });
808
+ }
809
+ return base;
810
+ }
811
+ function statusFromEvaluations(route, evaluations, fallback) {
812
+ if (evaluations.some((evaluation) => !evaluation.passed && evaluation.severity === "blocking"))
813
+ return "blocked";
814
+ if (route === "clarify")
815
+ return "needs_clarification";
816
+ if (route === "certified_answer")
817
+ return "completed";
818
+ return fallback;
819
+ }
820
+ function trustStateFromEvaluations(route, evaluations, fallback) {
821
+ if (evaluations.some((evaluation) => !evaluation.passed && evaluation.severity === "blocking"))
822
+ return "blocked";
823
+ if (route === "certified_answer")
824
+ return "certified";
825
+ if (route === "clarify")
826
+ return "not_applicable";
827
+ return fallback;
828
+ }
829
+ function defaultArtifacts(route, result, request) {
830
+ const trustState = defaultOutcome(route).trustState;
831
+ switch (route) {
832
+ case "certified_answer":
833
+ case "generated_answer":
834
+ return [{
835
+ id: `${route}:answer`,
836
+ kind: "answer",
837
+ title: route === "certified_answer" ? "Certified answer" : "Generated answer",
838
+ trustState,
839
+ payload: { question: request.question, answer: result.answer },
840
+ }];
841
+ case "research":
842
+ return [{
843
+ id: "research:run",
844
+ kind: "research_run",
845
+ title: "Research run",
846
+ trustState,
847
+ payload: { question: request.question },
848
+ }];
849
+ case "sql_cell":
850
+ return [{
851
+ id: "notebook:sql-cell",
852
+ kind: "sql_cell",
853
+ title: "Generated SQL cell",
854
+ trustState,
855
+ payload: { question: request.question },
856
+ }];
857
+ case "dql_block_draft":
858
+ return [{
859
+ id: "dql:block-draft",
860
+ kind: "dql_block_draft",
861
+ title: "DQL block draft",
862
+ trustState,
863
+ payload: { question: request.question },
864
+ }];
865
+ case "app_build":
866
+ return [{
867
+ id: "app:draft",
868
+ kind: "app_draft",
869
+ title: "App draft",
870
+ trustState,
871
+ payload: { question: request.question },
872
+ }];
873
+ default:
874
+ return [];
875
+ }
876
+ }
877
+ function stopReasonFor(route, status, trustState, artifacts) {
878
+ if (status === "blocked" || trustState === "blocked")
879
+ return "blocked";
880
+ if (status === "needs_clarification")
881
+ return "needs_clarification";
882
+ if (route === "certified_answer")
883
+ return "certified_answer_found";
884
+ if (artifacts.length > 0 && route !== "generated_answer")
885
+ return "artifact_created";
886
+ if (status === "needs_review")
887
+ return "human_review_required";
888
+ return "generated_review_required";
889
+ }
890
+ function defaultNextActions(route, status) {
891
+ if (status === "blocked")
892
+ return [];
893
+ if (route === "certified_answer") {
894
+ return [
895
+ { id: "research-gap", label: "Research missing breakdown", route: "research" },
896
+ { id: "build-app", label: "Build app from certified answer", route: "app_build" },
897
+ ];
898
+ }
899
+ if (route === "research") {
900
+ return [
901
+ { id: "insert-sql", label: "Insert SQL cell", route: "sql_cell", artifactKind: "sql_cell" },
902
+ { id: "create-block", label: "Create DQL draft", route: "dql_block_draft", artifactKind: "dql_block_draft" },
903
+ ];
904
+ }
905
+ if (route === "sql_cell") {
906
+ return [{ id: "create-block", label: "Promote to DQL draft", route: "dql_block_draft", artifactKind: "dql_block_draft" }];
907
+ }
908
+ if (route === "dql_block_draft") {
909
+ return [{ id: "open-review", label: "Open review checklist", artifactKind: "dql_block_draft" }];
910
+ }
911
+ if (route === "app_build") {
912
+ return [{ id: "open-app", label: "Open app draft", artifactKind: "app_draft" }];
913
+ }
914
+ return [];
915
+ }
916
+ //# sourceMappingURL=agent-run-engine.js.map