@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,193 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { NextResponse } from "next/server";
4
- import { getWorkspaceRecipesDir } from "@/lib/paths";
5
-
6
- function isValidId(id: string) {
7
- // Keep consistent with common recipe/team id expectations.
8
- return /^[a-z0-9][a-z0-9_-]{1,62}$/.test(id);
9
- }
10
-
11
- function yamlEscape(s: string) {
12
- // Minimal YAML escaping for simple strings.
13
- const trimmed = s.replace(/\r\n/g, "\n").trimEnd();
14
- if (!trimmed) return "\"\"";
15
- // If it's a safe bare string, keep it readable.
16
- if (/^[A-Za-z0-9 _.,:;()\[\]{}\-+/@]+$/.test(trimmed) && !/[:#\n]/.test(trimmed)) {
17
- return trimmed;
18
- }
19
- return JSON.stringify(trimmed);
20
- }
21
-
22
- export async function POST(req: Request) {
23
- const body = (await req.json()) as {
24
- dryRun?: boolean;
25
- recipeId?: string;
26
- teamId?: string;
27
- name?: string;
28
- description?: string;
29
- roles?: Array<{ roleId?: string; displayName?: string }>;
30
- };
31
-
32
- const dryRun = !!body.dryRun;
33
- const recipeId = String(body.recipeId ?? body.teamId ?? "").trim();
34
- const teamId = String(body.teamId ?? recipeId).trim();
35
- const name = typeof body.name === "string" ? body.name.trim() : "";
36
- const description = typeof body.description === "string" ? body.description.trim() : "";
37
- const roles = Array.isArray(body.roles) ? body.roles : [];
38
-
39
- if (!recipeId) return NextResponse.json({ ok: false, error: "recipeId is required" }, { status: 400 });
40
- if (!teamId) return NextResponse.json({ ok: false, error: "teamId is required" }, { status: 400 });
41
- if (!isValidId(recipeId)) {
42
- return NextResponse.json(
43
- { ok: false, error: "Invalid recipeId. Use lowercase letters/numbers with - or _ (2-63 chars)." },
44
- { status: 400 },
45
- );
46
- }
47
- if (!isValidId(teamId)) {
48
- return NextResponse.json(
49
- { ok: false, error: "Invalid teamId. Use lowercase letters/numbers with - or _ (2-63 chars)." },
50
- { status: 400 },
51
- );
52
- }
53
- if (!teamId.endsWith("-team")) {
54
- return NextResponse.json(
55
- { ok: false, error: "teamId must end with -team" },
56
- { status: 400 },
57
- );
58
- }
59
-
60
- if (roles.length < 1) {
61
- return NextResponse.json({ ok: false, error: "Select at least one role/agent" }, { status: 400 });
62
- }
63
-
64
- const normalizedRoles = roles
65
- .map((r) => ({
66
- roleId: String(r.roleId ?? "").trim(),
67
- displayName: typeof r.displayName === "string" ? r.displayName.trim() : "",
68
- }))
69
- .filter((r) => r.roleId);
70
-
71
- if (normalizedRoles.length < 1) {
72
- return NextResponse.json({ ok: false, error: "Each selected agent must have a roleId" }, { status: 400 });
73
- }
74
-
75
- const roleSet = new Set<string>();
76
- for (const r of normalizedRoles) {
77
- if (!isValidId(r.roleId)) {
78
- return NextResponse.json(
79
- { ok: false, error: `Invalid roleId: ${r.roleId}. Use lowercase letters/numbers with - or _.` },
80
- { status: 400 },
81
- );
82
- }
83
- if (roleSet.has(r.roleId)) {
84
- return NextResponse.json({ ok: false, error: `Duplicate roleId: ${r.roleId}` }, { status: 400 });
85
- }
86
- roleSet.add(r.roleId);
87
- }
88
-
89
- const dir = await getWorkspaceRecipesDir();
90
- const filePath = path.join(dir, `${recipeId}.md`);
91
-
92
- try {
93
- await fs.access(filePath);
94
- return NextResponse.json({ ok: false, error: `Recipe already exists: ${recipeId}` }, { status: 409 });
95
- } catch {
96
- // ok
97
- }
98
-
99
- const lines: string[] = [];
100
- lines.push("---");
101
- lines.push(`id: ${recipeId}`);
102
- lines.push(`name: ${yamlEscape(name || recipeId)}`);
103
- lines.push(`version: 0.1.0`);
104
- if (description) lines.push(`description: ${yamlEscape(description)}`);
105
- lines.push("kind: team");
106
- lines.push("requiredSkills: []");
107
- lines.push("team:");
108
- lines.push(` teamId: ${teamId}`);
109
- lines.push("agents:");
110
-
111
- for (const r of normalizedRoles) {
112
- lines.push(` - role: ${r.roleId}`);
113
- if (r.displayName) lines.push(` name: ${yamlEscape(r.displayName)}`);
114
- lines.push(" tools:");
115
- lines.push(" profile: coding");
116
- lines.push(" allow:");
117
- lines.push(" - group:fs");
118
- lines.push(" - group:web");
119
- lines.push(" - group:runtime");
120
- lines.push(" deny:");
121
- lines.push(" - exec");
122
- }
123
-
124
- lines.push("templates:");
125
-
126
- for (const r of normalizedRoles) {
127
- const roleId = r.roleId;
128
-
129
- lines.push(` ${roleId}.soul: |`);
130
- lines.push(` # SOUL.md`);
131
- lines.push(` `);
132
- lines.push(` Role: ${roleId}`);
133
- lines.push(` `);
134
-
135
- lines.push(` ${roleId}.agents: |`);
136
- lines.push(` # AGENTS.md`);
137
- lines.push(` `);
138
- lines.push(` You are the ${roleId} role in team ${teamId}.`);
139
- lines.push(` `);
140
-
141
- lines.push(` ${roleId}.tools: |`);
142
- lines.push(` # TOOLS.md`);
143
- lines.push(` `);
144
- lines.push(` - (empty)`);
145
- lines.push(` `);
146
-
147
- lines.push(` ${roleId}.status: |`);
148
- lines.push(` # STATUS.md`);
149
- lines.push(` `);
150
- lines.push(` - (empty)`);
151
- lines.push(` `);
152
-
153
- lines.push(` ${roleId}.notes: |`);
154
- lines.push(` # NOTES.md`);
155
- lines.push(` `);
156
- lines.push(` - (empty)`);
157
- lines.push("");
158
- }
159
-
160
- lines.push("files:");
161
- lines.push(" - path: SOUL.md");
162
- lines.push(" template: soul");
163
- lines.push(" mode: createOnly");
164
- lines.push(" - path: AGENTS.md");
165
- lines.push(" template: agents");
166
- lines.push(" mode: createOnly");
167
- lines.push(" - path: TOOLS.md");
168
- lines.push(" template: tools");
169
- lines.push(" mode: createOnly");
170
- lines.push(" - path: STATUS.md");
171
- lines.push(" template: status");
172
- lines.push(" mode: createOnly");
173
- lines.push(" - path: NOTES.md");
174
- lines.push(" template: notes");
175
- lines.push(" mode: createOnly");
176
-
177
- lines.push("---");
178
- lines.push("");
179
- lines.push("# Custom team recipe");
180
- lines.push("");
181
- lines.push("Generated by ClawKitchen Custom Team Builder.");
182
- lines.push("");
183
-
184
- const md = lines.join("\n");
185
-
186
- if (dryRun) {
187
- return NextResponse.json({ ok: true, dryRun: true, recipeId, teamId, filePath, md });
188
- }
189
-
190
- await fs.writeFile(filePath, md, "utf8");
191
-
192
- return NextResponse.json({ ok: true, recipeId, teamId, filePath });
193
- }
@@ -1,65 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { runOpenClaw } from "@/lib/openclaw";
4
-
5
- export async function getAttachedTeams(workspaceRoot: string, recipeId: string): Promise<string[]> {
6
- const attachedTeams: string[] = [];
7
- const teamsRoot = path.resolve(workspaceRoot, "..");
8
-
9
- try {
10
- const entries = await fs.readdir(teamsRoot, { withFileTypes: true });
11
- const workspaceDirs = entries.filter((e) => e.isDirectory() && e.name.startsWith("workspace-"));
12
-
13
- for (const dirent of workspaceDirs) {
14
- const metaPath = path.join(teamsRoot, dirent.name, "team.json");
15
- try {
16
- const raw = await fs.readFile(metaPath, "utf8");
17
- const meta = JSON.parse(raw) as { recipeId?: unknown; teamId?: unknown };
18
- if (String(meta.recipeId ?? "").trim() === recipeId) {
19
- attachedTeams.push(
20
- String(meta.teamId ?? dirent.name.replace(/^workspace-/, "")).trim() || dirent.name
21
- );
22
- }
23
- } catch {
24
- // ignore
25
- }
26
- }
27
- } catch {
28
- // ignore
29
- }
30
-
31
- return attachedTeams;
32
- }
33
-
34
- export async function getAttachedAgents(
35
- workspaceRoot: string,
36
- recipeId: string
37
- ): Promise<{ attachedAgents: string[]; hasSameIdAgent: boolean }> {
38
- const attachedAgents: string[] = [];
39
- let hasSameIdAgent = false;
40
-
41
- const agentsRes = await runOpenClaw(["agents", "list", "--json"]);
42
- if (!agentsRes.ok) return { attachedAgents, hasSameIdAgent };
43
-
44
- try {
45
- const agents = JSON.parse(agentsRes.stdout) as Array<{ id?: unknown }>;
46
- hasSameIdAgent = agents.some((a) => String(a.id ?? "").trim() === recipeId);
47
-
48
- for (const a of agents) {
49
- const agentId = String(a.id ?? "").trim();
50
- if (!agentId) continue;
51
- const metaPath = path.join(workspaceRoot, "agents", agentId, "agent.json");
52
- try {
53
- const raw = await fs.readFile(metaPath, "utf8");
54
- const meta = JSON.parse(raw) as { recipeId?: unknown };
55
- if (String(meta.recipeId ?? "").trim() === recipeId) attachedAgents.push(agentId);
56
- } catch {
57
- // ignore
58
- }
59
- }
60
- } catch {
61
- // ignore
62
- }
63
-
64
- return { attachedAgents, hasSameIdAgent };
65
- }
@@ -1,73 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import path from "node:path";
3
- import { runOpenClaw } from "@/lib/openclaw";
4
- import { findRecipeById, resolveRecipePath } from "@/lib/recipes";
5
- import fs from "node:fs/promises";
6
- import { getAttachedTeams, getAttachedAgents } from "./helpers";
7
-
8
- export async function POST(req: Request) {
9
- const body = (await req.json()) as { id?: string };
10
- const id = String(body.id ?? "").trim();
11
- if (!id) return NextResponse.json({ ok: false, error: "id is required" }, { status: 400 });
12
-
13
- const item = await findRecipeById(id);
14
- if (!item) return NextResponse.json({ ok: false, error: `Recipe not found: ${id}` }, { status: 404 });
15
- if (item.source === "builtin") {
16
- return NextResponse.json({ ok: false, error: `Recipe ${id} is builtin and cannot be deleted` }, { status: 403 });
17
- }
18
-
19
- const workspaceRoot = (await runOpenClaw(["config", "get", "agents.defaults.workspace"]))?.stdout?.trim();
20
- if (!workspaceRoot) {
21
- return NextResponse.json({ ok: false, error: "agents.defaults.workspace not set" }, { status: 500 });
22
- }
23
- const allowedDir = path.resolve(workspaceRoot, "recipes") + path.sep;
24
-
25
- const filePath = await resolveRecipePath(item);
26
- const resolved = path.resolve(filePath);
27
- if (!resolved.startsWith(allowedDir)) {
28
- return NextResponse.json({ ok: false, error: `Refusing to delete non-workspace recipe path: ${resolved}` }, { status: 403 });
29
- }
30
-
31
- const kind = (item.kind ?? "team") as "team" | "agent";
32
-
33
- if (kind === "team") {
34
- const attachedTeams = await getAttachedTeams(workspaceRoot, id);
35
- if (attachedTeams.length) {
36
- return NextResponse.json(
37
- {
38
- ok: false,
39
- error: `Team ${id} is in use by installed team(s): ${attachedTeams.join(", ")}. Remove the team(s) first, then delete the recipe. If no team is shown, you still have a .openclaw/workspace-${id} folder. Please remove the folder to delete this recipe.`,
40
- details: { attachedTeams },
41
- },
42
- { status: 409 }
43
- );
44
- }
45
- }
46
-
47
- if (kind === "agent") {
48
- const { attachedAgents, hasSameIdAgent } = await getAttachedAgents(workspaceRoot, id);
49
- if (hasSameIdAgent) {
50
- return NextResponse.json(
51
- {
52
- ok: false,
53
- error: `Agent recipe ${id} cannot be deleted because an active agent exists with the same id: ${id}. Delete the agent first, then delete the recipe.`,
54
- details: { agentId: id },
55
- },
56
- { status: 409 }
57
- );
58
- }
59
- if (attachedAgents.length) {
60
- return NextResponse.json(
61
- {
62
- ok: false,
63
- error: `Agent recipe ${id} is in use by active agent(s): ${attachedAgents.join(", ")}. Delete the agent(s) first, then delete the recipe.`,
64
- details: { attachedAgents },
65
- },
66
- { status: 409 }
67
- );
68
- }
69
- }
70
-
71
- await fs.rm(resolved, { force: true });
72
- return NextResponse.json({ ok: true, deleted: resolved });
73
- }
@@ -1,21 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import { runOpenClaw } from "@/lib/openclaw";
3
-
4
- export async function GET() {
5
- const { stdout, stderr } = await runOpenClaw(["recipes", "list"]);
6
- if (stderr.trim()) {
7
- // non-fatal warnings go to stderr sometimes; still try to parse stdout.
8
- }
9
-
10
- let data: unknown;
11
- try {
12
- data = JSON.parse(stdout);
13
- } catch {
14
- return NextResponse.json({ error: "Failed to parse openclaw recipes list output", stderr, stdout }, { status: 500 });
15
- }
16
-
17
- // NOTE: We intentionally return the full list here. openclaw can return both builtin + workspace
18
- // entries for the same (kind,id). The UI can decide which to prefer depending on context.
19
- const list = Array.isArray(data) ? data : [];
20
- return NextResponse.json({ recipes: list, stderr });
21
- }
@@ -1,156 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from "vitest";
2
- import {
3
- parseRecipeFrontmatter,
4
- buildNextMarkdown,
5
- handleRemove,
6
- handleAdd,
7
- handleAddLike,
8
- } from "../helpers";
9
-
10
- vi.mock("@/lib/openclaw", () => ({
11
- runOpenClaw: vi.fn(),
12
- }));
13
-
14
- import { runOpenClaw } from "@/lib/openclaw";
15
-
16
- describe("recipes team-agents helpers", () => {
17
- describe("parseRecipeFrontmatter", () => {
18
- it("parses valid YAML with agents and templates", () => {
19
- const yaml = `
20
- kind: team
21
- agents:
22
- - role: lead
23
- name: Lead
24
- templates:
25
- lead.prompt: "# Prompt"
26
- `;
27
- const { fm, agents, templates } = parseRecipeFrontmatter(yaml);
28
- expect(fm.kind).toBe("team");
29
- expect(agents).toHaveLength(1);
30
- expect(agents[0]).toEqual({ role: "lead", name: "Lead" });
31
- expect(templates).toEqual({ "lead.prompt": "# Prompt" });
32
- });
33
-
34
- it("handles empty YAML", () => {
35
- const { fm, agents, templates } = parseRecipeFrontmatter("");
36
- expect(fm).toEqual({});
37
- expect(agents).toEqual([]);
38
- expect(templates).toEqual({});
39
- });
40
-
41
- it("handles non-array agents", () => {
42
- const { agents } = parseRecipeFrontmatter("agents: not-an-array");
43
- expect(agents).toEqual([]);
44
- });
45
-
46
- it("handles non-object templates", () => {
47
- const { templates } = parseRecipeFrontmatter("templates: []");
48
- expect(templates).toEqual({});
49
- });
50
- });
51
-
52
- describe("buildNextMarkdown", () => {
53
- it("builds markdown with updated agents and templates", () => {
54
- const fm = { kind: "team" };
55
- const nextAgents = [{ role: "lead", name: "Lead" }];
56
- const nextTemplates = { "lead.prompt": "content" };
57
- const rest = "# Body";
58
- const result = buildNextMarkdown(fm, nextAgents, nextTemplates, rest);
59
- expect(result).toContain("---");
60
- expect(result).toContain("kind: team");
61
- expect(result).toContain("role: lead");
62
- expect(result).toContain("# Body");
63
- });
64
-
65
- it("omits templates when empty", () => {
66
- const fm = { kind: "team" };
67
- const nextAgents: Array<Record<string, unknown>> = [];
68
- const nextTemplates: Record<string, unknown> = {};
69
- const result = buildNextMarkdown(fm, nextAgents, nextTemplates, "body");
70
- expect(result).not.toContain("templates:");
71
- });
72
- });
73
-
74
- describe("handleRemove", () => {
75
- it("removes agent and role-prefixed templates", () => {
76
- const agents = [
77
- { role: "lead", name: "Lead" },
78
- { role: "qa", name: "QA" },
79
- ];
80
- const templates = {
81
- "lead.prompt": "x",
82
- "qa.prompt": "y",
83
- "other.stuff": "z",
84
- };
85
- const result = handleRemove(agents, templates, "lead");
86
- expect(result.nextAgents).toHaveLength(1);
87
- expect(result.nextAgents[0].role).toBe("qa");
88
- expect(result.nextTemplates).toEqual({ "qa.prompt": "y", "other.stuff": "z" });
89
- expect(result.addedRole).toBeNull();
90
- });
91
-
92
- it("returns addedRole null", () => {
93
- const result = handleRemove([{ role: "x" }], {}, "x");
94
- expect(result.addedRole).toBeNull();
95
- });
96
- });
97
-
98
- describe("handleAdd", () => {
99
- it("adds new agent when role not present", () => {
100
- const agents: Array<Record<string, unknown>> = [];
101
- const templates = {};
102
- const result = handleAdd(agents, templates, "qa", "QA Lead");
103
- expect(result.nextAgents).toHaveLength(1);
104
- expect(result.nextAgents[0]).toEqual({ role: "qa", name: "QA Lead" });
105
- expect(result.addedRole).toBe("qa");
106
- });
107
-
108
- it("updates existing agent when role present", () => {
109
- const agents = [{ role: "qa", name: "Old" }];
110
- const templates = {};
111
- const result = handleAdd(agents, templates, "qa", "New Name");
112
- expect(result.nextAgents).toHaveLength(1);
113
- expect(result.nextAgents[0]).toEqual({ role: "qa", name: "New Name" });
114
- expect(result.addedRole).toBe("qa");
115
- });
116
-
117
- it("copies templates without mutating", () => {
118
- const agents: Array<Record<string, unknown>> = [];
119
- const templates = { x: "y" };
120
- const result = handleAdd(agents, templates, "qa", "");
121
- expect(result.nextTemplates).toEqual({ x: "y" });
122
- });
123
- });
124
-
125
- describe("handleAddLike", () => {
126
- beforeEach(() => {
127
- vi.mocked(runOpenClaw).mockResolvedValue({
128
- ok: true,
129
- stdout: JSON.stringify([{ id: "team1-lead" }]),
130
- stderr: "",
131
- exitCode: 0,
132
- });
133
- });
134
-
135
- it("returns 400 when baseRole not found", async () => {
136
- const agents: Array<Record<string, unknown>> = [];
137
- const result = await handleAddLike(agents, {}, "lead", "New", "team1");
138
- expect(result).toBeInstanceOf(Response);
139
- const res = result as Response;
140
- expect(res.status).toBe(400);
141
- const json = await res.json();
142
- expect(json.error).toContain("baseRole not found");
143
- });
144
-
145
- it("adds clone with suffixed role when base exists", async () => {
146
- const agents = [{ role: "lead", name: "Lead" }];
147
- const templates = { "lead.prompt": "content" };
148
- const result = await handleAddLike(agents, templates, "lead", "Lead 2", "team1");
149
- expect(result).not.toBeInstanceOf(Response);
150
- const op = result as { nextAgents: Array<Record<string, unknown>>; nextTemplates: Record<string, unknown>; addedRole: string | null };
151
- expect(op.nextAgents).toHaveLength(2);
152
- expect(op.nextAgents[1].role).toMatch(/^lead(-\d+)?$/);
153
- expect(op.addedRole).toBeTruthy();
154
- });
155
- });
156
- });
@@ -1,151 +0,0 @@
1
- import YAML from "yaml";
2
- import { NextResponse } from "next/server";
3
- import { runOpenClaw } from "@/lib/openclaw";
4
- import { splitRecipeFrontmatter, normalizeRole } from "@/lib/recipe-team-agents";
5
-
6
- export { splitRecipeFrontmatter as splitFrontmatter, normalizeRole };
7
-
8
- export function parseRecipeFrontmatter(yamlText: string) {
9
- const fm = (YAML.parse(yamlText) ?? {}) as Record<string, unknown>;
10
- const agentsRaw = fm.agents;
11
- const agents: Array<Record<string, unknown>> = Array.isArray(agentsRaw)
12
- ? (agentsRaw as Array<Record<string, unknown>>)
13
- : [];
14
- const templatesRaw = fm.templates;
15
- const templates: Record<string, unknown> =
16
- templatesRaw && typeof templatesRaw === "object" && !Array.isArray(templatesRaw)
17
- ? (templatesRaw as Record<string, unknown>)
18
- : {};
19
- return { fm, agents, templates };
20
- }
21
-
22
- export function buildNextMarkdown(
23
- fm: Record<string, unknown>,
24
- nextAgents: Array<Record<string, unknown>>,
25
- nextTemplates: Record<string, unknown>,
26
- rest: string
27
- ) {
28
- const nextFm = {
29
- ...fm,
30
- agents: nextAgents,
31
- ...(Object.keys(nextTemplates).length ? { templates: nextTemplates } : {}),
32
- };
33
- const nextYaml = YAML.stringify(nextFm).trimEnd();
34
- return `---\n${nextYaml}\n---\n${rest}`;
35
- }
36
-
37
- export type OpResult = {
38
- nextAgents: Array<Record<string, unknown>>;
39
- nextTemplates: Record<string, unknown>;
40
- addedRole: string | null;
41
- };
42
-
43
- export function handleRemove(
44
- agents: Array<Record<string, unknown>>,
45
- templates: Record<string, unknown>,
46
- role: string
47
- ): OpResult {
48
- const nextAgents = agents.filter((a) => String(a.role ?? "") !== role);
49
- const nextTemplates = { ...templates };
50
- for (const k of Object.keys(nextTemplates)) {
51
- if (k.startsWith(`${role}.`)) delete nextTemplates[k];
52
- }
53
- return { nextAgents, nextTemplates, addedRole: null };
54
- }
55
-
56
- export function handleAdd(
57
- agents: Array<Record<string, unknown>>,
58
- templates: Record<string, unknown>,
59
- role: string,
60
- name: string
61
- ): OpResult {
62
- const next = {
63
- ...agents.find((a) => String(a.role ?? "") === role),
64
- role,
65
- ...(name ? { name } : {}),
66
- };
67
- const nextAgents = agents.slice();
68
- const idx = nextAgents.findIndex((a) => String(a.role ?? "") === role);
69
- if (idx === -1) nextAgents.push(next);
70
- else nextAgents[idx] = next;
71
- return { nextAgents, nextTemplates: { ...templates }, addedRole: role };
72
- }
73
-
74
- function maxSuffixFromUsedRoles(usedRoles: Set<string>, baseRole: string): number {
75
- const escaped = baseRole.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
76
- let n = 1;
77
- for (const r of usedRoles) {
78
- if (r === baseRole) n = Math.max(n, 1);
79
- const m = r.match(new RegExp(`^${escaped}-([0-9]+)$`));
80
- if (m) {
81
- const k = Number(m[1]);
82
- if (Number.isFinite(k)) n = Math.max(n, k);
83
- }
84
- }
85
- return n;
86
- }
87
-
88
- async function fetchExistingAgentIds(teamId: string): Promise<Set<string>> {
89
- if (!teamId) return new Set<string>();
90
- try {
91
- const res = await runOpenClaw(["agents", "list", "--json"]);
92
- if (!res.ok) return new Set<string>();
93
- const items = JSON.parse(res.stdout) as Array<{ id?: unknown }>;
94
- return new Set(items.map((a) => String(a.id ?? "").trim()).filter(Boolean));
95
- } catch {
96
- return new Set<string>();
97
- }
98
- }
99
-
100
- function pickNextRole(
101
- baseRole: string,
102
- usedRoles: Set<string>,
103
- existingAgentIds: Set<string>,
104
- teamId: string,
105
- n: number
106
- ): string {
107
- const isTaken = (role: string) =>
108
- usedRoles.has(role) || (teamId ? existingAgentIds.has(`${teamId}-${role}`) : false);
109
- if (!isTaken(baseRole)) return baseRole;
110
- let i = Math.max(2, n + 1);
111
- while (isTaken(`${baseRole}-${i}`)) i++;
112
- return `${baseRole}-${i}`;
113
- }
114
-
115
- export async function handleAddLike(
116
- agents: Array<Record<string, unknown>>,
117
- templates: Record<string, unknown>,
118
- baseRole: string,
119
- name: string,
120
- teamId: string
121
- ): Promise<OpResult | NextResponse> {
122
- const base = agents.find((a) => String(a.role ?? "") === baseRole);
123
- if (!base) {
124
- return NextResponse.json({ ok: false, error: `baseRole not found in recipe: ${baseRole}` }, { status: 400 });
125
- }
126
-
127
- const nextAgents = agents.slice();
128
- const nextTemplates = { ...templates };
129
- const usedRoles = new Set(nextAgents.map((a) => String(a.role ?? "").trim()).filter(Boolean));
130
- const n = maxSuffixFromUsedRoles(usedRoles, baseRole);
131
- const existingAgentIds = await fetchExistingAgentIds(teamId);
132
- const nextRole = pickNextRole(baseRole, usedRoles, existingAgentIds, teamId, n);
133
-
134
- const baseName = typeof (base as { name?: unknown }).name === "string" ? String((base as { name?: unknown }).name) : "";
135
- const autoSuffix = nextRole === baseRole ? "" : String(nextRole.slice(baseRole.length + 1));
136
- const suffixPart = autoSuffix ? ` ${autoSuffix}` : "";
137
- const nextName = name || (baseName ? baseName + suffixPart : "");
138
-
139
- const clone = { ...base, role: nextRole, ...(nextName ? { name: nextName } : {}) };
140
- nextAgents.push(clone);
141
-
142
- for (const [k, v] of Object.entries(templates)) {
143
- if (!k.startsWith(`${baseRole}.`)) continue;
144
- const suffix = k.slice(baseRole.length);
145
- const nextKey = `${nextRole}${suffix}`;
146
- if (nextTemplates[nextKey] === undefined) nextTemplates[nextKey] = v;
147
- }
148
-
149
- const addedRole = String((nextAgents[nextAgents.length - 1] as { role?: unknown } | undefined)?.role ?? "").trim() || null;
150
- return { nextAgents, nextTemplates, addedRole };
151
- }