@jiggai/kitchen 0.3.1 → 0.3.3

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 (307) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +2 -2
  3. package/.next/server/app/_global-error.html +2 -2
  4. package/.next/server/app/_global-error.rsc +1 -1
  5. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  6. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/server/app/_not-found.html +1 -1
  11. package/.next/server/app/_not-found.rsc +1 -1
  12. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  13. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  14. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  18. package/.next/server/app/channels.html +2 -2
  19. package/.next/server/app/channels.rsc +1 -1
  20. package/.next/server/app/channels.segments/_full.segment.rsc +1 -1
  21. package/.next/server/app/channels.segments/_head.segment.rsc +1 -1
  22. package/.next/server/app/channels.segments/_index.segment.rsc +1 -1
  23. package/.next/server/app/channels.segments/_tree.segment.rsc +1 -1
  24. package/.next/server/app/channels.segments/channels/__PAGE__.segment.rsc +1 -1
  25. package/.next/server/app/channels.segments/channels.segment.rsc +1 -1
  26. package/.next/server/app/cron-jobs.html +1 -1
  27. package/.next/server/app/cron-jobs.rsc +1 -1
  28. package/.next/server/app/cron-jobs.segments/_full.segment.rsc +1 -1
  29. package/.next/server/app/cron-jobs.segments/_head.segment.rsc +1 -1
  30. package/.next/server/app/cron-jobs.segments/_index.segment.rsc +1 -1
  31. package/.next/server/app/cron-jobs.segments/_tree.segment.rsc +1 -1
  32. package/.next/server/app/cron-jobs.segments/cron-jobs/__PAGE__.segment.rsc +1 -1
  33. package/.next/server/app/cron-jobs.segments/cron-jobs.segment.rsc +1 -1
  34. package/.next/server/app/goals/new.html +2 -2
  35. package/.next/server/app/goals/new.rsc +1 -1
  36. package/.next/server/app/goals/new.segments/_full.segment.rsc +1 -1
  37. package/.next/server/app/goals/new.segments/_head.segment.rsc +1 -1
  38. package/.next/server/app/goals/new.segments/_index.segment.rsc +1 -1
  39. package/.next/server/app/goals/new.segments/_tree.segment.rsc +1 -1
  40. package/.next/server/app/goals/new.segments/goals/new/__PAGE__.segment.rsc +1 -1
  41. package/.next/server/app/goals/new.segments/goals/new.segment.rsc +1 -1
  42. package/.next/server/app/goals/new.segments/goals.segment.rsc +1 -1
  43. package/.next/server/app/goals.html +1 -1
  44. package/.next/server/app/goals.rsc +1 -1
  45. package/.next/server/app/goals.segments/_full.segment.rsc +1 -1
  46. package/.next/server/app/goals.segments/_head.segment.rsc +1 -1
  47. package/.next/server/app/goals.segments/_index.segment.rsc +1 -1
  48. package/.next/server/app/goals.segments/_tree.segment.rsc +1 -1
  49. package/.next/server/app/goals.segments/goals/__PAGE__.segment.rsc +1 -1
  50. package/.next/server/app/goals.segments/goals.segment.rsc +1 -1
  51. package/.next/server/app/settings.html +1 -1
  52. package/.next/server/app/settings.rsc +1 -1
  53. package/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  54. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  55. package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  56. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  57. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
  58. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  59. package/.next/server/pages/404.html +1 -1
  60. package/.next/server/pages/500.html +2 -2
  61. package/package.json +1 -2
  62. package/src/app/HomeClient.tsx +0 -207
  63. package/src/app/agents/[agentId]/agent-editor-tabs.tsx +0 -298
  64. package/src/app/agents/[agentId]/agent-editor.tsx +0 -468
  65. package/src/app/agents/[agentId]/page.tsx +0 -32
  66. package/src/app/api/__tests__/agents-add-route.test.ts +0 -143
  67. package/src/app/api/__tests__/agents-file-route.test.ts +0 -117
  68. package/src/app/api/__tests__/agents-files-route.test.ts +0 -61
  69. package/src/app/api/__tests__/agents-id-route.test.ts +0 -104
  70. package/src/app/api/__tests__/agents-identity-route.test.ts +0 -92
  71. package/src/app/api/__tests__/agents-route.test.ts +0 -54
  72. package/src/app/api/__tests__/agents-skills-install-route.test.ts +0 -131
  73. package/src/app/api/__tests__/agents-skills-route.test.ts +0 -64
  74. package/src/app/api/__tests__/agents-update-route.test.ts +0 -95
  75. package/src/app/api/__tests__/channels-bindings-route.test.ts +0 -143
  76. package/src/app/api/__tests__/cron-delete-route.test.ts +0 -93
  77. package/src/app/api/__tests__/cron-job-route.test.ts +0 -78
  78. package/src/app/api/__tests__/cron-jobs-route.test.ts +0 -116
  79. package/src/app/api/__tests__/cron-recipe-installed-route.test.ts +0 -114
  80. package/src/app/api/__tests__/gateway-restart-route.test.ts +0 -36
  81. package/src/app/api/__tests__/goals-promote-route.test.ts +0 -200
  82. package/src/app/api/__tests__/goals-route.test.ts +0 -184
  83. package/src/app/api/__tests__/ids-check-route.test.ts +0 -188
  84. package/src/app/api/__tests__/marketplace-recipes-route.test.ts +0 -123
  85. package/src/app/api/__tests__/recipes-clone-route.test.ts +0 -221
  86. package/src/app/api/__tests__/recipes-delete-route.test.ts +0 -248
  87. package/src/app/api/__tests__/recipes-id-route.test.ts +0 -166
  88. package/src/app/api/__tests__/recipes-route.test.ts +0 -57
  89. package/src/app/api/__tests__/recipes-team-agents-route.test.ts +0 -135
  90. package/src/app/api/__tests__/scaffold-route.test.ts +0 -173
  91. package/src/app/api/__tests__/settings-cron-installation-route.test.ts +0 -82
  92. package/src/app/api/__tests__/skills-available-route.test.ts +0 -47
  93. package/src/app/api/__tests__/swarms-start-route.test.ts +0 -79
  94. package/src/app/api/__tests__/swarms-status-route.test.ts +0 -42
  95. package/src/app/api/__tests__/teams-file-route.test.ts +0 -129
  96. package/src/app/api/__tests__/teams-files-route.test.ts +0 -57
  97. package/src/app/api/__tests__/teams-meta-route.test.ts +0 -113
  98. package/src/app/api/__tests__/teams-orchestrator-install-route.test.ts +0 -66
  99. package/src/app/api/__tests__/teams-orchestrator-route.test.ts +0 -59
  100. package/src/app/api/__tests__/teams-remove-team-route.test.ts +0 -122
  101. package/src/app/api/__tests__/teams-skills-install-route.test.ts +0 -78
  102. package/src/app/api/__tests__/teams-skills-route.test.ts +0 -73
  103. package/src/app/api/__tests__/teams-workflow-runs-route.test.ts +0 -85
  104. package/src/app/api/__tests__/teams-workflows-route.test.ts +0 -110
  105. package/src/app/api/__tests__/tickets-move-route.test.ts +0 -60
  106. package/src/app/api/agents/[id]/route.ts +0 -83
  107. package/src/app/api/agents/add/route.ts +0 -114
  108. package/src/app/api/agents/file/route.ts +0 -45
  109. package/src/app/api/agents/files/route.ts +0 -23
  110. package/src/app/api/agents/identity/route.ts +0 -41
  111. package/src/app/api/agents/route.ts +0 -22
  112. package/src/app/api/agents/skills/install/route.ts +0 -34
  113. package/src/app/api/agents/skills/route.ts +0 -39
  114. package/src/app/api/agents/update/route.ts +0 -52
  115. package/src/app/api/channels/bindings/route.ts +0 -63
  116. package/src/app/api/cron/__tests__/helpers.test.ts +0 -164
  117. package/src/app/api/cron/delete/route.ts +0 -23
  118. package/src/app/api/cron/helpers.ts +0 -172
  119. package/src/app/api/cron/job/route.ts +0 -22
  120. package/src/app/api/cron/jobs/route.ts +0 -52
  121. package/src/app/api/cron/recipe-installed/route.ts +0 -65
  122. package/src/app/api/gateway/restart/route.ts +0 -20
  123. package/src/app/api/goals/[id]/promote/route.ts +0 -119
  124. package/src/app/api/goals/[id]/route.ts +0 -54
  125. package/src/app/api/goals/route.ts +0 -44
  126. package/src/app/api/ids/check/route.ts +0 -113
  127. package/src/app/api/marketplace/recipes/[slug]/route.ts +0 -16
  128. package/src/app/api/marketplace/recipes/route.ts +0 -22
  129. package/src/app/api/recipes/[id]/route.ts +0 -62
  130. package/src/app/api/recipes/clone/route.ts +0 -106
  131. package/src/app/api/recipes/custom-team/route.ts +0 -193
  132. package/src/app/api/recipes/delete/helpers.ts +0 -65
  133. package/src/app/api/recipes/delete/route.ts +0 -73
  134. package/src/app/api/recipes/route.ts +0 -21
  135. package/src/app/api/recipes/team-agents/__tests__/helpers.test.ts +0 -156
  136. package/src/app/api/recipes/team-agents/helpers.ts +0 -151
  137. package/src/app/api/recipes/team-agents/route.ts +0 -80
  138. package/src/app/api/scaffold/__tests__/helpers.test.ts +0 -186
  139. package/src/app/api/scaffold/helpers.ts +0 -214
  140. package/src/app/api/scaffold/route.ts +0 -95
  141. package/src/app/api/settings/cron-installation/route.ts +0 -58
  142. package/src/app/api/skills/available/route.ts +0 -23
  143. package/src/app/api/swarms/start/route.ts +0 -100
  144. package/src/app/api/swarms/status/route.ts +0 -31
  145. package/src/app/api/teams/[teamId]/tickets/assign/route.ts +0 -105
  146. package/src/app/api/teams/[teamId]/tickets/assignees/route.ts +0 -27
  147. package/src/app/api/teams/[teamId]/tickets/delete/route.ts +0 -55
  148. package/src/app/api/teams/[teamId]/tickets/move/route.ts +0 -70
  149. package/src/app/api/teams/[teamId]/tickets/move-to-goals/route.ts +0 -56
  150. package/src/app/api/teams/file/route.ts +0 -46
  151. package/src/app/api/teams/files/route.ts +0 -63
  152. package/src/app/api/teams/memory/route.ts +0 -250
  153. package/src/app/api/teams/meta/route.ts +0 -43
  154. package/src/app/api/teams/orchestrator/install/route.ts +0 -129
  155. package/src/app/api/teams/orchestrator/route.ts +0 -216
  156. package/src/app/api/teams/remove-team/route.ts +0 -37
  157. package/src/app/api/teams/skills/install/route.ts +0 -18
  158. package/src/app/api/teams/skills/route.ts +0 -25
  159. package/src/app/api/teams/workflow-runs/route.ts +0 -534
  160. package/src/app/api/teams/workflow-templates/route.ts +0 -71
  161. package/src/app/api/teams/workflows/route.ts +0 -55
  162. package/src/app/api/tickets/assign/route.ts +0 -94
  163. package/src/app/api/tickets/assignees/route.ts +0 -24
  164. package/src/app/api/tickets/move/route.ts +0 -69
  165. package/src/app/channels/channels-client.tsx +0 -271
  166. package/src/app/channels/page.tsx +0 -5
  167. package/src/app/cron-jobs/cron-jobs-client.tsx +0 -243
  168. package/src/app/cron-jobs/page.tsx +0 -34
  169. package/src/app/favicon.ico +0 -0
  170. package/src/app/global-error.tsx +0 -50
  171. package/src/app/globals.css +0 -153
  172. package/src/app/goals/[id]/goal-editor.tsx +0 -162
  173. package/src/app/goals/[id]/page.tsx +0 -6
  174. package/src/app/goals/goals-client.tsx +0 -201
  175. package/src/app/goals/new/page.tsx +0 -81
  176. package/src/app/goals/page.tsx +0 -10
  177. package/src/app/layout.tsx +0 -53
  178. package/src/app/manifest.ts +0 -15
  179. package/src/app/not-found.tsx +0 -8
  180. package/src/app/page.tsx +0 -33
  181. package/src/app/recipes/CreateAgentModal.tsx +0 -156
  182. package/src/app/recipes/CreateCustomTeamModal.tsx +0 -375
  183. package/src/app/recipes/CreateModalShell.tsx +0 -55
  184. package/src/app/recipes/CreateTeamModal.tsx +0 -91
  185. package/src/app/recipes/[id]/RecipeEditor/RecipeEditorCreateModal.tsx +0 -72
  186. package/src/app/recipes/[id]/RecipeEditor/RecipeEditorPanel.tsx +0 -216
  187. package/src/app/recipes/[id]/RecipeEditor/index.tsx +0 -271
  188. package/src/app/recipes/[id]/RecipeEditor/recipe-editor-utils.ts +0 -46
  189. package/src/app/recipes/[id]/RecipeEditor/types.ts +0 -52
  190. package/src/app/recipes/[id]/page.tsx +0 -37
  191. package/src/app/recipes/page.tsx +0 -101
  192. package/src/app/recipes/recipes-client.tsx +0 -620
  193. package/src/app/settings/page.tsx +0 -26
  194. package/src/app/settings/settings-client.tsx +0 -91
  195. package/src/app/teams/[teamId]/CloneTeamModal.tsx +0 -116
  196. package/src/app/teams/[teamId]/OrchestratorPanel.tsx +0 -255
  197. package/src/app/teams/[teamId]/OrchestratorSetupModal.tsx +0 -184
  198. package/src/app/teams/[teamId]/PublishChangesModal.tsx +0 -43
  199. package/src/app/teams/[teamId]/page.tsx +0 -49
  200. package/src/app/teams/[teamId]/team-editor/TeamAgentsTab.tsx +0 -145
  201. package/src/app/teams/[teamId]/team-editor/TeamCronTab.tsx +0 -72
  202. package/src/app/teams/[teamId]/team-editor/TeamFilesTab.tsx +0 -74
  203. package/src/app/teams/[teamId]/team-editor/TeamMemoryTab.tsx +0 -349
  204. package/src/app/teams/[teamId]/team-editor/TeamRecipeTab.tsx +0 -151
  205. package/src/app/teams/[teamId]/team-editor/TeamSkillsTab.tsx +0 -68
  206. package/src/app/teams/[teamId]/team-editor/index.tsx +0 -558
  207. package/src/app/teams/[teamId]/team-editor/team-editor-data.ts +0 -255
  208. package/src/app/teams/[teamId]/team-editor/team-editor-utils.ts +0 -78
  209. package/src/app/teams/[teamId]/team-editor/types.ts +0 -34
  210. package/src/app/teams/[teamId]/tickets/[ticket]/page.tsx +0 -35
  211. package/src/app/teams/[teamId]/tickets/page.tsx +0 -15
  212. package/src/app/teams/[teamId]/workflows/[workflowId]/WorkflowCanvas.tsx +0 -111
  213. package/src/app/teams/[teamId]/workflows/[workflowId]/page.tsx +0 -27
  214. package/src/app/teams/[teamId]/workflows/[workflowId]/workflows-editor-client.tsx +0 -1608
  215. package/src/app/teams/[teamId]/workflows/page.tsx +0 -40
  216. package/src/app/teams/[teamId]/workflows/workflows-client.tsx +0 -494
  217. package/src/app/tickets/TicketDetailClient.tsx +0 -147
  218. package/src/app/tickets/TicketsBoardClient.tsx +0 -200
  219. package/src/app/tickets/[ticket]/TicketAssignControl.tsx +0 -112
  220. package/src/app/tickets/[ticket]/page.tsx +0 -36
  221. package/src/app/tickets/page.tsx +0 -10
  222. package/src/components/AppShell.tsx +0 -286
  223. package/src/components/ConfirmationModal.tsx +0 -81
  224. package/src/components/DeleteEntityModal.tsx +0 -41
  225. package/src/components/ErrorBoundary.tsx +0 -70
  226. package/src/components/FileListWithOptionalToggle.tsx +0 -86
  227. package/src/components/GoalFormFields.tsx +0 -163
  228. package/src/components/ScaffoldOverlay.tsx +0 -78
  229. package/src/components/ThemeToggle.tsx +0 -53
  230. package/src/components/ToastProvider.tsx +0 -163
  231. package/src/components/__tests__/ConfirmationModal.test.tsx +0 -109
  232. package/src/components/__tests__/ErrorBoundary.test.tsx +0 -39
  233. package/src/components/__tests__/FileListWithOptionalToggle.test.tsx +0 -109
  234. package/src/components/__tests__/GoalFormFields.test.tsx +0 -117
  235. package/src/components/delete-modals.tsx +0 -59
  236. package/src/components/icons.tsx +0 -48
  237. package/src/lib/__tests__/agent-workspace.test.ts +0 -44
  238. package/src/lib/__tests__/agents.test.ts +0 -36
  239. package/src/lib/__tests__/api-route-helpers.test.ts +0 -188
  240. package/src/lib/__tests__/cron.test.ts +0 -45
  241. package/src/lib/__tests__/editor-utils.test.ts +0 -38
  242. package/src/lib/__tests__/errors.test.ts +0 -15
  243. package/src/lib/__tests__/exec.test.ts +0 -13
  244. package/src/lib/__tests__/fetch-json.test.ts +0 -118
  245. package/src/lib/__tests__/gateway.test.ts +0 -234
  246. package/src/lib/__tests__/goal-promote.test.ts +0 -39
  247. package/src/lib/__tests__/goals-client.test.ts +0 -26
  248. package/src/lib/__tests__/goals.test.ts +0 -275
  249. package/src/lib/__tests__/json.test.ts +0 -15
  250. package/src/lib/__tests__/kitchen-api.test.ts +0 -32
  251. package/src/lib/__tests__/marketplace.test.ts +0 -116
  252. package/src/lib/__tests__/openclaw.test.ts +0 -129
  253. package/src/lib/__tests__/paths.test.ts +0 -136
  254. package/src/lib/__tests__/poll.test.ts +0 -26
  255. package/src/lib/__tests__/recipe-clone.test.ts +0 -85
  256. package/src/lib/__tests__/recipe-team-agents.test.ts +0 -70
  257. package/src/lib/__tests__/recipes.test.ts +0 -199
  258. package/src/lib/__tests__/scaffold-client.test.ts +0 -106
  259. package/src/lib/__tests__/scaffold.test.ts +0 -64
  260. package/src/lib/__tests__/slugify.test.ts +0 -23
  261. package/src/lib/__tests__/tickets.test.ts +0 -158
  262. package/src/lib/__tests__/type-guards.test.ts +0 -18
  263. package/src/lib/__tests__/use-slugified-id.test.tsx +0 -120
  264. package/src/lib/agent-workspace.ts +0 -14
  265. package/src/lib/agents.ts +0 -17
  266. package/src/lib/api-route-helpers.ts +0 -157
  267. package/src/lib/cron.ts +0 -40
  268. package/src/lib/editor-utils.ts +0 -18
  269. package/src/lib/errors.ts +0 -7
  270. package/src/lib/exec.ts +0 -4
  271. package/src/lib/fetch-json.ts +0 -29
  272. package/src/lib/gateway.ts +0 -100
  273. package/src/lib/goal-promote.ts +0 -27
  274. package/src/lib/goals-client.ts +0 -69
  275. package/src/lib/goals.ts +0 -171
  276. package/src/lib/json.ts +0 -10
  277. package/src/lib/kitchen-api.ts +0 -19
  278. package/src/lib/marketplace.ts +0 -46
  279. package/src/lib/openclaw.ts +0 -59
  280. package/src/lib/paths.ts +0 -69
  281. package/src/lib/poll.ts +0 -18
  282. package/src/lib/recipe-clone.ts +0 -42
  283. package/src/lib/recipe-team-agents.ts +0 -30
  284. package/src/lib/recipes.ts +0 -95
  285. package/src/lib/scaffold-client.ts +0 -31
  286. package/src/lib/scaffold.ts +0 -37
  287. package/src/lib/slugify.ts +0 -25
  288. package/src/lib/swarms.ts +0 -25
  289. package/src/lib/tickets.ts +0 -192
  290. package/src/lib/type-guards.ts +0 -3
  291. package/src/lib/use-slugified-id.ts +0 -35
  292. package/src/lib/workflows/README.md +0 -11
  293. package/src/lib/workflows/__tests__/storage.test.ts +0 -129
  294. package/src/lib/workflows/__tests__/validate.test.ts +0 -92
  295. package/src/lib/workflows/api-handlers.ts +0 -35
  296. package/src/lib/workflows/readdir.ts +0 -23
  297. package/src/lib/workflows/runs-storage.ts +0 -59
  298. package/src/lib/workflows/runs-types.ts +0 -42
  299. package/src/lib/workflows/storage.ts +0 -70
  300. package/src/lib/workflows/templates/index.ts +0 -1
  301. package/src/lib/workflows/templates/marketing-cadence-v1.ts +0 -142
  302. package/src/lib/workflows/types.ts +0 -48
  303. package/src/lib/workflows/validate.ts +0 -92
  304. package/src/proxy.ts +0 -28
  305. /package/.next/static/{z86RoqzzXXrWnpi229zP6 → Jrrrm9HH5bKkSrQhe1j93}/_buildManifest.js +0 -0
  306. /package/.next/static/{z86RoqzzXXrWnpi229zP6 → Jrrrm9HH5bKkSrQhe1j93}/_clientMiddlewareManifest.json +0 -0
  307. /package/.next/static/{z86RoqzzXXrWnpi229zP6 → Jrrrm9HH5bKkSrQhe1j93}/_ssgManifest.js +0 -0
@@ -1,221 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from "vitest";
2
- import { POST } from "../recipes/clone/route";
3
- import path from "node:path";
4
-
5
- vi.mock("@/lib/openclaw", () => ({ runOpenClaw: vi.fn() }));
6
- vi.mock("@/lib/paths", () => ({ getWorkspaceRecipesDir: vi.fn() }));
7
- vi.mock("node:fs/promises", () => ({
8
- default: { stat: vi.fn(), mkdir: vi.fn(), writeFile: vi.fn() },
9
- }));
10
-
11
- import { runOpenClaw } from "@/lib/openclaw";
12
- import { getWorkspaceRecipesDir } from "@/lib/paths";
13
- import fs from "node:fs/promises";
14
-
15
- const sampleRecipe = `---
16
- id: source-agent
17
- kind: agent
18
- name: Source Agent
19
- ---
20
- # Content`;
21
-
22
- describe("api recipes clone route", () => {
23
- beforeEach(() => {
24
- vi.mocked(runOpenClaw).mockReset();
25
- vi.mocked(getWorkspaceRecipesDir).mockReset();
26
- vi.mocked(fs.stat).mockReset();
27
- vi.mocked(fs.mkdir).mockReset();
28
- vi.mocked(fs.writeFile).mockReset();
29
- });
30
-
31
- it("returns 400 when fromId missing", async () => {
32
- const res = await POST(
33
- new Request("https://test", {
34
- method: "POST",
35
- body: JSON.stringify({ toId: "target" }),
36
- })
37
- );
38
- expect(res.status).toBe(400);
39
- const json = await res.json();
40
- expect(json.error).toBe("Missing fromId");
41
- });
42
-
43
- it("returns 400 when toId missing", async () => {
44
- const res = await POST(
45
- new Request("https://test", {
46
- method: "POST",
47
- body: JSON.stringify({ fromId: "source" }),
48
- })
49
- );
50
- expect(res.status).toBe(400);
51
- const json = await res.json();
52
- expect(json.error).toBe("Missing toId");
53
- });
54
-
55
- it("returns 400 when recipes show fails", async () => {
56
- vi.mocked(runOpenClaw).mockResolvedValue({
57
- ok: false,
58
- exitCode: 1,
59
- stdout: "",
60
- stderr: "Not found",
61
- });
62
-
63
- const res = await POST(
64
- new Request("https://test", {
65
- method: "POST",
66
- body: JSON.stringify({ fromId: "source", toId: "target" }),
67
- })
68
- );
69
- expect(res.status).toBe(400);
70
- const json = await res.json();
71
- expect(json.error).toBe("Not found");
72
- });
73
-
74
- it("clones and returns ok for agent", async () => {
75
- const dir = "/mock-workspace/recipes";
76
- vi.mocked(runOpenClaw).mockResolvedValueOnce({
77
- ok: true,
78
- exitCode: 0,
79
- stdout: sampleRecipe,
80
- stderr: "",
81
- });
82
- vi.mocked(getWorkspaceRecipesDir).mockResolvedValue(dir);
83
- vi.mocked(fs.stat).mockRejectedValue(new Error("ENOENT"));
84
- vi.mocked(fs.mkdir).mockResolvedValue(undefined);
85
- vi.mocked(fs.writeFile).mockResolvedValue(undefined);
86
-
87
- const res = await POST(
88
- new Request("https://test", {
89
- method: "POST",
90
- body: JSON.stringify({ fromId: "source-agent", toId: "my-agent" }),
91
- })
92
- );
93
- expect(res.status).toBe(200);
94
- const json = await res.json();
95
- expect(json.ok).toBe(true);
96
- expect(json.recipeId).toBe("my-agent");
97
- expect(json.filePath).toBe(path.join(dir, "my-agent.md"));
98
- expect(fs.writeFile).toHaveBeenCalledWith(
99
- path.join(dir, "my-agent.md"),
100
- expect.stringContaining("id: my-agent"),
101
- "utf8"
102
- );
103
- });
104
-
105
- it("returns 409 when target exists and overwrite false", async () => {
106
- const dir = "/mock-workspace/recipes";
107
- vi.mocked(runOpenClaw).mockResolvedValueOnce({
108
- ok: true,
109
- exitCode: 0,
110
- stdout: sampleRecipe,
111
- stderr: "",
112
- });
113
- vi.mocked(getWorkspaceRecipesDir).mockResolvedValue(dir);
114
- vi.mocked(fs.stat).mockResolvedValue({} as never);
115
-
116
- const res = await POST(
117
- new Request("https://test", {
118
- method: "POST",
119
- body: JSON.stringify({ fromId: "source-agent", toId: "existing" }),
120
- })
121
- );
122
- expect(res.status).toBe(409);
123
- const json = await res.json();
124
- expect(json.error).toContain("already exists");
125
- expect(json.code).toBe("RECIPE_ID_TAKEN");
126
- expect(json.suggestions).toContain("custom-existing");
127
- });
128
-
129
- it("scaffolds when scaffold true for agent", async () => {
130
- const dir = "/mock-workspace/recipes";
131
- vi.mocked(runOpenClaw)
132
- .mockResolvedValueOnce({
133
- ok: true,
134
- exitCode: 0,
135
- stdout: sampleRecipe,
136
- stderr: "",
137
- })
138
- .mockResolvedValueOnce({
139
- ok: true,
140
- exitCode: 0,
141
- stdout: "",
142
- stderr: "",
143
- });
144
- vi.mocked(getWorkspaceRecipesDir).mockResolvedValue(dir);
145
- vi.mocked(fs.stat).mockRejectedValue(new Error("ENOENT"));
146
- vi.mocked(fs.mkdir).mockResolvedValue(undefined);
147
- vi.mocked(fs.writeFile).mockResolvedValue(undefined);
148
-
149
- const res = await POST(
150
- new Request("https://test", {
151
- method: "POST",
152
- body: JSON.stringify({
153
- fromId: "source-agent",
154
- toId: "my-agent",
155
- scaffold: true,
156
- }),
157
- })
158
- );
159
- expect(res.status).toBe(200);
160
- const json = await res.json();
161
- expect(json.scaffold?.ok).toBe(true);
162
- expect(runOpenClaw).toHaveBeenCalledTimes(2);
163
- expect(runOpenClaw).toHaveBeenNthCalledWith(2, [
164
- "recipes",
165
- "scaffold",
166
- "my-agent",
167
- "--agent-id",
168
- "my-agent",
169
- "--overwrite",
170
- "--overwrite-recipe",
171
- ]);
172
- });
173
-
174
- it("scaffolds team when kind is team", async () => {
175
- const teamRecipe = `---
176
- id: source-team
177
- kind: team
178
- name: Source Team
179
- ---
180
- # Team body`;
181
- const dir = "/mock-workspace/recipes";
182
- vi.mocked(runOpenClaw)
183
- .mockResolvedValueOnce({
184
- ok: true,
185
- exitCode: 0,
186
- stdout: teamRecipe,
187
- stderr: "",
188
- })
189
- .mockResolvedValueOnce({
190
- ok: true,
191
- exitCode: 0,
192
- stdout: "",
193
- stderr: "",
194
- });
195
- vi.mocked(getWorkspaceRecipesDir).mockResolvedValue(dir);
196
- vi.mocked(fs.stat).mockRejectedValue(new Error("ENOENT"));
197
- vi.mocked(fs.mkdir).mockResolvedValue(undefined);
198
- vi.mocked(fs.writeFile).mockResolvedValue(undefined);
199
-
200
- const res = await POST(
201
- new Request("https://test", {
202
- method: "POST",
203
- body: JSON.stringify({
204
- fromId: "source-team",
205
- toId: "my-team",
206
- scaffold: true,
207
- }),
208
- })
209
- );
210
- expect(res.status).toBe(200);
211
- expect(runOpenClaw).toHaveBeenNthCalledWith(2, [
212
- "recipes",
213
- "scaffold-team",
214
- "my-team",
215
- "--team-id",
216
- "my-team",
217
- "--overwrite",
218
- "--overwrite-recipe",
219
- ]);
220
- });
221
- });
@@ -1,248 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from "vitest";
2
- import { POST } from "../recipes/delete/route";
3
- import path from "node:path";
4
-
5
- vi.mock("@/lib/openclaw", () => ({ runOpenClaw: vi.fn() }));
6
- vi.mock("@/lib/recipes", () => ({
7
- findRecipeById: vi.fn(),
8
- resolveRecipePath: vi.fn(),
9
- }));
10
- vi.mock("node:fs/promises", () => ({
11
- default: { stat: vi.fn(), rm: vi.fn(), readdir: vi.fn(), readFile: vi.fn() },
12
- }));
13
-
14
- import { runOpenClaw } from "@/lib/openclaw";
15
- import { findRecipeById, resolveRecipePath } from "@/lib/recipes";
16
- import fs from "node:fs/promises";
17
-
18
- const workspaceItem = {
19
- id: "my-agent",
20
- name: "My Agent",
21
- kind: "agent" as const,
22
- source: "workspace" as const,
23
- };
24
-
25
- const teamWorkspaceItem = {
26
- id: "my-team",
27
- name: "My Team",
28
- kind: "team" as const,
29
- source: "workspace" as const,
30
- };
31
-
32
- const builtinItem = {
33
- id: "builtin-recipe",
34
- name: "Builtin",
35
- kind: "agent" as const,
36
- source: "builtin" as const,
37
- };
38
-
39
- describe("api recipes delete route", () => {
40
- beforeEach(() => {
41
- vi.mocked(runOpenClaw).mockReset();
42
- vi.mocked(findRecipeById).mockReset();
43
- vi.mocked(resolveRecipePath).mockReset();
44
- vi.mocked(fs.stat).mockReset();
45
- vi.mocked(fs.rm).mockReset();
46
- vi.mocked(fs.readdir).mockReset();
47
- vi.mocked(fs.readFile).mockReset();
48
- });
49
-
50
- it("returns 400 when id missing", async () => {
51
- const res = await POST(
52
- new Request("https://test", { method: "POST", body: JSON.stringify({}) })
53
- );
54
- expect(res.status).toBe(400);
55
- const json = await res.json();
56
- expect(json.error).toBe("id is required");
57
- });
58
-
59
- it("returns 404 when list fails or recipe not found", async () => {
60
- vi.mocked(findRecipeById).mockResolvedValue(null);
61
-
62
- const res = await POST(
63
- new Request("https://test", { method: "POST", body: JSON.stringify({ id: "x" }) })
64
- );
65
- expect(res.status).toBe(404);
66
- const json = await res.json();
67
- expect(json.error).toBe("Recipe not found: x");
68
- });
69
-
70
- it("returns 404 when recipe not found", async () => {
71
- vi.mocked(findRecipeById).mockResolvedValue(null);
72
-
73
- const res = await POST(
74
- new Request("https://test", { method: "POST", body: JSON.stringify({ id: "missing" }) })
75
- );
76
- expect(res.status).toBe(404);
77
- const json = await res.json();
78
- expect(json.error).toBe("Recipe not found: missing");
79
- });
80
-
81
- it("returns 403 when builtin", async () => {
82
- vi.mocked(findRecipeById).mockResolvedValue(builtinItem);
83
-
84
- const res = await POST(
85
- new Request("https://test", {
86
- method: "POST",
87
- body: JSON.stringify({ id: "builtin-recipe" }),
88
- })
89
- );
90
- expect(res.status).toBe(403);
91
- const json = await res.json();
92
- expect(json.error).toContain("builtin and cannot be deleted");
93
- });
94
-
95
- it("returns 500 when workspace not set", async () => {
96
- vi.mocked(findRecipeById).mockResolvedValue(workspaceItem);
97
- vi.mocked(runOpenClaw).mockResolvedValueOnce({
98
- ok: true,
99
- exitCode: 0,
100
- stdout: "",
101
- stderr: "",
102
- });
103
-
104
- const res = await POST(
105
- new Request("https://test", { method: "POST", body: JSON.stringify({ id: "my-agent" }) })
106
- );
107
- expect(res.status).toBe(500);
108
- const json = await res.json();
109
- expect(json.error).toBe("agents.defaults.workspace not set");
110
- });
111
-
112
- it("returns 403 when path outside workspace", async () => {
113
- const wsRoot = "/mock-workspace";
114
- vi.mocked(findRecipeById).mockResolvedValue(workspaceItem);
115
- vi.mocked(runOpenClaw).mockResolvedValueOnce({
116
- ok: true,
117
- exitCode: 0,
118
- stdout: wsRoot,
119
- stderr: "",
120
- });
121
- vi.mocked(resolveRecipePath).mockResolvedValue("/outside/path/recipe.md");
122
-
123
- const res = await POST(
124
- new Request("https://test", { method: "POST", body: JSON.stringify({ id: "my-agent" }) })
125
- );
126
- expect(res.status).toBe(403);
127
- const json = await res.json();
128
- expect(json.error).toContain("Refusing to delete non-workspace recipe path");
129
- });
130
-
131
- it("returns 409 when team has workspace", async () => {
132
- const wsRoot = "/mock-workspace";
133
- vi.mocked(findRecipeById).mockResolvedValue(teamWorkspaceItem);
134
- vi.mocked(runOpenClaw).mockResolvedValueOnce({
135
- ok: true,
136
- exitCode: 0,
137
- stdout: wsRoot,
138
- stderr: "",
139
- });
140
- vi.mocked(resolveRecipePath).mockResolvedValue(
141
- path.join(wsRoot, "recipes", "my-team.md")
142
- );
143
- vi.mocked(fs.readdir).mockResolvedValue([
144
- { name: "workspace-my-team", isDirectory: () => true } as never,
145
- ]);
146
- vi.mocked(fs.readFile).mockResolvedValue(
147
- JSON.stringify({ recipeId: "my-team", teamId: "my-team" })
148
- );
149
-
150
- const res = await POST(
151
- new Request("https://test", { method: "POST", body: JSON.stringify({ id: "my-team" }) })
152
- );
153
- expect(res.status).toBe(409);
154
- const json = await res.json();
155
- expect(json.error).toContain("in use by installed team");
156
- expect(json.details.attachedTeams).toContain("my-team");
157
- });
158
-
159
- it("returns 200 when team has agents in config but no attached workspace", async () => {
160
- const wsRoot = "/mock-workspace";
161
- vi.mocked(findRecipeById).mockResolvedValue(teamWorkspaceItem);
162
- vi.mocked(runOpenClaw)
163
- .mockResolvedValueOnce({
164
- ok: true,
165
- exitCode: 0,
166
- stdout: wsRoot,
167
- stderr: "",
168
- })
169
- .mockResolvedValueOnce({
170
- ok: true,
171
- exitCode: 0,
172
- stdout: JSON.stringify([{ id: "my-team-lead" }]),
173
- stderr: "",
174
- });
175
- vi.mocked(resolveRecipePath).mockResolvedValue(
176
- path.join(wsRoot, "recipes", "my-team.md")
177
- );
178
- vi.mocked(fs.readdir).mockResolvedValue([]);
179
- vi.mocked(fs.rm).mockResolvedValue(undefined);
180
-
181
- const res = await POST(
182
- new Request("https://test", { method: "POST", body: JSON.stringify({ id: "my-team" }) })
183
- );
184
- expect(res.status).toBe(200);
185
- const json = await res.json();
186
- expect(json.ok).toBe(true);
187
- });
188
-
189
- it("deletes and returns ok for agent recipe", async () => {
190
- const wsRoot = "/mock-workspace";
191
- const filePath = path.join(wsRoot, "recipes", "my-agent.md");
192
- vi.mocked(findRecipeById).mockResolvedValue(workspaceItem);
193
- vi.mocked(runOpenClaw)
194
- .mockResolvedValueOnce({
195
- ok: true,
196
- exitCode: 0,
197
- stdout: wsRoot,
198
- stderr: "",
199
- })
200
- .mockResolvedValueOnce({
201
- ok: true,
202
- exitCode: 0,
203
- stdout: JSON.stringify([{ id: "other-agent" }]),
204
- stderr: "",
205
- });
206
- vi.mocked(resolveRecipePath).mockResolvedValue(filePath);
207
- vi.mocked(fs.readFile).mockRejectedValue(new Error("ENOENT"));
208
- vi.mocked(fs.rm).mockResolvedValue(undefined);
209
-
210
- const res = await POST(
211
- new Request("https://test", { method: "POST", body: JSON.stringify({ id: "my-agent" }) })
212
- );
213
- expect(res.status).toBe(200);
214
- const json = await res.json();
215
- expect(json.ok).toBe(true);
216
- expect(json.deleted).toBe(path.resolve(filePath));
217
- expect(fs.rm).toHaveBeenCalledWith(path.resolve(filePath), { force: true });
218
- });
219
-
220
- it("deletes team recipe when not installed", async () => {
221
- const wsRoot = "/mock-workspace";
222
- const filePath = path.join(wsRoot, "recipes", "my-team.md");
223
- vi.mocked(findRecipeById).mockResolvedValue(teamWorkspaceItem);
224
- vi.mocked(runOpenClaw)
225
- .mockResolvedValueOnce({
226
- ok: true,
227
- exitCode: 0,
228
- stdout: wsRoot,
229
- stderr: "",
230
- })
231
- .mockResolvedValueOnce({
232
- ok: true,
233
- exitCode: 0,
234
- stdout: JSON.stringify([{ id: "other-agent" }]),
235
- stderr: "",
236
- });
237
- vi.mocked(resolveRecipePath).mockResolvedValue(filePath);
238
- vi.mocked(fs.stat).mockRejectedValue(new Error("ENOENT"));
239
- vi.mocked(fs.rm).mockResolvedValue(undefined);
240
-
241
- const res = await POST(
242
- new Request("https://test", { method: "POST", body: JSON.stringify({ id: "my-team" }) })
243
- );
244
- expect(res.status).toBe(200);
245
- const json = await res.json();
246
- expect(json.ok).toBe(true);
247
- });
248
- });
@@ -1,166 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from "vitest";
2
- import { GET, PUT } from "../recipes/[id]/route";
3
- import path from "node:path";
4
-
5
- vi.mock("@/lib/openclaw", () => ({ runOpenClaw: vi.fn() }));
6
- vi.mock("@/lib/recipes", () => ({
7
- findRecipeById: vi.fn(),
8
- parseFrontmatterId: vi.fn(),
9
- resolveRecipePath: vi.fn(),
10
- writeRecipeFile: vi.fn(),
11
- }));
12
-
13
- import { runOpenClaw } from "@/lib/openclaw";
14
- import { findRecipeById, parseFrontmatterId, resolveRecipePath, writeRecipeFile } from "@/lib/recipes";
15
-
16
- const workspaceItem = {
17
- id: "my-recipe",
18
- name: "My Recipe",
19
- kind: "agent" as const,
20
- source: "workspace" as const,
21
- };
22
-
23
- const builtinItem = {
24
- id: "builtin-recipe",
25
- name: "Builtin",
26
- kind: "agent" as const,
27
- source: "builtin" as const,
28
- };
29
-
30
- describe("api recipes [id] route", () => {
31
- beforeEach(() => {
32
- vi.mocked(runOpenClaw).mockReset();
33
- vi.mocked(findRecipeById).mockReset();
34
- vi.mocked(resolveRecipePath).mockReset();
35
- vi.mocked(writeRecipeFile).mockReset();
36
- vi.mocked(parseFrontmatterId).mockReset();
37
- });
38
-
39
- describe("GET /api/recipes/[id]", () => {
40
- it("returns 404 when recipe not in list", async () => {
41
- vi.mocked(findRecipeById).mockResolvedValue(null);
42
-
43
- const res = await GET(new Request("https://test"), {
44
- params: Promise.resolve({ id: "missing" }),
45
- });
46
- expect(res.status).toBe(404);
47
- const json = await res.json();
48
- expect(json.error).toBe("Recipe not found: missing");
49
- });
50
-
51
- it("returns recipe with content on success", async () => {
52
- vi.mocked(findRecipeById).mockResolvedValue(workspaceItem);
53
- vi.mocked(runOpenClaw).mockResolvedValueOnce({
54
- ok: true,
55
- exitCode: 0,
56
- stdout: "# Recipe content",
57
- stderr: "",
58
- });
59
- vi.mocked(resolveRecipePath).mockResolvedValue("/mock/recipes/my-recipe.md");
60
-
61
- const res = await GET(new Request("https://test"), {
62
- params: Promise.resolve({ id: "my-recipe" }),
63
- });
64
- expect(res.status).toBe(200);
65
- const json = await res.json();
66
- expect(json.recipe.id).toBe("my-recipe");
67
- expect(json.recipe.content).toBe("# Recipe content");
68
- expect(json.recipe.filePath).toBe("/mock/recipes/my-recipe.md");
69
- });
70
-
71
- it("returns filePath null when resolveRecipePath rejects", async () => {
72
- vi.mocked(findRecipeById).mockResolvedValue(workspaceItem);
73
- vi.mocked(runOpenClaw).mockResolvedValueOnce({
74
- ok: true,
75
- exitCode: 0,
76
- stdout: "# Content",
77
- stderr: "",
78
- });
79
- vi.mocked(resolveRecipePath).mockRejectedValue(new Error("no path"));
80
-
81
- const res = await GET(new Request("https://test"), {
82
- params: Promise.resolve({ id: "my-recipe" }),
83
- });
84
- expect(res.status).toBe(200);
85
- const json = await res.json();
86
- expect(json.recipe.filePath).toBeNull();
87
- });
88
- });
89
-
90
- describe("PUT /api/recipes/[id]", () => {
91
- it("returns 400 when content missing", async () => {
92
- const res = await PUT(
93
- new Request("https://test", { method: "PUT", body: JSON.stringify({}) }),
94
- { params: Promise.resolve({ id: "my-recipe" }) }
95
- );
96
- expect(res.status).toBe(400);
97
- const json = await res.json();
98
- expect(json.error).toBe("Missing content");
99
- });
100
-
101
- it("returns 400 when frontmatter id mismatch", async () => {
102
- vi.mocked(parseFrontmatterId).mockReturnValue("other-id");
103
- const res = await PUT(
104
- new Request("https://test", {
105
- method: "PUT",
106
- body: JSON.stringify({ content: "---\nid: other-id\n---\nbody" }),
107
- }),
108
- { params: Promise.resolve({ id: "my-recipe" }) }
109
- );
110
- expect(res.status).toBe(400);
111
- const json = await res.json();
112
- expect(json.error).toContain("Frontmatter id (other-id) must match URL id (my-recipe)");
113
- });
114
-
115
- it("returns 404 when recipe not found", async () => {
116
- vi.mocked(parseFrontmatterId).mockReturnValue("my-recipe");
117
- vi.mocked(findRecipeById).mockResolvedValue(null);
118
-
119
- const res = await PUT(
120
- new Request("https://test", {
121
- method: "PUT",
122
- body: JSON.stringify({ content: "---\nid: my-recipe\n---\nbody" }),
123
- }),
124
- { params: Promise.resolve({ id: "my-recipe" }) }
125
- );
126
- expect(res.status).toBe(404);
127
- });
128
-
129
- it("returns 403 when builtin recipe", async () => {
130
- vi.mocked(parseFrontmatterId).mockReturnValue("builtin-recipe");
131
- vi.mocked(findRecipeById).mockResolvedValue(builtinItem);
132
-
133
- const res = await PUT(
134
- new Request("https://test", {
135
- method: "PUT",
136
- body: JSON.stringify({ content: "---\nid: builtin-recipe\n---\nbody" }),
137
- }),
138
- { params: Promise.resolve({ id: "builtin-recipe" }) }
139
- );
140
- expect(res.status).toBe(403);
141
- const json = await res.json();
142
- expect(json.error).toContain("builtin and cannot be modified");
143
- });
144
-
145
- it("writes and returns ok on success", async () => {
146
- const content = "---\nid: my-recipe\n---\nBody text";
147
- vi.mocked(parseFrontmatterId).mockReturnValue("my-recipe");
148
- vi.mocked(findRecipeById).mockResolvedValue(workspaceItem);
149
- vi.mocked(resolveRecipePath).mockResolvedValue(path.join("/mock", "my-recipe.md"));
150
- vi.mocked(writeRecipeFile).mockResolvedValue(undefined);
151
-
152
- const res = await PUT(
153
- new Request("https://test", {
154
- method: "PUT",
155
- body: JSON.stringify({ content }),
156
- }),
157
- { params: Promise.resolve({ id: "my-recipe" }) }
158
- );
159
- expect(res.status).toBe(200);
160
- const json = await res.json();
161
- expect(json.ok).toBe(true);
162
- expect(json.filePath).toBe(path.join("/mock", "my-recipe.md"));
163
- expect(writeRecipeFile).toHaveBeenCalledWith(path.join("/mock", "my-recipe.md"), content);
164
- });
165
- });
166
- });
@@ -1,57 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from "vitest";
2
- import { GET } from "../recipes/route";
3
-
4
- vi.mock("@/lib/openclaw", () => ({
5
- runOpenClaw: vi.fn(),
6
- }));
7
-
8
- import { runOpenClaw } from "@/lib/openclaw";
9
-
10
- describe("api recipes route", () => {
11
- beforeEach(() => {
12
- vi.mocked(runOpenClaw).mockReset();
13
- });
14
-
15
- it("returns recipes when openclaw outputs valid JSON array", async () => {
16
- vi.mocked(runOpenClaw).mockResolvedValue({
17
- ok: true,
18
- exitCode: 0,
19
- stdout: JSON.stringify([{ id: "r1", name: "Recipe 1" }]),
20
- stderr: "",
21
- });
22
-
23
- const res = await GET();
24
- expect(res.status).toBe(200);
25
- const json = await res.json();
26
- expect(json.recipes).toHaveLength(1);
27
- expect(json.recipes[0].id).toBe("r1");
28
- });
29
-
30
- it("returns 500 when stdout is invalid JSON", async () => {
31
- vi.mocked(runOpenClaw).mockResolvedValue({
32
- ok: true,
33
- exitCode: 0,
34
- stdout: "not json",
35
- stderr: "",
36
- });
37
-
38
- const res = await GET();
39
- expect(res.status).toBe(500);
40
- const json = await res.json();
41
- expect(json.error).toBe("Failed to parse openclaw recipes list output");
42
- });
43
-
44
- it("returns empty array when data is not array", async () => {
45
- vi.mocked(runOpenClaw).mockResolvedValue({
46
- ok: true,
47
- exitCode: 0,
48
- stdout: JSON.stringify({ not: "array" }),
49
- stderr: "",
50
- });
51
-
52
- const res = await GET();
53
- expect(res.status).toBe(200);
54
- const json = await res.json();
55
- expect(json.recipes).toEqual([]);
56
- });
57
- });