@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,684 @@
1
+ import { computeLoopRegions, topoSort, validateFlow } from './validation.js';
2
+ export function getByPath(obj, path) {
3
+ if (path === '$')
4
+ return obj;
5
+ return path.split('.').reduce((acc, key) => acc !== null && typeof acc === 'object' ? acc[key] : undefined, obj);
6
+ }
7
+ // Matches the V8 shapes ("Cannot read properties of undefined/null (reading 'x')")
8
+ // and the older/Safari-style shape ("undefined is not an object (evaluating 'a.x')")
9
+ // for reading a property off undefined or null.
10
+ const UNDEFINED_READ_PATTERN = /^(Cannot read properties of (?:undefined|null) \(reading '[^']*'\)|undefined is not an object \(evaluating '[^']*'\))$/;
11
+ /**
12
+ * Enriches a "reading property of undefined/null" TypeError message with the
13
+ * node's resolved input keys, so the recorded error points at what the node
14
+ * actually received instead of just the raw JS engine message. Every other
15
+ * message passes through byte-identical.
16
+ */
17
+ export function explainUndefinedRead(message, input) {
18
+ if (!UNDEFINED_READ_PATTERN.test(message))
19
+ return message;
20
+ // A field mapped in `inputMap` whose source produced nothing still appears as
21
+ // a key, holding `undefined` — and that is almost always the culprit here.
22
+ // Listing it alongside the fields that actually carry a value is what turns
23
+ // this from a raw JS message into a pointer at the broken mapping.
24
+ const present = Object.keys(input).filter((k) => input[k] !== undefined).sort();
25
+ const undef = Object.keys(input).filter((k) => input[k] === undefined).sort();
26
+ const got = present.length > 0 ? `[${present.join(', ')}]` : '(none)';
27
+ const missing = undef.length > 0
28
+ ? ` These mapped fields resolved to undefined: ${undef.join(', ')}.`
29
+ : '';
30
+ return `${message} — this node received ${got}.${missing} Check the node's inputMap and the run input.`;
31
+ }
32
+ /** A `{"$env": "NAME"}` reference resolves to `vars["NAME"]`. */
33
+ function envRefName(value) {
34
+ if (value !== null &&
35
+ typeof value === 'object' &&
36
+ !Array.isArray(value) &&
37
+ typeof value.$env === 'string') {
38
+ return value.$env;
39
+ }
40
+ return undefined;
41
+ }
42
+ function resolveEnvRef(name, vars) {
43
+ if (!(name in vars)) {
44
+ // The most common cause is not a missing key but a run with NO environment
45
+ // at all: the in-tab engine has no vars, so flows seeded with $env refs
46
+ // need the server runner. Say so, instead of a bare "missing".
47
+ const hint = Object.keys(vars).length === 0
48
+ ? ' — this run has no environment variables (in-tab/browser runs never do; make sure the runner is online and the engine is set to Server or Auto)'
49
+ : '';
50
+ throw new Error(`Missing environment variable: ${name}${hint}`);
51
+ }
52
+ return vars[name];
53
+ }
54
+ /**
55
+ * Recursively replaces every `{"$env": "NAME"}` reference in a value (objects
56
+ * and arrays walked) with its resolved var. A missing var throws
57
+ * `Missing environment variable: NAME`. Non-ref values pass through untouched.
58
+ */
59
+ function resolveEnvDeep(value, vars) {
60
+ const name = envRefName(value);
61
+ if (name !== undefined)
62
+ return resolveEnvRef(name, vars);
63
+ if (Array.isArray(value))
64
+ return value.map((v) => resolveEnvDeep(v, vars));
65
+ if (value !== null && typeof value === 'object') {
66
+ const out = {};
67
+ for (const [key, v] of Object.entries(value)) {
68
+ out[key] = resolveEnvDeep(v, vars);
69
+ }
70
+ return out;
71
+ }
72
+ return value;
73
+ }
74
+ /**
75
+ * Appends one ExecutionRecord to a loop-body (or ForEach) node's
76
+ * accumulated per-iteration history. Pure — callers must read the prior
77
+ * array themselves, from whatever point in the node's state lifecycle it is
78
+ * still intact (setNodeState replaces the state object wholesale on every
79
+ * call, so an intermediate 'running'/'skipped' write can otherwise wipe it
80
+ * out before the caller gets a chance to read it back).
81
+ */
82
+ function appendExecution(prior, record) {
83
+ return [...(prior ?? []), record];
84
+ }
85
+ export class FlowRun {
86
+ run;
87
+ flow;
88
+ registry;
89
+ trace;
90
+ events;
91
+ now;
92
+ newId;
93
+ secrets;
94
+ vars;
95
+ environment;
96
+ safeMode;
97
+ runInput;
98
+ pins;
99
+ mockRun;
100
+ mocks;
101
+ subflowRunner;
102
+ order;
103
+ outputs = new Map();
104
+ skippedNodes = new Set();
105
+ branchTaken = new Map();
106
+ regionByForEach;
107
+ /** Nodes a completed loop region already executed; the main cursor consumes them silently. */
108
+ loopExecuted = new Set();
109
+ activeLoop;
110
+ cursor = 0;
111
+ /** Run-wide execution counter — numbers every node execution in the log. */
112
+ execSeq = 0;
113
+ constructor(opts) {
114
+ this.flow = opts.flow;
115
+ this.registry = opts.registry;
116
+ this.trace = opts.trace;
117
+ this.events = opts.events ?? {};
118
+ this.now = opts.now ?? (() => new Date().toISOString());
119
+ this.newId = opts.newId ?? (() => crypto.randomUUID());
120
+ this.secrets = opts.secrets ?? {};
121
+ this.vars = opts.vars ?? {};
122
+ this.environment = opts.environment;
123
+ this.safeMode = opts.safeMode ?? false;
124
+ // Resolve {"$env": NAME} anywhere in the run-input payload up front, so
125
+ // nodes read concrete values from ctx.runInput and a missing var fails the
126
+ // run at start rather than deep inside a node.
127
+ this.runInput = resolveEnvDeep(opts.input ?? {}, this.vars);
128
+ this.pins = opts.pins ?? {};
129
+ this.mockRun = opts.mockRun ?? false;
130
+ this.mocks = opts.mocks ?? {};
131
+ this.subflowRunner = opts.subflowRunner;
132
+ this.order = topoSort(this.flow);
133
+ this.regionByForEach = new Map(computeLoopRegions(this.flow).map((r) => [r.forEachId, r]));
134
+ this.run = {
135
+ id: this.newId(),
136
+ workflowId: this.flow.id,
137
+ status: 'running',
138
+ startedAt: this.now(),
139
+ ...(opts.environment !== undefined ? { environment: opts.environment } : {}),
140
+ ...(opts.safeMode !== undefined ? { safeMode: opts.safeMode } : {}),
141
+ nodeStates: {},
142
+ };
143
+ // No events from the constructor: subscribers' closures may not be
144
+ // initialized yet. Initial states are readable via handle.run.
145
+ for (const node of this.flow.nodes) {
146
+ this.run.nodeStates[node.id] = { status: 'queued' };
147
+ }
148
+ }
149
+ async step() {
150
+ if (this.run.status !== 'running' || this.cursor >= this.order.length)
151
+ return false;
152
+ if (this.activeLoop)
153
+ return this.stepLoop();
154
+ // Consume nodes a finished loop region already executed (topo order may
155
+ // interleave them with parallel non-region nodes), and nodes whose every
156
+ // incoming edge is dead (untaken route branch or skipped ancestor) —
157
+ // the latter are marked skipped, not executed.
158
+ while (this.cursor < this.order.length) {
159
+ const id = this.order[this.cursor];
160
+ if (this.loopExecuted.has(id)) {
161
+ this.cursor += 1;
162
+ continue;
163
+ }
164
+ if (!this.isReachable(id)) {
165
+ this.cursor += 1;
166
+ this.skippedNodes.add(id);
167
+ this.setNodeState(id, { status: 'skipped' });
168
+ continue;
169
+ }
170
+ break;
171
+ }
172
+ if (this.cursor >= this.order.length) {
173
+ this.finish('succeeded');
174
+ return false;
175
+ }
176
+ const nodeId = this.order[this.cursor];
177
+ this.cursor += 1;
178
+ // A reachable ForEach hands execution to the loop machinery, which owns
179
+ // the whole region (ForEach + body + Collect) until the Collect
180
+ // completes; the main cursor then resumes right after it.
181
+ const region = this.regionByForEach.get(nodeId);
182
+ if (region)
183
+ return this.startLoop(region);
184
+ const ok = await this.executeNode(nodeId);
185
+ if (!ok)
186
+ return false;
187
+ return this.epilogue();
188
+ }
189
+ /**
190
+ * Runs one node's implementation (or reuses its pin) end to end: resolve
191
+ * config/input, execute, record state + trace, handle failure. Shared by
192
+ * the main cursor and by loop-body execution, which additionally tags the
193
+ * resulting NodeRunState with the current iteration.
194
+ */
195
+ async executeNode(nodeId, iteration) {
196
+ const node = this.flow.nodes.find((n) => n.id === nodeId);
197
+ const iterationState = iteration ? { iteration } : {};
198
+ // Ordered execution receipt: every execution gets a run-wide sequence
199
+ // number, so the log is proof of exactly what ran, in what order — and
200
+ // that non-loop nodes run once (only loop iterations repeat a label).
201
+ this.execSeq += 1;
202
+ this.emitLog('debug', `#${this.execSeq} ▶ execute${iteration ? ` (iteration ${iteration.index + 1}/${iteration.total})` : ''}`, node);
203
+ // Captured up front: the 'running' state set below replaces the node's
204
+ // state object wholesale, which would otherwise wipe out `executions`
205
+ // before the completion branch gets a chance to read it back out.
206
+ const priorExecutions = iteration ? this.run.nodeStates[nodeId]?.executions : undefined;
207
+ if (Object.prototype.hasOwnProperty.call(this.pins, nodeId)) {
208
+ const output = this.pins[nodeId];
209
+ const at = this.now();
210
+ this.outputs.set(nodeId, output);
211
+ this.captureBranch(nodeId, output);
212
+ const executions = iteration
213
+ ? appendExecution(priorExecutions, { iteration, output, status: 'succeeded' })
214
+ : undefined;
215
+ this.setNodeState(nodeId, {
216
+ status: 'succeeded', output, pinned: true, startedAt: at, completedAt: at, ...iterationState,
217
+ ...(executions ? { executions } : {}),
218
+ });
219
+ this.emitLog('info', `${node.label}: using pinned output`, node);
220
+ return true;
221
+ }
222
+ const { definition, implementation } = this.registry.get(node.type);
223
+ const startedAt = this.now();
224
+ let input = {};
225
+ // Hoisted above the try so both the success path and the shared catch
226
+ // below can read the final attempt count. Stays at 1 (so `attempts` is
227
+ // never stamped) unless the retry loop actually runs more than once —
228
+ // config/input-resolution failures above the loop never touch it.
229
+ let attempt = 1;
230
+ // In a mock run, a mocked node's canned output and an infra-no-mock
231
+ // failure both apply BEFORE config resolution: the node's real
232
+ // implementation never runs either way, so a config value it would
233
+ // otherwise need (e.g. a `$secret` ref with no secret configured) must
234
+ // never fail the node first. Only the inputMap-derived fields (other
235
+ // nodes' outputs — never secrets) are resolved for `state.input` here;
236
+ // config-sourced input fields are skipped (lenient), since resolving them
237
+ // is exactly the thing that could throw.
238
+ const isMocked = this.mockRun && Object.prototype.hasOwnProperty.call(this.mocks, nodeId);
239
+ const isInfraNoMock = this.mockRun &&
240
+ !isMocked &&
241
+ (definition.traceKind === 'db' || definition.traceKind === 'http' || definition.traceKind === 'llm');
242
+ try {
243
+ if (isMocked) {
244
+ // Canned output: short-circuits before config resolution and the
245
+ // retry loop — the implementation is never invoked, so no retry
246
+ // machinery applies and a `$secret` config ref never throws.
247
+ input = this.resolveInputLenient(node);
248
+ this.setNodeState(nodeId, { status: 'running', input, startedAt, ...iterationState });
249
+ const output = this.mocks[nodeId];
250
+ const completedAt = this.now();
251
+ this.outputs.set(nodeId, output);
252
+ this.captureBranch(nodeId, output);
253
+ const executions = iteration
254
+ ? appendExecution(priorExecutions, { iteration, input, output, status: 'succeeded' })
255
+ : undefined;
256
+ this.setNodeState(nodeId, {
257
+ status: 'succeeded', input, output, startedAt, completedAt, mocked: true, ...iterationState,
258
+ ...(executions ? { executions } : {}),
259
+ });
260
+ this.recordSample(node, input, startedAt, completedAt, 'succeeded', output);
261
+ return true;
262
+ }
263
+ if (isInfraNoMock) {
264
+ // Fail-loud infrastructure boundary: thrown before config resolution
265
+ // (a `$secret` ref must not out-race this with "Missing secret"), as
266
+ // a normal implementation error (caught below), so `optional`
267
+ // fail-soft, halting semantics, and the error-workflow path all apply
268
+ // unchanged.
269
+ input = this.resolveInputLenient(node);
270
+ this.setNodeState(nodeId, { status: 'running', input, startedAt, ...iterationState });
271
+ throw new Error(`"${node.label}" would touch real infrastructure (${definition.traceKind}) and has no mock — ` +
272
+ `in Mock, infrastructure nodes need a canned output. Add one under this scenario's mocks ` +
273
+ `(Cover with AI writes them), or go Live to run it for real.`);
274
+ }
275
+ const config = this.resolveConfig(node);
276
+ input = this.resolveInput(node, config);
277
+ this.setNodeState(nodeId, { status: 'running', input, startedAt, ...iterationState });
278
+ const rawRetry = node.retry;
279
+ const maxTries = rawRetry && typeof rawRetry === 'object' && Number.isFinite(rawRetry.maxTries) && rawRetry.maxTries >= 1
280
+ ? rawRetry.maxTries
281
+ : 1;
282
+ const waitMs = rawRetry?.waitMs ?? 0;
283
+ const callImplementation = () => implementation({
284
+ input,
285
+ config,
286
+ secrets: this.secrets,
287
+ vars: this.vars,
288
+ environment: this.environment,
289
+ safeMode: this.safeMode,
290
+ runInput: this.runInput,
291
+ log: (level, message) => this.emitLog(level, message, node),
292
+ // Inject the calling node's id so the host can prefix child logs and
293
+ // report cycle context; the node's ctx.runSubflow stays (id, input).
294
+ ...(this.subflowRunner
295
+ ? {
296
+ runSubflow: (workflowId, subInput) => this.subflowRunner(workflowId, subInput, node.id),
297
+ }
298
+ : {}),
299
+ });
300
+ let output;
301
+ for (attempt = 1;; attempt++) {
302
+ try {
303
+ output = await callImplementation();
304
+ break;
305
+ }
306
+ catch (err) {
307
+ if (attempt >= maxTries)
308
+ throw err;
309
+ const msg = err instanceof Error ? err.message : String(err);
310
+ this.emitLog('warn', `retry ${attempt}/${maxTries} after error: ${msg}`, node);
311
+ await new Promise((r) => setTimeout(r, waitMs));
312
+ }
313
+ }
314
+ const completedAt = this.now();
315
+ this.outputs.set(nodeId, output);
316
+ this.captureBranch(nodeId, output);
317
+ const executions = iteration
318
+ ? appendExecution(priorExecutions, { iteration, input, output, status: 'succeeded' })
319
+ : undefined;
320
+ const mutationBlocked = this.safeMode && definition.effects === 'mutation';
321
+ this.setNodeState(nodeId, {
322
+ status: 'succeeded', input, output, startedAt, completedAt, ...iterationState,
323
+ ...(mutationBlocked ? { mutationBlocked: true } : {}),
324
+ ...(executions ? { executions } : {}),
325
+ ...(attempt > 1 ? { attempts: attempt } : {}),
326
+ });
327
+ this.recordSample(node, input, startedAt, completedAt, 'succeeded', output);
328
+ return true;
329
+ }
330
+ catch (err) {
331
+ const completedAt = this.now();
332
+ const rawMessage = err instanceof Error ? err.message : String(err);
333
+ const error = err instanceof TypeError ? explainUndefinedRead(rawMessage, input) : rawMessage;
334
+ const executions = iteration
335
+ ? appendExecution(priorExecutions, { iteration, input, error, status: 'failed' })
336
+ : undefined;
337
+ this.setNodeState(nodeId, {
338
+ status: 'failed', input, error, startedAt, completedAt, ...iterationState,
339
+ ...(executions ? { executions } : {}),
340
+ ...(attempt > 1 ? { attempts: attempt } : {}),
341
+ });
342
+ this.recordSample(node, input, startedAt, completedAt, 'failed');
343
+ this.emitLog('error', error, node);
344
+ // Fail-soft: an `optional` node's failure is recorded (state stays
345
+ // 'failed', visible in the runbook) but does not abort the run. It set no
346
+ // output, so its downstream edges are dead (isReachable) and dependent
347
+ // nodes skip, while independent branches keep running. The cursor was
348
+ // already advanced past this node (step()), so returning true resumes at
349
+ // the next node instead of finishing the run.
350
+ if (node.optional) {
351
+ return true;
352
+ }
353
+ this.finish('failed');
354
+ return false;
355
+ }
356
+ }
357
+ // ── ForEach/Collect loop region execution ───────────────────────────
358
+ //
359
+ // A region is driven across many step() calls, one node-execution each:
360
+ // the ForEach "executes" once per iteration (producing {item, index,
361
+ // total}), then each body node runs once per iteration in region topo
362
+ // order (with branch-skip state reset per iteration), then once all
363
+ // iterations are done the Collect executes exactly once. The main cursor
364
+ // is parked at the position right after the Collect for the duration.
365
+ /** First step on a reachable ForEach: resolve items, chunk, and start (or short-circuit) the region. */
366
+ async startLoop(region) {
367
+ const forEachNode = this.flow.nodes.find((n) => n.id === region.forEachId);
368
+ const collectNode = this.flow.nodes.find((n) => n.id === region.collectId);
369
+ const bodySet = new Set(region.bodyIds);
370
+ const bodyOrder = this.order.filter((id) => bodySet.has(id));
371
+ const startedAt = this.now();
372
+ let input = {};
373
+ try {
374
+ const config = this.resolveConfig(forEachNode);
375
+ input = this.resolveInput(forEachNode, config);
376
+ const items = input.items;
377
+ if (!Array.isArray(items)) {
378
+ throw new Error(`ForEach "${forEachNode.label}": "items" input is not an array`);
379
+ }
380
+ const rawBatchSize = Number(config.batchSize);
381
+ const batchSize = Number.isFinite(rawBatchSize) && rawBatchSize > 0 ? Math.floor(rawBatchSize) : 1;
382
+ let chunks = [];
383
+ for (let i = 0; i < items.length; i += batchSize) {
384
+ chunks.push(batchSize === 1 ? items[i] : items.slice(i, i + batchSize));
385
+ }
386
+ const rawMax = config.maxIterations;
387
+ const maxIterations = rawMax === undefined || rawMax === null || rawMax === '' ? undefined : Number(rawMax);
388
+ if (maxIterations !== undefined && Number.isFinite(maxIterations) && chunks.length > maxIterations) {
389
+ const truncated = chunks.length - maxIterations;
390
+ this.emitLog('warn', `maxIterations ${maxIterations} reached, truncating ${truncated} iterations`, forEachNode);
391
+ chunks = chunks.slice(0, maxIterations);
392
+ }
393
+ this.activeLoop = {
394
+ region, forEachNode, collectNode, bodyOrder,
395
+ items, chunks, iterIndex: 0, bodyCursor: 0, results: [],
396
+ };
397
+ if (chunks.length === 0) {
398
+ const completedAt = this.now();
399
+ const output = { item: undefined, index: 0, total: 0 };
400
+ this.outputs.set(forEachNode.id, output);
401
+ this.setNodeState(forEachNode.id, {
402
+ status: 'succeeded', input, output, startedAt, completedAt, iteration: { index: 0, total: 0 },
403
+ });
404
+ this.recordSample(forEachNode, output, startedAt, completedAt, 'succeeded', output);
405
+ for (const id of bodyOrder) {
406
+ this.skippedNodes.add(id);
407
+ this.setNodeState(id, { status: 'skipped', iteration: { index: 0, total: 0 } });
408
+ }
409
+ return true;
410
+ }
411
+ this.beginIteration(this.activeLoop);
412
+ return true;
413
+ }
414
+ catch (err) {
415
+ const completedAt = this.now();
416
+ const error = err instanceof Error ? err.message : String(err);
417
+ this.setNodeState(forEachNode.id, { status: 'failed', input, error, startedAt, completedAt });
418
+ this.recordSample(forEachNode, input, startedAt, completedAt, 'failed');
419
+ this.emitLog('error', error, forEachNode);
420
+ this.finish('failed');
421
+ return false;
422
+ }
423
+ }
424
+ /**
425
+ * Sets the ForEach's per-iteration output and resets body branch-skip
426
+ * state. Also clears each body id's prior-iteration output: without this,
427
+ * a body node that a branch skips this iteration would still resolve
428
+ * downstream inputMap reads to whatever it produced last iteration, since
429
+ * `outputs` is otherwise only ever overwritten (never cleared) by execution.
430
+ */
431
+ beginIteration(loop) {
432
+ const total = loop.chunks.length;
433
+ const chunk = loop.chunks[loop.iterIndex];
434
+ const output = { item: chunk, index: loop.iterIndex, total };
435
+ const at = this.now();
436
+ const iteration = { index: loop.iterIndex, total };
437
+ this.outputs.set(loop.forEachNode.id, output);
438
+ const executions = appendExecution(this.run.nodeStates[loop.forEachNode.id]?.executions, { iteration, output, status: 'succeeded' });
439
+ this.setNodeState(loop.forEachNode.id, {
440
+ status: 'succeeded',
441
+ input: { items: loop.items },
442
+ output,
443
+ startedAt: at,
444
+ completedAt: at,
445
+ iteration,
446
+ executions,
447
+ });
448
+ this.recordSample(loop.forEachNode, chunk, at, at, 'succeeded', output);
449
+ for (const id of loop.bodyOrder) {
450
+ this.skippedNodes.delete(id);
451
+ this.branchTaken.delete(id);
452
+ this.outputs.delete(id);
453
+ }
454
+ loop.bodyCursor = 0;
455
+ }
456
+ /** One step of an in-progress region: a body node, an iteration advance, or the final Collect. */
457
+ async stepLoop() {
458
+ const loop = this.activeLoop;
459
+ if (loop.chunks.length === 0) {
460
+ this.finishLoop(loop);
461
+ return this.epilogue();
462
+ }
463
+ while (loop.bodyCursor < loop.bodyOrder.length &&
464
+ !this.isReachable(loop.bodyOrder[loop.bodyCursor])) {
465
+ const deadId = loop.bodyOrder[loop.bodyCursor];
466
+ loop.bodyCursor += 1;
467
+ this.skippedNodes.add(deadId);
468
+ // A skip is a status, not an execution: no ExecutionRecord is appended
469
+ // for this iteration. But setNodeState replaces the state object
470
+ // wholesale, so any executions accumulated in earlier iterations (this
471
+ // branch may have been taken before, or will be again) must be carried
472
+ // forward explicitly or they'd be lost.
473
+ const priorExecutions = this.run.nodeStates[deadId]?.executions;
474
+ this.setNodeState(deadId, {
475
+ status: 'skipped',
476
+ iteration: { index: loop.iterIndex, total: loop.chunks.length },
477
+ ...(priorExecutions ? { executions: priorExecutions } : {}),
478
+ });
479
+ }
480
+ if (loop.bodyCursor < loop.bodyOrder.length) {
481
+ const nodeId = loop.bodyOrder[loop.bodyCursor];
482
+ loop.bodyCursor += 1;
483
+ const ok = await this.executeNode(nodeId, { index: loop.iterIndex, total: loop.chunks.length });
484
+ return ok;
485
+ }
486
+ // This iteration's body is exhausted: gather Collect's mapped value and advance.
487
+ loop.results.push(this.resolveCollectValue(loop.collectNode));
488
+ loop.iterIndex += 1;
489
+ if (loop.iterIndex < loop.chunks.length) {
490
+ this.beginIteration(loop);
491
+ return true;
492
+ }
493
+ this.finishLoop(loop);
494
+ return this.epilogue();
495
+ }
496
+ /** Resolves Collect's `value` input against the current iteration's body outputs. */
497
+ resolveCollectValue(collectNode) {
498
+ const config = this.resolveConfig(collectNode);
499
+ const input = this.resolveInput(collectNode, config);
500
+ return input.value;
501
+ }
502
+ /**
503
+ * Executes Collect once with the accumulated results and hands the region's
504
+ * nodes to `loopExecuted` so the main cursor consumes them silently — it
505
+ * must NOT jump past the Collect, because topo order can interleave
506
+ * parallel non-region nodes between the ForEach and the Collect.
507
+ */
508
+ finishLoop(loop) {
509
+ const at = this.now();
510
+ const output = { items: loop.results, count: loop.results.length };
511
+ const lastValue = loop.results.length > 0 ? loop.results[loop.results.length - 1] : undefined;
512
+ this.outputs.set(loop.collectNode.id, output);
513
+ this.setNodeState(loop.collectNode.id, {
514
+ status: 'succeeded', input: { value: lastValue }, output, startedAt: at, completedAt: at,
515
+ });
516
+ this.recordSample(loop.collectNode, { value: lastValue }, at, at, 'succeeded', output);
517
+ for (const id of [...loop.region.bodyIds, loop.region.collectId]) {
518
+ this.loopExecuted.add(id);
519
+ }
520
+ this.activeLoop = undefined;
521
+ }
522
+ /** Remember a node's taken branch when its output carries `$branch`. */
523
+ captureBranch(nodeId, output) {
524
+ if (output !== null && typeof output === 'object') {
525
+ const branch = output.$branch;
526
+ if (typeof branch === 'string')
527
+ this.branchTaken.set(nodeId, branch);
528
+ }
529
+ }
530
+ /**
531
+ * A node is reachable if it has no incoming edges, or at least one incoming
532
+ * edge from a non-skipped, executed source whose branch (if the edge names
533
+ * one via sourceHandle) was taken.
534
+ */
535
+ isReachable(nodeId) {
536
+ const incoming = this.flow.edges.filter((e) => e.target === nodeId);
537
+ if (incoming.length === 0)
538
+ return true;
539
+ const live = (edge) => {
540
+ if (this.skippedNodes.has(edge.source))
541
+ return false;
542
+ if (!this.outputs.has(edge.source))
543
+ return false;
544
+ if (!edge.sourceHandle)
545
+ return true;
546
+ return this.branchTaken.get(edge.source) === edge.sourceHandle;
547
+ };
548
+ // Branch edges are guards: when a node sits on named branches, only those
549
+ // edges decide reachability — a plain data edge from an always-executed
550
+ // ancestor must not smuggle the node past a dead gate. Nodes with no
551
+ // branch edges keep join semantics (any live data edge suffices).
552
+ const branchEdges = incoming.filter((e) => e.sourceHandle);
553
+ if (branchEdges.length > 0)
554
+ return branchEdges.some(live);
555
+ return incoming.some(live);
556
+ }
557
+ epilogue() {
558
+ if (this.cursor >= this.order.length) {
559
+ this.finish('succeeded');
560
+ return false;
561
+ }
562
+ return true;
563
+ }
564
+ async runToEnd() {
565
+ while (await this.step()) {
566
+ // step until done
567
+ }
568
+ return this.run;
569
+ }
570
+ cancel() {
571
+ if (this.run.status !== 'running')
572
+ return;
573
+ this.finish('cancelled');
574
+ }
575
+ /**
576
+ * Resolve {"$secret": NAME} and {"$env": NAME} config values; a missing
577
+ * secret/var fails the node. `$secret` is only recognized at a config
578
+ * value's top level (matching today's behavior — secrets are never nested
579
+ * inside larger config structures). `$env` is resolved deeply, the same
580
+ * walk used for the run-input payload, so refs nested in objects/arrays
581
+ * (e.g. an Input node's `config.defaults`) resolve too.
582
+ */
583
+ resolveConfig(node) {
584
+ const config = {};
585
+ for (const [key, value] of Object.entries(node.config)) {
586
+ if (value !== null &&
587
+ typeof value === 'object' &&
588
+ !Array.isArray(value) &&
589
+ typeof value.$secret === 'string') {
590
+ const name = value.$secret;
591
+ if (!(name in this.secrets))
592
+ throw new Error(`Missing secret: ${name}`);
593
+ config[key] = this.secrets[name];
594
+ }
595
+ else {
596
+ config[key] = resolveEnvDeep(value, this.vars);
597
+ }
598
+ }
599
+ return config;
600
+ }
601
+ resolveInput(node, config) {
602
+ const input = {};
603
+ const inputSchema = this.registry.get(node.type).definition.inputSchema;
604
+ for (const field of inputSchema?.fields ?? []) {
605
+ if (config[field.name] !== undefined)
606
+ input[field.name] = config[field.name];
607
+ }
608
+ for (const [field, mapping] of Object.entries(node.inputMap ?? {})) {
609
+ input[field] = getByPath(this.outputs.get(mapping.sourceNodeId), mapping.sourceField);
610
+ }
611
+ return input;
612
+ }
613
+ /**
614
+ * A lenient variant of `resolveInput` for nodes whose implementation never
615
+ * runs (mocked, or an infra node with no mock, in a mock run): resolves
616
+ * only `inputMap`-sourced fields (other nodes' already-computed outputs,
617
+ * which can never carry a `$secret`/`$env` ref). Skips the config-sourced
618
+ * fields `resolveInput` also merges in — those come from `resolveConfig`,
619
+ * which this path deliberately never calls, since that is exactly what
620
+ * could throw (e.g. `Missing secret`) for a node this run will never
621
+ * actually execute.
622
+ */
623
+ resolveInputLenient(node) {
624
+ const input = {};
625
+ for (const [field, mapping] of Object.entries(node.inputMap ?? {})) {
626
+ input[field] = getByPath(this.outputs.get(mapping.sourceNodeId), mapping.sourceField);
627
+ }
628
+ return input;
629
+ }
630
+ finish(status) {
631
+ // Only nodes that never got a chance to run are swept to 'skipped' here.
632
+ // While a loop region is active, `this.cursor` stays parked at the
633
+ // region's entry for the whole loop lifetime (body/collect execution is
634
+ // driven by the loop's own bodyCursor, not the main cursor), so this
635
+ // range can include body nodes that already reached a terminal state in
636
+ // an earlier iteration — or, on failure, the very node whose 'failed'
637
+ // state we must not clobber back to 'skipped'.
638
+ for (; this.cursor < this.order.length; this.cursor += 1) {
639
+ const id = this.order[this.cursor];
640
+ if (this.run.nodeStates[id]?.status === 'queued') {
641
+ this.setNodeState(id, { status: 'skipped' });
642
+ }
643
+ }
644
+ this.run.status = status;
645
+ this.run.completedAt = this.now();
646
+ this.events.onRunFinished?.(this.run);
647
+ }
648
+ setNodeState(nodeId, state) {
649
+ this.run.nodeStates[nodeId] = state;
650
+ this.events.onNodeStateChange?.(nodeId, state);
651
+ }
652
+ emitLog(level, message, node) {
653
+ this.events.onLog?.({
654
+ timestamp: this.now(),
655
+ level,
656
+ runId: this.run.id,
657
+ nodeId: node.id,
658
+ nodeLabel: node.label,
659
+ message,
660
+ });
661
+ }
662
+ recordSample(node, input, startedAt, completedAt, status, output) {
663
+ this.trace?.record({
664
+ id: this.newId(),
665
+ workflowId: this.flow.id,
666
+ runId: this.run.id,
667
+ nodeId: node.id,
668
+ nodeType: node.type,
669
+ nodeLabel: node.label,
670
+ input,
671
+ output,
672
+ status,
673
+ startedAt,
674
+ completedAt,
675
+ });
676
+ }
677
+ }
678
+ export function startRun(opts) {
679
+ const errors = validateFlow(opts.flow, opts.registry).filter((i) => i.severity === 'error');
680
+ if (errors.length > 0) {
681
+ throw new Error(`Invalid flow: ${errors.map((i) => i.message).join('; ')}`);
682
+ }
683
+ return new FlowRun(opts);
684
+ }