@xdelivered/emberflow 0.2.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 (414) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/bin/commands.test.ts +185 -0
  4. package/bin/commands.ts +306 -0
  5. package/bin/emberflow.mjs +38 -0
  6. package/bin/init.test.ts +165 -0
  7. package/bin/init.ts +297 -0
  8. package/bin/runtime.mjs +105 -0
  9. package/bin/runtime.test.ts +167 -0
  10. package/bin/skillTargets.test.ts +108 -0
  11. package/bin/skillTargets.ts +36 -0
  12. package/bin/skillTemplates.test.ts +37 -0
  13. package/bin/tsconfig.json +23 -0
  14. package/dist/bin/commands.d.ts +24 -0
  15. package/dist/bin/commands.js +252 -0
  16. package/dist/bin/init.d.ts +17 -0
  17. package/dist/bin/init.js +241 -0
  18. package/dist/bin/runtime.d.mts +39 -0
  19. package/dist/bin/runtime.mjs +96 -0
  20. package/dist/bin/skillTargets.d.ts +13 -0
  21. package/dist/bin/skillTargets.js +22 -0
  22. package/dist/server/agents/claudeAdapter.d.ts +8 -0
  23. package/dist/server/agents/claudeAdapter.js +190 -0
  24. package/dist/server/agents/claudeParse.d.ts +2 -0
  25. package/dist/server/agents/claudeParse.js +162 -0
  26. package/dist/server/agents/codexAdapter.d.ts +13 -0
  27. package/dist/server/agents/codexAdapter.js +174 -0
  28. package/dist/server/agents/codexParse.d.ts +2 -0
  29. package/dist/server/agents/codexParse.js +74 -0
  30. package/dist/server/agents/detect.d.ts +23 -0
  31. package/dist/server/agents/detect.js +35 -0
  32. package/dist/server/agents/gitScope.d.ts +24 -0
  33. package/dist/server/agents/gitScope.js +95 -0
  34. package/dist/server/agents/modelRejectionHint.d.ts +8 -0
  35. package/dist/server/agents/modelRejectionHint.js +12 -0
  36. package/dist/server/agents/prompt.d.ts +79 -0
  37. package/dist/server/agents/prompt.js +364 -0
  38. package/dist/server/agents/runManager.d.ts +90 -0
  39. package/dist/server/agents/runManager.js +259 -0
  40. package/dist/server/agents/types.d.ts +15 -0
  41. package/dist/server/agents/types.js +1 -0
  42. package/dist/server/apiStore.d.ts +60 -0
  43. package/dist/server/apiStore.js +249 -0
  44. package/dist/server/auth/enforce.d.ts +1 -0
  45. package/dist/server/auth/enforce.js +6 -0
  46. package/dist/server/auth/types.d.ts +1 -0
  47. package/dist/server/auth/types.js +1 -0
  48. package/dist/server/auth/verifiers.d.ts +1 -0
  49. package/dist/server/auth/verifiers.js +4 -0
  50. package/dist/server/authAttach.d.ts +19 -0
  51. package/dist/server/authAttach.js +79 -0
  52. package/dist/server/cli.d.ts +7 -0
  53. package/dist/server/cli.js +804 -0
  54. package/dist/server/client.d.ts +130 -0
  55. package/dist/server/client.js +247 -0
  56. package/dist/server/environments.d.ts +103 -0
  57. package/dist/server/environments.js +486 -0
  58. package/dist/server/flowStore.d.ts +37 -0
  59. package/dist/server/flowStore.js +117 -0
  60. package/dist/server/httpOperations.d.ts +32 -0
  61. package/dist/server/httpOperations.js +62 -0
  62. package/dist/server/index.d.ts +1 -0
  63. package/dist/server/index.js +1022 -0
  64. package/dist/server/infrastructure.d.ts +51 -0
  65. package/dist/server/infrastructure.js +116 -0
  66. package/dist/server/login.d.ts +21 -0
  67. package/dist/server/login.js +44 -0
  68. package/dist/server/mcp.d.ts +1 -0
  69. package/dist/server/mcp.js +182 -0
  70. package/dist/server/migrateFlows.d.ts +8 -0
  71. package/dist/server/migrateFlows.js +25 -0
  72. package/dist/server/mockHandler.d.ts +15 -0
  73. package/dist/server/mockHandler.js +35 -0
  74. package/dist/server/nodesPayload.d.ts +14 -0
  75. package/dist/server/nodesPayload.js +14 -0
  76. package/dist/server/normalizeFlow.d.ts +21 -0
  77. package/dist/server/normalizeFlow.js +48 -0
  78. package/dist/server/openBrowser.d.ts +8 -0
  79. package/dist/server/openBrowser.js +16 -0
  80. package/dist/server/operationResult.d.ts +12 -0
  81. package/dist/server/operationResult.js +26 -0
  82. package/dist/server/pathGuard.d.ts +1 -0
  83. package/dist/server/pathGuard.js +16 -0
  84. package/dist/server/pathSafety.d.ts +20 -0
  85. package/dist/server/pathSafety.js +35 -0
  86. package/dist/server/projectConfig.d.ts +47 -0
  87. package/dist/server/projectConfig.js +61 -0
  88. package/dist/server/projectMode.d.ts +26 -0
  89. package/dist/server/projectMode.js +44 -0
  90. package/dist/server/redact.d.ts +6 -0
  91. package/dist/server/redact.js +37 -0
  92. package/dist/server/runRegistry.d.ts +84 -0
  93. package/dist/server/runRegistry.js +200 -0
  94. package/dist/server/runtime/RuntimeAdapter.d.ts +22 -0
  95. package/dist/server/runtime/RuntimeAdapter.js +1 -0
  96. package/dist/server/runtime/expressAdapter.d.ts +19 -0
  97. package/dist/server/runtime/expressAdapter.js +56 -0
  98. package/dist/server/scenarioTest.d.ts +14 -0
  99. package/dist/server/scenarioTest.js +66 -0
  100. package/dist/server/subflowRunner.d.ts +32 -0
  101. package/dist/server/subflowRunner.js +72 -0
  102. package/dist/server/testRunner.d.ts +70 -0
  103. package/dist/server/testRunner.js +151 -0
  104. package/dist/src/config.d.ts +3 -0
  105. package/dist/src/config.js +2 -0
  106. package/dist/src/engine/authVerify.d.ts +33 -0
  107. package/dist/src/engine/authVerify.js +63 -0
  108. package/dist/src/engine/diagnostics.d.ts +62 -0
  109. package/dist/src/engine/diagnostics.js +148 -0
  110. package/dist/src/engine/executor.d.ts +151 -0
  111. package/dist/src/engine/executor.js +684 -0
  112. package/dist/src/engine/httpError.d.ts +7 -0
  113. package/dist/src/engine/httpError.js +14 -0
  114. package/dist/src/engine/index.d.ts +11 -0
  115. package/dist/src/engine/index.js +11 -0
  116. package/dist/src/engine/output.d.ts +11 -0
  117. package/dist/src/engine/output.js +20 -0
  118. package/dist/src/engine/publish.d.ts +30 -0
  119. package/dist/src/engine/publish.js +95 -0
  120. package/dist/src/engine/registry.d.ts +34 -0
  121. package/dist/src/engine/registry.js +67 -0
  122. package/dist/src/engine/scenarios.d.ts +12 -0
  123. package/dist/src/engine/scenarios.js +19 -0
  124. package/dist/src/engine/schemaCheck.d.ts +4 -0
  125. package/dist/src/engine/schemaCheck.js +34 -0
  126. package/dist/src/engine/trace.d.ts +12 -0
  127. package/dist/src/engine/trace.js +12 -0
  128. package/dist/src/engine/types.d.ts +308 -0
  129. package/dist/src/engine/types.js +1 -0
  130. package/dist/src/engine/validation.d.ts +26 -0
  131. package/dist/src/engine/validation.js +318 -0
  132. package/dist/src/flows/anomaly-flows.d.ts +23 -0
  133. package/dist/src/flows/anomaly-flows.js +151 -0
  134. package/dist/src/flows/city-sweep-flow.d.ts +10 -0
  135. package/dist/src/flows/city-sweep-flow.js +127 -0
  136. package/dist/src/flows/login-flow.d.ts +13 -0
  137. package/dist/src/flows/login-flow.js +145 -0
  138. package/dist/src/flows/pradar-flows.d.ts +5 -0
  139. package/dist/src/flows/pradar-flows.js +375 -0
  140. package/dist/src/flows/weather-flow.d.ts +12 -0
  141. package/dist/src/flows/weather-flow.js +150 -0
  142. package/dist/src/lib/pathParams.d.ts +3 -0
  143. package/dist/src/lib/pathParams.js +6 -0
  144. package/dist/src/nodes/anomaly.d.ts +2 -0
  145. package/dist/src/nodes/anomaly.js +556 -0
  146. package/dist/src/nodes/flow-control.d.ts +7 -0
  147. package/dist/src/nodes/flow-control.js +270 -0
  148. package/dist/src/nodes/index.d.ts +3 -0
  149. package/dist/src/nodes/index.js +20 -0
  150. package/dist/src/nodes/login.d.ts +7 -0
  151. package/dist/src/nodes/login.js +158 -0
  152. package/dist/src/nodes/loops.d.ts +11 -0
  153. package/dist/src/nodes/loops.js +60 -0
  154. package/dist/src/nodes/pradar/index.d.ts +7 -0
  155. package/dist/src/nodes/pradar/index.js +9 -0
  156. package/dist/src/nodes/pradar/logic.d.ts +189 -0
  157. package/dist/src/nodes/pradar/logic.js +370 -0
  158. package/dist/src/nodes/pradar/nodes.d.ts +42 -0
  159. package/dist/src/nodes/pradar/nodes.js +801 -0
  160. package/dist/src/nodes/requireAuth.d.ts +20 -0
  161. package/dist/src/nodes/requireAuth.js +48 -0
  162. package/dist/src/nodes/response.d.ts +8 -0
  163. package/dist/src/nodes/response.js +33 -0
  164. package/dist/src/nodes/weather.d.ts +33 -0
  165. package/dist/src/nodes/weather.js +260 -0
  166. package/package.json +92 -0
  167. package/server/agentRoute.test.ts +157 -0
  168. package/server/agents/__fixtures__/fake-claude-writer.mjs +23 -0
  169. package/server/agents/__fixtures__/fake-claude.mjs +55 -0
  170. package/server/agents/__fixtures__/fake-codex-writer.mjs +27 -0
  171. package/server/agents/__fixtures__/fake-codex.mjs +52 -0
  172. package/server/agents/__fixtures__/fake-version-empty.mjs +3 -0
  173. package/server/agents/__fixtures__/fake-version-fail.mjs +4 -0
  174. package/server/agents/__fixtures__/fake-version-garbage.mjs +4 -0
  175. package/server/agents/__fixtures__/fake-version-good.mjs +4 -0
  176. package/server/agents/__fixtures__/fake-version-hang.mjs +3 -0
  177. package/server/agents/claudeAdapter.test.ts +127 -0
  178. package/server/agents/claudeAdapter.ts +212 -0
  179. package/server/agents/claudeParse.ts +193 -0
  180. package/server/agents/codexAdapter.test.ts +125 -0
  181. package/server/agents/codexAdapter.ts +198 -0
  182. package/server/agents/codexParse.test.ts +58 -0
  183. package/server/agents/codexParse.ts +126 -0
  184. package/server/agents/detect.test.ts +65 -0
  185. package/server/agents/detect.ts +43 -0
  186. package/server/agents/gitScope.test.ts +129 -0
  187. package/server/agents/gitScope.ts +110 -0
  188. package/server/agents/modelRejectionHint.test.ts +30 -0
  189. package/server/agents/modelRejectionHint.ts +14 -0
  190. package/server/agents/prompt.test.ts +613 -0
  191. package/server/agents/prompt.ts +527 -0
  192. package/server/agents/runManager.test.ts +251 -0
  193. package/server/agents/runManager.ts +286 -0
  194. package/server/agents/types.ts +17 -0
  195. package/server/apiMount.test.ts +591 -0
  196. package/server/apiStore.test.ts +324 -0
  197. package/server/apiStore.ts +241 -0
  198. package/server/auth/enforce.test.ts +61 -0
  199. package/server/auth/enforce.ts +6 -0
  200. package/server/auth/types.ts +5 -0
  201. package/server/auth/verifiers.test.ts +37 -0
  202. package/server/auth/verifiers.ts +9 -0
  203. package/server/authAttach.test.ts +175 -0
  204. package/server/authAttach.ts +99 -0
  205. package/server/cli.ts +847 -0
  206. package/server/cliAuth.test.ts +229 -0
  207. package/server/cliCreate.test.ts +131 -0
  208. package/server/cliDoctor.test.ts +326 -0
  209. package/server/cliTest.test.ts +735 -0
  210. package/server/client.ts +314 -0
  211. package/server/demoProject.test.ts +29 -0
  212. package/server/diagnosticsRoute.test.ts +169 -0
  213. package/server/environments.test.ts +620 -0
  214. package/server/environments.ts +603 -0
  215. package/server/environmentsRoute.test.ts +385 -0
  216. package/server/errorWorkflow.test.ts +205 -0
  217. package/server/flowStore.test.ts +84 -0
  218. package/server/flowStore.ts +125 -0
  219. package/server/httpOperations.test.ts +377 -0
  220. package/server/httpOperations.ts +96 -0
  221. package/server/index.ts +1111 -0
  222. package/server/infrastructure.test.ts +154 -0
  223. package/server/infrastructure.ts +161 -0
  224. package/server/infrastructureRoute.test.ts +105 -0
  225. package/server/login.test.ts +153 -0
  226. package/server/login.ts +67 -0
  227. package/server/mcp.ts +278 -0
  228. package/server/migrateFlows.test.ts +55 -0
  229. package/server/migrateFlows.ts +23 -0
  230. package/server/mockHandler.test.ts +138 -0
  231. package/server/mockHandler.ts +52 -0
  232. package/server/mockRun.test.ts +367 -0
  233. package/server/mockServe.test.ts +467 -0
  234. package/server/nodesPayload.test.ts +29 -0
  235. package/server/nodesPayload.ts +26 -0
  236. package/server/normalizeFlow.test.ts +80 -0
  237. package/server/normalizeFlow.ts +59 -0
  238. package/server/openBrowser.test.ts +20 -0
  239. package/server/openBrowser.ts +22 -0
  240. package/server/operationResult.test.ts +67 -0
  241. package/server/operationResult.ts +27 -0
  242. package/server/pathGuard.test.ts +19 -0
  243. package/server/pathGuard.ts +12 -0
  244. package/server/pathSafety.test.ts +38 -0
  245. package/server/pathSafety.ts +33 -0
  246. package/server/projectConfig.test.ts +67 -0
  247. package/server/projectConfig.ts +101 -0
  248. package/server/projectMode.test.ts +106 -0
  249. package/server/projectMode.ts +59 -0
  250. package/server/redact.test.ts +88 -0
  251. package/server/redact.ts +35 -0
  252. package/server/redactEgress.test.ts +281 -0
  253. package/server/runRegistry.ts +270 -0
  254. package/server/runsAuth.test.ts +119 -0
  255. package/server/runtime/RuntimeAdapter.ts +24 -0
  256. package/server/runtime/expressAdapter.test.ts +38 -0
  257. package/server/runtime/expressAdapter.ts +70 -0
  258. package/server/scenarioTest.test.ts +148 -0
  259. package/server/scenarioTest.ts +88 -0
  260. package/server/setupStatus.test.ts +127 -0
  261. package/server/subflowRunner.ts +104 -0
  262. package/server/testRunner.ts +216 -0
  263. package/server/tsconfig.json +20 -0
  264. package/server/workflowTestRoute.test.ts +297 -0
  265. package/src/App.tsx +233 -0
  266. package/src/components/AgentConsole.tsx +461 -0
  267. package/src/components/BranchRulesEditor.tsx +156 -0
  268. package/src/components/CreateModal.tsx +275 -0
  269. package/src/components/Dock.tsx +487 -0
  270. package/src/components/EnvironmentDialog.tsx +569 -0
  271. package/src/components/EnvironmentPicker.tsx +357 -0
  272. package/src/components/EnvironmentsDialog.tsx +144 -0
  273. package/src/components/ExecutionPager.tsx +67 -0
  274. package/src/components/InfraPanel.test.tsx +103 -0
  275. package/src/components/InfraPanel.tsx +171 -0
  276. package/src/components/Inspector.tsx +1182 -0
  277. package/src/components/Json.tsx +50 -0
  278. package/src/components/JsonModal.tsx +52 -0
  279. package/src/components/LogMessage.tsx +61 -0
  280. package/src/components/NodeRunModal.tsx +213 -0
  281. package/src/components/RunLogPanel.tsx +397 -0
  282. package/src/components/RunbookView.tsx +1085 -0
  283. package/src/components/Scenarios.tsx +476 -0
  284. package/src/components/SettingsDialog.tsx +229 -0
  285. package/src/components/Sidebar.tsx +436 -0
  286. package/src/components/StatusBar.tsx +302 -0
  287. package/src/components/Toolbar.tsx +663 -0
  288. package/src/components/WelcomeDialog.test.tsx +107 -0
  289. package/src/components/WelcomeDialog.tsx +412 -0
  290. package/src/components/agentMarkdown.test.tsx +91 -0
  291. package/src/components/agentMarkdown.tsx +158 -0
  292. package/src/components/ui/badge.tsx +31 -0
  293. package/src/components/ui/button.tsx +46 -0
  294. package/src/components/ui/combobox.tsx +114 -0
  295. package/src/components/ui/command.tsx +82 -0
  296. package/src/components/ui/dialog.tsx +73 -0
  297. package/src/components/ui/input.tsx +20 -0
  298. package/src/components/ui/popover.tsx +32 -0
  299. package/src/components/ui/resizable.tsx +44 -0
  300. package/src/components/ui/tabs.tsx +36 -0
  301. package/src/config.ts +4 -0
  302. package/src/engine/authVerify.ts +81 -0
  303. package/src/engine/diagnostics.test.ts +437 -0
  304. package/src/engine/diagnostics.ts +211 -0
  305. package/src/engine/executor.test.ts +1339 -0
  306. package/src/engine/executor.ts +840 -0
  307. package/src/engine/httpError.test.ts +12 -0
  308. package/src/engine/httpError.ts +16 -0
  309. package/src/engine/index.ts +11 -0
  310. package/src/engine/output.ts +21 -0
  311. package/src/engine/publish.test.ts +176 -0
  312. package/src/engine/publish.ts +127 -0
  313. package/src/engine/registry.test.ts +64 -0
  314. package/src/engine/registry.ts +80 -0
  315. package/src/engine/scenarios.test.ts +55 -0
  316. package/src/engine/scenarios.ts +29 -0
  317. package/src/engine/schemaCheck.test.ts +21 -0
  318. package/src/engine/schemaCheck.ts +35 -0
  319. package/src/engine/trace.test.ts +19 -0
  320. package/src/engine/trace.ts +23 -0
  321. package/src/engine/types.test.ts +22 -0
  322. package/src/engine/types.ts +316 -0
  323. package/src/engine/validation.test.ts +314 -0
  324. package/src/engine/validation.ts +354 -0
  325. package/src/flows/anomaly-flows.test.ts +81 -0
  326. package/src/flows/anomaly-flows.ts +202 -0
  327. package/src/flows/city-sweep-flow.ts +131 -0
  328. package/src/flows/flows.test.ts +30 -0
  329. package/src/flows/login-flow.ts +150 -0
  330. package/src/flows/pradar-flows.test.ts +204 -0
  331. package/src/flows/pradar-flows.ts +419 -0
  332. package/src/flows/weather-flow.ts +154 -0
  333. package/src/globals.css +147 -0
  334. package/src/lib/pathParams.test.ts +24 -0
  335. package/src/lib/pathParams.ts +6 -0
  336. package/src/lib/registerLens.test.ts +79 -0
  337. package/src/lib/registerLens.ts +59 -0
  338. package/src/lib/runbookModel.test.ts +61 -0
  339. package/src/lib/runbookModel.ts +290 -0
  340. package/src/lib/runbookProjection.test.ts +417 -0
  341. package/src/lib/runbookProjection.ts +254 -0
  342. package/src/lib/useTailAnchor.ts +53 -0
  343. package/src/lib/utils.ts +7 -0
  344. package/src/main.tsx +13 -0
  345. package/src/nodes/anomaly.test.ts +98 -0
  346. package/src/nodes/anomaly.ts +637 -0
  347. package/src/nodes/effects-scan.test.ts +44 -0
  348. package/src/nodes/flow-control.test.ts +280 -0
  349. package/src/nodes/flow-control.ts +305 -0
  350. package/src/nodes/index.ts +22 -0
  351. package/src/nodes/login.test.ts +81 -0
  352. package/src/nodes/login.ts +186 -0
  353. package/src/nodes/loops.ts +69 -0
  354. package/src/nodes/pradar/index.ts +11 -0
  355. package/src/nodes/pradar/logic.test.ts +288 -0
  356. package/src/nodes/pradar/logic.ts +582 -0
  357. package/src/nodes/pradar/nodes.test.ts +146 -0
  358. package/src/nodes/pradar/nodes.ts +955 -0
  359. package/src/nodes/requireAuth.test.ts +75 -0
  360. package/src/nodes/requireAuth.ts +55 -0
  361. package/src/nodes/response.test.ts +33 -0
  362. package/src/nodes/response.ts +39 -0
  363. package/src/nodes/traceDetail.test.ts +39 -0
  364. package/src/nodes/weather.test.ts +65 -0
  365. package/src/nodes/weather.ts +306 -0
  366. package/src/store/agentClient.test.ts +103 -0
  367. package/src/store/agentClient.ts +131 -0
  368. package/src/store/apiTree.test.ts +17 -0
  369. package/src/store/apiTree.ts +73 -0
  370. package/src/store/builderStore.test.ts +1234 -0
  371. package/src/store/builderStore.ts +1879 -0
  372. package/src/store/infraClient.ts +63 -0
  373. package/src/store/nodeMeta.test.ts +24 -0
  374. package/src/store/nodeMeta.ts +21 -0
  375. package/src/store/persistence.test.ts +15 -0
  376. package/src/store/persistence.ts +54 -0
  377. package/src/store/serverRunner.ts +381 -0
  378. package/src/store/setupClient.ts +57 -0
  379. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  380. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  381. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  382. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  383. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  384. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  385. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  386. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  387. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  388. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  389. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  390. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  391. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  392. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  393. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  394. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  395. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  396. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  397. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  398. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  399. package/studio-dist/assets/index-DNJwW-hM.css +1 -0
  400. package/studio-dist/assets/index-O26dKRjW.js +65 -0
  401. package/studio-dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  402. package/studio-dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  403. package/studio-dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  404. package/studio-dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  405. package/studio-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  406. package/studio-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  407. package/studio-dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  408. package/studio-dist/favicon.svg +1 -0
  409. package/studio-dist/icons.svg +24 -0
  410. package/studio-dist/index.html +14 -0
  411. package/templates/skills/emberflow-basics/SKILL.md +316 -0
  412. package/templates/skills/emberflow-model-process/SKILL.md +209 -0
  413. package/templates/skills/emberflow-new-workflow/SKILL.md +211 -0
  414. package/templates/skills/emberflow-review-workflow/SKILL.md +148 -0
@@ -0,0 +1,131 @@
1
+ import type { WorkflowDefinition, WorkflowEdge, WorkflowNode } from '../engine';
2
+
3
+ /**
4
+ * ForEach/Collect loop over a list of cities:
5
+ * input(cities) → fe(ForEach) → geocode → forecast → classify → col(Collect) → result
6
+ *
7
+ * Each iteration resolves one city, fetches its forecast, and grades it
8
+ * severe/mild; Collect gathers every iteration's classification into a
9
+ * single array that the Result node surfaces as the sweep report.
10
+ */
11
+ export function createCitySweepFlow(): WorkflowDefinition {
12
+ const nodes: WorkflowNode[] = [
13
+ {
14
+ id: 'input',
15
+ type: 'Input',
16
+ label: 'Sweep Request',
17
+ position: { x: -180, y: 240 },
18
+ config: {
19
+ fields: [{ name: 'cities', type: 'array', required: true }],
20
+ defaults: { cities: ['Belfast', 'Valencia', 'Reykjavik'] },
21
+ },
22
+ },
23
+ {
24
+ id: 'fe',
25
+ type: 'ForEach',
26
+ label: 'Each City',
27
+ position: { x: 60, y: 240 },
28
+ config: {},
29
+ inputMap: {
30
+ items: { sourceNodeId: 'input', sourceField: 'cities' },
31
+ },
32
+ },
33
+ {
34
+ id: 'geocode',
35
+ type: 'GeocodeCity',
36
+ label: 'Geocode City',
37
+ position: { x: 300, y: 240 },
38
+ config: {},
39
+ inputMap: {
40
+ city: { sourceNodeId: 'fe', sourceField: 'item' },
41
+ },
42
+ },
43
+ {
44
+ id: 'forecast',
45
+ type: 'FetchForecast',
46
+ label: 'Fetch Forecast',
47
+ position: { x: 540, y: 240 },
48
+ config: {},
49
+ inputMap: {
50
+ latitude: { sourceNodeId: 'geocode', sourceField: 'latitude' },
51
+ longitude: { sourceNodeId: 'geocode', sourceField: 'longitude' },
52
+ },
53
+ },
54
+ {
55
+ id: 'classify',
56
+ type: 'ClassifyWeather',
57
+ label: 'Classify Weather',
58
+ position: { x: 780, y: 240 },
59
+ config: {},
60
+ inputMap: {
61
+ windKmh: { sourceNodeId: 'forecast', sourceField: 'windKmh' },
62
+ rainChancePct: { sourceNodeId: 'forecast', sourceField: 'rainChancePct' },
63
+ place: { sourceNodeId: 'geocode', sourceField: 'place' },
64
+ },
65
+ },
66
+ {
67
+ id: 'col',
68
+ type: 'Collect',
69
+ label: 'Verdicts',
70
+ position: { x: 1020, y: 240 },
71
+ config: {},
72
+ inputMap: {
73
+ value: { sourceNodeId: 'classify', sourceField: '$' },
74
+ },
75
+ },
76
+ {
77
+ id: 'result',
78
+ type: 'Result',
79
+ label: 'Sweep Report',
80
+ position: { x: 1260, y: 240 },
81
+ config: {},
82
+ inputMap: {
83
+ data: { sourceNodeId: 'col', sourceField: '$' },
84
+ },
85
+ },
86
+ ];
87
+
88
+ const edges: WorkflowEdge[] = [
89
+ { id: 'e0', source: 'input', target: 'fe', targetHandle: 'items' },
90
+ { id: 'e1', source: 'fe', target: 'geocode', targetHandle: 'city' },
91
+ { id: 'e2', source: 'geocode', target: 'forecast', targetHandle: 'latitude' },
92
+ { id: 'e3', source: 'geocode', target: 'forecast', targetHandle: 'longitude' },
93
+ { id: 'e4', source: 'forecast', target: 'classify', targetHandle: 'windKmh' },
94
+ { id: 'e5', source: 'forecast', target: 'classify', targetHandle: 'rainChancePct' },
95
+ { id: 'e6', source: 'classify', target: 'col', targetHandle: 'value' },
96
+ { id: 'e7', source: 'col', target: 'result', targetHandle: 'data' },
97
+ ];
98
+
99
+ return {
100
+ id: 'city-sweep',
101
+ name: 'City Sweep',
102
+ version: 1,
103
+ nodes,
104
+ edges,
105
+ // Live-weather flow: results depend on real conditions, so scenarios
106
+ // mainly exercise loop shape (batch count, empty list) rather than a
107
+ // specific branch outcome.
108
+ scenarios: [
109
+ {
110
+ id: 'scn-two',
111
+ name: 'two cities',
112
+ description: 'Two iterations through the loop body',
113
+ input: { cities: ['Belfast', 'Valencia'] },
114
+ },
115
+ {
116
+ id: 'scn-five',
117
+ name: 'five cities',
118
+ description: 'Five iterations — GeocodeCity takes one city at a time, so batchSize stays 1',
119
+ input: { cities: ['Belfast', 'Valencia', 'Reykjavik', 'Tokyo', 'Nairobi'] },
120
+ },
121
+ {
122
+ id: 'scn-empty',
123
+ name: 'empty list',
124
+ description: 'No cities — ForEach/Collect run zero iterations',
125
+ input: { cities: [] },
126
+ },
127
+ ],
128
+ createdAt: '2026-07-02T00:00:00Z',
129
+ updatedAt: '2026-07-02T00:00:00Z',
130
+ };
131
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createDefaultRegistry } from '../nodes';
3
+ import { validateFlow } from '../engine';
4
+ import { createWeatherFlow } from './weather-flow';
5
+ import { createLoginFlow } from './login-flow';
6
+ import { createAnomalyFlows } from './anomaly-flows';
7
+ import { createCitySweepFlow } from './city-sweep-flow';
8
+
9
+ /**
10
+ * Every example/sample flow shipped with the builder must validate cleanly
11
+ * (no error-severity issues) against the default registry — this is the
12
+ * catch-all that would have caught e.g. a loop region leaking an edge, or a
13
+ * branch node's edges/config drifting out of sync with each other.
14
+ */
15
+ describe('example flows', () => {
16
+ const registry = createDefaultRegistry(0);
17
+ const flows = [
18
+ createWeatherFlow(),
19
+ createLoginFlow(),
20
+ ...createAnomalyFlows(),
21
+ createCitySweepFlow(),
22
+ ];
23
+
24
+ for (const flow of flows) {
25
+ it(`${flow.name} has no validation errors`, () => {
26
+ const issues = validateFlow(flow, registry).filter((i) => i.severity === 'error');
27
+ expect(issues, JSON.stringify(issues)).toEqual([]);
28
+ });
29
+ }
30
+ });
@@ -0,0 +1,150 @@
1
+ import type { FieldMapping, WorkflowDefinition, WorkflowNode, WorkflowEdge } from '../engine';
2
+
3
+ const from = (sourceNodeId: string, sourceField: string): FieldMapping => ({ sourceNodeId, sourceField });
4
+
5
+ /**
6
+ * Branching login flow:
7
+ * input → validate → fetch → route(isNew?)
8
+ * ├─ new → welcome ─┐
9
+ * └─ existing → checkPlan ┴→ issueToken → result
10
+ *
11
+ * Route reads `isNew` off the fetched user: `true` takes the welcome branch,
12
+ * anything else falls back to `existing` and runs the plan check. Both branches
13
+ * converge on issueToken; on the new-user path checkPlan is skipped, so its
14
+ * `plan` input resolves undefined and IssueToken falls back to 'free'.
15
+ */
16
+ export function createLoginFlow(): WorkflowDefinition {
17
+ const nodes: WorkflowNode[] = [
18
+ {
19
+ id: 'input',
20
+ type: 'Input',
21
+ label: 'Login Request',
22
+ position: { x: 60, y: 220 },
23
+ config: {
24
+ fields: [
25
+ { name: 'username', type: 'string', required: true },
26
+ { name: 'password', type: 'string', required: true },
27
+ ],
28
+ defaults: { username: 'ada', password: 'lovelace' },
29
+ },
30
+ },
31
+ {
32
+ id: 'validate',
33
+ type: 'ValidateCredentials',
34
+ label: 'Validate Credentials',
35
+ position: { x: 300, y: 220 },
36
+ config: {},
37
+ inputMap: {
38
+ username: from('input', 'username'),
39
+ password: from('input', 'password'),
40
+ },
41
+ },
42
+ {
43
+ id: 'fetch',
44
+ type: 'FetchUser',
45
+ label: 'Fetch User',
46
+ position: { x: 540, y: 220 },
47
+ config: {},
48
+ inputMap: {
49
+ userId: { sourceNodeId: 'validate', sourceField: 'userId' },
50
+ },
51
+ },
52
+ {
53
+ id: 'route',
54
+ type: 'Route',
55
+ label: 'New user?',
56
+ position: { x: 780, y: 220 },
57
+ config: { field: 'isNew', branches: ['true'], fallback: 'existing' },
58
+ inputMap: {
59
+ value: { sourceNodeId: 'fetch', sourceField: '$' },
60
+ },
61
+ },
62
+ {
63
+ id: 'welcome',
64
+ type: 'WelcomeUser',
65
+ label: 'Welcome User',
66
+ position: { x: 1020, y: 100 },
67
+ config: {},
68
+ inputMap: {
69
+ user: { sourceNodeId: 'fetch', sourceField: '$' },
70
+ },
71
+ },
72
+ {
73
+ id: 'checkPlan',
74
+ type: 'CheckPlan',
75
+ label: 'Check Plan',
76
+ position: { x: 1020, y: 340 },
77
+ config: {},
78
+ inputMap: {
79
+ user: { sourceNodeId: 'fetch', sourceField: '$' },
80
+ },
81
+ },
82
+ {
83
+ id: 'issueToken',
84
+ type: 'IssueToken',
85
+ label: 'Issue Token',
86
+ position: { x: 1260, y: 220 },
87
+ config: {},
88
+ inputMap: {
89
+ userId: { sourceNodeId: 'fetch', sourceField: 'id' },
90
+ plan: { sourceNodeId: 'checkPlan', sourceField: 'plan' },
91
+ },
92
+ },
93
+ {
94
+ id: 'result',
95
+ type: 'Result',
96
+ label: 'Result',
97
+ position: { x: 1500, y: 220 },
98
+ config: {},
99
+ inputMap: {
100
+ data: { sourceNodeId: 'issueToken', sourceField: '$' },
101
+ },
102
+ },
103
+ ];
104
+
105
+ // Branch handles live on the Route's outgoing edges (sourceHandle = branch
106
+ // name). welcome→issueToken carries no targetHandle: it is a structural join,
107
+ // not a data mapping (issueToken's inputs come from fetch and checkPlan).
108
+ const edges: WorkflowEdge[] = [
109
+ { id: 'e0', source: 'input', target: 'validate', targetHandle: 'username' },
110
+ { id: 'e1', source: 'validate', target: 'fetch', targetHandle: 'userId' },
111
+ { id: 'e2', source: 'fetch', target: 'route', targetHandle: 'value' },
112
+ { id: 'e3', source: 'route', target: 'welcome', sourceHandle: 'true' },
113
+ { id: 'e4', source: 'route', target: 'checkPlan', sourceHandle: 'existing' },
114
+ { id: 'e5', source: 'welcome', target: 'issueToken' },
115
+ { id: 'e6', source: 'checkPlan', target: 'issueToken', targetHandle: 'plan' },
116
+ { id: 'e7', source: 'issueToken', target: 'result', targetHandle: 'data' },
117
+ ];
118
+
119
+ return {
120
+ id: 'login-example',
121
+ name: 'Login Example',
122
+ version: 1,
123
+ nodes,
124
+ edges,
125
+ // FetchUser derives isNew from a `new` name prefix and plan from a `pro`
126
+ // substring, so the username alone steers every branch.
127
+ scenarios: [
128
+ {
129
+ id: 'scn-new-user',
130
+ name: 'new user',
131
+ description: 'newton is new — welcome branch, checkPlan skipped',
132
+ input: { username: 'newton', password: 'apple123' },
133
+ },
134
+ {
135
+ id: 'scn-existing-pro',
136
+ name: 'existing pro',
137
+ description: 'ada-pro exists on the pro plan — plan-check branch',
138
+ input: { username: 'ada-pro', password: 'lovelace' },
139
+ },
140
+ {
141
+ id: 'scn-bad-password',
142
+ name: 'bad password',
143
+ description: 'password under 4 chars — validation fails the run',
144
+ input: { username: 'ada', password: 'abc' },
145
+ },
146
+ ],
147
+ createdAt: '2026-07-02T00:00:00Z',
148
+ updatedAt: '2026-07-02T00:00:00Z',
149
+ };
150
+ }
@@ -0,0 +1,204 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createDefaultRegistry } from '../nodes';
3
+ import { runOutput, startRun, validateFlow } from '../engine';
4
+ import type { StartRunOptions, WorkflowDefinition } from '../engine';
5
+ import { createPradarFlows } from './pradar-flows';
6
+
7
+ const registry = createDefaultRegistry(0);
8
+ const flows = createPradarFlows();
9
+
10
+ const EXPECTED_IDS = ['ev-evaluate-cycle', 'ev-threshold-intelligence', 'ev-night-charge'];
11
+ const NIGHT = '2026-07-02T23:30:00Z'; // hour 23, night window (mirrors pradar-flows.ts)
12
+
13
+ function flow(id: string): WorkflowDefinition {
14
+ const f = flows.find((x) => x.id === id);
15
+ if (!f) throw new Error(`no flow ${id}`);
16
+ return f;
17
+ }
18
+ function scenarioInput(id: string, name: string): Record<string, unknown> {
19
+ const scn = flow(id).scenarios!.find((s) => s.name === name);
20
+ if (!scn) throw new Error(`no scenario ${name} in ${id}`);
21
+ return scn.input;
22
+ }
23
+ function branchOf(run: Awaited<ReturnType<ReturnType<typeof startRun>['runToEnd']>>, nodeId: string): string | undefined {
24
+ return (run.nodeStates[nodeId]?.output as { $branch?: string } | undefined)?.$branch;
25
+ }
26
+
27
+ describe('EV Demo flows', () => {
28
+ it('exports the three expected flows with stable ids + folder', () => {
29
+ expect(flows.map((f) => f.id)).toEqual(EXPECTED_IDS);
30
+ for (const f of flows) {
31
+ expect(f.folder).toBe('EV Charging Demo');
32
+ expect(f.createdAt).toBe('2026-07-03T00:00:00Z');
33
+ }
34
+ });
35
+
36
+ for (const f of flows) {
37
+ describe(f.name, () => {
38
+ it('validates with no errors', () => {
39
+ const errors = validateFlow(f, registry).filter((i) => i.severity === 'error');
40
+ expect(errors, JSON.stringify(errors, null, 2)).toEqual([]);
41
+ });
42
+ it('uses only registered node types', () => {
43
+ for (const node of f.nodes) expect(registry.has(node.type), `${f.id}: ${node.type}`).toBe(true);
44
+ });
45
+ });
46
+ }
47
+
48
+ // ── Evaluate Cycle: drive representative scenarios end-to-end ──────────────
49
+ async function runCycle(name: string) {
50
+ return startRun({ flow: flow('ev-evaluate-cycle'), registry, input: scenarioInput('ev-evaluate-cycle', name) }).runToEnd();
51
+ }
52
+
53
+ it('sunny-start → charge decision starts and Start Charge dry-runs', async () => {
54
+ const run = await runCycle('sunny-start');
55
+ expect(run.status).toBe('succeeded');
56
+ expect(branchOf(run, 'thresholds')).toBe('ok');
57
+ expect(branchOf(run, 'target')).toBe('belowTarget');
58
+ expect(branchOf(run, 'decision')).toBe('start');
59
+ expect(run.nodeStates.startCharge.status).toBe('succeeded');
60
+ expect((run.nodeStates.startCharge.output as { sent: boolean; wouldSend: string }).sent).toBe(false);
61
+ expect((run.nodeStates.startCharge.output as { wouldSend: string }).wouldSend).toBe('start');
62
+ expect(run.nodeStates.stopCharge.status).toBe('skipped');
63
+ });
64
+
65
+ it('not-worth-it → thresholds skip branch, no target check', async () => {
66
+ const run = await runCycle('not-worth-it');
67
+ expect(run.status).toBe('succeeded');
68
+ expect(branchOf(run, 'thresholds')).toBe('skip');
69
+ expect(run.nodeStates.resultSkip.status).toBe('succeeded');
70
+ expect(run.nodeStates.target.status).toBe('skipped');
71
+ });
72
+
73
+ it('charging-stop → charge decision stops', async () => {
74
+ const run = await runCycle('charging-stop');
75
+ expect(branchOf(run, 'decision')).toBe('stop');
76
+ expect(run.nodeStates.stopCharge.status).toBe('succeeded');
77
+ expect(run.nodeStates.startCharge.status).toBe('skipped');
78
+ });
79
+
80
+ it('unscheduled-stop → guard stops the unscheduled charge', async () => {
81
+ const run = await runCycle('unscheduled-stop');
82
+ expect(branchOf(run, 'guard')).toBe('stop');
83
+ expect(run.nodeStates.stopUnsched.status).toBe('succeeded');
84
+ expect(run.nodeStates.decision.status).toBe('skipped');
85
+ });
86
+
87
+ it('reconcile-adopt → guard continues, reconcile adopts', async () => {
88
+ const run = await runCycle('reconcile-adopt');
89
+ expect(branchOf(run, 'guard')).toBe('continue');
90
+ expect(branchOf(run, 'reconcile')).toBe('adopt');
91
+ expect(run.nodeStates.resultAdopt.status).toBe('succeeded');
92
+ expect(run.nodeStates.decision.status).toBe('skipped');
93
+ });
94
+
95
+ it('ev-at-target → target atTarget, stop at target', async () => {
96
+ const run = await runCycle('ev-at-target');
97
+ expect(branchOf(run, 'target')).toBe('atTarget');
98
+ expect(run.nodeStates.stopAtTarget.status).toBe('succeeded');
99
+ expect(run.nodeStates.guard.status).toBe('skipped');
100
+ });
101
+
102
+ it('unplugged → target notPlugged', async () => {
103
+ const run = await runCycle('unplugged');
104
+ expect(branchOf(run, 'target')).toBe('notPlugged');
105
+ expect(run.nodeStates.resultNotPlugged.status).toBe('succeeded');
106
+ });
107
+
108
+ it('sunset-stop → off-hours branch stops the charge', async () => {
109
+ const run = await runCycle('sunset-stop');
110
+ expect(branchOf(run, 'condSolar')).toBe('off');
111
+ expect(branchOf(run, 'condNight')).toBe('notNight');
112
+ expect(branchOf(run, 'condOffCharging')).toBe('stop');
113
+ expect(run.nodeStates.offStop.status).toBe('succeeded');
114
+ expect(run.nodeStates.surplus.status).toBe('skipped');
115
+ });
116
+
117
+ it('trend-rising → trend override lowers start, decision starts', async () => {
118
+ const run = await runCycle('trend-rising');
119
+ expect((run.nodeStates.trend.output as { surplusTrend: number }).surplusTrend).toBe(300);
120
+ expect((run.nodeStates.thresholds.output as { startW: number }).startW).toBe(1000);
121
+ expect(branchOf(run, 'decision')).toBe('start');
122
+ });
123
+
124
+ it('charging-continue → holds the charging session', async () => {
125
+ const run = await runCycle('charging-continue');
126
+ expect(branchOf(run, 'decision')).toBe('hold_charging');
127
+ expect(run.nodeStates.resultHoldCharging.status).toBe('succeeded');
128
+ });
129
+
130
+ // A host subflow runner that resolves child flows from this flow set and runs
131
+ // them on the same registry — the test-side mirror of the browser/server host.
132
+ const hostRunner: NonNullable<StartRunOptions['subflowRunner']> = async (workflowId, input) => {
133
+ const child = flows.find((f) => f.id === workflowId);
134
+ if (!child) return { status: 'failed', error: `Unknown workflow: ${workflowId}` };
135
+ const childRun = await startRun({ flow: child, registry, input, subflowRunner: hostRunner }).runToEnd();
136
+ if (childRun.status !== 'succeeded') return { status: 'failed', error: childRun.status };
137
+ return { status: 'succeeded', output: runOutput(childRun, child) };
138
+ };
139
+
140
+ it('night-charge → routes the night window through the Night Charge subflow', async () => {
141
+ const run = await startRun({
142
+ flow: flow('ev-evaluate-cycle'),
143
+ registry,
144
+ input: scenarioInput('ev-evaluate-cycle', 'night-charge'),
145
+ subflowRunner: hostRunner,
146
+ }).runToEnd();
147
+ expect(run.status).toBe('succeeded');
148
+ expect(branchOf(run, 'condSolar')).toBe('off');
149
+ expect(branchOf(run, 'condNight')).toBe('night');
150
+ expect(run.nodeStates.subflowNight.status).toBe('succeeded');
151
+
152
+ // The subflow's decision matches running Night Charge standalone with the
153
+ // same derived snapshot (EV 30% at 23:30, plugged in) — a critical charge.
154
+ const childInput = { evBattery: 30, pluggedIn: true, tomorrowKwh: 24, now: NIGHT };
155
+ const standalone = await startRun({ flow: flow('ev-night-charge'), registry, input: childInput }).runToEnd();
156
+ expect(branchOf(standalone, 'night')).toBe('charge');
157
+ expect(run.nodeStates.subflowNight.output).toEqual(runOutput(standalone, flow('ev-night-charge')));
158
+ expect((run.nodeStates.resultNight.output as { data: unknown }).data).toEqual(
159
+ runOutput(standalone, flow('ev-night-charge')),
160
+ );
161
+ });
162
+
163
+ // ── Night Charge ──────────────────────────────────────────────────────────
164
+ async function runNight(name: string) {
165
+ return startRun({ flow: flow('ev-night-charge'), registry, input: scenarioInput('ev-night-charge', name) }).runToEnd();
166
+ }
167
+
168
+ it('critical-35 → charges', async () => {
169
+ const run = await runNight('critical-35');
170
+ expect(branchOf(run, 'night')).toBe('charge');
171
+ expect(run.nodeStates.startCharge.status).toBe('succeeded');
172
+ });
173
+ it('moderate-good-forecast → skips', async () => {
174
+ const run = await runNight('moderate-good-forecast');
175
+ expect(branchOf(run, 'night')).toBe('skip');
176
+ expect(run.nodeStates.startCharge.status).toBe('skipped');
177
+ });
178
+ it('wrong-hour → skips on the night-window gate', async () => {
179
+ const run = await runNight('wrong-hour');
180
+ expect(branchOf(run, 'night')).toBe('skip');
181
+ expect((run.nodeStates.night.output as { gateReason: string }).gateReason).toContain('night window');
182
+ });
183
+ it('unplugged-night → skips on the plugged-in gate', async () => {
184
+ const run = await runNight('unplugged-night');
185
+ expect(branchOf(run, 'night')).toBe('skip');
186
+ expect((run.nodeStates.night.output as { gateReason: string }).gateReason).toContain('plugged');
187
+ });
188
+
189
+ // ── Threshold Intelligence ──────────────────────────────────────────────
190
+ async function runThresh(name: string) {
191
+ return startRun({ flow: flow('ev-threshold-intelligence'), registry, input: scenarioInput('ev-threshold-intelligence', name) }).runToEnd();
192
+ }
193
+ it('predictive export → 500/200', async () => {
194
+ const run = await runThresh('predictive export');
195
+ const out = run.nodeStates.thresholds.output as { startW: number; stopW: number };
196
+ expect(out.startW).toBe(500);
197
+ expect(out.stopW).toBe(200);
198
+ });
199
+ it('worth-it skip → skip branch + estimate not worth it', async () => {
200
+ const run = await runThresh('worth-it skip');
201
+ expect(branchOf(run, 'thresholds')).toBe('skip');
202
+ expect((run.nodeStates.estimate.output as { worthIt: boolean }).worthIt).toBe(false);
203
+ });
204
+ });