@duckcodeailabs/dql-cli 1.4.4 → 1.5.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 (299) hide show
  1. package/LICENSE +123 -0
  2. package/README.md +72 -0
  3. package/{apps-api.d.ts → dist/apps-api.d.ts} +2 -0
  4. package/dist/apps-api.d.ts.map +1 -0
  5. package/{apps-api.js → dist/apps-api.js} +326 -4
  6. package/dist/apps-api.js.map +1 -0
  7. package/{args.test.js → dist/args.test.js} +8 -0
  8. package/{args.test.js.map → dist/args.test.js.map} +1 -1
  9. package/dist/assets/dql-notebook/assets/index-R3UrqjLQ.css +1 -0
  10. package/dist/assets/dql-notebook/assets/index-mlfOQ2me.js +857 -0
  11. package/{assets → dist/assets}/dql-notebook/index.html +2 -2
  12. package/dist/block-studio-import.d.ts +58 -0
  13. package/dist/block-studio-import.d.ts.map +1 -0
  14. package/dist/block-studio-import.js +390 -0
  15. package/dist/block-studio-import.js.map +1 -0
  16. package/dist/block-studio-import.test.d.ts +2 -0
  17. package/dist/block-studio-import.test.d.ts.map +1 -0
  18. package/dist/block-studio-import.test.js +106 -0
  19. package/dist/block-studio-import.test.js.map +1 -0
  20. package/dist/commands/agent.d.ts.map +1 -0
  21. package/{commands → dist/commands}/agent.js +98 -5
  22. package/dist/commands/agent.js.map +1 -0
  23. package/dist/commands/import.d.ts +3 -0
  24. package/dist/commands/import.d.ts.map +1 -0
  25. package/dist/commands/import.js +50 -0
  26. package/dist/commands/import.js.map +1 -0
  27. package/{commands → dist/commands}/migrate.d.ts.map +1 -1
  28. package/{commands → dist/commands}/migrate.js +5 -0
  29. package/dist/commands/migrate.js.map +1 -0
  30. package/dist/commands/validate.d.ts.map +1 -0
  31. package/dist/commands/validate.js +163 -0
  32. package/dist/commands/validate.js.map +1 -0
  33. package/dist/commands/validate.test.d.ts +2 -0
  34. package/dist/commands/validate.test.d.ts.map +1 -0
  35. package/dist/commands/validate.test.js +55 -0
  36. package/dist/commands/validate.test.js.map +1 -0
  37. package/{index.js → dist/index.js} +5 -0
  38. package/dist/index.js.map +1 -0
  39. package/{llm → dist/llm}/index.d.ts.map +1 -1
  40. package/{llm → dist/llm}/index.js +4 -3
  41. package/{llm → dist/llm}/index.js.map +1 -1
  42. package/{llm → dist/llm}/providers/dql-agent-provider.d.ts +1 -1
  43. package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -0
  44. package/dist/llm/providers/dql-agent-provider.js +287 -0
  45. package/dist/llm/providers/dql-agent-provider.js.map +1 -0
  46. package/{llm → dist/llm}/types.d.ts +3 -1
  47. package/dist/llm/types.d.ts.map +1 -0
  48. package/{local-runtime.d.ts.map → dist/local-runtime.d.ts.map} +1 -1
  49. package/{local-runtime.js → dist/local-runtime.js} +551 -49
  50. package/dist/local-runtime.js.map +1 -0
  51. package/{schedule → dist/schedule}/runner.d.ts.map +1 -1
  52. package/{schedule → dist/schedule}/runner.js +4 -0
  53. package/{schedule → dist/schedule}/runner.js.map +1 -1
  54. package/dist/settings/provider-settings.d.ts +33 -0
  55. package/dist/settings/provider-settings.d.ts.map +1 -0
  56. package/dist/settings/provider-settings.js +91 -0
  57. package/dist/settings/provider-settings.js.map +1 -0
  58. package/package.json +31 -20
  59. package/apps-api.d.ts.map +0 -1
  60. package/apps-api.js.map +0 -1
  61. package/assets/dql-notebook/assets/index-DUTeFz5j.js +0 -858
  62. package/assets/dql-notebook/assets/index-DrhoZmtv.css +0 -1
  63. package/commands/agent.d.ts.map +0 -1
  64. package/commands/agent.js.map +0 -1
  65. package/commands/migrate.js.map +0 -1
  66. package/commands/validate.d.ts.map +0 -1
  67. package/commands/validate.js +0 -116
  68. package/commands/validate.js.map +0 -1
  69. package/index.js.map +0 -1
  70. package/llm/providers/dql-agent-provider.d.ts.map +0 -1
  71. package/llm/providers/dql-agent-provider.js +0 -99
  72. package/llm/providers/dql-agent-provider.js.map +0 -1
  73. package/llm/types.d.ts.map +0 -1
  74. package/local-runtime.js.map +0 -1
  75. /package/{apps-api.test.d.ts → dist/apps-api.test.d.ts} +0 -0
  76. /package/{apps-api.test.d.ts.map → dist/apps-api.test.d.ts.map} +0 -0
  77. /package/{apps-api.test.js → dist/apps-api.test.js} +0 -0
  78. /package/{apps-api.test.js.map → dist/apps-api.test.js.map} +0 -0
  79. /package/{args.d.ts → dist/args.d.ts} +0 -0
  80. /package/{args.d.ts.map → dist/args.d.ts.map} +0 -0
  81. /package/{args.js → dist/args.js} +0 -0
  82. /package/{args.js.map → dist/args.js.map} +0 -0
  83. /package/{args.test.d.ts → dist/args.test.d.ts} +0 -0
  84. /package/{args.test.d.ts.map → dist/args.test.d.ts.map} +0 -0
  85. /package/{assets → dist/assets}/dql-notebook/assets/codemirror-DJYUkPr1.js +0 -0
  86. /package/{assets → dist/assets}/dql-notebook/assets/react-CRB3T2We.js +0 -0
  87. /package/{assets → dist/assets}/notebook-browser/app.js +0 -0
  88. /package/{assets → dist/assets}/notebook-browser/index.html +0 -0
  89. /package/{assets → dist/assets}/notebook-browser/styles.css +0 -0
  90. /package/{block-templates.d.ts → dist/block-templates.d.ts} +0 -0
  91. /package/{block-templates.d.ts.map → dist/block-templates.d.ts.map} +0 -0
  92. /package/{block-templates.js → dist/block-templates.js} +0 -0
  93. /package/{block-templates.js.map → dist/block-templates.js.map} +0 -0
  94. /package/{commands → dist/commands}/agent.d.ts +0 -0
  95. /package/{commands → dist/commands}/app.d.ts +0 -0
  96. /package/{commands → dist/commands}/app.d.ts.map +0 -0
  97. /package/{commands → dist/commands}/app.js +0 -0
  98. /package/{commands → dist/commands}/app.js.map +0 -0
  99. /package/{commands → dist/commands}/build.d.ts +0 -0
  100. /package/{commands → dist/commands}/build.d.ts.map +0 -0
  101. /package/{commands → dist/commands}/build.js +0 -0
  102. /package/{commands → dist/commands}/build.js.map +0 -0
  103. /package/{commands → dist/commands}/build.test.d.ts +0 -0
  104. /package/{commands → dist/commands}/build.test.d.ts.map +0 -0
  105. /package/{commands → dist/commands}/build.test.js +0 -0
  106. /package/{commands → dist/commands}/build.test.js.map +0 -0
  107. /package/{commands → dist/commands}/certify.d.ts +0 -0
  108. /package/{commands → dist/commands}/certify.d.ts.map +0 -0
  109. /package/{commands → dist/commands}/certify.js +0 -0
  110. /package/{commands → dist/commands}/certify.js.map +0 -0
  111. /package/{commands → dist/commands}/compile.d.ts +0 -0
  112. /package/{commands → dist/commands}/compile.d.ts.map +0 -0
  113. /package/{commands → dist/commands}/compile.js +0 -0
  114. /package/{commands → dist/commands}/compile.js.map +0 -0
  115. /package/{commands → dist/commands}/compile.test.d.ts +0 -0
  116. /package/{commands → dist/commands}/compile.test.d.ts.map +0 -0
  117. /package/{commands → dist/commands}/compile.test.js +0 -0
  118. /package/{commands → dist/commands}/compile.test.js.map +0 -0
  119. /package/{commands → dist/commands}/diff.d.ts +0 -0
  120. /package/{commands → dist/commands}/diff.d.ts.map +0 -0
  121. /package/{commands → dist/commands}/diff.js +0 -0
  122. /package/{commands → dist/commands}/diff.js.map +0 -0
  123. /package/{commands → dist/commands}/doctor.d.ts +0 -0
  124. /package/{commands → dist/commands}/doctor.d.ts.map +0 -0
  125. /package/{commands → dist/commands}/doctor.js +0 -0
  126. /package/{commands → dist/commands}/doctor.js.map +0 -0
  127. /package/{commands → dist/commands}/doctor.test.d.ts +0 -0
  128. /package/{commands → dist/commands}/doctor.test.d.ts.map +0 -0
  129. /package/{commands → dist/commands}/doctor.test.js +0 -0
  130. /package/{commands → dist/commands}/doctor.test.js.map +0 -0
  131. /package/{commands → dist/commands}/fmt.d.ts +0 -0
  132. /package/{commands → dist/commands}/fmt.d.ts.map +0 -0
  133. /package/{commands → dist/commands}/fmt.js +0 -0
  134. /package/{commands → dist/commands}/fmt.js.map +0 -0
  135. /package/{commands → dist/commands}/info.d.ts +0 -0
  136. /package/{commands → dist/commands}/info.d.ts.map +0 -0
  137. /package/{commands → dist/commands}/info.js +0 -0
  138. /package/{commands → dist/commands}/info.js.map +0 -0
  139. /package/{commands → dist/commands}/init.d.ts +0 -0
  140. /package/{commands → dist/commands}/init.d.ts.map +0 -0
  141. /package/{commands → dist/commands}/init.js +0 -0
  142. /package/{commands → dist/commands}/init.js.map +0 -0
  143. /package/{commands → dist/commands}/init.test.d.ts +0 -0
  144. /package/{commands → dist/commands}/init.test.d.ts.map +0 -0
  145. /package/{commands → dist/commands}/init.test.js +0 -0
  146. /package/{commands → dist/commands}/init.test.js.map +0 -0
  147. /package/{commands → dist/commands}/lineage.d.ts +0 -0
  148. /package/{commands → dist/commands}/lineage.d.ts.map +0 -0
  149. /package/{commands → dist/commands}/lineage.js +0 -0
  150. /package/{commands → dist/commands}/lineage.js.map +0 -0
  151. /package/{commands → dist/commands}/mcp.d.ts +0 -0
  152. /package/{commands → dist/commands}/mcp.d.ts.map +0 -0
  153. /package/{commands → dist/commands}/mcp.js +0 -0
  154. /package/{commands → dist/commands}/mcp.js.map +0 -0
  155. /package/{commands → dist/commands}/migrate.d.ts +0 -0
  156. /package/{commands → dist/commands}/new.d.ts +0 -0
  157. /package/{commands → dist/commands}/new.d.ts.map +0 -0
  158. /package/{commands → dist/commands}/new.js +0 -0
  159. /package/{commands → dist/commands}/new.js.map +0 -0
  160. /package/{commands → dist/commands}/new.test.d.ts +0 -0
  161. /package/{commands → dist/commands}/new.test.d.ts.map +0 -0
  162. /package/{commands → dist/commands}/new.test.js +0 -0
  163. /package/{commands → dist/commands}/new.test.js.map +0 -0
  164. /package/{commands → dist/commands}/notebook.d.ts +0 -0
  165. /package/{commands → dist/commands}/notebook.d.ts.map +0 -0
  166. /package/{commands → dist/commands}/notebook.js +0 -0
  167. /package/{commands → dist/commands}/notebook.js.map +0 -0
  168. /package/{commands → dist/commands}/parse.d.ts +0 -0
  169. /package/{commands → dist/commands}/parse.d.ts.map +0 -0
  170. /package/{commands → dist/commands}/parse.js +0 -0
  171. /package/{commands → dist/commands}/parse.js.map +0 -0
  172. /package/{commands → dist/commands}/preview.d.ts +0 -0
  173. /package/{commands → dist/commands}/preview.d.ts.map +0 -0
  174. /package/{commands → dist/commands}/preview.js +0 -0
  175. /package/{commands → dist/commands}/preview.js.map +0 -0
  176. /package/{commands → dist/commands}/schedule.d.ts +0 -0
  177. /package/{commands → dist/commands}/schedule.d.ts.map +0 -0
  178. /package/{commands → dist/commands}/schedule.js +0 -0
  179. /package/{commands → dist/commands}/schedule.js.map +0 -0
  180. /package/{commands → dist/commands}/semantic.d.ts +0 -0
  181. /package/{commands → dist/commands}/semantic.d.ts.map +0 -0
  182. /package/{commands → dist/commands}/semantic.js +0 -0
  183. /package/{commands → dist/commands}/semantic.js.map +0 -0
  184. /package/{commands → dist/commands}/serve.d.ts +0 -0
  185. /package/{commands → dist/commands}/serve.d.ts.map +0 -0
  186. /package/{commands → dist/commands}/serve.js +0 -0
  187. /package/{commands → dist/commands}/serve.js.map +0 -0
  188. /package/{commands → dist/commands}/slack.d.ts +0 -0
  189. /package/{commands → dist/commands}/slack.d.ts.map +0 -0
  190. /package/{commands → dist/commands}/slack.js +0 -0
  191. /package/{commands → dist/commands}/slack.js.map +0 -0
  192. /package/{commands → dist/commands}/sync.d.ts +0 -0
  193. /package/{commands → dist/commands}/sync.d.ts.map +0 -0
  194. /package/{commands → dist/commands}/sync.js +0 -0
  195. /package/{commands → dist/commands}/sync.js.map +0 -0
  196. /package/{commands → dist/commands}/sync.test.d.ts +0 -0
  197. /package/{commands → dist/commands}/sync.test.d.ts.map +0 -0
  198. /package/{commands → dist/commands}/sync.test.js +0 -0
  199. /package/{commands → dist/commands}/sync.test.js.map +0 -0
  200. /package/{commands → dist/commands}/test.d.ts +0 -0
  201. /package/{commands → dist/commands}/test.d.ts.map +0 -0
  202. /package/{commands → dist/commands}/test.js +0 -0
  203. /package/{commands → dist/commands}/test.js.map +0 -0
  204. /package/{commands → dist/commands}/validate.d.ts +0 -0
  205. /package/{commands → dist/commands}/verify.d.ts +0 -0
  206. /package/{commands → dist/commands}/verify.d.ts.map +0 -0
  207. /package/{commands → dist/commands}/verify.js +0 -0
  208. /package/{commands → dist/commands}/verify.js.map +0 -0
  209. /package/{digest.d.ts → dist/digest.d.ts} +0 -0
  210. /package/{digest.d.ts.map → dist/digest.d.ts.map} +0 -0
  211. /package/{digest.js → dist/digest.js} +0 -0
  212. /package/{digest.js.map → dist/digest.js.map} +0 -0
  213. /package/{git-service.d.ts → dist/git-service.d.ts} +0 -0
  214. /package/{git-service.d.ts.map → dist/git-service.d.ts.map} +0 -0
  215. /package/{git-service.js → dist/git-service.js} +0 -0
  216. /package/{git-service.js.map → dist/git-service.js.map} +0 -0
  217. /package/{governance-runtime.d.ts → dist/governance-runtime.d.ts} +0 -0
  218. /package/{governance-runtime.d.ts.map → dist/governance-runtime.d.ts.map} +0 -0
  219. /package/{governance-runtime.js → dist/governance-runtime.js} +0 -0
  220. /package/{governance-runtime.js.map → dist/governance-runtime.js.map} +0 -0
  221. /package/{index.d.ts → dist/index.d.ts} +0 -0
  222. /package/{index.d.ts.map → dist/index.d.ts.map} +0 -0
  223. /package/{llm → dist/llm}/index.d.ts +0 -0
  224. /package/{llm → dist/llm}/providers/claude-agent-sdk.d.ts +0 -0
  225. /package/{llm → dist/llm}/providers/claude-agent-sdk.d.ts.map +0 -0
  226. /package/{llm → dist/llm}/providers/claude-agent-sdk.js +0 -0
  227. /package/{llm → dist/llm}/providers/claude-agent-sdk.js.map +0 -0
  228. /package/{llm → dist/llm}/providers/claude-code.d.ts +0 -0
  229. /package/{llm → dist/llm}/providers/claude-code.d.ts.map +0 -0
  230. /package/{llm → dist/llm}/providers/claude-code.js +0 -0
  231. /package/{llm → dist/llm}/providers/claude-code.js.map +0 -0
  232. /package/{llm → dist/llm}/tools.d.ts +0 -0
  233. /package/{llm → dist/llm}/tools.d.ts.map +0 -0
  234. /package/{llm → dist/llm}/tools.js +0 -0
  235. /package/{llm → dist/llm}/tools.js.map +0 -0
  236. /package/{llm → dist/llm}/types.js +0 -0
  237. /package/{llm → dist/llm}/types.js.map +0 -0
  238. /package/{local-runtime.d.ts → dist/local-runtime.d.ts} +0 -0
  239. /package/{local-runtime.test.d.ts → dist/local-runtime.test.d.ts} +0 -0
  240. /package/{local-runtime.test.d.ts.map → dist/local-runtime.test.d.ts.map} +0 -0
  241. /package/{local-runtime.test.js → dist/local-runtime.test.js} +0 -0
  242. /package/{local-runtime.test.js.map → dist/local-runtime.test.js.map} +0 -0
  243. /package/{metricflow.d.ts → dist/metricflow.d.ts} +0 -0
  244. /package/{metricflow.d.ts.map → dist/metricflow.d.ts.map} +0 -0
  245. /package/{metricflow.js → dist/metricflow.js} +0 -0
  246. /package/{metricflow.js.map → dist/metricflow.js.map} +0 -0
  247. /package/{metricflow.test.d.ts → dist/metricflow.test.d.ts} +0 -0
  248. /package/{metricflow.test.d.ts.map → dist/metricflow.test.d.ts.map} +0 -0
  249. /package/{metricflow.test.js → dist/metricflow.test.js} +0 -0
  250. /package/{metricflow.test.js.map → dist/metricflow.test.js.map} +0 -0
  251. /package/{open-browser.d.ts → dist/open-browser.d.ts} +0 -0
  252. /package/{open-browser.d.ts.map → dist/open-browser.d.ts.map} +0 -0
  253. /package/{open-browser.js → dist/open-browser.js} +0 -0
  254. /package/{open-browser.js.map → dist/open-browser.js.map} +0 -0
  255. /package/{schedule → dist/schedule}/alerts.d.ts +0 -0
  256. /package/{schedule → dist/schedule}/alerts.d.ts.map +0 -0
  257. /package/{schedule → dist/schedule}/alerts.js +0 -0
  258. /package/{schedule → dist/schedule}/alerts.js.map +0 -0
  259. /package/{schedule → dist/schedule}/discovery.d.ts +0 -0
  260. /package/{schedule → dist/schedule}/discovery.d.ts.map +0 -0
  261. /package/{schedule → dist/schedule}/discovery.js +0 -0
  262. /package/{schedule → dist/schedule}/discovery.js.map +0 -0
  263. /package/{schedule → dist/schedule}/notifiers/email.d.ts +0 -0
  264. /package/{schedule → dist/schedule}/notifiers/email.d.ts.map +0 -0
  265. /package/{schedule → dist/schedule}/notifiers/email.js +0 -0
  266. /package/{schedule → dist/schedule}/notifiers/email.js.map +0 -0
  267. /package/{schedule → dist/schedule}/notifiers/file.d.ts +0 -0
  268. /package/{schedule → dist/schedule}/notifiers/file.d.ts.map +0 -0
  269. /package/{schedule → dist/schedule}/notifiers/file.js +0 -0
  270. /package/{schedule → dist/schedule}/notifiers/file.js.map +0 -0
  271. /package/{schedule → dist/schedule}/notifiers/index.d.ts +0 -0
  272. /package/{schedule → dist/schedule}/notifiers/index.d.ts.map +0 -0
  273. /package/{schedule → dist/schedule}/notifiers/index.js +0 -0
  274. /package/{schedule → dist/schedule}/notifiers/index.js.map +0 -0
  275. /package/{schedule → dist/schedule}/notifiers/slack.d.ts +0 -0
  276. /package/{schedule → dist/schedule}/notifiers/slack.d.ts.map +0 -0
  277. /package/{schedule → dist/schedule}/notifiers/slack.js +0 -0
  278. /package/{schedule → dist/schedule}/notifiers/slack.js.map +0 -0
  279. /package/{schedule → dist/schedule}/runner.d.ts +0 -0
  280. /package/{schedule → dist/schedule}/runs.d.ts +0 -0
  281. /package/{schedule → dist/schedule}/runs.d.ts.map +0 -0
  282. /package/{schedule → dist/schedule}/runs.js +0 -0
  283. /package/{schedule → dist/schedule}/runs.js.map +0 -0
  284. /package/{schedule → dist/schedule}/service.d.ts +0 -0
  285. /package/{schedule → dist/schedule}/service.d.ts.map +0 -0
  286. /package/{schedule → dist/schedule}/service.js +0 -0
  287. /package/{schedule → dist/schedule}/service.js.map +0 -0
  288. /package/{schedule → dist/schedule}/types.d.ts +0 -0
  289. /package/{schedule → dist/schedule}/types.d.ts.map +0 -0
  290. /package/{schedule → dist/schedule}/types.js +0 -0
  291. /package/{schedule → dist/schedule}/types.js.map +0 -0
  292. /package/{semantic-import.d.ts → dist/semantic-import.d.ts} +0 -0
  293. /package/{semantic-import.d.ts.map → dist/semantic-import.d.ts.map} +0 -0
  294. /package/{semantic-import.js → dist/semantic-import.js} +0 -0
  295. /package/{semantic-import.js.map → dist/semantic-import.js.map} +0 -0
  296. /package/{semantic-import.test.d.ts → dist/semantic-import.test.d.ts} +0 -0
  297. /package/{semantic-import.test.d.ts.map → dist/semantic-import.test.d.ts.map} +0 -0
  298. /package/{semantic-import.test.js → dist/semantic-import.test.js} +0 -0
  299. /package/{semantic-import.test.js.map → dist/semantic-import.test.js.map} +0 -0
@@ -7,9 +7,13 @@ import { loadSemanticLayerFromDir, resolveSemanticLayerAsync, Parser, buildLinea
7
7
  import { load as loadYaml } from 'js-yaml';
8
8
  import { listBlockTemplates } from './block-templates.js';
9
9
  import { getRunner as getLLMRunner } from './llm/index.js';
10
+ import { ClaudeProvider, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, defaultMemoryPath, ensureDefaultMemoryFiles, } from '@duckcodeailabs/dql-agent';
10
11
  import { handleAppsApi } from './apps-api.js';
12
+ import { getEffectiveProviderConfig, listProviderSettings, saveProviderSettings, } from './settings/provider-settings.js';
11
13
  import { DQLAccessDeniedError, activePersonaAppId, assertAppAccess, loadRuntimeApp, runtimeVariables, } from './governance-runtime.js';
14
+ import { LocalAppStorage, defaultLocalAppsDbPath } from '@duckcodeailabs/dql-project';
12
15
  import { buildSemanticObjectDetail, buildSemanticTree, computeSyncDiff, loadSemanticImportManifest, performSemanticImport, previewSemanticImport, syncSemanticImport, } from './semantic-import.js';
16
+ import { createBlockStudioImportSession, loadBlockStudioImportSession, readBlockStudioImportCandidate, updateBlockStudioImportCandidate, writeBlockStudioImportCandidate, } from './block-studio-import.js';
13
17
  import { MetricFlowUnavailableError, compileMetricFlowQuery, hasDbtSemanticManifest, } from './metricflow.js';
14
18
  export async function startLocalServer(opts) {
15
19
  const { rootDir, executor, connection: rawConnection, preferredPort, projectRoot = process.cwd() } = opts;
@@ -68,6 +72,55 @@ export async function startLocalServer(opts) {
68
72
  catch { /* non-fatal */ }
69
73
  }
70
74
  }
75
+ const executeLocalSqlForStoredResult = async (sql) => {
76
+ const semantic = prepareSemanticSql(sql, semanticLayer);
77
+ if (semantic.unresolvedRefs.length > 0) {
78
+ throw new Error(`Unknown semantic reference${semantic.unresolvedRefs.length > 1 ? 's' : ''}: ${semantic.unresolvedRefs.join(', ')}`);
79
+ }
80
+ const prepared = prepareLocalExecution(semantic.sql, connection, projectRoot, projectConfig);
81
+ const result = await executor.executeQuery(prepared.sql, [], runtimeVariables({}), prepared.connection);
82
+ return normalizeQueryResult(result, semantic.semanticRefs);
83
+ };
84
+ const executeCertifiedBlockForAgent = async (node) => {
85
+ if (node.kind !== 'block') {
86
+ throw new Error(`Certified ${node.kind} "${node.name}" is a navigation artifact and cannot be executed as a block.`);
87
+ }
88
+ const manifest = buildManifest({ projectRoot });
89
+ const block = manifest.blocks[node.name] ?? manifest.blocks[node.nodeId.replace(/^block:/, '')];
90
+ if (!block) {
91
+ throw new Error(`Matched block "${node.name}" is not present in the project manifest.`);
92
+ }
93
+ const absBlockPath = join(projectRoot, block.filePath);
94
+ const source = readFileSync(absBlockPath, 'utf-8');
95
+ const semanticCompose = semanticLayer
96
+ ? composeSemanticBlockSql(source, semanticLayer, {
97
+ driver: connection.driver,
98
+ projectRoot,
99
+ projectConfig,
100
+ detectedProvider: semanticDetectedProvider,
101
+ })
102
+ : null;
103
+ const plan = buildExecutionPlan({ id: `agent-${block.name}`, type: 'dql', source, title: block.name }, { semanticLayer, driver: connection.driver });
104
+ if (!plan && !semanticCompose?.sql) {
105
+ const semanticError = semanticCompose?.diagnostics.find((diagnostic) => diagnostic.severity === 'error')?.message;
106
+ throw new Error(semanticError ?? `Block "${block.name}" produced no executable SQL.`);
107
+ }
108
+ const prepared = prepareLocalExecution(semanticCompose?.sql ?? plan.sql, connection, projectRoot, projectConfig);
109
+ const app = loadRuntimeApp(projectRoot, activePersonaAppId());
110
+ assertAppAccess({ app, domain: block.domain ?? app?.domain, level: 'execute' });
111
+ const rawResult = await executor.executeQuery(prepared.sql, plan?.sqlParams ?? [], runtimeVariables(plan?.variables ?? {}), prepared.connection);
112
+ const normalized = normalizeQueryResult(rawResult);
113
+ return {
114
+ columns: normalized.columns,
115
+ rows: normalized.rows,
116
+ rowCount: normalized.rowCount,
117
+ executionTime: normalized.executionTime,
118
+ chartConfig: plan?.chartConfig ?? (block.chartType ? { chart: block.chartType } : undefined),
119
+ sql: prepared.sql,
120
+ blockName: block.name,
121
+ blockPath: block.filePath,
122
+ };
123
+ };
71
124
  // SSE clients for /api/watch hot-reload
72
125
  const sseClients = new Set();
73
126
  // Watch notebooks/, workbooks/, semantic-layer/, and data/ dirs for changes
@@ -122,13 +175,56 @@ export async function startLocalServer(opts) {
122
175
  catch { /* dir not watchable */ }
123
176
  }
124
177
  }
178
+ const validateImportCandidate = (candidate) => ({
179
+ ...candidate,
180
+ validation: validateBlockStudioSource(candidate.dqlSource, semanticLayer),
181
+ });
182
+ const runBlockStudioPreviewSource = async (source, targetConnection = connection) => {
183
+ let tableMapping;
184
+ if (semanticLayer) {
185
+ try {
186
+ const tablesResult = await executor.executeQuery(`SELECT table_schema, table_name
187
+ FROM information_schema.tables
188
+ WHERE table_schema NOT IN ('information_schema', 'pg_catalog')`, [], {}, targetConnection);
189
+ tableMapping = buildSemanticTableMapping(semanticLayer, tablesResult.rows);
190
+ }
191
+ catch {
192
+ tableMapping = undefined;
193
+ }
194
+ }
195
+ const semanticCompose = semanticLayer
196
+ ? composeSemanticBlockSql(source, semanticLayer, {
197
+ driver: targetConnection.driver,
198
+ tableMapping,
199
+ projectRoot,
200
+ projectConfig,
201
+ detectedProvider: semanticDetectedProvider,
202
+ })
203
+ : null;
204
+ const validation = validateBlockStudioSource(source, semanticLayer);
205
+ const executableSql = semanticCompose?.sql ?? validation.executableSql;
206
+ if (!executableSql) {
207
+ const message = semanticCompose?.diagnostics.find((item) => item.severity === 'error')?.message
208
+ ?? validation.diagnostics.find((item) => item.severity === 'error')?.message
209
+ ?? 'No executable SQL found in block source.';
210
+ throw new Error(message);
211
+ }
212
+ const plan = buildExecutionPlan({ id: 'block-studio', type: 'dql', source, title: 'Block Studio' }, { semanticLayer, driver: targetConnection.driver });
213
+ const sql = resolveProjectRelativeSqlPaths(semanticCompose?.sql ?? plan?.sql ?? executableSql, projectRoot, projectConfig.dataDir);
214
+ const result = await executor.executeQuery(sql, plan?.sqlParams ?? [], runtimeVariables(plan?.variables ?? {}), targetConnection);
215
+ return {
216
+ sql: plan?.sql ?? executableSql,
217
+ result: normalizeQueryResult(result),
218
+ chartConfig: plan?.chartConfig ?? validation.chartConfig ?? null,
219
+ };
220
+ };
125
221
  const server = createServer(async (req, res) => {
126
222
  const requestUrl = req.url || '/';
127
223
  const url = new URL(requestUrl, 'http://127.0.0.1');
128
224
  const path = url.pathname || '/';
129
225
  // CORS — needed for dql-notebook SPA dev mode
130
226
  res.setHeader('Access-Control-Allow-Origin', '*');
131
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS');
227
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
132
228
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
133
229
  if (req.method === 'OPTIONS') {
134
230
  res.writeHead(204);
@@ -145,6 +241,121 @@ export async function startLocalServer(opts) {
145
241
  res.end(serializeJSON({ groups: collectSettingsEnvStatus() }));
146
242
  return;
147
243
  }
244
+ if (req.method === 'GET' && path === '/api/settings/providers') {
245
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
246
+ res.end(serializeJSON({ providers: listProviderSettings(projectRoot) }));
247
+ return;
248
+ }
249
+ if (req.method === 'POST' && path === '/api/settings/providers') {
250
+ try {
251
+ const body = await readJSON(req);
252
+ if (!isProviderSettingsId(body?.id)) {
253
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
254
+ res.end(serializeJSON({ error: 'Unknown provider id.' }));
255
+ return;
256
+ }
257
+ const providers = saveProviderSettings(projectRoot, {
258
+ id: body.id,
259
+ enabled: typeof body.enabled === 'boolean' ? body.enabled : undefined,
260
+ apiKey: typeof body.apiKey === 'string' ? body.apiKey : undefined,
261
+ baseUrl: typeof body.baseUrl === 'string' ? body.baseUrl : undefined,
262
+ model: typeof body.model === 'string' ? body.model : undefined,
263
+ });
264
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
265
+ res.end(serializeJSON({ ok: true, providers }));
266
+ }
267
+ catch (error) {
268
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
269
+ res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
270
+ }
271
+ return;
272
+ }
273
+ if (req.method === 'POST' && path === '/api/settings/providers/test') {
274
+ try {
275
+ const body = await readJSON(req);
276
+ if (!isProviderSettingsId(body?.id)) {
277
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
278
+ res.end(serializeJSON({ ok: false, error: 'Unknown provider id.' }));
279
+ return;
280
+ }
281
+ const ok = await testProviderConfig(projectRoot, body.id);
282
+ res.writeHead(ok.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
283
+ res.end(serializeJSON(ok));
284
+ }
285
+ catch (error) {
286
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
287
+ res.end(serializeJSON({ ok: false, error: error instanceof Error ? error.message : String(error) }));
288
+ }
289
+ return;
290
+ }
291
+ if (req.method === 'GET' && path === '/api/agent/memory') {
292
+ const memory = new MemoryStore(defaultMemoryPath(projectRoot));
293
+ try {
294
+ const scope = url.searchParams.get('scope') ?? undefined;
295
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
296
+ res.end(serializeJSON({ memories: memory.list(isMemoryScope(scope) ? scope : undefined) }));
297
+ }
298
+ finally {
299
+ memory.close();
300
+ }
301
+ return;
302
+ }
303
+ if (req.method === 'POST' && path === '/api/agent/memory') {
304
+ const body = await readJSON(req).catch(() => null);
305
+ if (!body || !isMemoryScope(body.scope) || typeof body.title !== 'string' || typeof body.content !== 'string') {
306
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
307
+ res.end(serializeJSON({ error: 'scope, title, and content are required.' }));
308
+ return;
309
+ }
310
+ const memory = new MemoryStore(defaultMemoryPath(projectRoot));
311
+ try {
312
+ const saved = memory.upsert({
313
+ id: typeof body.id === 'string' ? body.id : undefined,
314
+ scope: body.scope,
315
+ scopeId: typeof body.scopeId === 'string' ? body.scopeId : undefined,
316
+ title: body.title,
317
+ content: body.content,
318
+ tags: Array.isArray(body.tags) ? body.tags.map(String) : undefined,
319
+ source: typeof body.source === 'string' ? body.source : 'settings-ui',
320
+ confidence: typeof body.confidence === 'number' ? body.confidence : undefined,
321
+ importance: typeof body.importance === 'number' ? body.importance : undefined,
322
+ validFrom: typeof body.validFrom === 'string' ? body.validFrom : undefined,
323
+ validTo: typeof body.validTo === 'string' ? body.validTo : undefined,
324
+ supersedes: typeof body.supersedes === 'string' ? body.supersedes : undefined,
325
+ enabled: typeof body.enabled === 'boolean' ? body.enabled : undefined,
326
+ });
327
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
328
+ res.end(serializeJSON({ ok: true, memory: saved }));
329
+ }
330
+ finally {
331
+ memory.close();
332
+ }
333
+ return;
334
+ }
335
+ if (req.method === 'DELETE' && path === '/api/agent/memory') {
336
+ const id = url.searchParams.get('id');
337
+ if (!id) {
338
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
339
+ res.end(serializeJSON({ error: 'id is required.' }));
340
+ return;
341
+ }
342
+ const memory = new MemoryStore(defaultMemoryPath(projectRoot));
343
+ try {
344
+ memory.delete(id);
345
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
346
+ res.end(serializeJSON({ ok: true }));
347
+ }
348
+ finally {
349
+ memory.close();
350
+ }
351
+ return;
352
+ }
353
+ if (req.method === 'POST' && path === '/api/agent/memory/default-files') {
354
+ const files = ensureDefaultMemoryFiles(projectRoot).map((p) => relative(projectRoot, p));
355
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
356
+ res.end(serializeJSON({ ok: true, files }));
357
+ return;
358
+ }
148
359
  const appDashRun = path.match(/^\/api\/apps\/([^/]+)\/dashboards\/([^/]+)\/run$/);
149
360
  if (req.method === 'POST' && appDashRun) {
150
361
  try {
@@ -162,13 +373,61 @@ export async function startLocalServer(opts) {
162
373
  ? body.variables
163
374
  : {};
164
375
  const tiles = [];
376
+ const localApps = new LocalAppStorage(defaultLocalAppsDbPath(projectRoot));
165
377
  for (const item of loaded.dashboard.layout.items) {
378
+ if (item.text) {
379
+ tiles.push({
380
+ tileId: item.i,
381
+ status: 'ok',
382
+ tileType: 'text',
383
+ title: item.title,
384
+ viz: item.viz,
385
+ text: item.text,
386
+ });
387
+ continue;
388
+ }
389
+ if (item.aiPin) {
390
+ let pin = localApps.getAiPin(item.aiPin.id);
391
+ if (!pin) {
392
+ tiles.push({
393
+ tileId: item.i,
394
+ status: 'unresolved',
395
+ tileType: 'aiPin',
396
+ error: `AI pin "${item.aiPin.id}" could not be found`,
397
+ });
398
+ continue;
399
+ }
400
+ if (pin.refreshCadence === 'daily' && pin.sql && isAiPinRefreshDue(pin.lastRefreshedAt)) {
401
+ try {
402
+ const refreshed = await executeLocalSqlForStoredResult(pin.sql);
403
+ pin = localApps.updateAiPinResult(pin.id, refreshed) ?? pin;
404
+ }
405
+ catch (err) {
406
+ pin = localApps.updateAiPinResult(pin.id, pin.result, err instanceof Error ? err.message : String(err)) ?? pin;
407
+ }
408
+ }
409
+ tiles.push({
410
+ tileId: item.i,
411
+ status: 'ok',
412
+ tileType: 'aiPin',
413
+ title: item.title ?? pin.title,
414
+ viz: item.viz,
415
+ chartConfig: pin.chartConfig ?? { chart: item.viz.type },
416
+ result: pin.result,
417
+ aiPin: pin,
418
+ citation: {
419
+ kind: 'ai_pin',
420
+ name: pin.title,
421
+ },
422
+ });
423
+ continue;
424
+ }
166
425
  const block = resolveDashboardItemBlock(item, manifest);
167
426
  if (!block) {
168
427
  tiles.push({
169
428
  tileId: item.i,
170
429
  status: 'unresolved',
171
- blockRef: isBlockIdRef(item.block) ? item.block.blockId : item.block.ref,
430
+ blockRef: item.block ? (isBlockIdRef(item.block) ? item.block.blockId : item.block.ref) : '(missing source)',
172
431
  error: 'Block reference could not be resolved',
173
432
  });
174
433
  continue;
@@ -237,6 +496,7 @@ export async function startLocalServer(opts) {
237
496
  }
238
497
  }
239
498
  }
499
+ localApps.close();
240
500
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
241
501
  res.end(serializeJSON({
242
502
  appId,
@@ -254,7 +514,14 @@ export async function startLocalServer(opts) {
254
514
  // Apps, dashboards, persona — see apps-api.ts. Returns true if handled.
255
515
  if (path.startsWith('/api/apps') || path === '/api/persona') {
256
516
  try {
257
- const handled = await handleAppsApi({ req, res, url, path, projectRoot });
517
+ const handled = await handleAppsApi({
518
+ req,
519
+ res,
520
+ url,
521
+ path,
522
+ projectRoot,
523
+ executeSql: executeLocalSqlForStoredResult,
524
+ });
258
525
  if (handled)
259
526
  return;
260
527
  }
@@ -972,6 +1239,110 @@ export async function startLocalServer(opts) {
972
1239
  }
973
1240
  return;
974
1241
  }
1242
+ if (req.method === 'POST' && path === '/api/block-studio/import/preview') {
1243
+ try {
1244
+ const body = await readJSON(req);
1245
+ const inputPath = typeof body.path === 'string' ? body.path : '';
1246
+ const session = createBlockStudioImportSession(projectRoot, {
1247
+ inputPath,
1248
+ sourceKind: typeof body.sourceKind === 'string' ? body.sourceKind : 'raw-sql',
1249
+ domain: typeof body.domain === 'string' ? body.domain : undefined,
1250
+ owner: typeof body.owner === 'string' ? body.owner : undefined,
1251
+ tags: Array.isArray(body.tags) ? body.tags.map(String) : undefined,
1252
+ });
1253
+ const candidates = session.candidates.map(validateImportCandidate);
1254
+ const validatedSession = { ...session, candidates };
1255
+ for (const candidate of candidates) {
1256
+ writeBlockStudioImportCandidate(projectRoot, session.id, candidate);
1257
+ }
1258
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1259
+ res.end(serializeJSON(validatedSession));
1260
+ }
1261
+ catch (error) {
1262
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
1263
+ res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
1264
+ }
1265
+ return;
1266
+ }
1267
+ const importPathMatch = path.match(/^\/api\/block-studio\/imports\/([^/]+)(?:\/candidates\/([^/]+)(?:\/(run|save))?)?$/);
1268
+ if (importPathMatch) {
1269
+ const importId = decodeURIComponent(importPathMatch[1]);
1270
+ const candidateId = importPathMatch[2] ? decodeURIComponent(importPathMatch[2]) : null;
1271
+ const action = importPathMatch[3] ?? null;
1272
+ try {
1273
+ if (req.method === 'GET' && !candidateId) {
1274
+ const session = loadBlockStudioImportSession(projectRoot, importId);
1275
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1276
+ res.end(serializeJSON(session));
1277
+ return;
1278
+ }
1279
+ if (req.method === 'PATCH' && candidateId && !action) {
1280
+ const body = await readJSON(req);
1281
+ const reviewStatus = typeof body.reviewStatus === 'string' && ['draft', 'saved', 'rejected'].includes(body.reviewStatus)
1282
+ ? body.reviewStatus
1283
+ : undefined;
1284
+ const candidate = updateBlockStudioImportCandidate(projectRoot, importId, candidateId, {
1285
+ name: typeof body.name === 'string' ? body.name : undefined,
1286
+ domain: typeof body.domain === 'string' ? body.domain : undefined,
1287
+ description: typeof body.description === 'string' ? body.description : undefined,
1288
+ owner: typeof body.owner === 'string' ? body.owner : undefined,
1289
+ tags: Array.isArray(body.tags) ? body.tags.map(String) : undefined,
1290
+ sql: typeof body.sql === 'string' ? body.sql : undefined,
1291
+ reviewStatus,
1292
+ });
1293
+ const validated = validateImportCandidate(candidate);
1294
+ writeBlockStudioImportCandidate(projectRoot, importId, validated);
1295
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1296
+ res.end(serializeJSON(validated));
1297
+ return;
1298
+ }
1299
+ if (req.method === 'POST' && candidateId && action === 'run') {
1300
+ const candidate = readBlockStudioImportCandidate(projectRoot, importId, candidateId);
1301
+ const preview = await runBlockStudioPreviewSource(candidate.dqlSource);
1302
+ const next = { ...candidate, preview, validation: validateBlockStudioSource(candidate.dqlSource, semanticLayer) };
1303
+ writeBlockStudioImportCandidate(projectRoot, importId, next);
1304
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1305
+ res.end(serializeJSON(next));
1306
+ return;
1307
+ }
1308
+ if (req.method === 'POST' && candidateId && action === 'save') {
1309
+ const candidate = readBlockStudioImportCandidate(projectRoot, importId, candidateId);
1310
+ const savedPath = saveBlockStudioArtifacts(projectRoot, {
1311
+ source: candidate.dqlSource,
1312
+ name: candidate.name,
1313
+ domain: candidate.domain,
1314
+ description: candidate.description,
1315
+ owner: candidate.owner,
1316
+ tags: candidate.tags,
1317
+ lineage: candidate.lineage.sourceTables,
1318
+ importMeta: {
1319
+ importId,
1320
+ candidateId,
1321
+ sourceKind: candidate.sourceKind,
1322
+ sourcePath: candidate.sourcePath,
1323
+ },
1324
+ });
1325
+ const next = { ...candidate, reviewStatus: 'saved', savedPath };
1326
+ writeBlockStudioImportCandidate(projectRoot, importId, next);
1327
+ const payload = openBlockStudioDocument(projectRoot, savedPath, semanticLayer);
1328
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1329
+ res.end(serializeJSON({ candidate: next, block: payload }));
1330
+ return;
1331
+ }
1332
+ res.writeHead(405, { 'Content-Type': 'application/json; charset=utf-8' });
1333
+ res.end(serializeJSON({ error: 'Unsupported import operation.' }));
1334
+ }
1335
+ catch (error) {
1336
+ if (error instanceof Error && error.message === 'BLOCK_EXISTS') {
1337
+ res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });
1338
+ res.end(serializeJSON({ error: 'Block already exists' }));
1339
+ return;
1340
+ }
1341
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
1342
+ res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
1343
+ }
1344
+ return;
1345
+ }
975
1346
  if (req.method === 'GET' && path === '/api/block-studio/catalog') {
976
1347
  try {
977
1348
  const cfg = loadProjectConfig(projectRoot);
@@ -1037,46 +1408,9 @@ export async function startLocalServer(opts) {
1037
1408
  const body = await readJSON(req);
1038
1409
  const source = typeof body.source === 'string' ? body.source : '';
1039
1410
  const targetConnection = isConnectionConfig(body.connection) ? body.connection : connection;
1040
- let tableMapping;
1041
- if (semanticLayer) {
1042
- try {
1043
- const tablesResult = await executor.executeQuery(`SELECT table_schema, table_name
1044
- FROM information_schema.tables
1045
- WHERE table_schema NOT IN ('information_schema', 'pg_catalog')`, [], {}, targetConnection);
1046
- tableMapping = buildSemanticTableMapping(semanticLayer, tablesResult.rows);
1047
- }
1048
- catch {
1049
- tableMapping = undefined;
1050
- }
1051
- }
1052
- const semanticCompose = semanticLayer
1053
- ? composeSemanticBlockSql(source, semanticLayer, {
1054
- driver: targetConnection.driver,
1055
- tableMapping,
1056
- projectRoot,
1057
- projectConfig,
1058
- detectedProvider: semanticDetectedProvider,
1059
- })
1060
- : null;
1061
- const validation = validateBlockStudioSource(source, semanticLayer);
1062
- const executableSql = semanticCompose?.sql ?? validation.executableSql;
1063
- if (!executableSql) {
1064
- res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
1065
- const message = semanticCompose?.diagnostics.find((item) => item.severity === 'error')?.message
1066
- ?? validation.diagnostics.find((item) => item.severity === 'error')?.message
1067
- ?? 'No executable SQL found in block source.';
1068
- res.end(serializeJSON({ error: message, diagnostics: validation.diagnostics }));
1069
- return;
1070
- }
1071
- const plan = buildExecutionPlan({ id: 'block-studio', type: 'dql', source, title: 'Block Studio' }, { semanticLayer, driver: targetConnection.driver });
1072
- const sql = resolveProjectRelativeSqlPaths(semanticCompose?.sql ?? plan?.sql ?? executableSql, projectRoot, projectConfig.dataDir);
1073
- const result = await executor.executeQuery(sql, plan?.sqlParams ?? [], runtimeVariables(plan?.variables ?? {}), targetConnection);
1411
+ const preview = await runBlockStudioPreviewSource(source, targetConnection);
1074
1412
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1075
- res.end(serializeJSON({
1076
- sql: plan?.sql ?? executableSql,
1077
- result: normalizeQueryResult(result),
1078
- chartConfig: plan?.chartConfig ?? validation.chartConfig ?? null,
1079
- }));
1413
+ res.end(serializeJSON(preview));
1080
1414
  }
1081
1415
  catch (error) {
1082
1416
  res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
@@ -1109,6 +1443,15 @@ export async function startLocalServer(opts) {
1109
1443
  description: metadata.description,
1110
1444
  owner: metadata.owner,
1111
1445
  tags: Array.isArray(metadata.tags) ? metadata.tags.map(String) : [],
1446
+ lineage: Array.isArray(metadata.lineage) ? metadata.lineage.map(String) : undefined,
1447
+ importMeta: metadata.sourceKind || metadata.sourcePath || metadata.importId || metadata.candidateId
1448
+ ? {
1449
+ importId: metadata.importId,
1450
+ candidateId: metadata.candidateId,
1451
+ sourceKind: metadata.sourceKind,
1452
+ sourcePath: metadata.sourcePath,
1453
+ }
1454
+ : undefined,
1112
1455
  });
1113
1456
  const payload = openBlockStudioDocument(projectRoot, savedPath, semanticLayer);
1114
1457
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
@@ -1833,10 +2176,11 @@ export async function startLocalServer(opts) {
1833
2176
  return;
1834
2177
  }
1835
2178
  const { provider, messages, upstream } = body;
1836
- const runner = isLLMProviderId(provider) ? getLLMRunner(provider) : null;
1837
- if (!runner) {
2179
+ const resolvedProvider = isLLMProviderId(provider) ? provider : resolveDefaultLLMProvider(projectRoot);
2180
+ const runner = resolvedProvider ? getLLMRunner(resolvedProvider) : null;
2181
+ if (!resolvedProvider || !runner) {
1838
2182
  res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
1839
- res.end(serializeJSON({ error: `Unknown provider: ${provider}` }));
2183
+ res.end(serializeJSON({ error: 'No AI provider is configured. Configure OpenAI, Gemini, Ollama, or a custom OpenAI-compatible endpoint in Settings.' }));
1840
2184
  return;
1841
2185
  }
1842
2186
  if (!Array.isArray(messages) || messages.length === 0) {
@@ -1853,7 +2197,7 @@ export async function startLocalServer(opts) {
1853
2197
  req.on('close', () => controller.abort());
1854
2198
  const emit = (turn) => { res.write(`data: ${JSON.stringify(turn)}\n\n`); };
1855
2199
  try {
1856
- await runner.run({ provider: provider, messages, upstream, projectRoot }, emit, controller.signal);
2200
+ await runner.run({ provider: resolvedProvider, messages, upstream, projectRoot, executeCertifiedBlock: executeCertifiedBlockForAgent }, emit, controller.signal);
1857
2201
  }
1858
2202
  catch (err) {
1859
2203
  emit({ kind: 'error', message: err instanceof Error ? err.message : String(err) });
@@ -2205,6 +2549,24 @@ export async function startLocalServer(opts) {
2205
2549
  }
2206
2550
  return;
2207
2551
  }
2552
+ if (req.method === 'GET' && path === '/api/lineage/scope') {
2553
+ try {
2554
+ const graph = buildProjectLineageGraph(projectRoot, semanticLayer);
2555
+ const result = buildScopedLineage(graph, {
2556
+ domain: url.searchParams.get('domain') ?? undefined,
2557
+ appId: url.searchParams.get('appId') ?? undefined,
2558
+ dashboardId: url.searchParams.get('dashboardId') ?? undefined,
2559
+ blockId: url.searchParams.get('blockId') ?? undefined,
2560
+ });
2561
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
2562
+ res.end(serializeJSON(result));
2563
+ }
2564
+ catch (error) {
2565
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
2566
+ res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
2567
+ }
2568
+ return;
2569
+ }
2208
2570
  if (req.method === 'GET' && path === '/api/lineage/query') {
2209
2571
  try {
2210
2572
  const graph = buildProjectLineageGraph(projectRoot, semanticLayer);
@@ -2404,8 +2766,10 @@ export async function startLocalServer(opts) {
2404
2766
  res.end(serializeJSON({ error: 'Missing notebook cell payload.' }));
2405
2767
  return;
2406
2768
  }
2769
+ const resolved = resolveNotebookBlockReferenceCell(cell, projectRoot);
2770
+ const executableCell = resolved.cell;
2407
2771
  const cellConnection = isConnectionConfig(body.connection) ? body.connection : connection;
2408
- const plan = buildExecutionPlan(cell, { semanticLayer, driver: cellConnection.driver });
2772
+ const plan = buildExecutionPlan(executableCell, { semanticLayer, driver: cellConnection.driver });
2409
2773
  if (!plan) {
2410
2774
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
2411
2775
  res.end(serializeJSON({ cellType: cell.type, result: null }));
@@ -2413,12 +2777,14 @@ export async function startLocalServer(opts) {
2413
2777
  }
2414
2778
  const prepared = prepareLocalExecution(plan.sql, isConnectionConfig(body.connection) ? body.connection : connection, projectRoot, projectConfig);
2415
2779
  const app = loadRuntimeApp(projectRoot, typeof body.appId === 'string' ? body.appId : activePersonaAppId());
2416
- assertAppAccess({ app, domain: app?.domain, level: 'execute' });
2780
+ assertAppAccess({ app, domain: resolved.domain ?? app?.domain, level: 'execute' });
2417
2781
  const rawResult = await executor.executeQuery(prepared.sql, plan.sqlParams, runtimeVariables(plan.variables), prepared.connection);
2418
2782
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
2419
2783
  res.end(serializeJSON({
2420
2784
  cellType: cell.type,
2421
2785
  title: plan.title,
2786
+ blockName: resolved.blockName,
2787
+ blockPath: resolved.blockPath,
2422
2788
  chartConfig: plan.chartConfig,
2423
2789
  tests: plan.tests,
2424
2790
  result: normalizeQueryResult(rawResult),
@@ -2549,7 +2915,18 @@ function isLLMProviderId(value) {
2549
2915
  || value === 'claude-code'
2550
2916
  || value === 'openai'
2551
2917
  || value === 'gemini'
2552
- || value === 'ollama';
2918
+ || value === 'ollama'
2919
+ || value === 'custom-openai';
2920
+ }
2921
+ function resolveDefaultLLMProvider(projectRoot) {
2922
+ const settings = listProviderSettings(projectRoot);
2923
+ const preferred = ['openai', 'gemini', 'ollama', 'custom-openai'];
2924
+ for (const id of preferred) {
2925
+ const provider = settings.find((item) => item.id === id);
2926
+ if (provider?.enabled && provider.hasApiKey)
2927
+ return id;
2928
+ }
2929
+ return null;
2553
2930
  }
2554
2931
  function loadAppDashboard(projectRoot, appId, dashboardId) {
2555
2932
  for (const p of findAppDocuments(projectRoot)) {
@@ -2566,6 +2943,8 @@ function loadAppDashboard(projectRoot, appId, dashboardId) {
2566
2943
  return null;
2567
2944
  }
2568
2945
  function resolveDashboardItemBlock(item, manifest) {
2946
+ if (!item.block)
2947
+ return null;
2569
2948
  if (isBlockIdRef(item.block)) {
2570
2949
  return manifest.blocks[item.block.blockId] ?? null;
2571
2950
  }
@@ -2785,6 +3164,33 @@ function normalizeNotebookCell(value) {
2785
3164
  config: candidate.config,
2786
3165
  };
2787
3166
  }
3167
+ function resolveNotebookBlockReferenceCell(cell, projectRoot) {
3168
+ if (cell.type !== 'dql')
3169
+ return { cell };
3170
+ const match = cell.source.trim().match(/^@block\(\s*["']([^"']+)["']\s*\)$/i);
3171
+ if (!match)
3172
+ return { cell };
3173
+ const ref = match[1].trim();
3174
+ const manifest = buildManifest({ projectRoot });
3175
+ const block = manifest.blocks[ref] ?? Object.values(manifest.blocks).find((candidate) => candidate.filePath === ref);
3176
+ if (!block) {
3177
+ throw new Error(`Block reference "${ref}" could not be resolved.`);
3178
+ }
3179
+ const blockPath = join(projectRoot, block.filePath);
3180
+ if (!existsSync(blockPath)) {
3181
+ throw new Error(`Block "${block.name}" file is missing: ${block.filePath}`);
3182
+ }
3183
+ return {
3184
+ cell: {
3185
+ ...cell,
3186
+ title: cell.title ?? block.name,
3187
+ source: readFileSync(blockPath, 'utf-8'),
3188
+ },
3189
+ blockName: block.name,
3190
+ blockPath: block.filePath,
3191
+ domain: block.domain,
3192
+ };
3193
+ }
2788
3194
  function isConnectionConfig(value) {
2789
3195
  return Boolean(value && typeof value === 'object' && 'driver' in value);
2790
3196
  }
@@ -3362,6 +3768,8 @@ function saveBlockStudioArtifacts(projectRoot, options) {
3362
3768
  tags: options.tags,
3363
3769
  provider: 'dql',
3364
3770
  content: options.source,
3771
+ lineage: options.lineage,
3772
+ importMeta: options.importMeta,
3365
3773
  });
3366
3774
  if (previousPath && previousPath !== targetRelativePath) {
3367
3775
  const previousAbsPath = join(projectRoot, previousPath);
@@ -3769,6 +4177,17 @@ function writeBlockCompanionFile(projectRoot, options) {
3769
4177
  if (options.gitPath)
3770
4178
  lines.push(` path: ${yamlScalar(options.gitPath)}`);
3771
4179
  }
4180
+ if (options.importMeta) {
4181
+ lines.push('import:');
4182
+ if (options.importMeta.importId)
4183
+ lines.push(` importId: ${yamlScalar(options.importMeta.importId)}`);
4184
+ if (options.importMeta.candidateId)
4185
+ lines.push(` candidateId: ${yamlScalar(options.importMeta.candidateId)}`);
4186
+ if (options.importMeta.sourceKind)
4187
+ lines.push(` sourceKind: ${yamlScalar(options.importMeta.sourceKind)}`);
4188
+ if (options.importMeta.sourcePath)
4189
+ lines.push(` sourcePath: ${yamlScalar(options.importMeta.sourcePath)}`);
4190
+ }
3772
4191
  lines.push('reviewStatus: draft');
3773
4192
  writeFileSync(companionPath, lines.join('\n') + '\n', 'utf-8');
3774
4193
  return relative(projectRoot, companionPath).replaceAll('\\', '/');
@@ -3994,6 +4413,38 @@ function resolveLineageNode(graph, rawNodeId) {
3994
4413
  const result = queryLineage(graph, { focus: rawNodeId });
3995
4414
  return result.focalNode;
3996
4415
  }
4416
+ function buildScopedLineage(graph, scope) {
4417
+ const focus = scope.blockId
4418
+ ? `block:${scope.blockId}`
4419
+ : scope.dashboardId
4420
+ ? `dashboard:${scope.appId ? `${scope.appId}/${scope.dashboardId}` : scope.dashboardId}`
4421
+ : scope.appId
4422
+ ? `app:${scope.appId}`
4423
+ : undefined;
4424
+ const result = queryLineage(graph, {
4425
+ focus,
4426
+ domain: focus ? undefined : scope.domain,
4427
+ upstreamDepth: 8,
4428
+ downstreamDepth: 4,
4429
+ });
4430
+ const graphJson = result.graph ?? graph.toJSON();
4431
+ const breadcrumbs = [
4432
+ scope.domain ? graph.getNode(`domain:${scope.domain}`) : null,
4433
+ scope.appId ? graph.getNode(`app:${scope.appId}`) : null,
4434
+ scope.dashboardId ? graph.getNode(`dashboard:${scope.appId ? `${scope.appId}/${scope.dashboardId}` : scope.dashboardId}`) : null,
4435
+ scope.blockId ? graph.getNode(`block:${scope.blockId}`) : null,
4436
+ ].filter(Boolean);
4437
+ const paths = focus ? queryCompleteLineagePaths(graph, focus, { maxDepth: 12, maxPaths: 20 }) : null;
4438
+ return {
4439
+ scope,
4440
+ focus,
4441
+ graph: graphJson,
4442
+ focalNode: result.focalNode,
4443
+ breadcrumbs,
4444
+ paths,
4445
+ view: 'Domain > App > Dashboard tab > Tile > Block > Semantic/dbt/source',
4446
+ };
4447
+ }
3997
4448
  function extractProp(block, key) {
3998
4449
  // Check direct AST fields first (parser puts domain, owner, type directly on the node)
3999
4450
  if (block[key] !== undefined && block[key] !== null)
@@ -4354,4 +4805,55 @@ function collectSettingsEnvStatus() {
4354
4805
  },
4355
4806
  ];
4356
4807
  }
4808
+ function isProviderSettingsId(value) {
4809
+ return value === 'anthropic'
4810
+ || value === 'openai'
4811
+ || value === 'gemini'
4812
+ || value === 'ollama'
4813
+ || value === 'custom-openai';
4814
+ }
4815
+ async function testProviderConfig(projectRoot, id) {
4816
+ const config = getEffectiveProviderConfig(projectRoot, id);
4817
+ let provider;
4818
+ switch (id) {
4819
+ case 'anthropic':
4820
+ provider = new ClaudeProvider({ apiKey: config.apiKey, model: config.model });
4821
+ break;
4822
+ case 'gemini':
4823
+ provider = new GeminiProvider({ apiKey: config.apiKey, model: config.model });
4824
+ break;
4825
+ case 'ollama':
4826
+ provider = new OllamaProvider({ baseUrl: config.baseUrl, model: config.model });
4827
+ break;
4828
+ case 'custom-openai':
4829
+ provider = new OpenAIProvider({ apiKey: config.apiKey, baseUrl: config.baseUrl, model: config.model, allowNoApiKey: true });
4830
+ break;
4831
+ case 'openai':
4832
+ default:
4833
+ provider = new OpenAIProvider({ apiKey: config.apiKey, baseUrl: config.baseUrl, model: config.model });
4834
+ break;
4835
+ }
4836
+ const ok = await provider.available();
4837
+ return {
4838
+ ok,
4839
+ message: ok
4840
+ ? `${id} is configured.`
4841
+ : `${id} is not configured or not reachable. Check API key, base URL, and local service state.`,
4842
+ };
4843
+ }
4844
+ function isAiPinRefreshDue(lastRefreshedAt) {
4845
+ if (!lastRefreshedAt)
4846
+ return true;
4847
+ const last = Date.parse(lastRefreshedAt);
4848
+ if (!Number.isFinite(last))
4849
+ return true;
4850
+ return Date.now() - last >= 24 * 60 * 60 * 1000;
4851
+ }
4852
+ function isMemoryScope(value) {
4853
+ return value === 'thread'
4854
+ || value === 'notebook'
4855
+ || value === 'project'
4856
+ || value === 'user'
4857
+ || value === 'artifact';
4858
+ }
4357
4859
  //# sourceMappingURL=local-runtime.js.map