@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,23 @@
1
+ #!/usr/bin/env node
2
+ // Test fixture for runManager tests: behaves like fake-claude.mjs (prints a
3
+ // started/message/command/done sequence) but ALSO mutates a real tracked
4
+ // file in the project dir. spawnClaude runs the child with cwd=projectDir
5
+ // (unlike codex, which passes -C explicitly), so this fixture just writes
6
+ // relative to process.cwd().
7
+ import { writeFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+
10
+ writeFileSync(join(process.cwd(), 'flows', 'hello.json'), 'agent was here\n');
11
+
12
+ const lines = [
13
+ { type: 'system', subtype: 'init', session_id: 'fake-session' },
14
+ { type: 'assistant', message: { content: [{ type: 'text', text: 'Working on it' }] } },
15
+ { type: 'assistant', message: { content: [{ type: 'tool_use', name: 'Bash', input: { command: 'ls' } }] } },
16
+ { type: 'result', subtype: 'success', usage: { output_tokens: 5 } },
17
+ ];
18
+
19
+ for (const line of lines) {
20
+ process.stdout.write(JSON.stringify(line) + '\n');
21
+ }
22
+
23
+ process.exit(0);
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // Test fixture: fakes `claude -p ... --output-format stream-json` output. Ignores all args.
3
+ // Prints fixture stream-json JSONL lines to stdout, then exits.
4
+ // Modes (env vars):
5
+ // default → system/init, assistant text, assistant Bash tool_use, result/success; exit 0
6
+ // FAKE_CLAUDE_FAIL=1 → system/init, assistant text + stderr noise; exit 1 (no result)
7
+ // FAKE_CLAUDE_NODONE=1 → system/init, assistant text; exit 0 (no result event)
8
+ // FAKE_CLAUDE_MODEL_REJECT=1 → system/init, assistant text + stderr "unknown model"; exit 1 (no result)
9
+ // FAKE_CLAUDE_HANG=1 → system/init, assistant text; ignores SIGTERM and hangs
10
+ // until killed (SIGKILL), to exercise cancel() escalation
11
+
12
+ const lines = [
13
+ { type: 'system', subtype: 'init', session_id: 'fake-session' },
14
+ {
15
+ type: 'assistant',
16
+ message: { content: [{ type: 'text', text: 'Working on it' }] },
17
+ },
18
+ {
19
+ type: 'assistant',
20
+ message: { content: [{ type: 'tool_use', id: 't1', name: 'Bash', input: { command: 'ls' } }] },
21
+ },
22
+ {
23
+ type: 'user',
24
+ message: { content: [{ type: 'tool_result', tool_use_id: 't1', content: 'file1\nfile2' }] },
25
+ },
26
+ ];
27
+
28
+ for (const line of lines) {
29
+ process.stdout.write(JSON.stringify(line) + '\n');
30
+ }
31
+
32
+ if (process.env.FAKE_CLAUDE_HANG === '1') {
33
+ process.on('SIGTERM', () => {
34
+ // Simulate a stuck child that ignores SIGTERM — only SIGKILL (uncatchable)
35
+ // will end it.
36
+ });
37
+ setInterval(() => {}, 100000);
38
+ } else {
39
+ if (process.env.FAKE_CLAUDE_FAIL === '1') {
40
+ process.stderr.write('some non-fatal noise\n');
41
+ process.exit(1);
42
+ }
43
+
44
+ if (process.env.FAKE_CLAUDE_MODEL_REJECT === '1') {
45
+ process.stderr.write('Error: unknown model "claude-9000"\n');
46
+ process.exit(1);
47
+ }
48
+
49
+ if (process.env.FAKE_CLAUDE_NODONE === '1') {
50
+ process.exit(0);
51
+ }
52
+
53
+ process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', usage: { output_tokens: 5 } }) + '\n');
54
+ process.exit(0);
55
+ }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ // Test fixture for runManager tests: behaves like fake-codex.mjs (prints a
3
+ // started/command/message/done sequence) but ALSO mutates a real tracked
4
+ // file in the project dir it was invoked with `-C <dir>`, so diff/revert
5
+ // against a real git snapshot can be exercised end-to-end. `git diff` never
6
+ // shows untracked files, so this must touch a file that's already committed
7
+ // (unlike a brand-new file) for the diff assertion to see real content.
8
+ import { writeFileSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+
11
+ const cIndex = process.argv.indexOf('-C');
12
+ const projectDir = cIndex !== -1 ? process.argv[cIndex + 1] : process.cwd();
13
+
14
+ writeFileSync(join(projectDir, 'flows', 'hello.json'), 'agent was here\n');
15
+
16
+ const lines = [
17
+ { type: 'thread.started' },
18
+ { type: 'item.started', item: { id: 'i1', type: 'command_execution', command: 'ls', status: 'in_progress' } },
19
+ { type: 'item.completed', item: { type: 'agent_message', text: 'Done listing files' } },
20
+ { type: 'turn.completed', usage: { output_tokens: 5 } },
21
+ ];
22
+
23
+ for (const line of lines) {
24
+ process.stdout.write(JSON.stringify(line) + '\n');
25
+ }
26
+
27
+ process.exit(0);
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ // Test fixture: fakes `codex exec --json` output. Ignores all args.
3
+ // Prints fixture JSONL lines to stdout, then exits.
4
+ // Modes (env vars):
5
+ // default → started, command, message, turn.completed; exit 0
6
+ // FAKE_CODEX_FAIL=1 → started, command, message + stderr noise; exit 1 (no done)
7
+ // FAKE_CODEX_MANY=1 → started, command, message, 27 filler, turn.completed; exit 0 (race test)
8
+ // FAKE_CODEX_NODONE=1 → started, command, message; exit 0 (no turn.completed)
9
+ // FAKE_CODEX_MODEL_REJECT=1 → started, command, message + stderr "unsupported model"; exit 1 (no done)
10
+ // FAKE_CODEX_HANG=1 → started, command, message; ignores SIGTERM and hangs
11
+ // until killed (SIGKILL), to exercise cancel() escalation
12
+
13
+ const lines = [
14
+ { type: 'thread.started' },
15
+ { type: 'item.started', item: { id: 'i1', type: 'command_execution', command: 'ls', status: 'in_progress' } },
16
+ { type: 'item.completed', item: { type: 'agent_message', text: 'Done listing files' } },
17
+ ];
18
+
19
+ if (process.env.FAKE_CODEX_MANY === '1') {
20
+ for (let i = 0; i < 27; i++) {
21
+ lines.push({ type: 'item.completed', item: { type: 'agent_message', text: `filler ${i}` } });
22
+ }
23
+ }
24
+
25
+ for (const line of lines) {
26
+ process.stdout.write(JSON.stringify(line) + '\n');
27
+ }
28
+
29
+ if (process.env.FAKE_CODEX_HANG === '1') {
30
+ process.on('SIGTERM', () => {
31
+ // Simulate a stuck child that ignores SIGTERM — only SIGKILL (uncatchable)
32
+ // will end it.
33
+ });
34
+ setInterval(() => {}, 100000);
35
+ } else {
36
+ if (process.env.FAKE_CODEX_FAIL === '1') {
37
+ process.stderr.write('some non-fatal mcp auth error\n');
38
+ process.exit(1);
39
+ }
40
+
41
+ if (process.env.FAKE_CODEX_MODEL_REJECT === '1') {
42
+ process.stderr.write('Error: unsupported model \'gpt-5.6-sol\'\n');
43
+ process.exit(1);
44
+ }
45
+
46
+ if (process.env.FAKE_CODEX_NODONE === '1') {
47
+ process.exit(0);
48
+ }
49
+
50
+ process.stdout.write(JSON.stringify({ type: 'turn.completed', usage: { output_tokens: 5 } }) + '\n');
51
+ process.exit(0);
52
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // Test fixture for detect.test.ts: exits clean with empty stdout.
3
+ process.exit(0);
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // Test fixture for detect.test.ts: exits nonzero (binary present but --version unsupported/broken).
3
+ process.stderr.write('unknown flag --version\n');
4
+ process.exit(1);
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // Test fixture for detect.test.ts: exits clean but prints no parseable semver token.
3
+ process.stdout.write('some unversioned build\n');
4
+ process.exit(0);
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // Test fixture for detect.test.ts: well-formed --version output.
3
+ process.stdout.write('fake-agent-cli 0.142.5\n');
4
+ process.exit(0);
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // Test fixture for detect.test.ts: never exits — exercises the probe() timeout.
3
+ setInterval(() => {}, 100000);
@@ -0,0 +1,127 @@
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 { spawnClaude } from './claudeAdapter';
6
+ import type { AgentEvent } from './types';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const fakeClaudePath = path.join(__dirname, '__fixtures__', 'fake-claude.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('spawnClaude', () => {
20
+ it('normalizes fake-claude stdout into [started, message, command in_progress, command completed, done]', async () => {
21
+ const agent = spawnClaude('do the thing', os.tmpdir(), { bin: fakeClaudePath });
22
+ const events = await collect(agent.events);
23
+ expect(events).toEqual([
24
+ { type: 'started' },
25
+ { type: 'message', text: 'Working on it' },
26
+ { type: 'command', command: 'ls', commandStatus: 'in_progress', toolUseId: 't1' },
27
+ { type: 'command', command: 'ls', commandStatus: 'completed', output: 'file1\nfile2', toolUseId: 't1' },
28
+ { type: 'done', usage: { output_tokens: 5 } },
29
+ ]);
30
+ });
31
+
32
+ it('resolves a Bash command group to completed with output when the tool_result arrives (AgentConsole dedup fixture)', async () => {
33
+ const agent = spawnClaude('do the thing', os.tmpdir(), { bin: fakeClaudePath });
34
+ const events = await collect(agent.events);
35
+ const inProgress = events.find((e) => e.type === 'command' && e.commandStatus === 'in_progress');
36
+ const completed = events.find((e) => e.type === 'command' && e.commandStatus === 'completed');
37
+ expect(inProgress).toEqual({ type: 'command', command: 'ls', commandStatus: 'in_progress', toolUseId: 't1' });
38
+ // Same `command` text as the in_progress event — this is what lets
39
+ // AgentConsole's existing dedup (in_progress + same command text →
40
+ // replaced by completed) resolve the group to "Ran" + output.
41
+ expect(completed).toEqual({
42
+ type: 'command',
43
+ command: 'ls',
44
+ commandStatus: 'completed',
45
+ output: 'file1\nfile2',
46
+ toolUseId: 't1',
47
+ });
48
+ });
49
+
50
+ it('yields a terminal error event when the process exits non-zero without a result event', async () => {
51
+ process.env.FAKE_CLAUDE_FAIL = '1';
52
+ try {
53
+ const agent = spawnClaude('do the thing', os.tmpdir(), { bin: fakeClaudePath });
54
+ const events = await collect(agent.events);
55
+ expect(events[0]).toEqual({ type: 'started' });
56
+ const last = events[events.length - 1];
57
+ expect(last.type).toBe('error');
58
+ expect(last.text).not.toContain('hint:');
59
+ } finally {
60
+ delete process.env.FAKE_CLAUDE_FAIL;
61
+ }
62
+ });
63
+
64
+ it('appends an actionable hint to the terminal error when stderr looks like a model-rejection', async () => {
65
+ process.env.FAKE_CLAUDE_MODEL_REJECT = '1';
66
+ try {
67
+ const agent = spawnClaude('do the thing', os.tmpdir(), { bin: fakeClaudePath });
68
+ const events = await collect(agent.events);
69
+ const last = events[events.length - 1];
70
+ expect(last.type).toBe('error');
71
+ expect(last.text).toContain(
72
+ 'hint: your claude CLI may be too old for the selected model — upgrade it or switch backend in Settings.',
73
+ );
74
+ } finally {
75
+ delete process.env.FAKE_CLAUDE_MODEL_REJECT;
76
+ }
77
+ });
78
+
79
+ it('yields a terminal error (and does not crash) when the binary cannot be spawned', async () => {
80
+ const agent = spawnClaude('do the thing', os.tmpdir(), { bin: '/nonexistent/nope' });
81
+ const events = await collect(agent.events);
82
+ expect(events.length).toBeGreaterThan(0);
83
+ const last = events[events.length - 1];
84
+ expect(last.type).toBe('error');
85
+ expect(events.filter((e) => e.type === 'done')).toEqual([]);
86
+ });
87
+
88
+ it('cancel() sends SIGTERM and does not throw', async () => {
89
+ const agent = spawnClaude('do the thing', os.tmpdir(), { bin: fakeClaudePath });
90
+ expect(() => agent.cancel()).not.toThrow();
91
+ await collect(agent.events);
92
+ });
93
+
94
+ it('cancel() escalates to SIGKILL if the process ignores SIGTERM', async () => {
95
+ process.env.FAKE_CLAUDE_HANG = '1';
96
+ try {
97
+ const agent = spawnClaude('do the thing', os.tmpdir(), { bin: fakeClaudePath, killGraceMs: 50 });
98
+ // Let the fixture install its SIGTERM handler before we cancel.
99
+ await new Promise((r) => setTimeout(r, 100));
100
+ agent.cancel();
101
+ const events = await collect(agent.events);
102
+ const last = events[events.length - 1];
103
+ // SIGKILL can't be caught, so the process dies without emitting its
104
+ // own terminal event — the adapter must synthesize an error.
105
+ expect(last.type).toBe('error');
106
+ } finally {
107
+ delete process.env.FAKE_CLAUDE_HANG;
108
+ }
109
+ }, 10000);
110
+
111
+ it('synthesizes a terminal done when the process exits 0 without a result event', async () => {
112
+ process.env.FAKE_CLAUDE_NODONE = '1';
113
+ try {
114
+ const agent = spawnClaude('do the thing', os.tmpdir(), { bin: fakeClaudePath });
115
+ const events = await collect(agent.events);
116
+ expect(events).toEqual([
117
+ { type: 'started' },
118
+ { type: 'message', text: 'Working on it' },
119
+ { type: 'command', command: 'ls', commandStatus: 'in_progress', toolUseId: 't1' },
120
+ { type: 'command', command: 'ls', commandStatus: 'completed', output: 'file1\nfile2', toolUseId: 't1' },
121
+ { type: 'done' },
122
+ ]);
123
+ } finally {
124
+ delete process.env.FAKE_CLAUDE_NODONE;
125
+ }
126
+ });
127
+ });
@@ -0,0 +1,212 @@
1
+ import { spawn, type ChildProcessByStdio } from 'node:child_process';
2
+ import type { Readable } from 'node:stream';
3
+ import readline from 'node:readline';
4
+ import { parseClaudeLine } from './claudeParse';
5
+ import { modelRejectionHint } from './modelRejectionHint';
6
+ import type { AgentEvent } from './types';
7
+ import type { SpawnedAgent } from './codexAdapter';
8
+
9
+ const STDERR_TAIL_BYTES = 2048;
10
+
11
+ export interface ClaudeOptions {
12
+ model?: string;
13
+ bin?: string;
14
+ /** Grace period (ms) between SIGTERM and the SIGKILL escalation in cancel(). Test hook. */
15
+ killGraceMs?: number;
16
+ }
17
+
18
+ export function spawnClaude(prompt: string, projectDir: string, opts: ClaudeOptions = {}): SpawnedAgent {
19
+ const bin = opts.bin ?? 'claude';
20
+ const args = [
21
+ '-p',
22
+ prompt,
23
+ '--output-format',
24
+ 'stream-json',
25
+ '--verbose',
26
+ // Never ask for per-call approval — the studio runs claude non-interactively
27
+ // (-p + SSE panel), so an approval prompt has no one to answer it and the
28
+ // command simply fails ("This command requires approval"), which broke even
29
+ // our own CLI calls (list-environments). Mirrors the codex adapter's
30
+ // never-ask contract; the --add-dir working-directory sandbox still fences
31
+ // file reads/edits to the project.
32
+ '--permission-mode',
33
+ 'bypassPermissions',
34
+ '--add-dir',
35
+ projectDir,
36
+ ...(opts.model ? ['--model', opts.model] : []),
37
+ ];
38
+
39
+ // detached: give claude its own process group so cancel() can tree-kill any
40
+ // subprocesses it spawns, not just the top-level process.
41
+ // stdio ['ignore','pipe','pipe'] → stdin is null, stdout/stderr are Readable.
42
+ const child: ChildProcessByStdio<null, Readable, Readable> = bin.endsWith('.mjs')
43
+ ? spawn(process.execPath, [bin, ...args], { cwd: projectDir, stdio: ['ignore', 'pipe', 'pipe'], detached: true })
44
+ : spawn(bin, args, { cwd: projectDir, stdio: ['ignore', 'pipe', 'pipe'], detached: true });
45
+
46
+ // Buffer only the last ~2KB of stderr — non-fatal in general, but useful as
47
+ // a diagnostic tail when the process dies without a terminal event (see
48
+ // maybeFinalize below).
49
+ let stderrTail = '';
50
+ child.stderr.on('data', (chunk: Buffer) => {
51
+ stderrTail = (stderrTail + chunk.toString('utf8')).slice(-STDERR_TAIL_BYTES);
52
+ });
53
+
54
+ const queue: AgentEvent[] = [];
55
+ const resolvers: Array<() => void> = [];
56
+ let done = false;
57
+ let sawTerminal = false;
58
+
59
+ function push(event: AgentEvent) {
60
+ queue.push(event);
61
+ const resolve = resolvers.shift();
62
+ if (resolve) resolve();
63
+ }
64
+
65
+ function finish() {
66
+ done = true;
67
+ while (resolvers.length) {
68
+ const resolve = resolvers.shift();
69
+ if (resolve) resolve();
70
+ }
71
+ }
72
+
73
+ // Tracks in-flight Bash commands by their tool_use id, so that when the
74
+ // matching tool_result (a separate 'user' stream-json line) arrives we can
75
+ // recover the original command text — the tool_result line only carries the
76
+ // id + output, not the command. AgentConsole dedups an in_progress command
77
+ // event with a later completed one by matching `command` text, so the
78
+ // completed event we emit here must reuse the same text.
79
+ const pendingCommands = new Map<string, string>();
80
+
81
+ const rl = readline.createInterface({ input: child.stdout });
82
+ rl.on('line', (line) => {
83
+ for (const event of parseClaudeLine(line)) {
84
+ if (event.type === 'done' || event.type === 'error') sawTerminal = true;
85
+
86
+ if (event.type === 'command' && event.commandStatus === 'in_progress' && event.toolUseId && event.command) {
87
+ pendingCommands.set(event.toolUseId, event.command);
88
+ push(event);
89
+ continue;
90
+ }
91
+
92
+ if (event.type === 'command' && event.commandStatus !== 'in_progress' && event.toolUseId) {
93
+ // A tool_result — only surface it if it corresponds to a Bash command
94
+ // we're tracking; skip gracefully otherwise (e.g. a non-Bash tool).
95
+ const command = pendingCommands.get(event.toolUseId);
96
+ if (!command) continue;
97
+ pendingCommands.delete(event.toolUseId);
98
+ push({ ...event, command });
99
+ continue;
100
+ }
101
+
102
+ push(event);
103
+ }
104
+ });
105
+
106
+ // Finalize only once BOTH the stdout stream is fully drained (readline 'close')
107
+ // AND the process has exited ('close', which fires after all stdio is flushed —
108
+ // unlike 'exit', which can fire while trailing stdout lines are still buffered).
109
+ let rlClosed = false;
110
+ let procClosed = false;
111
+ let exitCode: number | null = null;
112
+ let escalateTimer: NodeJS.Timeout | undefined;
113
+
114
+ function clearEscalation() {
115
+ if (escalateTimer) {
116
+ clearTimeout(escalateTimer);
117
+ escalateTimer = undefined;
118
+ }
119
+ }
120
+
121
+ function maybeFinalize() {
122
+ if (!rlClosed || !procClosed) return;
123
+ clearEscalation();
124
+ if (!sawTerminal) {
125
+ if (exitCode === 0) {
126
+ // Clean exit but the model never emitted a result event — synthesize a done.
127
+ push({ type: 'done' });
128
+ } else {
129
+ const hint = modelRejectionHint('claude', stderrTail);
130
+ const text = hint ? `claude exited with code ${exitCode} (${hint})` : `claude exited with code ${exitCode}`;
131
+ push({ type: 'error', text });
132
+ }
133
+ sawTerminal = true;
134
+ }
135
+ finish();
136
+ }
137
+
138
+ rl.on('close', () => {
139
+ rlClosed = true;
140
+ maybeFinalize();
141
+ });
142
+
143
+ child.on('close', (code) => {
144
+ procClosed = true;
145
+ exitCode = code;
146
+ maybeFinalize();
147
+ });
148
+
149
+ // A failed spawn (missing binary, bad EMBERFLOW_*_BIN, permissions, ...) emits
150
+ // an 'error' event on the child instead of ever exiting/closing normally. Left
151
+ // unhandled, Node rethrows it and crashes the whole runner process — so we
152
+ // must handle it and finalize the stream ourselves. 'close' still fires after
153
+ // a spawn error (with code null), so guard against double-finalizing there.
154
+ let spawnFailed = false;
155
+ child.on('error', (err) => {
156
+ if (spawnFailed) return;
157
+ spawnFailed = true;
158
+ clearEscalation();
159
+ push({ type: 'error', text: `failed to start claude: ${err.message}` });
160
+ sawTerminal = true;
161
+ rlClosed = true;
162
+ procClosed = true;
163
+ finish();
164
+ });
165
+
166
+ async function* generate(): AsyncGenerator<AgentEvent> {
167
+ while (true) {
168
+ if (queue.length > 0) {
169
+ yield queue.shift()!;
170
+ continue;
171
+ }
172
+ if (done) return;
173
+ await new Promise<void>((resolve) => resolvers.push(resolve));
174
+ }
175
+ }
176
+
177
+ // Kill the whole process group (negative pid) so claude's own subprocesses die
178
+ // too; fall back to a plain kill if the group signal fails (e.g. already gone
179
+ // or no group). Used by both the initial SIGTERM and the SIGKILL escalation.
180
+ function killGroup(signal: NodeJS.Signals) {
181
+ try {
182
+ if (child.pid != null) {
183
+ process.kill(-child.pid, signal);
184
+ return;
185
+ }
186
+ } catch {
187
+ // group kill failed — fall through to a direct kill
188
+ }
189
+ try {
190
+ child.kill(signal);
191
+ } catch {
192
+ // process already gone — nothing to do
193
+ }
194
+ }
195
+
196
+ return {
197
+ events: { [Symbol.asyncIterator]: generate },
198
+ cancel() {
199
+ killGroup('SIGTERM');
200
+
201
+ // If the process (or a stubborn grandchild/MCP server) ignores SIGTERM,
202
+ // escalate to SIGKILL after a grace period so a hung agent can't
203
+ // permanently lock the run slot. Cleared once close/error finalizes.
204
+ clearEscalation();
205
+ escalateTimer = setTimeout(() => {
206
+ escalateTimer = undefined;
207
+ killGroup('SIGKILL');
208
+ }, opts.killGraceMs ?? 3000);
209
+ escalateTimer.unref?.();
210
+ },
211
+ };
212
+ }