@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,461 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import { ArrowUpIcon, ChevronRightIcon, SparklesIcon, TerminalIcon, XIcon, ZapIcon } from 'lucide-react';
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { Button } from '@/components/ui/button';
5
+ import { cn } from '@/lib/utils';
6
+ import { useBuilderStore } from '../store/builderStore';
7
+ import type { AgentEvent } from '../store/agentClient';
8
+ import { renderAgentMarkdown } from './agentMarkdown';
9
+
10
+
11
+ const statusDot: Record<string, string> = {
12
+ running: 'bg-highlight animate-pulse',
13
+ done: 'bg-success',
14
+ error: 'bg-destructive',
15
+ };
16
+
17
+ /** A command's status → a small glyph colour. */
18
+ const cmdDot: Record<string, string> = {
19
+ in_progress: 'bg-highlight animate-pulse',
20
+ completed: 'bg-success/70',
21
+ failed: 'bg-destructive',
22
+ };
23
+
24
+ /** The `emberflow` CLI operation commands the agent uses (run/inspect/delete). */
25
+ const EMBERFLOW_OPS = [
26
+ 'run', 'get-workflow', 'list-workflows', 'list-nodes', 'node-schema', 'list-environments',
27
+ 'create', 'delete', 'rename', 'validate', 'publish', 'save',
28
+ ];
29
+
30
+ /** If a shell command invokes the emberflow CLI bin with an operation command,
31
+ * pull out the operation + its target id so it can render as a distinct step. */
32
+ function parseEmberflowOp(command?: string): { op: string; target?: string } | null {
33
+ if (!command || !/emberflow\.mjs\b/.test(command)) return null;
34
+ const after = command.split(/emberflow\.mjs\b/)[1] ?? '';
35
+ const toks = after.trim().split(/\s+/).filter(Boolean);
36
+ const op = toks.find((t) => EMBERFLOW_OPS.includes(t));
37
+ if (!op) return null;
38
+ const target = toks.slice(toks.indexOf(op) + 1).find((t) => !t.startsWith('-'));
39
+ return { op, target };
40
+ }
41
+
42
+ /**
43
+ * A run of consecutive shell commands, collapsed to a single quiet line
44
+ * ("Ran N commands") that expands to the individual commands + their output.
45
+ * A failed command in the group forces it open and tints it. This is the big
46
+ * de-noiser: a single edit can run 30 inspection commands — nobody wants 30
47
+ * rows, they want the reasoning with the mechanics tucked underneath.
48
+ */
49
+ function CommandGroup({ commands }: { commands: AgentEvent[] }) {
50
+ const anyFailed = commands.some((c) => c.commandStatus === 'failed');
51
+ const [open, setOpen] = useState(anyFailed);
52
+ const running = commands.some((c) => c.commandStatus === 'in_progress');
53
+ const label = commands.length === 1 ? '1 command' : `${commands.length} commands`;
54
+ return (
55
+ <div className="my-0.5">
56
+ <button
57
+ type="button"
58
+ onClick={() => setOpen((o) => !o)}
59
+ className="group flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left transition-colors hover:bg-secondary/50"
60
+ >
61
+ <ChevronRightIcon
62
+ className={cn('size-3 shrink-0 text-muted-foreground/50 transition-transform', open && 'rotate-90')}
63
+ />
64
+ <span
65
+ className={cn(
66
+ 'size-1.5 shrink-0 rounded-full',
67
+ running ? cmdDot.in_progress : anyFailed ? cmdDot.failed : cmdDot.completed,
68
+ )}
69
+ />
70
+ <span className={cn('text-[11px]', anyFailed ? 'text-destructive/90' : 'text-muted-foreground/70')}>
71
+ {running ? 'Running' : 'Ran'} {label}
72
+ </span>
73
+ </button>
74
+ {open && (
75
+ <div className="ml-[18px] mt-0.5 space-y-0.5 border-l border-border/50 pl-2">
76
+ {commands.map((c, k) => (
77
+ <div key={k}>
78
+ <div className="flex items-center gap-1.5">
79
+ <span className={cn('size-1 shrink-0 rounded-full', cmdDot[c.commandStatus ?? 'in_progress'])} />
80
+ <span className="min-w-0 truncate font-mono text-[10.5px] text-muted-foreground/80">{c.command}</span>
81
+ {c.commandStatus === 'failed' && c.exitCode != null && (
82
+ <span className="ml-auto shrink-0 font-mono text-[9px] text-destructive">exit {c.exitCode}</span>
83
+ )}
84
+ </div>
85
+ {(c.commandStatus === 'failed' || open) && c.output && c.output.trim() && (
86
+ <pre className="mt-0.5 ml-3 max-h-32 overflow-auto rounded border border-border/50 bg-background/40 p-1.5 font-mono text-[10px] leading-relaxed whitespace-pre-wrap text-muted-foreground/70">
87
+ {c.output}
88
+ </pre>
89
+ )}
90
+ </div>
91
+ ))}
92
+ </div>
93
+ )}
94
+ </div>
95
+ );
96
+ }
97
+
98
+ /** An MCP tool call the agent made against the runner (run/list/delete/etc.) —
99
+ * visually distinct from a shell command (MCP-blue, a "MCP" chip), expandable
100
+ * to its result. This is the operational channel: files for edits, MCP for
101
+ * actions like running the operation or deleting it. */
102
+ function McpCall({ e }: { e: AgentEvent }) {
103
+ const [open, setOpen] = useState(false);
104
+ const status = e.mcpStatus ?? 'completed';
105
+ const failed = status === 'failed';
106
+ const running = status === 'in_progress';
107
+ return (
108
+ <div className="my-0.5">
109
+ <button
110
+ type="button"
111
+ onClick={() => setOpen((o) => !o)}
112
+ className="group flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left transition-colors hover:bg-secondary/50"
113
+ >
114
+ <ChevronRightIcon
115
+ className={cn('size-3 shrink-0 text-muted-foreground/50 transition-transform', open && 'rotate-90')}
116
+ />
117
+ <span className={cn('size-1.5 shrink-0 rounded-full', running ? 'animate-pulse bg-highlight' : failed ? 'bg-destructive' : 'bg-[#8fb8d8]')} />
118
+ <ZapIcon className="size-3 shrink-0 text-[#8fb8d8]" />
119
+ <span className="shrink-0 rounded border border-[#8fb8d8]/40 px-1 font-mono text-[8.5px] uppercase tracking-wide text-[#8fb8d8]">
120
+ MCP
121
+ </span>
122
+ <span className={cn('min-w-0 truncate font-mono text-[11px]', failed ? 'text-destructive/90' : 'text-foreground/85')}>
123
+ {e.mcpServer}·{e.mcpTool}
124
+ </span>
125
+ <span className="ml-auto shrink-0 text-[9.5px] text-muted-foreground/60">{running ? '…' : status}</span>
126
+ </button>
127
+ {open && e.output && e.output.trim() && (
128
+ <pre className="ml-[18px] mt-0.5 max-h-40 overflow-auto rounded border border-border/50 bg-background/40 p-1.5 font-mono text-[10px] leading-relaxed whitespace-pre-wrap text-muted-foreground/75">
129
+ {e.output}
130
+ </pre>
131
+ )}
132
+ </div>
133
+ );
134
+ }
135
+
136
+ /** An `emberflow` CLI operation the agent ran (run/inspect/delete against the
137
+ * live runner) — surfaced as a distinct operation step, expandable to its
138
+ * output. This is the agent's operational channel: files for edits, CLI ops
139
+ * for running/inspecting/removing. */
140
+ function OpCall({ op, target, e }: { op: string; target?: string; e: AgentEvent }) {
141
+ const [open, setOpen] = useState(false);
142
+ const status = e.commandStatus ?? 'completed';
143
+ const failed = status === 'failed' || (e.exitCode != null && e.exitCode !== 0);
144
+ const running = status === 'in_progress';
145
+ return (
146
+ <div className="my-0.5">
147
+ <button
148
+ type="button"
149
+ onClick={() => setOpen((o) => !o)}
150
+ className="group flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left transition-colors hover:bg-secondary/50"
151
+ >
152
+ <ChevronRightIcon className={cn('size-3 shrink-0 text-muted-foreground/50 transition-transform', open && 'rotate-90')} />
153
+ <span className={cn('size-1.5 shrink-0 rounded-full', running ? 'animate-pulse bg-highlight' : failed ? 'bg-destructive' : 'bg-[#8fb8d8]')} />
154
+ <ZapIcon className="size-3 shrink-0 text-[#8fb8d8]" />
155
+ <span className="shrink-0 rounded border border-[#8fb8d8]/40 px-1 font-mono text-[8.5px] uppercase tracking-wide text-[#8fb8d8]">
156
+ OP
157
+ </span>
158
+ <span className={cn('min-w-0 truncate font-mono text-[11px]', failed ? 'text-destructive/90' : 'text-foreground/85')}>
159
+ {op}{target ? ` · ${target}` : ''}
160
+ </span>
161
+ <span className="ml-auto shrink-0 text-[9.5px] text-muted-foreground/60">{running ? '…' : failed ? 'failed' : 'ok'}</span>
162
+ </button>
163
+ {open && e.output && e.output.trim() && (
164
+ <pre className="ml-[18px] mt-0.5 max-h-40 overflow-auto rounded border border-border/50 bg-background/40 p-1.5 font-mono text-[10px] leading-relaxed whitespace-pre-wrap text-muted-foreground/75">
165
+ {e.output}
166
+ </pre>
167
+ )}
168
+ </div>
169
+ );
170
+ }
171
+
172
+ /**
173
+ * Docked panel that streams a studio-triggered coding-agent run. Presents it
174
+ * the way Codex/Claude present their own work: the agent's reasoning is the
175
+ * content (comfortable prose), commands collapse to quiet one-liners you can
176
+ * expand, and background diagnostics (MCP/hook noise) are tucked into a count.
177
+ * Then the diff with a Revert button once the run finishes.
178
+ */
179
+ export function AgentConsole({ onDismiss }: { onDismiss: () => void }) {
180
+ const agentRun = useBuilderStore((s) => s.agentRun);
181
+ const revertAgentRun = useBuilderStore((s) => s.revertAgentRun);
182
+ const runAgent = useBuilderStore((s) => s.runAgent);
183
+ const flowId = useBuilderStore((s) => s.flow.id);
184
+ const flowName = useBuilderStore((s) => s.flow.name);
185
+ const envSetup = useBuilderStore((s) => s.agentEnvSetup);
186
+ const [instruction, setInstruction] = useState('');
187
+ const running = agentRun?.status === 'running';
188
+
189
+ // Slide in from the right on mount: start translated off-screen, then flip to
190
+ // 0 on the next frame so the transition runs. (A CSS keyframe races the
191
+ // resizable panel's JS-driven width and often no-ops; this is reliable.)
192
+ const [entered, setEntered] = useState(false);
193
+ useEffect(() => {
194
+ const r = requestAnimationFrame(() => setEntered(true));
195
+ return () => cancelAnimationFrame(r);
196
+ }, []);
197
+
198
+ const send = () => {
199
+ const text = instruction.trim();
200
+ if (!text || running) return;
201
+ // Chat drives the OPEN operation — unless the panel was opened from the
202
+ // Environments dialog, in which case it drives environment setup and
203
+ // follow-ups keep refining the environments file.
204
+ if (envSetup) {
205
+ void runAgent({ action: 'setup-environments', instruction: text });
206
+ } else {
207
+ // One input, no modes: the edit-flow prompt classifies the message —
208
+ // questions get answered read-only, change requests get edited. The
209
+ // explicit `ask` intent still exists for programmatic callers.
210
+ void runAgent({ action: 'edit-flow', flowId, instruction: text });
211
+ }
212
+ setInstruction('');
213
+ };
214
+
215
+ // Partition the raw stream into what's worth showing vs. noise. The agent's
216
+ // ⚠ diagnostics (an MCP server failing to auth, a hook firing) are real but
217
+ // not what the user is watching for — collapse them to a count.
218
+ const { blocks, noiseCount } = useMemo(() => {
219
+ // Fold the raw stream into display blocks: reasoning prose and error lines
220
+ // stand alone; consecutive commands collapse into one CommandGroup. Started/
221
+ // done are structural; ⚠ diagnostics are noise → a count.
222
+ type Block =
223
+ | { kind: 'message' | 'error' | 'approval' | 'mcp'; e: AgentEvent }
224
+ | { kind: 'op'; e: AgentEvent; op: string; target?: string }
225
+ | { kind: 'commands'; commands: AgentEvent[] };
226
+ const blocks: Block[] = [];
227
+ let noiseCount = 0;
228
+ let group: AgentEvent[] | null = null;
229
+ const flush = (): void => {
230
+ if (group && group.length) blocks.push({ kind: 'commands', commands: group });
231
+ group = null;
232
+ };
233
+ for (const e of agentRun?.events ?? []) {
234
+ // An `emberflow` CLI operation (run/inspect/delete) — surface it as a
235
+ // distinct "operation" step, not a generic shell command.
236
+ const opInfo = e.type === 'command' ? parseEmberflowOp(e.command) : null;
237
+ if (e.type === 'command' && opInfo) {
238
+ flush();
239
+ const last = blocks[blocks.length - 1];
240
+ if (last && last.kind === 'op' && last.e.command === e.command && last.e.commandStatus === 'in_progress') {
241
+ last.e = e;
242
+ } else {
243
+ blocks.push({ kind: 'op', e, op: opInfo.op, target: opInfo.target });
244
+ }
245
+ continue;
246
+ }
247
+ if (e.type === 'command') {
248
+ group ??= [];
249
+ // Each command streams as a start (in_progress) then a complete
250
+ // (completed/failed) event with the same text — collapse the pair into
251
+ // one entry carrying the final status + output, so counts are accurate.
252
+ const last = group[group.length - 1];
253
+ if (last && last.command === e.command && last.commandStatus === 'in_progress') {
254
+ group[group.length - 1] = e;
255
+ } else {
256
+ group.push(e);
257
+ }
258
+ continue;
259
+ }
260
+ flush();
261
+ if (e.type === 'mcp') {
262
+ // MCP calls stream in_progress → completed for the same tool; collapse
263
+ // the pair so the completed status/result replaces the pending one.
264
+ const last = blocks[blocks.length - 1];
265
+ if (last && last.kind === 'mcp' && last.e.mcpTool === e.mcpTool && last.e.mcpStatus === 'in_progress') {
266
+ last.e = e;
267
+ } else {
268
+ blocks.push({ kind: 'mcp', e });
269
+ }
270
+ continue;
271
+ }
272
+ if (e.type === 'started' || e.type === 'done') continue;
273
+ if (e.type === 'message' && e.text?.startsWith('⚠')) {
274
+ noiseCount += 1;
275
+ continue;
276
+ }
277
+ if (e.type === 'message') blocks.push({ kind: 'message', e });
278
+ else if (e.type === 'error') blocks.push({ kind: 'error', e });
279
+ else if (e.type === 'approval-request') blocks.push({ kind: 'approval', e });
280
+ }
281
+ flush();
282
+ return { blocks, noiseCount };
283
+ }, [agentRun?.events]);
284
+
285
+ const finished = agentRun != null && agentRun.status !== 'running';
286
+
287
+ return (
288
+ <div
289
+ className={cn(
290
+ 'flex h-full min-h-0 flex-col overflow-hidden border-l border-border bg-card transition-transform duration-200 ease-out motion-reduce:transition-none',
291
+ entered ? 'translate-x-0' : 'translate-x-full',
292
+ )}
293
+ >
294
+ <header className="flex h-11 shrink-0 items-center gap-2.5 border-b border-border/70 px-3.5">
295
+ <span className={cn('size-2 rounded-full', agentRun ? statusDot[agentRun.status] : 'bg-muted-foreground/40')} />
296
+ <span className="min-w-0 truncate text-[12.5px] font-medium">
297
+ {envSetup ? 'Agent — environments' : 'Agent'}
298
+ </span>
299
+ {agentRun && (
300
+ <Badge variant="outline" className="shrink-0 text-[9px] uppercase">
301
+ {agentRun.status}
302
+ </Badge>
303
+ )}
304
+ <Button
305
+ variant="ghost"
306
+ size="icon"
307
+ className="ml-auto size-6 shrink-0 text-muted-foreground hover:text-foreground"
308
+ onClick={onDismiss}
309
+ aria-label="Dismiss agent console"
310
+ >
311
+ <XIcon className="size-3.5" />
312
+ </Button>
313
+ </header>
314
+
315
+ <div className="min-h-0 flex-1 space-y-1.5 overflow-y-auto px-3.5 py-3">
316
+ {/* Your message — the instruction you sent, shown first like a chat. */}
317
+ {agentRun?.instruction && (
318
+ <div className="mb-1 flex justify-end">
319
+ <div className="max-w-[90%] rounded-lg rounded-br-sm bg-highlight/15 px-2.5 py-1.5 text-[12.5px] leading-relaxed whitespace-pre-wrap text-foreground">
320
+ {agentRun.instruction}
321
+ </div>
322
+ </div>
323
+ )}
324
+ {!agentRun && (
325
+ <div className="flex h-full flex-col items-center justify-center gap-3 px-4 text-center">
326
+ <span className="flex size-9 items-center justify-center rounded-full bg-highlight/12 text-highlight">
327
+ <SparklesIcon className="size-4" />
328
+ </span>
329
+ <p className="text-[12.5px] leading-relaxed text-muted-foreground">
330
+ {envSetup ? (
331
+ <>
332
+ Describe the environments this project should have — dev? prod? test? Base URLs
333
+ and which ones are protected. The agent writes the config; secret values stay
334
+ yours to add afterwards.
335
+ </>
336
+ ) : (
337
+ <>
338
+ Ask the agent to change <span className="text-foreground/80">{flowName}</span>. It edits the
339
+ operation — adding nodes, wiring, or a new registered node — and the canvas updates live when it’s done.
340
+ </>
341
+ )}
342
+ </p>
343
+ </div>
344
+ )}
345
+ {agentRun && blocks.length === 0 && !finished && (
346
+ <div className="flex items-center gap-2 text-[12px] text-muted-foreground/70">
347
+ <span className="size-1.5 animate-pulse rounded-full bg-highlight" />
348
+ Thinking…
349
+ </div>
350
+ )}
351
+ {blocks.map((b, i) => {
352
+ if (b.kind === 'commands') return <CommandGroup key={i} commands={b.commands} />;
353
+ if (b.kind === 'op') return <OpCall key={i} op={b.op} target={b.target} e={b.e} />;
354
+ if (b.kind === 'mcp') return <McpCall key={i} e={b.e} />;
355
+ if (b.kind === 'message') {
356
+ // The reasoning: the star of the panel — comfortable, readable prose.
357
+ // Render the markdown subset (bold/code/lists/headings). The renderer
358
+ // owns block structure, so whitespace-pre-wrap stays OFF here.
359
+ return (
360
+ <div key={i} className="space-y-1.5">
361
+ {renderAgentMarkdown(b.e.text ?? '')}
362
+ </div>
363
+ );
364
+ }
365
+ if (b.kind === 'error') {
366
+ return (
367
+ <p key={i} className="text-[12px] leading-relaxed text-destructive">
368
+ {b.e.text ?? 'The agent run failed.'}
369
+ </p>
370
+ );
371
+ }
372
+ return (
373
+ <p key={i} className="text-[12px] text-yellow-500">
374
+ Approval requested{b.e.text ? `: ${b.e.text}` : ''}
375
+ </p>
376
+ );
377
+ })}
378
+
379
+ {/* Live working indicator once there's content but the run's still going. */}
380
+ {!finished && blocks.length > 0 && (
381
+ <div className="flex items-center gap-2 pt-1 text-[11px] text-muted-foreground/60">
382
+ <span className="size-1.5 animate-pulse rounded-full bg-highlight" />
383
+ Working…
384
+ </div>
385
+ )}
386
+
387
+ {/* Background diagnostics, tucked away — acknowledged, not shouting. */}
388
+ {noiseCount > 0 && (
389
+ <div className="flex items-center gap-1.5 pt-1 text-[10.5px] text-muted-foreground/45">
390
+ <TerminalIcon className="size-3" />
391
+ {noiseCount} background {noiseCount === 1 ? 'diagnostic' : 'diagnostics'} hidden
392
+ </div>
393
+ )}
394
+ </div>
395
+
396
+ {finished && agentRun && (agentRun.diff !== undefined || agentRun.status === 'error') && (
397
+ <div className="min-h-0 shrink-0 border-t border-border/70">
398
+ <div className="max-h-64 overflow-auto p-3">
399
+ {agentRun.diff ? (
400
+ <>
401
+ <div className="mb-1.5 text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
402
+ Changes{agentRun.files && agentRun.files.length > 0 ? ` · ${agentRun.files.join(', ')}` : ''}
403
+ </div>
404
+ <pre className="overflow-auto rounded-md border border-border bg-background/40 p-2 font-mono text-[10.5px] leading-relaxed whitespace-pre-wrap">
405
+ {agentRun.diff}
406
+ </pre>
407
+ </>
408
+ ) : (
409
+ <div className="text-[12px] text-muted-foreground/70">No changes were made.</div>
410
+ )}
411
+ </div>
412
+ </div>
413
+ )}
414
+
415
+ {/* Revert stays available after a completed change. */}
416
+ {finished && agentRun && agentRun.status === 'done' && agentRun.diff && (
417
+ <div className="flex shrink-0 items-center border-t border-border/70 px-3.5 py-1.5">
418
+ <Button variant="destructive" size="sm" onClick={() => void revertAgentRun()}>
419
+ Revert last change
420
+ </Button>
421
+ </div>
422
+ )}
423
+
424
+ {/* Persistent chat input — the way to drive the open operation. */}
425
+ <footer className="shrink-0 border-t border-border/70 p-2.5">
426
+ <div className="flex items-end gap-2">
427
+ <textarea
428
+ value={instruction}
429
+ onChange={(e) => setInstruction(e.target.value)}
430
+ onKeyDown={(e) => {
431
+ if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
432
+ e.preventDefault();
433
+ send();
434
+ }
435
+ }}
436
+ disabled={running}
437
+ placeholder={
438
+ running
439
+ ? 'Agent is working…'
440
+ : envSetup
441
+ ? 'e.g. dev on localhost:3001, prod at api.example.com (protected)'
442
+ : `Ask about ${flowName}, or tell the agent what to change…`
443
+ }
444
+ rows={2}
445
+ className="min-h-0 flex-1 resize-none rounded-md border border-input bg-input/30 px-2.5 py-1.5 text-[12.5px] leading-relaxed text-foreground outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-60"
446
+ />
447
+ <Button
448
+ size="icon"
449
+ className="size-8 shrink-0"
450
+ onClick={send}
451
+ disabled={running || !instruction.trim()}
452
+ aria-label="Send to agent"
453
+ title="Send (⌘⏎)"
454
+ >
455
+ <ArrowUpIcon className="size-4" />
456
+ </Button>
457
+ </div>
458
+ </footer>
459
+ </div>
460
+ );
461
+ }
@@ -0,0 +1,156 @@
1
+ import { PlusIcon, Trash2Icon } from 'lucide-react';
2
+ import { Button } from '@/components/ui/button';
3
+ import { Combobox } from '@/components/ui/combobox';
4
+ import type { ComboboxOption } from '@/components/ui/combobox';
5
+ import { Input } from '@/components/ui/input';
6
+ import { useBuilderStore } from '../store/builderStore';
7
+ import type { WorkflowNode } from '../engine';
8
+
9
+ /** A single Conditional rule row: `{ name, op, value? }`. Mirrors src/nodes/flow-control.ts. */
10
+ interface BranchRule {
11
+ name: string;
12
+ op: string;
13
+ value?: unknown;
14
+ }
15
+
16
+ const OP_OPTIONS: ComboboxOption[] = [
17
+ { value: 'eq', label: '= eq' },
18
+ { value: 'neq', label: '≠ neq' },
19
+ { value: 'gt', label: '> gt' },
20
+ { value: 'gte', label: '≥ gte' },
21
+ { value: 'lt', label: '< lt' },
22
+ { value: 'lte', label: '≤ lte' },
23
+ { value: 'contains', label: 'contains' },
24
+ { value: 'exists', label: 'exists' },
25
+ { value: 'truthy', label: 'truthy' },
26
+ ];
27
+
28
+ /** exists/truthy read only the input value — no comparand to configure. */
29
+ const NO_COMPARAND_OPS = new Set(['exists', 'truthy']);
30
+
31
+ /** Numbers, booleans, and everything else stays a string. */
32
+ export function parseComparand(text: string): unknown {
33
+ if (text === 'true') return true;
34
+ if (text === 'false') return false;
35
+ if (text.trim() !== '' && Number.isFinite(Number(text))) return Number(text);
36
+ return text;
37
+ }
38
+
39
+ function comparandText(value: unknown): string {
40
+ if (value === undefined) return '';
41
+ return typeof value === 'string' ? value : String(value);
42
+ }
43
+
44
+ function asRules(raw: unknown): BranchRule[] {
45
+ if (!Array.isArray(raw)) return [];
46
+ return raw.map((r) => {
47
+ const rule = (r !== null && typeof r === 'object' ? r : {}) as Record<string, unknown>;
48
+ return {
49
+ name: typeof rule.name === 'string' ? rule.name : '',
50
+ op: typeof rule.op === 'string' ? rule.op : 'eq',
51
+ value: rule.value,
52
+ };
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Custom Config editor for Conditional nodes — an ordered list of
58
+ * name/operator/comparand rules plus a fallback branch, in place of the
59
+ * generic config field list (which can't express structured rule rows).
60
+ */
61
+ export function BranchRulesEditor({ node }: { node: WorkflowNode }) {
62
+ const updateNodeConfig = useBuilderStore((s) => s.updateNodeConfig);
63
+ const rules = asRules(node.config.branches);
64
+ const fallback = typeof node.config.fallback === 'string' ? node.config.fallback : '';
65
+
66
+ const setRules = (next: BranchRule[]) => updateNodeConfig(node.id, 'branches', next);
67
+
68
+ const updateRule = (index: number, patch: Partial<BranchRule>) => {
69
+ setRules(rules.map((r, i) => (i === index ? { ...r, ...patch } : r)));
70
+ };
71
+
72
+ const removeRule = (index: number) => {
73
+ setRules(rules.filter((_, i) => i !== index));
74
+ };
75
+
76
+ const addRule = () => {
77
+ setRules([...rules, { name: '', op: 'eq', value: '' }]);
78
+ };
79
+
80
+ return (
81
+ <div>
82
+ {rules.length === 0 ? (
83
+ <div className="mb-2.5 text-[12.5px] leading-relaxed text-muted-foreground">
84
+ No rules — every input takes the fallback.
85
+ </div>
86
+ ) : (
87
+ <div className="mb-2 space-y-1">
88
+ {rules.map((rule, index) => {
89
+ const showComparand = !NO_COMPARAND_OPS.has(rule.op);
90
+ return (
91
+ <div key={index} className="group flex items-center gap-1.5">
92
+ <span className="w-3 shrink-0 text-right font-mono text-[10px] text-muted-foreground/50">
93
+ {index + 1}
94
+ </span>
95
+ <Input
96
+ className="min-w-0 flex-1"
97
+ value={rule.name}
98
+ placeholder="branch name"
99
+ spellCheck={false}
100
+ onChange={(e) => updateRule(index, { name: e.target.value })}
101
+ />
102
+ <Combobox
103
+ className="w-28 shrink-0"
104
+ options={OP_OPTIONS}
105
+ value={rule.op}
106
+ onChange={(op) => updateRule(index, { op })}
107
+ placeholder="op"
108
+ searchPlaceholder="Search operators…"
109
+ />
110
+ {showComparand ? (
111
+ <Input
112
+ className="min-w-0 flex-1"
113
+ value={comparandText(rule.value)}
114
+ placeholder="value"
115
+ spellCheck={false}
116
+ onChange={(e) => updateRule(index, { value: parseComparand(e.target.value) })}
117
+ />
118
+ ) : (
119
+ <div className="flex-1" />
120
+ )}
121
+ <Button
122
+ variant="ghost"
123
+ size="icon"
124
+ className="size-6 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 hover:text-destructive"
125
+ onClick={() => removeRule(index)}
126
+ aria-label="Remove rule"
127
+ >
128
+ <Trash2Icon className="size-3" />
129
+ </Button>
130
+ </div>
131
+ );
132
+ })}
133
+ </div>
134
+ )}
135
+ <Button
136
+ variant="ghost"
137
+ size="xs"
138
+ className="-ml-1.5 gap-1 text-muted-foreground"
139
+ onClick={addRule}
140
+ >
141
+ <PlusIcon className="size-3.5" />
142
+ Add branch
143
+ </Button>
144
+ <div className="mt-2.5 flex items-center gap-1.5 border-t border-border pt-2.5">
145
+ <span className="shrink-0 text-[11.5px] text-muted-foreground">otherwise →</span>
146
+ <Input
147
+ className="min-w-0 flex-1"
148
+ value={fallback}
149
+ placeholder="fallback branch"
150
+ spellCheck={false}
151
+ onChange={(e) => updateNodeConfig(node.id, 'fallback', e.target.value)}
152
+ />
153
+ </div>
154
+ </div>
155
+ );
156
+ }