@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,193 @@
1
+ import type { AgentEvent } from './types';
2
+
3
+ interface ClaudeToolResultTextBlock {
4
+ type: string;
5
+ text?: string;
6
+ }
7
+
8
+ interface ClaudeContentBlock {
9
+ type: string;
10
+ text?: string;
11
+ name?: string;
12
+ input?: Record<string, unknown>;
13
+ id?: string; // tool_use id (assistant tool_use blocks)
14
+ tool_use_id?: string; // correlates a tool_result back to its tool_use
15
+ content?: string | ClaudeToolResultTextBlock[]; // tool_result payload
16
+ is_error?: boolean; // tool_result failure flag
17
+ }
18
+
19
+ interface ClaudeLine {
20
+ type: string;
21
+ subtype?: string;
22
+ is_error?: boolean;
23
+ message?: {
24
+ content?: ClaudeContentBlock[];
25
+ };
26
+ result?: string;
27
+ usage?: Record<string, number>;
28
+ }
29
+
30
+ /** Best-effort extraction of a shell command from a Bash tool_use input. */
31
+ function bashCommandFrom(block: ClaudeContentBlock): string | undefined {
32
+ const input = block.input;
33
+ if (!input) return undefined;
34
+ const command = input.command;
35
+ return typeof command === 'string' ? command : undefined;
36
+ }
37
+
38
+ /** Fallback label for a tool whose input we don't format specially (or that's
39
+ * missing its expected field): `<name> <compact-json>` capped at 80 chars. */
40
+ function compactLabel(name: string, input: Record<string, unknown>): string {
41
+ const json = JSON.stringify(input ?? {});
42
+ return `${name} ${json.length > 80 ? json.slice(0, 80) : json}`;
43
+ }
44
+
45
+ /**
46
+ * Human label for a non-mcp tool_use block, used as a `command` event's
47
+ * `command` string so Read/Edit/Grep/… fold into the UI's "Ran N commands"
48
+ * group instead of leaking as reasoning prose. Bash keeps its raw shell command;
49
+ * the common Claude tools get a purpose-built one-liner; anything else gets the
50
+ * compact `<name> <json>` fallback.
51
+ */
52
+ function commandLabelFor(block: ClaudeContentBlock): string {
53
+ const name = block.name ?? 'tool';
54
+ const input = (block.input ?? {}) as Record<string, unknown>;
55
+ const str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined);
56
+
57
+ switch (name) {
58
+ case 'Bash':
59
+ return bashCommandFrom(block) ?? compactLabel(name, input);
60
+ case 'Read': {
61
+ const fp = str(input.file_path);
62
+ if (!fp) return compactLabel(name, input);
63
+ const bits: string[] = [];
64
+ if (typeof input.offset === 'number') bits.push(`offset ${input.offset}`);
65
+ if (typeof input.limit === 'number') bits.push(`limit ${input.limit}`);
66
+ return `Read ${fp}${bits.length ? ` (${bits.join(', ')})` : ''}`;
67
+ }
68
+ case 'Edit':
69
+ case 'Write': {
70
+ const fp = str(input.file_path);
71
+ return fp ? `${name} ${fp}` : compactLabel(name, input);
72
+ }
73
+ case 'Grep': {
74
+ const pattern = str(input.pattern);
75
+ if (!pattern) return compactLabel(name, input);
76
+ const path = str(input.path);
77
+ return `Grep ${pattern}${path ? ` ${path}` : ''}`;
78
+ }
79
+ case 'Glob': {
80
+ const pattern = str(input.pattern);
81
+ return pattern ? `Glob ${pattern}` : compactLabel(name, input);
82
+ }
83
+ case 'TodoWrite':
84
+ return 'update todos';
85
+ default:
86
+ return compactLabel(name, input);
87
+ }
88
+ }
89
+
90
+ /** Flattens a tool_result's `content` (string, or array of text blocks) to plain text. */
91
+ function toolResultTextFrom(block: ClaudeContentBlock): string {
92
+ const content = block.content;
93
+ if (typeof content === 'string') return content;
94
+ if (Array.isArray(content)) {
95
+ return content
96
+ .filter((c) => c.type === 'text' && typeof c.text === 'string')
97
+ .map((c) => c.text)
98
+ .join('');
99
+ }
100
+ return '';
101
+ }
102
+
103
+ export function parseClaudeLine(line: string): AgentEvent[] {
104
+ const trimmed = line.trim();
105
+ if (!trimmed) return [];
106
+
107
+ let parsed: ClaudeLine;
108
+ try {
109
+ parsed = JSON.parse(trimmed);
110
+ } catch {
111
+ return [];
112
+ }
113
+
114
+ switch (parsed.type) {
115
+ case 'system': {
116
+ if (parsed.subtype === 'init') return [{ type: 'started' }];
117
+ // hook_started / hook_response and any other system noise — ignore.
118
+ return [];
119
+ }
120
+
121
+ case 'assistant': {
122
+ const blocks = parsed.message?.content ?? [];
123
+ // Claude routinely emits multiple content blocks per message (e.g.
124
+ // explain-then-call `[text, tool_use]`, or parallel tool calls) — emit
125
+ // one AgentEvent per meaningful block, in order, so the console shows the
126
+ // full picture. `thinking` and other block types are skipped.
127
+ const events: AgentEvent[] = [];
128
+ for (const block of blocks) {
129
+ if (block.type === 'text' && block.text) {
130
+ events.push({ type: 'message', text: block.text });
131
+ } else if (block.type === 'tool_use') {
132
+ // MCP tools surface as `mcp__<server>__<tool>` — show them as MCP calls.
133
+ if (block.name?.startsWith('mcp__')) {
134
+ const [, server, ...toolParts] = block.name.split('__');
135
+ events.push({
136
+ type: 'mcp',
137
+ mcpServer: server || 'mcp',
138
+ mcpTool: toolParts.join('__') || 'tool',
139
+ mcpStatus: 'in_progress',
140
+ ...(block.id ? { toolUseId: block.id } : {}),
141
+ });
142
+ continue;
143
+ }
144
+ // Every other tool (Bash, Read, Edit, Grep, Glob, TodoWrite, …) is a
145
+ // `command` event with a human label, correlated by tool_use_id like
146
+ // Bash always was — so it folds into the UI's "Ran N commands" group
147
+ // instead of leaking as reasoning prose.
148
+ events.push({
149
+ type: 'command',
150
+ command: commandLabelFor(block),
151
+ commandStatus: 'in_progress',
152
+ ...(block.id ? { toolUseId: block.id } : {}),
153
+ });
154
+ }
155
+ }
156
+ return events;
157
+ }
158
+
159
+ case 'user': {
160
+ // Tool results. Emit a partial completion carrying the tool_use_id +
161
+ // output text + status; the adapter looks up the original command text
162
+ // by id (it isn't repeated in the tool_result line) and skips gracefully
163
+ // if the id doesn't correspond to a command it's tracking (e.g. a
164
+ // non-Bash tool result).
165
+ const blocks = parsed.message?.content ?? [];
166
+ const events: AgentEvent[] = [];
167
+ for (const block of blocks) {
168
+ if (block.type === 'tool_result' && block.tool_use_id) {
169
+ events.push({
170
+ type: 'command',
171
+ toolUseId: block.tool_use_id,
172
+ commandStatus: block.is_error ? 'failed' : 'completed',
173
+ output: toolResultTextFrom(block),
174
+ });
175
+ }
176
+ }
177
+ return events;
178
+ }
179
+
180
+ case 'result': {
181
+ if (parsed.subtype === 'error' || parsed.is_error) {
182
+ return [{ type: 'error', text: parsed.result ?? 'error' }];
183
+ }
184
+ return [{ type: 'done', usage: parsed.usage }];
185
+ }
186
+
187
+ case 'rate_limit_event':
188
+ return [];
189
+
190
+ default:
191
+ return [];
192
+ }
193
+ }
@@ -0,0 +1,125 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { spawnCodex } from './codexAdapter';
6
+ import type { AgentEvent } from './types';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const fakeCodexPath = path.join(__dirname, '__fixtures__', 'fake-codex.mjs');
10
+
11
+ async function collect(events: AsyncIterable<AgentEvent>): Promise<AgentEvent[]> {
12
+ const out: AgentEvent[] = [];
13
+ for await (const e of events) {
14
+ out.push(e);
15
+ }
16
+ return out;
17
+ }
18
+
19
+ describe('spawnCodex', () => {
20
+ it('normalizes fake-codex stdout into [started, command, message, done]', async () => {
21
+ const agent = spawnCodex('do the thing', os.tmpdir(), { bin: fakeCodexPath });
22
+ const events = await collect(agent.events);
23
+ expect(events).toEqual([
24
+ { type: 'started' },
25
+ { type: 'command', command: 'ls', commandStatus: 'in_progress' },
26
+ { type: 'message', text: 'Done listing files' },
27
+ { type: 'done', usage: { output_tokens: 5 } },
28
+ ]);
29
+ });
30
+
31
+ it('yields a terminal error event when the process exits non-zero without a done event', async () => {
32
+ process.env.FAKE_CODEX_FAIL = '1';
33
+ try {
34
+ const agent = spawnCodex('do the thing', os.tmpdir(), { bin: fakeCodexPath });
35
+ const events = await collect(agent.events);
36
+ expect(events[0]).toEqual({ type: 'started' });
37
+ expect(events[1]).toEqual({ type: 'command', command: 'ls', commandStatus: 'in_progress' });
38
+ expect(events[2]).toEqual({ type: 'message', text: 'Done listing files' });
39
+ const last = events[events.length - 1];
40
+ expect(last.type).toBe('error');
41
+ expect(last.text).not.toContain('hint:');
42
+ } finally {
43
+ delete process.env.FAKE_CODEX_FAIL;
44
+ }
45
+ });
46
+
47
+ it('appends an actionable hint to the terminal error when stderr looks like a model-rejection', async () => {
48
+ process.env.FAKE_CODEX_MODEL_REJECT = '1';
49
+ try {
50
+ const agent = spawnCodex('do the thing', os.tmpdir(), { bin: fakeCodexPath });
51
+ const events = await collect(agent.events);
52
+ const last = events[events.length - 1];
53
+ expect(last.type).toBe('error');
54
+ expect(last.text).toContain(
55
+ 'hint: your codex CLI may be too old for the selected model — upgrade it or switch backend in Settings.',
56
+ );
57
+ } finally {
58
+ delete process.env.FAKE_CODEX_MODEL_REJECT;
59
+ }
60
+ });
61
+
62
+ it('delivers every event including the trailing done when the child emits many lines then exits (no event-loss race)', async () => {
63
+ process.env.FAKE_CODEX_MANY = '1';
64
+ try {
65
+ const agent = spawnCodex('do the thing', os.tmpdir(), { bin: fakeCodexPath });
66
+ const events = await collect(agent.events);
67
+ // started + command + message + 27 filler messages + done = 31 events
68
+ expect(events.length).toBe(31);
69
+ expect(events[0]).toEqual({ type: 'started' });
70
+ expect(events[events.length - 1]).toEqual({ type: 'done', usage: { output_tokens: 5 } });
71
+ const fillers = events.filter((e) => e.type === 'message' && e.text?.startsWith('filler '));
72
+ expect(fillers.length).toBe(27);
73
+ } finally {
74
+ delete process.env.FAKE_CODEX_MANY;
75
+ }
76
+ });
77
+
78
+ it('yields a terminal error (and does not crash) when the binary cannot be spawned', async () => {
79
+ const agent = spawnCodex('do the thing', os.tmpdir(), { bin: '/nonexistent/nope' });
80
+ const events = await collect(agent.events);
81
+ expect(events.length).toBeGreaterThan(0);
82
+ const last = events[events.length - 1];
83
+ expect(last.type).toBe('error');
84
+ expect(events.filter((e) => e.type === 'done')).toEqual([]);
85
+ });
86
+
87
+ it('cancel() sends SIGTERM and does not throw', async () => {
88
+ const agent = spawnCodex('do the thing', os.tmpdir(), { bin: fakeCodexPath });
89
+ expect(() => agent.cancel()).not.toThrow();
90
+ await collect(agent.events);
91
+ });
92
+
93
+ it('cancel() escalates to SIGKILL if the process ignores SIGTERM', async () => {
94
+ process.env.FAKE_CODEX_HANG = '1';
95
+ try {
96
+ const agent = spawnCodex('do the thing', os.tmpdir(), { bin: fakeCodexPath, killGraceMs: 50 });
97
+ // Let the fixture install its SIGTERM handler before we cancel.
98
+ await new Promise((r) => setTimeout(r, 100));
99
+ agent.cancel();
100
+ const events = await collect(agent.events);
101
+ const last = events[events.length - 1];
102
+ // SIGKILL can't be caught, so the process dies without emitting its
103
+ // own terminal event — the adapter must synthesize an error.
104
+ expect(last.type).toBe('error');
105
+ } finally {
106
+ delete process.env.FAKE_CODEX_HANG;
107
+ }
108
+ }, 10000);
109
+
110
+ it('synthesizes a terminal done when the process exits 0 without a turn.completed', async () => {
111
+ process.env.FAKE_CODEX_NODONE = '1';
112
+ try {
113
+ const agent = spawnCodex('do the thing', os.tmpdir(), { bin: fakeCodexPath });
114
+ const events = await collect(agent.events);
115
+ expect(events).toEqual([
116
+ { type: 'started' },
117
+ { type: 'command', command: 'ls', commandStatus: 'in_progress' },
118
+ { type: 'message', text: 'Done listing files' },
119
+ { type: 'done' },
120
+ ]);
121
+ } finally {
122
+ delete process.env.FAKE_CODEX_NODONE;
123
+ }
124
+ });
125
+ });
@@ -0,0 +1,198 @@
1
+ import { spawn, type ChildProcessByStdio } from 'node:child_process';
2
+ import type { Readable } from 'node:stream';
3
+ import readline from 'node:readline';
4
+ import { parseCodexLine } from './codexParse';
5
+ import { modelRejectionHint } from './modelRejectionHint';
6
+ import type { AgentEvent } from './types';
7
+
8
+ const STDERR_TAIL_BYTES = 2048;
9
+
10
+ export interface SpawnedAgent {
11
+ events: AsyncIterable<AgentEvent>;
12
+ cancel(): void;
13
+ }
14
+
15
+ export interface CodexOptions {
16
+ model?: string;
17
+ reasoning?: 'low' | 'medium' | 'high';
18
+ bin?: string;
19
+ /** Grace period (ms) between SIGTERM and the SIGKILL escalation in cancel(). Test hook. */
20
+ killGraceMs?: number;
21
+ }
22
+
23
+ export function spawnCodex(prompt: string, projectDir: string, opts: CodexOptions = {}): SpawnedAgent {
24
+ const bin = opts.bin ?? 'codex';
25
+ const args = [
26
+ // Never ask for per-call approval — the studio runs codex non-interactively,
27
+ // so an approval prompt has no one to answer it and codex cancels the call
28
+ // ("user cancelled MCP tool call"). This is a top-level flag (before `exec`).
29
+ // The sandbox (-s workspace-write, below) still constrains what can run.
30
+ '-a',
31
+ 'never',
32
+ 'exec',
33
+ '-C',
34
+ projectDir,
35
+ '-s',
36
+ 'workspace-write',
37
+ // Keep the workspace-write FILE sandbox, but allow network — the agent's CLI
38
+ // ops (node bin/emberflow.mjs run/get-workflow/…) must reach the runner on
39
+ // 127.0.0.1; workspace-write blocks all network by default (curl → 000).
40
+ '-c',
41
+ 'sandbox_workspace_write.network_access=true',
42
+ '-c',
43
+ `model_reasoning_effort=${opts.reasoning ?? 'low'}`,
44
+ '--json',
45
+ ...(opts.model ? ['-m', opts.model] : []),
46
+ prompt,
47
+ ];
48
+
49
+ // detached: give codex its own process group so cancel() can tree-kill any
50
+ // subprocesses it spawns (workspace-write), not just the top-level process.
51
+ // stdio ['ignore','pipe','pipe'] → stdin is null, stdout/stderr are Readable.
52
+ const child: ChildProcessByStdio<null, Readable, Readable> = bin.endsWith('.mjs')
53
+ ? spawn(process.execPath, [bin, ...args], { stdio: ['ignore', 'pipe', 'pipe'], detached: true })
54
+ : spawn(bin, args, { stdio: ['ignore', 'pipe', 'pipe'], detached: true });
55
+
56
+ // Buffer only the last ~2KB of stderr — non-fatal in general (e.g. the user's
57
+ // global MCP servers spew auth errors there), but useful as a diagnostic tail
58
+ // when the process dies without a terminal event (see maybeFinalize below).
59
+ let stderrTail = '';
60
+ child.stderr.on('data', (chunk: Buffer) => {
61
+ stderrTail = (stderrTail + chunk.toString('utf8')).slice(-STDERR_TAIL_BYTES);
62
+ });
63
+
64
+ const queue: AgentEvent[] = [];
65
+ const resolvers: Array<() => void> = [];
66
+ let done = false;
67
+ let sawTerminal = false;
68
+
69
+ function push(event: AgentEvent) {
70
+ queue.push(event);
71
+ const resolve = resolvers.shift();
72
+ if (resolve) resolve();
73
+ }
74
+
75
+ function finish() {
76
+ done = true;
77
+ while (resolvers.length) {
78
+ const resolve = resolvers.shift();
79
+ if (resolve) resolve();
80
+ }
81
+ }
82
+
83
+ const rl = readline.createInterface({ input: child.stdout });
84
+ rl.on('line', (line) => {
85
+ const event = parseCodexLine(line);
86
+ if (event) {
87
+ if (event.type === 'done' || event.type === 'error') sawTerminal = true;
88
+ push(event);
89
+ }
90
+ });
91
+
92
+ // Finalize only once BOTH the stdout stream is fully drained (readline 'close')
93
+ // AND the process has exited ('close', which fires after all stdio is flushed —
94
+ // unlike 'exit', which can fire while trailing stdout lines are still buffered).
95
+ let rlClosed = false;
96
+ let procClosed = false;
97
+ let exitCode: number | null = null;
98
+ let escalateTimer: NodeJS.Timeout | undefined;
99
+
100
+ function clearEscalation() {
101
+ if (escalateTimer) {
102
+ clearTimeout(escalateTimer);
103
+ escalateTimer = undefined;
104
+ }
105
+ }
106
+
107
+ function maybeFinalize() {
108
+ if (!rlClosed || !procClosed) return;
109
+ clearEscalation();
110
+ if (!sawTerminal) {
111
+ if (exitCode === 0) {
112
+ // Clean exit but the model never emitted turn.completed — synthesize a done.
113
+ push({ type: 'done' });
114
+ } else {
115
+ const hint = modelRejectionHint('codex', stderrTail);
116
+ const text = hint ? `codex exited with code ${exitCode} (${hint})` : `codex exited with code ${exitCode}`;
117
+ push({ type: 'error', text });
118
+ }
119
+ sawTerminal = true;
120
+ }
121
+ finish();
122
+ }
123
+
124
+ rl.on('close', () => {
125
+ rlClosed = true;
126
+ maybeFinalize();
127
+ });
128
+
129
+ child.on('close', (code) => {
130
+ procClosed = true;
131
+ exitCode = code;
132
+ maybeFinalize();
133
+ });
134
+
135
+ // A failed spawn (missing binary, bad EMBERFLOW_*_BIN, permissions, ...) emits
136
+ // an 'error' event on the child instead of ever exiting/closing normally. Left
137
+ // unhandled, Node rethrows it and crashes the whole runner process — so we
138
+ // must handle it and finalize the stream ourselves. 'close' still fires after
139
+ // a spawn error (with code null), so guard against double-finalizing there.
140
+ let spawnFailed = false;
141
+ child.on('error', (err) => {
142
+ if (spawnFailed) return;
143
+ spawnFailed = true;
144
+ clearEscalation();
145
+ push({ type: 'error', text: `failed to start codex: ${err.message}` });
146
+ sawTerminal = true;
147
+ rlClosed = true;
148
+ procClosed = true;
149
+ finish();
150
+ });
151
+
152
+ async function* generate(): AsyncGenerator<AgentEvent> {
153
+ while (true) {
154
+ if (queue.length > 0) {
155
+ yield queue.shift()!;
156
+ continue;
157
+ }
158
+ if (done) return;
159
+ await new Promise<void>((resolve) => resolvers.push(resolve));
160
+ }
161
+ }
162
+
163
+ // Kill the whole process group (negative pid) so codex's own subprocesses die
164
+ // too; fall back to a plain kill if the group signal fails (e.g. already gone
165
+ // or no group). Used by both the initial SIGTERM and the SIGKILL escalation.
166
+ function killGroup(signal: NodeJS.Signals) {
167
+ try {
168
+ if (child.pid != null) {
169
+ process.kill(-child.pid, signal);
170
+ return;
171
+ }
172
+ } catch {
173
+ // group kill failed — fall through to a direct kill
174
+ }
175
+ try {
176
+ child.kill(signal);
177
+ } catch {
178
+ // process already gone — nothing to do
179
+ }
180
+ }
181
+
182
+ return {
183
+ events: { [Symbol.asyncIterator]: generate },
184
+ cancel() {
185
+ killGroup('SIGTERM');
186
+
187
+ // If the process (or a stubborn grandchild/MCP server) ignores SIGTERM,
188
+ // escalate to SIGKILL after a grace period so a hung agent can't
189
+ // permanently lock the run slot. Cleared once close/error finalizes.
190
+ clearEscalation();
191
+ escalateTimer = setTimeout(() => {
192
+ escalateTimer = undefined;
193
+ killGroup('SIGKILL');
194
+ }, opts.killGraceMs ?? 3000);
195
+ escalateTimer.unref?.();
196
+ },
197
+ };
198
+ }
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseCodexLine } from './codexParse';
3
+
4
+ describe('parseCodexLine', () => {
5
+ it('maps a command_execution start', () => {
6
+ const e = parseCodexLine('{"type":"item.started","item":{"id":"i3","type":"command_execution","command":"ls","status":"in_progress"}}');
7
+ expect(e).toEqual({ type: 'command', command: 'ls', commandStatus: 'in_progress' });
8
+ });
9
+ it('maps a completed (failed) command with exit code + output', () => {
10
+ const e = parseCodexLine('{"type":"item.completed","item":{"id":"i3","type":"command_execution","command":"curl x","exit_code":6,"aggregated_output":"000","status":"failed"}}');
11
+ expect(e).toEqual({ type: 'command', command: 'curl x', exitCode: 6, output: '000', commandStatus: 'failed' });
12
+ });
13
+ it('maps an agent_message to message', () => {
14
+ expect(parseCodexLine('{"type":"item.completed","item":{"type":"agent_message","text":"Updated file"}}'))
15
+ .toEqual({ type: 'message', text: 'Updated file' });
16
+ });
17
+
18
+ it('maps a non-fatal error ITEM to a warning message (NOT a terminal error)', () => {
19
+ // codex `error` items (e.g. an MCP server failing to auth) must not fail the
20
+ // whole run — only a non-zero process exit does. See codexParse.ts.
21
+ expect(parseCodexLine('{"type":"item.completed","item":{"type":"error","text":"mcp auth failed"}}'))
22
+ .toEqual({ type: 'message', text: '⚠ mcp auth failed' });
23
+ });
24
+ it('reads the error detail from `message` when `text` is absent — the shape current codex ships', () => {
25
+ // Without this, a fatal-cause diagnostic (e.g. "model requires a newer
26
+ // version of Codex") renders as a bare "⚠ error" and the user learns nothing.
27
+ expect(
28
+ parseCodexLine('{"type":"item.completed","item":{"type":"error","message":"model requires a newer version of Codex"}}'),
29
+ ).toEqual({ type: 'message', text: '⚠ model requires a newer version of Codex' });
30
+ });
31
+ it('maps an mcp_tool_call (flat server/tool) to an mcp event with status', () => {
32
+ expect(
33
+ parseCodexLine('{"type":"item.started","item":{"type":"mcp_tool_call","server":"emberflow","tool":"run_operation","status":"in_progress"}}'),
34
+ ).toEqual({ type: 'mcp', mcpServer: 'emberflow', mcpTool: 'run_operation', mcpStatus: 'in_progress' });
35
+ });
36
+ it('maps a completed mcp_tool_call, reading server/tool from invocation + result', () => {
37
+ expect(
38
+ parseCodexLine('{"type":"item.completed","item":{"type":"mcp_tool_call","invocation":{"server":"emberflow","tool":"run_operation"},"status":"completed","result":{"status":200}}}'),
39
+ ).toEqual({ type: 'mcp', mcpServer: 'emberflow', mcpTool: 'run_operation', mcpStatus: 'completed', output: '{\n "status": 200\n}' });
40
+ });
41
+ it('tolerates missing fields on an mcp_tool_call (falls back to mcp/tool)', () => {
42
+ expect(parseCodexLine('{"type":"item.completed","item":{"type":"mcp_tool_call"}}'))
43
+ .toEqual({ type: 'mcp', mcpServer: 'mcp', mcpTool: 'tool', mcpStatus: undefined });
44
+ });
45
+ it('maps turn.completed to done with usage', () => {
46
+ expect(parseCodexLine('{"type":"turn.completed","usage":{"output_tokens":5}}'))
47
+ .toEqual({ type: 'done', usage: { output_tokens: 5 } });
48
+ });
49
+ it('maps thread.started/turn.started to started', () => {
50
+ expect(parseCodexLine('{"type":"thread.started"}')).toEqual({ type: 'started' });
51
+ expect(parseCodexLine('{"type":"turn.started"}')).toEqual({ type: 'started' });
52
+ });
53
+ it('ignores unknown and blank lines', () => {
54
+ expect(parseCodexLine('{"type":"whatever"}')).toBeNull();
55
+ expect(parseCodexLine('')).toBeNull();
56
+ expect(parseCodexLine('not json')).toBeNull();
57
+ });
58
+ });