@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,100 +0,0 @@
1
- import { readOpenClawConfig } from "@/lib/paths";
2
-
3
- type ToolsInvokeRequest = {
4
- tool: string;
5
- action?: string;
6
- args?: Record<string, unknown>;
7
- sessionKey?: string;
8
- dryRun?: boolean;
9
- };
10
-
11
- type ToolsInvokeResponse = {
12
- ok: boolean;
13
- result?: unknown;
14
- error?: { message?: string } | string;
15
- };
16
-
17
- type GatewayConfigGetResult = {
18
- path: string;
19
- exists: boolean;
20
- raw: string;
21
- hash: string;
22
- };
23
-
24
- type ToolTextEnvelope = {
25
- ok: boolean;
26
- result: GatewayConfigGetResult;
27
- };
28
-
29
- async function getGatewayBaseUrlAndToken() {
30
- // ClawKitchen runs in-process with the OpenClaw Gateway (as a plugin), so we can read
31
- // the gateway port/token from the loaded config.
32
- const cfg = await readOpenClawConfig();
33
- const port = cfg.gateway?.port ?? 18789;
34
- const token = cfg.gateway?.auth?.token;
35
- if (!token) throw new Error("Missing gateway token (gateway.auth.token in ~/.openclaw/openclaw.json)");
36
-
37
- const baseUrl = `http://127.0.0.1:${port}`;
38
- return { baseUrl, token };
39
- }
40
-
41
- export async function toolsInvoke<T = unknown>(req: ToolsInvokeRequest): Promise<T> {
42
- const { baseUrl, token } = await getGatewayBaseUrlAndToken();
43
-
44
- const res = await fetch(`${baseUrl}/tools/invoke`, {
45
- method: "POST",
46
- headers: {
47
- "content-type": "application/json",
48
- authorization: `Bearer ${token}`,
49
- },
50
- body: JSON.stringify(req),
51
- });
52
-
53
- const json = (await res.json()) as ToolsInvokeResponse;
54
- if (!res.ok || !json.ok) {
55
- const msg =
56
- (typeof json.error === "object" && json.error?.message) ||
57
- (typeof json.error === "string" ? json.error : null) ||
58
- `tools/invoke failed (${res.status})`;
59
- throw new Error(msg);
60
- }
61
-
62
- return json.result as T;
63
- }
64
-
65
- export function getContentText(content: Array<{ type: string; text?: string }> | undefined): string | undefined {
66
- return content?.find((c) => c.type === "text")?.text;
67
- }
68
-
69
- export async function gatewayConfigGet(): Promise<{ raw: string; hash: string }> {
70
- const toolResult = await toolsInvoke<{ content: Array<{ type: string; text?: string }> }>({
71
- tool: "gateway",
72
- args: { action: "config.get", raw: "{}" },
73
- });
74
-
75
- const text = getContentText(toolResult?.content);
76
- if (!text) throw new Error("gateway config.get: missing text payload");
77
-
78
- const parsed = JSON.parse(text) as ToolTextEnvelope;
79
- const raw = String(parsed?.result?.raw ?? "");
80
- const hash = String(parsed?.result?.hash ?? "");
81
- if (!raw) throw new Error("gateway config.get: missing result.raw");
82
- if (!hash) throw new Error("gateway config.get: missing result.hash");
83
- return { raw, hash };
84
- }
85
-
86
- export async function gatewayConfigPatch(patch: unknown, note?: string) {
87
- const { hash } = await gatewayConfigGet();
88
- const raw = JSON.stringify(patch, null, 2);
89
-
90
- return toolsInvoke({
91
- tool: "gateway",
92
- args: {
93
- action: "config.patch",
94
- raw,
95
- baseHash: hash,
96
- note: note ?? "ClawKitchen settings update",
97
- restartDelayMs: 1000,
98
- },
99
- });
100
- }
@@ -1,27 +0,0 @@
1
- import { slugifyId } from "./slugify";
2
-
3
- /** Slugify for file path parts (max 80 chars). */
4
- export function slugifyFilePart(input: string): string {
5
- return slugifyId(input, 80);
6
- }
7
-
8
- const WORKFLOW_MARKER = "<!-- goal-workflow -->";
9
-
10
- export function ensureWorkflowInstructions(body: string): string {
11
- if (body.includes(WORKFLOW_MARKER)) return body;
12
- const snippet = [
13
- "",
14
- "## Workflow",
15
- WORKFLOW_MARKER,
16
- "- Use **Promote to inbox** to send this goal to the development-team inbox for scoping.",
17
- "- When promoted, set goal status to **active**.",
18
- "- Track implementation work via tickets (add links/IDs under a **Tickets** section in this goal).",
19
- "- When development is complete (all associated tickets marked done), set goal status to **done**.",
20
- "",
21
- "## Tickets",
22
- "- (add ticket links/ids)",
23
- "",
24
- ].join("\n");
25
-
26
- return (body ?? "").trim() + snippet;
27
- }
@@ -1,69 +0,0 @@
1
- "use client";
2
-
3
- import { useMemo, useState } from "react";
4
-
5
- /** Client-safe goal types and utils (no node deps). */
6
-
7
- export type GoalStatus = "planned" | "active" | "done";
8
-
9
- export type GoalFrontmatter = {
10
- id: string;
11
- title: string;
12
- status: GoalStatus;
13
- tags: string[];
14
- teams: string[];
15
- updatedAt: string;
16
- };
17
-
18
- /** Form state shape for GoalFormFields. */
19
- export type GoalFormState = {
20
- title: string;
21
- setTitle: (v: string) => void;
22
- status: GoalStatus;
23
- setStatus: (v: GoalStatus) => void;
24
- tagsRaw: string;
25
- setTagsRaw: (v: string) => void;
26
- teamsRaw: string;
27
- setTeamsRaw: (v: string) => void;
28
- body: string;
29
- setBody: (v: string) => void;
30
- };
31
-
32
- /** Parses comma-separated string into trimmed non-empty array. */
33
- export function parseCommaList(raw: string): string[] {
34
- return raw.split(",").map((s) => s.trim()).filter(Boolean);
35
- }
36
-
37
- /** Shared form state for goal create/edit. Returns formState for GoalFormFields and parsed tags/teams. */
38
- export function useGoalFormState(initial?: Partial<GoalFormState>): {
39
- formState: GoalFormState;
40
- tags: string[];
41
- teams: string[];
42
- } {
43
- const [title, setTitle] = useState(initial?.title ?? "");
44
- const [status, setStatus] = useState<GoalStatus>(initial?.status ?? "planned");
45
- const [tagsRaw, setTagsRaw] = useState(initial?.tagsRaw ?? "");
46
- const [teamsRaw, setTeamsRaw] = useState(initial?.teamsRaw ?? "");
47
- const [body, setBody] = useState(initial?.body ?? "");
48
-
49
- const formState: GoalFormState = useMemo(
50
- () => ({
51
- title,
52
- setTitle,
53
- status,
54
- setStatus,
55
- tagsRaw,
56
- setTagsRaw,
57
- teamsRaw,
58
- setTeamsRaw,
59
- body,
60
- setBody,
61
- }),
62
- [title, status, tagsRaw, teamsRaw, body]
63
- );
64
-
65
- const tags = useMemo(() => parseCommaList(tagsRaw), [tagsRaw]);
66
- const teams = useMemo(() => parseCommaList(teamsRaw), [teamsRaw]);
67
-
68
- return { formState, tags, teams };
69
- }
package/src/lib/goals.ts DELETED
@@ -1,171 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { NextResponse } from "next/server";
4
- import YAML from "yaml";
5
- import { errorMessage } from "@/lib/errors";
6
- import { getWorkspaceGoalsDir } from "@/lib/paths";
7
-
8
- export type GoalStatus = "planned" | "active" | "done";
9
-
10
- /** Parses comma-separated string into trimmed non-empty array. */
11
- export function parseCommaList(raw: string): string[] {
12
- return raw.split(",").map((s) => s.trim()).filter(Boolean);
13
- }
14
-
15
- export type GoalFrontmatter = {
16
- id: string;
17
- title: string;
18
- status: GoalStatus;
19
- tags: string[];
20
- teams: string[];
21
- updatedAt: string; // ISO
22
- };
23
-
24
- export type GoalSummary = GoalFrontmatter & {
25
- filename: string;
26
- };
27
-
28
- const ID_RE = /^[a-z0-9][a-z0-9-]{1,63}$/;
29
-
30
- export function assertSafeGoalId(id: string) {
31
- if (!ID_RE.test(id)) {
32
- throw new Error(
33
- `Invalid goal id "${id}". Use 2-64 chars: lowercase letters, numbers, hyphens. Example: "increase-trial-activation".`
34
- );
35
- }
36
- }
37
-
38
- /** Maps goal-related errors to HTTP status: 400 for validation, 500 for other. */
39
- export function goalErrorStatus(msg: string): 400 | 500 {
40
- return /Invalid goal id|Path traversal/.test(msg) ? 400 : 500;
41
- }
42
-
43
- /** Returns NextResponse for goal API errors. Use in catch blocks. */
44
- export function goalErrorResponse(e: unknown): NextResponse {
45
- const msg = errorMessage(e);
46
- const status = goalErrorStatus(msg);
47
- return NextResponse.json({ error: msg }, { status });
48
- }
49
-
50
- export function splitFrontmatter(md: string): { fm: unknown; body: string } {
51
- if (md.startsWith("---\n")) {
52
- const end = md.indexOf("\n---\n", 4);
53
- if (end !== -1) {
54
- const yamlText = md.slice(4, end + 1);
55
- const body = md.slice(end + 5);
56
- const fm = YAML.parse(yamlText) as unknown;
57
- return { fm: fm ?? {}, body };
58
- }
59
- }
60
- return { fm: {}, body: md };
61
- }
62
-
63
- export function normalizeFrontmatter(input: unknown, fallbackId: string, fallbackTitle: string): GoalFrontmatter {
64
- const now = new Date().toISOString();
65
- const obj = (input && typeof input === "object") ? (input as Record<string, unknown>) : {};
66
- const id = String(obj.id ?? fallbackId).trim();
67
- const title = String(obj.title ?? fallbackTitle ?? id).trim() || id;
68
- const statusRaw = String(obj.status ?? "planned").trim();
69
- const status: GoalStatus = statusRaw === "active" || statusRaw === "done" ? statusRaw : "planned";
70
- const tags = Array.isArray(obj.tags) ? (obj.tags as unknown[]).map(String) : [];
71
- const teams = Array.isArray(obj.teams) ? (obj.teams as unknown[]).map(String) : [];
72
- const updatedAt = String(obj.updatedAt ?? now);
73
-
74
- return { id, title, status, tags, teams, updatedAt };
75
- }
76
-
77
- async function ensureGoalsDir() {
78
- const dir = await getWorkspaceGoalsDir();
79
- await fs.mkdir(dir, { recursive: true });
80
- return dir;
81
- }
82
-
83
- async function goalPathForId(id: string) {
84
- assertSafeGoalId(id);
85
- const root = await ensureGoalsDir();
86
- const full = path.join(root, `${id}.md`);
87
- const normalizedRoot = path.resolve(root) + path.sep;
88
- const normalizedFull = path.resolve(full);
89
- if (!normalizedFull.startsWith(normalizedRoot)) throw new Error("Path traversal rejected");
90
- return { root, full };
91
- }
92
-
93
- export async function listGoals(): Promise<GoalSummary[]> {
94
- const dir = await ensureGoalsDir();
95
- const files = (await fs.readdir(dir)).filter((f) => f.endsWith(".md"));
96
-
97
- const out: GoalSummary[] = [];
98
- for (const f of files) {
99
- const full = path.join(dir, f);
100
- const md = await fs.readFile(full, "utf8");
101
- const id = f.replace(/\.md$/, "");
102
- const { fm } = splitFrontmatter(md);
103
- const normalized = normalizeFrontmatter(fm, id, id);
104
- out.push({ ...normalized, filename: f });
105
- }
106
-
107
- // sort: active first, then planned, then done; within status by updatedAt desc
108
- const rank: Record<GoalStatus, number> = { active: 0, planned: 1, done: 2 };
109
- out.sort((a, b) => {
110
- const ra = rank[a.status];
111
- const rb = rank[b.status];
112
- if (ra !== rb) return ra - rb;
113
- return String(b.updatedAt).localeCompare(String(a.updatedAt));
114
- });
115
-
116
- return out;
117
- }
118
-
119
- export async function readGoal(id: string): Promise<{ frontmatter: GoalFrontmatter; body: string; raw: string } | null> {
120
- const { full } = await goalPathForId(id);
121
- try {
122
- const raw = await fs.readFile(full, "utf8");
123
- const { fm, body } = splitFrontmatter(raw);
124
- const fmObj = (fm && typeof fm === "object") ? (fm as Record<string, unknown>) : {};
125
- const frontmatter = normalizeFrontmatter(fm, id, String(fmObj.title ?? id));
126
- return { frontmatter, body, raw };
127
- } catch (e: unknown) {
128
- if (typeof e === "object" && e && (e as { code?: string }).code === "ENOENT") return null;
129
- throw e;
130
- }
131
- }
132
-
133
- export async function writeGoal(params: {
134
- id: string;
135
- title: string;
136
- status?: GoalStatus;
137
- tags?: string[];
138
- teams?: string[];
139
- body: string;
140
- }): Promise<{ frontmatter: GoalFrontmatter; raw: string }>
141
- {
142
- const { id } = params;
143
- const { full } = await goalPathForId(id);
144
-
145
- const updatedAt = new Date().toISOString();
146
- const fm: GoalFrontmatter = {
147
- id,
148
- title: params.title,
149
- status: params.status ?? "planned",
150
- tags: params.tags ?? [],
151
- teams: params.teams ?? [],
152
- updatedAt,
153
- };
154
-
155
- const raw = `---\n${YAML.stringify(fm).trim()}\n---\n\n${(params.body ?? "").trim()}\n`;
156
- await fs.writeFile(full, raw, "utf8");
157
- return { frontmatter: fm, raw };
158
- }
159
-
160
- export async function deleteGoal(id: string): Promise<{ ok: true } | { ok: false; reason: "not_found" }> {
161
- const { full } = await goalPathForId(id);
162
- try {
163
- await fs.unlink(full);
164
- return { ok: true };
165
- } catch (e: unknown) {
166
- if (typeof e === "object" && e && (e as { code?: string }).code === "ENOENT") {
167
- return { ok: false, reason: "not_found" };
168
- }
169
- throw e;
170
- }
171
- }
package/src/lib/json.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * Safely parse JSON. Returns null on parse error.
3
- */
4
- export function safeJsonParse(raw: string): unknown {
5
- try {
6
- return JSON.parse(raw);
7
- } catch {
8
- return null;
9
- }
10
- }
@@ -1,19 +0,0 @@
1
- // NOTE: This file is compiled by Next.js.
2
- // Do not import `openclaw/plugin-sdk` here (it is provided by the gateway runtime, not as an npm dep).
3
-
4
- type KitchenApi = {
5
- config: unknown;
6
- runtime: {
7
- system: { runCommandWithTimeout: (argv: string[], opts: { timeoutMs: number }) => Promise<{ stdout?: string; stderr?: string }> };
8
- };
9
- };
10
-
11
- export function getKitchenApi(): KitchenApi {
12
- const api = (globalThis as unknown as { __clawkitchen_api?: KitchenApi }).__clawkitchen_api;
13
- if (!api) {
14
- throw new Error(
15
- "ClawKitchen: OpenClaw plugin API not available. (This should only happen if Kitchen is started outside the gateway process.)",
16
- );
17
- }
18
- return api;
19
- }
@@ -1,46 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
-
4
- export type MarketplaceRecipe = {
5
- slug: string;
6
- kind: "team" | "agent";
7
- name: string;
8
- description: string;
9
- version: string;
10
- tags: string[];
11
- sourceUrl: string;
12
- homepageUrl?: string;
13
- };
14
-
15
- export type MarketplaceRegistry = {
16
- version: number;
17
- generatedAt: string;
18
- recipes: MarketplaceRecipe[];
19
- };
20
-
21
- const REGISTRY_PATH = path.join(process.cwd(), "marketplace", "registry.json");
22
-
23
- export async function loadRegistry(): Promise<MarketplaceRegistry> {
24
- const raw = await fs.readFile(REGISTRY_PATH, "utf8");
25
- const data = JSON.parse(raw) as MarketplaceRegistry;
26
- const obj = data as unknown as { recipes?: unknown };
27
- if (!data || typeof data !== "object" || !Array.isArray(obj.recipes)) {
28
- throw new Error("Invalid marketplace registry.json");
29
- }
30
- return data;
31
- }
32
-
33
- export function search(recipes: MarketplaceRecipe[], q: string | null) {
34
- const query = (q ?? "").trim().toLowerCase();
35
- if (!query) return recipes;
36
-
37
- return recipes.filter((r) => {
38
- const hay = [r.slug, r.name, r.description, ...(r.tags ?? [])].join(" ").toLowerCase();
39
- return hay.includes(query);
40
- });
41
- }
42
-
43
- export function getBySlug(recipes: MarketplaceRecipe[], slug: string) {
44
- const s = slug.trim().toLowerCase();
45
- return recipes.find((r) => r.slug.toLowerCase() === s) ?? null;
46
- }
@@ -1,59 +0,0 @@
1
- import { getKitchenApi } from "@/lib/kitchen-api";
2
-
3
- export type OpenClawExecResult = {
4
- ok: boolean;
5
- exitCode: number;
6
- stdout: string;
7
- stderr: string;
8
- };
9
-
10
- function extractStdout(err: { stdout?: unknown }): string {
11
- if (typeof err.stdout === "string") return err.stdout;
12
- if (err.stdout && typeof err.stdout === "object" && "toString" in err.stdout) {
13
- return String((err.stdout as { toString: () => string }).toString());
14
- }
15
- return "";
16
- }
17
-
18
- function resolveExitCode(res: { exitCode?: unknown; code?: unknown; status?: unknown }): number {
19
- if (typeof res.exitCode === "number") return res.exitCode;
20
- if (typeof res.code === "number") return res.code;
21
- if (typeof res.status === "number") return res.status;
22
- return 0;
23
- }
24
-
25
- function extractStderr(err: { stderr?: unknown; message?: unknown }, fallback: unknown): string {
26
- if (typeof err.stderr === "string") return err.stderr;
27
- if (err.stderr && typeof err.stderr === "object" && "toString" in err.stderr) {
28
- return String((err.stderr as { toString: () => string }).toString());
29
- }
30
- if (typeof err.message === "string") return err.message;
31
- return String(fallback);
32
- }
33
-
34
- export async function runOpenClaw(args: string[]): Promise<OpenClawExecResult> {
35
- const api = getKitchenApi();
36
- try {
37
- const res = (await api.runtime.system.runCommandWithTimeout(["openclaw", ...args], { timeoutMs: 120000 })) as {
38
- stdout?: unknown;
39
- stderr?: unknown;
40
- exitCode?: unknown;
41
- code?: unknown;
42
- status?: unknown;
43
- };
44
-
45
- const stdout = String(res.stdout ?? "");
46
- const stderr = String(res.stderr ?? "");
47
- const exitCode = resolveExitCode(res);
48
-
49
-
50
- if (exitCode !== 0) return { ok: false, exitCode, stdout, stderr };
51
- return { ok: true, exitCode: 0, stdout, stderr };
52
- } catch (e: unknown) {
53
- const err = e as { code?: unknown; stdout?: unknown; stderr?: unknown; message?: unknown };
54
- const exitCode = typeof err.code === "number" ? err.code : 1;
55
- const stdout = extractStdout(err);
56
- const stderr = extractStderr(err, e);
57
- return { ok: false, exitCode, stdout, stderr };
58
- }
59
- }
package/src/lib/paths.ts DELETED
@@ -1,69 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
-
5
- type OpenClawConfig = {
6
- agents?: { defaults?: { workspace?: string } };
7
- gateway?: { port?: number; auth?: { token?: string } };
8
- tools?: {
9
- agentToAgent?: {
10
- enabled?: boolean;
11
- allow?: string[];
12
- };
13
- };
14
- plugins?: {
15
- installs?: { recipes?: { installPath?: string; sourcePath?: string } };
16
- load?: { paths?: string[] };
17
- };
18
- };
19
-
20
- export async function readOpenClawConfig(): Promise<OpenClawConfig> {
21
- const p = path.join(os.homedir(), ".openclaw", "openclaw.json");
22
- const text = await fs.readFile(p, "utf8");
23
- return JSON.parse(text) as OpenClawConfig;
24
- }
25
-
26
- export async function getWorkspaceDir() {
27
- const cfg = await readOpenClawConfig();
28
- const ws = cfg.agents?.defaults?.workspace;
29
- if (!ws) throw new Error("agents.defaults.workspace is not set in ~/.openclaw/openclaw.json");
30
- return ws;
31
- }
32
-
33
- export async function getWorkspaceRecipesDir() {
34
- const ws = await getWorkspaceDir();
35
- return path.join(ws, "recipes");
36
- }
37
-
38
- export async function getWorkspaceGoalsDir() {
39
- const ws = await getWorkspaceDir();
40
- return path.join(ws, "notes", "goals");
41
- }
42
-
43
- export async function getTeamWorkspaceDir(teamId: string) {
44
- const home = os.homedir();
45
- if (!home) throw new Error("Could not resolve home directory");
46
- return path.join(home, ".openclaw", `workspace-${teamId}`);
47
- }
48
-
49
- /** Team workspace dir derived from agents.defaults.workspace (sibling: .. / workspace-{teamId}) */
50
- export function teamDirFromBaseWorkspace(baseWorkspace: string, teamId: string) {
51
- return path.resolve(baseWorkspace, "..", `workspace-${teamId}`);
52
- }
53
-
54
- /** Rejects path traversal and empty names; returns normalized name. */
55
- export function assertSafeRelativeFileName(name: string): string {
56
- const n = name.replace(/\\/g, "/");
57
- if (!n || n.startsWith("/") || n.includes("..")) throw new Error("Invalid file name");
58
- return n;
59
- }
60
-
61
- export async function getBuiltinRecipesDir() {
62
- const cfg = await readOpenClawConfig();
63
- const p =
64
- cfg.plugins?.installs?.recipes?.installPath ||
65
- cfg.plugins?.installs?.recipes?.sourcePath ||
66
- cfg.plugins?.load?.paths?.[0];
67
- if (!p) throw new Error("Could not determine recipes plugin install path from ~/.openclaw/openclaw.json");
68
- return path.join(p, "recipes", "default");
69
- }
package/src/lib/poll.ts DELETED
@@ -1,18 +0,0 @@
1
- /**
2
- * Polls until check returns a non-null value or timeout. Returns the value or null on timeout.
3
- */
4
- export async function pollUntil<T>(
5
- check: () => Promise<T | null>,
6
- opts: { timeoutMs: number; intervalMs?: number }
7
- ): Promise<T | null> {
8
- const intervalMs = opts.intervalMs ?? 500;
9
- const started = Date.now();
10
-
11
- while (Date.now() - started < opts.timeoutMs) {
12
- const result = await check();
13
- if (result !== null) return result;
14
- await new Promise((r) => setTimeout(r, intervalMs));
15
- }
16
-
17
- return null;
18
- }
@@ -1,42 +0,0 @@
1
- import YAML from "yaml";
2
-
3
- export function suggestIds(baseId: string): string[] {
4
- const b = String(baseId || "recipe").trim();
5
- return [`custom-${b}`, `my-${b}`, `${b}-2`, `${b}-alt`];
6
- }
7
-
8
- export function scaffoldCmdForKind(kind: string, toId: string): string[] | null {
9
- if (kind === "team") {
10
- return ["recipes", "scaffold-team", toId, "--team-id", toId, "--overwrite", "--overwrite-recipe"];
11
- }
12
- if (kind === "agent") {
13
- return ["recipes", "scaffold", toId, "--agent-id", toId, "--overwrite", "--overwrite-recipe"];
14
- }
15
- return null;
16
- }
17
-
18
- export function patchFrontmatter(
19
- original: string,
20
- toId: string,
21
- toName: string | undefined
22
- ): { next: string; kind: string } {
23
- const end = original.indexOf("\n---\n", 4);
24
- if (end === -1) throw new Error("Recipe frontmatter not terminated (---)");
25
- const yamlText = original.slice(4, end + 1);
26
- const fm = (YAML.parse(yamlText) ?? {}) as Record<string, unknown>;
27
- const kind = String(fm.kind ?? "").trim().toLowerCase();
28
-
29
- const teamPatch =
30
- kind === "team"
31
- ? {
32
- team: {
33
- ...(typeof fm.team === "object" && fm.team ? (fm.team as Record<string, unknown>) : {}),
34
- teamId: toId,
35
- },
36
- }
37
- : {};
38
- const patched: Record<string, unknown> = { ...fm, id: toId, ...(toName ? { name: toName } : {}), ...teamPatch };
39
- const nextYaml = YAML.stringify(patched).trimEnd();
40
- const next = `---\n${nextYaml}\n---\n${original.slice(end + 5)}`;
41
- return { next, kind };
42
- }
@@ -1,30 +0,0 @@
1
- export function splitRecipeFrontmatter(md: string): { yamlText: string; rest: string } {
2
- if (!md.startsWith("---\n")) throw new Error("Recipe markdown must start with YAML frontmatter (---)");
3
- const end = md.indexOf("\n---\n", 4);
4
- if (end === -1) throw new Error("Recipe frontmatter not terminated (---)");
5
- const yamlText = md.slice(4, end + 1);
6
- const rest = md.slice(end + 5);
7
- return { yamlText, rest };
8
- }
9
-
10
- export function normalizeRole(role: string): string {
11
- const r = role.trim();
12
- if (!r) throw new Error("role is required");
13
- if (!/^[a-z][a-z0-9-]{0,62}$/i.test(r)) throw new Error("role must be alphanumeric/dash");
14
- return r;
15
- }
16
-
17
- /** Validates create-team or create-agent id. Returns error message or null. */
18
- export function validateCreateId(
19
- recipe: { id: string } | null,
20
- id: string,
21
- entityLabel: "team" | "agent"
22
- ): string | null {
23
- if (!recipe) return null;
24
- const t = id.trim();
25
- const label = entityLabel === "team" ? "Team id" : "Agent id";
26
- if (!t) return `${label} is required.`;
27
- if (t === recipe.id)
28
- return `${label} cannot be the same as the recipe id (${recipe.id}). Choose a new ${entityLabel} id.`;
29
- return null;
30
- }