@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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/channels.html +2 -2
- package/.next/server/app/channels.rsc +1 -1
- package/.next/server/app/channels.segments/_full.segment.rsc +1 -1
- package/.next/server/app/channels.segments/_head.segment.rsc +1 -1
- package/.next/server/app/channels.segments/_index.segment.rsc +1 -1
- package/.next/server/app/channels.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/channels.segments/channels/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/channels.segments/channels.segment.rsc +1 -1
- package/.next/server/app/cron-jobs.html +1 -1
- package/.next/server/app/cron-jobs.rsc +1 -1
- package/.next/server/app/cron-jobs.segments/_full.segment.rsc +1 -1
- package/.next/server/app/cron-jobs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/cron-jobs.segments/_index.segment.rsc +1 -1
- package/.next/server/app/cron-jobs.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/cron-jobs.segments/cron-jobs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/cron-jobs.segments/cron-jobs.segment.rsc +1 -1
- package/.next/server/app/goals/new.html +2 -2
- package/.next/server/app/goals/new.rsc +1 -1
- package/.next/server/app/goals/new.segments/_full.segment.rsc +1 -1
- package/.next/server/app/goals/new.segments/_head.segment.rsc +1 -1
- package/.next/server/app/goals/new.segments/_index.segment.rsc +1 -1
- package/.next/server/app/goals/new.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/goals/new.segments/goals/new/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/goals/new.segments/goals/new.segment.rsc +1 -1
- package/.next/server/app/goals/new.segments/goals.segment.rsc +1 -1
- package/.next/server/app/goals.html +1 -1
- package/.next/server/app/goals.rsc +1 -1
- package/.next/server/app/goals.segments/_full.segment.rsc +1 -1
- package/.next/server/app/goals.segments/_head.segment.rsc +1 -1
- package/.next/server/app/goals.segments/_index.segment.rsc +1 -1
- package/.next/server/app/goals.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/goals.segments/goals/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/goals.segments/goals.segment.rsc +1 -1
- package/.next/server/app/settings.html +1 -1
- package/.next/server/app/settings.rsc +1 -1
- package/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +2 -2
- package/package.json +1 -2
- package/src/app/HomeClient.tsx +0 -207
- package/src/app/agents/[agentId]/agent-editor-tabs.tsx +0 -298
- package/src/app/agents/[agentId]/agent-editor.tsx +0 -468
- package/src/app/agents/[agentId]/page.tsx +0 -32
- package/src/app/api/__tests__/agents-add-route.test.ts +0 -143
- package/src/app/api/__tests__/agents-file-route.test.ts +0 -117
- package/src/app/api/__tests__/agents-files-route.test.ts +0 -61
- package/src/app/api/__tests__/agents-id-route.test.ts +0 -104
- package/src/app/api/__tests__/agents-identity-route.test.ts +0 -92
- package/src/app/api/__tests__/agents-route.test.ts +0 -54
- package/src/app/api/__tests__/agents-skills-install-route.test.ts +0 -131
- package/src/app/api/__tests__/agents-skills-route.test.ts +0 -64
- package/src/app/api/__tests__/agents-update-route.test.ts +0 -95
- package/src/app/api/__tests__/channels-bindings-route.test.ts +0 -143
- package/src/app/api/__tests__/cron-delete-route.test.ts +0 -93
- package/src/app/api/__tests__/cron-job-route.test.ts +0 -78
- package/src/app/api/__tests__/cron-jobs-route.test.ts +0 -116
- package/src/app/api/__tests__/cron-recipe-installed-route.test.ts +0 -114
- package/src/app/api/__tests__/gateway-restart-route.test.ts +0 -36
- package/src/app/api/__tests__/goals-promote-route.test.ts +0 -200
- package/src/app/api/__tests__/goals-route.test.ts +0 -184
- package/src/app/api/__tests__/ids-check-route.test.ts +0 -188
- package/src/app/api/__tests__/marketplace-recipes-route.test.ts +0 -123
- package/src/app/api/__tests__/recipes-clone-route.test.ts +0 -221
- package/src/app/api/__tests__/recipes-delete-route.test.ts +0 -248
- package/src/app/api/__tests__/recipes-id-route.test.ts +0 -166
- package/src/app/api/__tests__/recipes-route.test.ts +0 -57
- package/src/app/api/__tests__/recipes-team-agents-route.test.ts +0 -135
- package/src/app/api/__tests__/scaffold-route.test.ts +0 -173
- package/src/app/api/__tests__/settings-cron-installation-route.test.ts +0 -82
- package/src/app/api/__tests__/skills-available-route.test.ts +0 -47
- package/src/app/api/__tests__/swarms-start-route.test.ts +0 -79
- package/src/app/api/__tests__/swarms-status-route.test.ts +0 -42
- package/src/app/api/__tests__/teams-file-route.test.ts +0 -129
- package/src/app/api/__tests__/teams-files-route.test.ts +0 -57
- package/src/app/api/__tests__/teams-meta-route.test.ts +0 -113
- package/src/app/api/__tests__/teams-orchestrator-install-route.test.ts +0 -66
- package/src/app/api/__tests__/teams-orchestrator-route.test.ts +0 -59
- package/src/app/api/__tests__/teams-remove-team-route.test.ts +0 -122
- package/src/app/api/__tests__/teams-skills-install-route.test.ts +0 -78
- package/src/app/api/__tests__/teams-skills-route.test.ts +0 -73
- package/src/app/api/__tests__/teams-workflow-runs-route.test.ts +0 -85
- package/src/app/api/__tests__/teams-workflows-route.test.ts +0 -110
- package/src/app/api/__tests__/tickets-move-route.test.ts +0 -60
- package/src/app/api/agents/[id]/route.ts +0 -83
- package/src/app/api/agents/add/route.ts +0 -114
- package/src/app/api/agents/file/route.ts +0 -45
- package/src/app/api/agents/files/route.ts +0 -23
- package/src/app/api/agents/identity/route.ts +0 -41
- package/src/app/api/agents/route.ts +0 -22
- package/src/app/api/agents/skills/install/route.ts +0 -34
- package/src/app/api/agents/skills/route.ts +0 -39
- package/src/app/api/agents/update/route.ts +0 -52
- package/src/app/api/channels/bindings/route.ts +0 -63
- package/src/app/api/cron/__tests__/helpers.test.ts +0 -164
- package/src/app/api/cron/delete/route.ts +0 -23
- package/src/app/api/cron/helpers.ts +0 -172
- package/src/app/api/cron/job/route.ts +0 -22
- package/src/app/api/cron/jobs/route.ts +0 -52
- package/src/app/api/cron/recipe-installed/route.ts +0 -65
- package/src/app/api/gateway/restart/route.ts +0 -20
- package/src/app/api/goals/[id]/promote/route.ts +0 -119
- package/src/app/api/goals/[id]/route.ts +0 -54
- package/src/app/api/goals/route.ts +0 -44
- package/src/app/api/ids/check/route.ts +0 -113
- package/src/app/api/marketplace/recipes/[slug]/route.ts +0 -16
- package/src/app/api/marketplace/recipes/route.ts +0 -22
- package/src/app/api/recipes/[id]/route.ts +0 -62
- package/src/app/api/recipes/clone/route.ts +0 -106
- package/src/app/api/recipes/custom-team/route.ts +0 -193
- package/src/app/api/recipes/delete/helpers.ts +0 -65
- package/src/app/api/recipes/delete/route.ts +0 -73
- package/src/app/api/recipes/route.ts +0 -21
- package/src/app/api/recipes/team-agents/__tests__/helpers.test.ts +0 -156
- package/src/app/api/recipes/team-agents/helpers.ts +0 -151
- package/src/app/api/recipes/team-agents/route.ts +0 -80
- package/src/app/api/scaffold/__tests__/helpers.test.ts +0 -186
- package/src/app/api/scaffold/helpers.ts +0 -214
- package/src/app/api/scaffold/route.ts +0 -95
- package/src/app/api/settings/cron-installation/route.ts +0 -58
- package/src/app/api/skills/available/route.ts +0 -23
- package/src/app/api/swarms/start/route.ts +0 -100
- package/src/app/api/swarms/status/route.ts +0 -31
- package/src/app/api/teams/[teamId]/tickets/assign/route.ts +0 -105
- package/src/app/api/teams/[teamId]/tickets/assignees/route.ts +0 -27
- package/src/app/api/teams/[teamId]/tickets/delete/route.ts +0 -55
- package/src/app/api/teams/[teamId]/tickets/move/route.ts +0 -70
- package/src/app/api/teams/[teamId]/tickets/move-to-goals/route.ts +0 -56
- package/src/app/api/teams/file/route.ts +0 -46
- package/src/app/api/teams/files/route.ts +0 -63
- package/src/app/api/teams/memory/route.ts +0 -250
- package/src/app/api/teams/meta/route.ts +0 -43
- package/src/app/api/teams/orchestrator/install/route.ts +0 -129
- package/src/app/api/teams/orchestrator/route.ts +0 -216
- package/src/app/api/teams/remove-team/route.ts +0 -37
- package/src/app/api/teams/skills/install/route.ts +0 -18
- package/src/app/api/teams/skills/route.ts +0 -25
- package/src/app/api/teams/workflow-runs/route.ts +0 -534
- package/src/app/api/teams/workflow-templates/route.ts +0 -71
- package/src/app/api/teams/workflows/route.ts +0 -55
- package/src/app/api/tickets/assign/route.ts +0 -94
- package/src/app/api/tickets/assignees/route.ts +0 -24
- package/src/app/api/tickets/move/route.ts +0 -69
- package/src/app/channels/channels-client.tsx +0 -271
- package/src/app/channels/page.tsx +0 -5
- package/src/app/cron-jobs/cron-jobs-client.tsx +0 -243
- package/src/app/cron-jobs/page.tsx +0 -34
- package/src/app/favicon.ico +0 -0
- package/src/app/global-error.tsx +0 -50
- package/src/app/globals.css +0 -153
- package/src/app/goals/[id]/goal-editor.tsx +0 -162
- package/src/app/goals/[id]/page.tsx +0 -6
- package/src/app/goals/goals-client.tsx +0 -201
- package/src/app/goals/new/page.tsx +0 -81
- package/src/app/goals/page.tsx +0 -10
- package/src/app/layout.tsx +0 -53
- package/src/app/manifest.ts +0 -15
- package/src/app/not-found.tsx +0 -8
- package/src/app/page.tsx +0 -33
- package/src/app/recipes/CreateAgentModal.tsx +0 -156
- package/src/app/recipes/CreateCustomTeamModal.tsx +0 -375
- package/src/app/recipes/CreateModalShell.tsx +0 -55
- package/src/app/recipes/CreateTeamModal.tsx +0 -91
- package/src/app/recipes/[id]/RecipeEditor/RecipeEditorCreateModal.tsx +0 -72
- package/src/app/recipes/[id]/RecipeEditor/RecipeEditorPanel.tsx +0 -216
- package/src/app/recipes/[id]/RecipeEditor/index.tsx +0 -271
- package/src/app/recipes/[id]/RecipeEditor/recipe-editor-utils.ts +0 -46
- package/src/app/recipes/[id]/RecipeEditor/types.ts +0 -52
- package/src/app/recipes/[id]/page.tsx +0 -37
- package/src/app/recipes/page.tsx +0 -101
- package/src/app/recipes/recipes-client.tsx +0 -620
- package/src/app/settings/page.tsx +0 -26
- package/src/app/settings/settings-client.tsx +0 -91
- package/src/app/teams/[teamId]/CloneTeamModal.tsx +0 -116
- package/src/app/teams/[teamId]/OrchestratorPanel.tsx +0 -255
- package/src/app/teams/[teamId]/OrchestratorSetupModal.tsx +0 -184
- package/src/app/teams/[teamId]/PublishChangesModal.tsx +0 -43
- package/src/app/teams/[teamId]/page.tsx +0 -49
- package/src/app/teams/[teamId]/team-editor/TeamAgentsTab.tsx +0 -145
- package/src/app/teams/[teamId]/team-editor/TeamCronTab.tsx +0 -72
- package/src/app/teams/[teamId]/team-editor/TeamFilesTab.tsx +0 -74
- package/src/app/teams/[teamId]/team-editor/TeamMemoryTab.tsx +0 -349
- package/src/app/teams/[teamId]/team-editor/TeamRecipeTab.tsx +0 -151
- package/src/app/teams/[teamId]/team-editor/TeamSkillsTab.tsx +0 -68
- package/src/app/teams/[teamId]/team-editor/index.tsx +0 -558
- package/src/app/teams/[teamId]/team-editor/team-editor-data.ts +0 -255
- package/src/app/teams/[teamId]/team-editor/team-editor-utils.ts +0 -78
- package/src/app/teams/[teamId]/team-editor/types.ts +0 -34
- package/src/app/teams/[teamId]/tickets/[ticket]/page.tsx +0 -35
- package/src/app/teams/[teamId]/tickets/page.tsx +0 -15
- package/src/app/teams/[teamId]/workflows/[workflowId]/WorkflowCanvas.tsx +0 -111
- package/src/app/teams/[teamId]/workflows/[workflowId]/page.tsx +0 -27
- package/src/app/teams/[teamId]/workflows/[workflowId]/workflows-editor-client.tsx +0 -1608
- package/src/app/teams/[teamId]/workflows/page.tsx +0 -40
- package/src/app/teams/[teamId]/workflows/workflows-client.tsx +0 -494
- package/src/app/tickets/TicketDetailClient.tsx +0 -147
- package/src/app/tickets/TicketsBoardClient.tsx +0 -200
- package/src/app/tickets/[ticket]/TicketAssignControl.tsx +0 -112
- package/src/app/tickets/[ticket]/page.tsx +0 -36
- package/src/app/tickets/page.tsx +0 -10
- package/src/components/AppShell.tsx +0 -286
- package/src/components/ConfirmationModal.tsx +0 -81
- package/src/components/DeleteEntityModal.tsx +0 -41
- package/src/components/ErrorBoundary.tsx +0 -70
- package/src/components/FileListWithOptionalToggle.tsx +0 -86
- package/src/components/GoalFormFields.tsx +0 -163
- package/src/components/ScaffoldOverlay.tsx +0 -78
- package/src/components/ThemeToggle.tsx +0 -53
- package/src/components/ToastProvider.tsx +0 -163
- package/src/components/__tests__/ConfirmationModal.test.tsx +0 -109
- package/src/components/__tests__/ErrorBoundary.test.tsx +0 -39
- package/src/components/__tests__/FileListWithOptionalToggle.test.tsx +0 -109
- package/src/components/__tests__/GoalFormFields.test.tsx +0 -117
- package/src/components/delete-modals.tsx +0 -59
- package/src/components/icons.tsx +0 -48
- package/src/lib/__tests__/agent-workspace.test.ts +0 -44
- package/src/lib/__tests__/agents.test.ts +0 -36
- package/src/lib/__tests__/api-route-helpers.test.ts +0 -188
- package/src/lib/__tests__/cron.test.ts +0 -45
- package/src/lib/__tests__/editor-utils.test.ts +0 -38
- package/src/lib/__tests__/errors.test.ts +0 -15
- package/src/lib/__tests__/exec.test.ts +0 -13
- package/src/lib/__tests__/fetch-json.test.ts +0 -118
- package/src/lib/__tests__/gateway.test.ts +0 -234
- package/src/lib/__tests__/goal-promote.test.ts +0 -39
- package/src/lib/__tests__/goals-client.test.ts +0 -26
- package/src/lib/__tests__/goals.test.ts +0 -275
- package/src/lib/__tests__/json.test.ts +0 -15
- package/src/lib/__tests__/kitchen-api.test.ts +0 -32
- package/src/lib/__tests__/marketplace.test.ts +0 -116
- package/src/lib/__tests__/openclaw.test.ts +0 -129
- package/src/lib/__tests__/paths.test.ts +0 -136
- package/src/lib/__tests__/poll.test.ts +0 -26
- package/src/lib/__tests__/recipe-clone.test.ts +0 -85
- package/src/lib/__tests__/recipe-team-agents.test.ts +0 -70
- package/src/lib/__tests__/recipes.test.ts +0 -199
- package/src/lib/__tests__/scaffold-client.test.ts +0 -106
- package/src/lib/__tests__/scaffold.test.ts +0 -64
- package/src/lib/__tests__/slugify.test.ts +0 -23
- package/src/lib/__tests__/tickets.test.ts +0 -158
- package/src/lib/__tests__/type-guards.test.ts +0 -18
- package/src/lib/__tests__/use-slugified-id.test.tsx +0 -120
- package/src/lib/agent-workspace.ts +0 -14
- package/src/lib/agents.ts +0 -17
- package/src/lib/api-route-helpers.ts +0 -157
- package/src/lib/cron.ts +0 -40
- package/src/lib/editor-utils.ts +0 -18
- package/src/lib/errors.ts +0 -7
- package/src/lib/exec.ts +0 -4
- package/src/lib/fetch-json.ts +0 -29
- package/src/lib/gateway.ts +0 -100
- package/src/lib/goal-promote.ts +0 -27
- package/src/lib/goals-client.ts +0 -69
- package/src/lib/goals.ts +0 -171
- package/src/lib/json.ts +0 -10
- package/src/lib/kitchen-api.ts +0 -19
- package/src/lib/marketplace.ts +0 -46
- package/src/lib/openclaw.ts +0 -59
- package/src/lib/paths.ts +0 -69
- package/src/lib/poll.ts +0 -18
- package/src/lib/recipe-clone.ts +0 -42
- package/src/lib/recipe-team-agents.ts +0 -30
- package/src/lib/recipes.ts +0 -95
- package/src/lib/scaffold-client.ts +0 -31
- package/src/lib/scaffold.ts +0 -37
- package/src/lib/slugify.ts +0 -25
- package/src/lib/swarms.ts +0 -25
- package/src/lib/tickets.ts +0 -192
- package/src/lib/type-guards.ts +0 -3
- package/src/lib/use-slugified-id.ts +0 -35
- package/src/lib/workflows/README.md +0 -11
- package/src/lib/workflows/__tests__/storage.test.ts +0 -129
- package/src/lib/workflows/__tests__/validate.test.ts +0 -92
- package/src/lib/workflows/api-handlers.ts +0 -35
- package/src/lib/workflows/readdir.ts +0 -23
- package/src/lib/workflows/runs-storage.ts +0 -59
- package/src/lib/workflows/runs-types.ts +0 -42
- package/src/lib/workflows/storage.ts +0 -70
- package/src/lib/workflows/templates/index.ts +0 -1
- package/src/lib/workflows/templates/marketing-cadence-v1.ts +0 -142
- package/src/lib/workflows/types.ts +0 -48
- package/src/lib/workflows/validate.ts +0 -92
- package/src/proxy.ts +0 -28
- /package/.next/static/{z86RoqzzXXrWnpi229zP6 → Jrrrm9HH5bKkSrQhe1j93}/_buildManifest.js +0 -0
- /package/.next/static/{z86RoqzzXXrWnpi229zP6 → Jrrrm9HH5bKkSrQhe1j93}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{z86RoqzzXXrWnpi229zP6 → Jrrrm9HH5bKkSrQhe1j93}/_ssgManifest.js +0 -0
package/src/lib/recipes.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import YAML from "yaml";
|
|
4
|
-
import { runOpenClaw } from "./openclaw";
|
|
5
|
-
import { getBuiltinRecipesDir, getWorkspaceRecipesDir } from "./paths";
|
|
6
|
-
|
|
7
|
-
export type RecipeListItem = {
|
|
8
|
-
id: string;
|
|
9
|
-
name: string;
|
|
10
|
-
kind: "agent" | "team";
|
|
11
|
-
source: "builtin" | "workspace";
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type RecipeDetail = RecipeListItem & {
|
|
15
|
-
content: string;
|
|
16
|
-
filePath: string | null;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export function parseFrontmatterId(md: string): string {
|
|
20
|
-
if (!md.startsWith("---\n")) throw new Error("Recipe markdown must start with YAML frontmatter (---)");
|
|
21
|
-
const end = md.indexOf("\n---\n", 4);
|
|
22
|
-
if (end === -1) throw new Error("Recipe frontmatter not terminated (---)");
|
|
23
|
-
const yamlText = md.slice(4, end + 1);
|
|
24
|
-
const fm = YAML.parse(yamlText) as { id?: string };
|
|
25
|
-
if (!fm?.id) throw new Error("Recipe frontmatter must include id");
|
|
26
|
-
return fm.id;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Ensures the frontmatter id field matches the given id. Preserves body. Returns md unchanged if no valid frontmatter. */
|
|
30
|
-
export function forceFrontmatterId(md: string, id: string): string {
|
|
31
|
-
if (!md.startsWith("---\n")) return md;
|
|
32
|
-
const end = md.indexOf("\n---\n", 4);
|
|
33
|
-
if (end === -1) return md;
|
|
34
|
-
const fm = md.slice(4, end);
|
|
35
|
-
const body = md.slice(end + 5);
|
|
36
|
-
|
|
37
|
-
const lines = fm.split("\n");
|
|
38
|
-
let found = false;
|
|
39
|
-
const nextLines = lines.map((line) => {
|
|
40
|
-
if (/^id\s*:/i.test(line)) {
|
|
41
|
-
found = true;
|
|
42
|
-
return `id: ${id}`;
|
|
43
|
-
}
|
|
44
|
-
return line;
|
|
45
|
-
});
|
|
46
|
-
if (!found) nextLines.unshift(`id: ${id}`);
|
|
47
|
-
|
|
48
|
-
return `---\n${nextLines.join("\n")}\n---\n${body}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Returns display name for a team from recipe list, or null if not found. */
|
|
52
|
-
export async function getTeamDisplayName(teamId: string): Promise<string | null> {
|
|
53
|
-
const recipes = await listRecipes();
|
|
54
|
-
const match = recipes.find((r) => r.kind === "team" && r.id === teamId);
|
|
55
|
-
return match?.name ?? null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Fetches recipe list from openclaw. Returns empty array on failure. */
|
|
59
|
-
export async function listRecipes(): Promise<RecipeListItem[]> {
|
|
60
|
-
const list = await runOpenClaw(["recipes", "list"]);
|
|
61
|
-
if (!list.ok) return [];
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(list.stdout) as RecipeListItem[];
|
|
64
|
-
} catch {
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Fetches recipe list and returns the item with the given id, or null. */
|
|
70
|
-
export async function findRecipeById(id: string): Promise<RecipeListItem | null> {
|
|
71
|
-
const recipes = await listRecipes();
|
|
72
|
-
return recipes.find((r) => r.id === id) ?? null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export async function resolveRecipePath(item: RecipeListItem): Promise<string> {
|
|
76
|
-
const dir = item.source === "builtin" ? await getBuiltinRecipesDir() : await getWorkspaceRecipesDir();
|
|
77
|
-
// Current convention: <id>.md in the directory.
|
|
78
|
-
return path.join(dir, `${item.id}.md`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function readRecipe(item: RecipeListItem, contentFromCli: string): Promise<RecipeDetail> {
|
|
82
|
-
// Prefer CLI-provided markdown, but also compute path for saving.
|
|
83
|
-
let filePath: string | null = null;
|
|
84
|
-
try {
|
|
85
|
-
filePath = await resolveRecipePath(item);
|
|
86
|
-
} catch {
|
|
87
|
-
filePath = null;
|
|
88
|
-
}
|
|
89
|
-
return { ...item, content: contentFromCli, filePath };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export async function writeRecipeFile(filePath: string, md: string) {
|
|
93
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
94
|
-
await fs.writeFile(filePath, md, "utf8");
|
|
95
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/** Client-side scaffold API. Shared by RecipeEditor and recipes-client. */
|
|
2
|
-
|
|
3
|
-
export type ScaffoldTeamBody = {
|
|
4
|
-
kind: "team";
|
|
5
|
-
recipeId: string;
|
|
6
|
-
teamId: string;
|
|
7
|
-
cronInstallChoice?: "yes" | "no";
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type ScaffoldAgentBody = {
|
|
11
|
-
kind: "agent";
|
|
12
|
-
recipeId: string;
|
|
13
|
-
agentId: string;
|
|
14
|
-
name?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export async function fetchScaffold(
|
|
18
|
-
body: ScaffoldTeamBody | ScaffoldAgentBody
|
|
19
|
-
): Promise<{ res: Response; json: unknown }> {
|
|
20
|
-
const res = await fetch("/api/scaffold", {
|
|
21
|
-
method: "POST",
|
|
22
|
-
headers: { "content-type": "application/json" },
|
|
23
|
-
body: JSON.stringify({
|
|
24
|
-
...body,
|
|
25
|
-
applyConfig: true,
|
|
26
|
-
overwrite: false,
|
|
27
|
-
}),
|
|
28
|
-
});
|
|
29
|
-
const json = await res.json();
|
|
30
|
-
return { res, json };
|
|
31
|
-
}
|
package/src/lib/scaffold.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export type ScaffoldReqBody =
|
|
2
|
-
| {
|
|
3
|
-
kind: "agent";
|
|
4
|
-
recipeId: string;
|
|
5
|
-
agentId?: string;
|
|
6
|
-
name?: string;
|
|
7
|
-
applyConfig?: boolean;
|
|
8
|
-
overwrite?: boolean;
|
|
9
|
-
}
|
|
10
|
-
| {
|
|
11
|
-
kind: "team";
|
|
12
|
-
recipeId: string;
|
|
13
|
-
teamId?: string;
|
|
14
|
-
applyConfig?: boolean;
|
|
15
|
-
overwrite?: boolean;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type BuildScaffoldArgsOptions = {
|
|
19
|
-
allowExisting?: boolean;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export function buildScaffoldArgs(
|
|
23
|
-
body: ScaffoldReqBody,
|
|
24
|
-
options?: BuildScaffoldArgsOptions
|
|
25
|
-
): string[] {
|
|
26
|
-
const args: string[] = ["recipes", body.kind === "team" ? "scaffold-team" : "scaffold", body.recipeId];
|
|
27
|
-
if (body.overwrite) args.push("--overwrite");
|
|
28
|
-
if (body.applyConfig) args.push("--apply-config");
|
|
29
|
-
if (options?.allowExisting || body.overwrite) args.push("--overwrite-recipe");
|
|
30
|
-
if (body.kind === "agent") {
|
|
31
|
-
if (body.agentId) args.push("--agent-id", body.agentId);
|
|
32
|
-
if (body.name) args.push("--name", body.name);
|
|
33
|
-
} else if (body.teamId) {
|
|
34
|
-
args.push("--team-id", body.teamId);
|
|
35
|
-
}
|
|
36
|
-
return args;
|
|
37
|
-
}
|
package/src/lib/slugify.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slugify a string for use in ids, file names, or URLs.
|
|
3
|
-
* Normalizes to lowercase, replaces non-alphanumeric sequences with hyphens, trims edges.
|
|
4
|
-
* Uses character-by-character processing to avoid regex ReDoS.
|
|
5
|
-
* @param maxLength - optional max length (default 80). Use 64 for goal ids.
|
|
6
|
-
*/
|
|
7
|
-
export function slugifyId(input: string, maxLength = 80): string {
|
|
8
|
-
const s = String(input ?? "").toLowerCase().trim();
|
|
9
|
-
const cap = Math.min(s.length, maxLength * 2);
|
|
10
|
-
let result = "";
|
|
11
|
-
let prevHyphen = false;
|
|
12
|
-
for (let i = 0; i < cap; i++) {
|
|
13
|
-
const c = s[i];
|
|
14
|
-
if ((c >= "a" && c <= "z") || (c >= "0" && c <= "9")) {
|
|
15
|
-
result += c;
|
|
16
|
-
prevHyphen = false;
|
|
17
|
-
} else if ((c === " " || c === "-" || c === "_") && !prevHyphen) {
|
|
18
|
-
result += "-";
|
|
19
|
-
prevHyphen = true;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
while (result.startsWith("-")) result = result.slice(1);
|
|
23
|
-
while (result.endsWith("-")) result = result.slice(0, -1);
|
|
24
|
-
return result.slice(0, maxLength);
|
|
25
|
-
}
|
package/src/lib/swarms.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
/** Validates and returns trimmed id; throws if empty or invalid format. */
|
|
6
|
-
export function normalizeId(kind: string, id: string): string {
|
|
7
|
-
const s = String(id ?? "").trim();
|
|
8
|
-
if (!s) throw new Error(`${kind} is required`);
|
|
9
|
-
if (!/^[a-z0-9][a-z0-9-]{0,62}$/i.test(s)) {
|
|
10
|
-
throw new Error(`${kind} must match /^[a-z0-9][a-z0-9-]{0,62}$/i`);
|
|
11
|
-
}
|
|
12
|
-
return s;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Resolves workspace path for an agent/orchestrator by id (convention: workspace-<id>). */
|
|
16
|
-
export async function resolveAgentWorkspace(agentId: string): Promise<string> {
|
|
17
|
-
const cfgPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
18
|
-
const raw = await fs.readFile(cfgPath, "utf8");
|
|
19
|
-
const cfg = JSON.parse(raw) as { agents?: { defaults?: { workspace?: string } } };
|
|
20
|
-
|
|
21
|
-
const baseWorkspace = String(cfg?.agents?.defaults?.workspace ?? "").trim();
|
|
22
|
-
if (!baseWorkspace) throw new Error("agents.defaults.workspace not set");
|
|
23
|
-
|
|
24
|
-
return path.resolve(baseWorkspace, "..", `workspace-${agentId}`);
|
|
25
|
-
}
|
package/src/lib/tickets.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
export type TicketStage = "backlog" | "in-progress" | "testing" | "done";
|
|
6
|
-
|
|
7
|
-
export interface TicketSummary {
|
|
8
|
-
number: number;
|
|
9
|
-
id: string;
|
|
10
|
-
title: string;
|
|
11
|
-
owner: string | null;
|
|
12
|
-
stage: TicketStage;
|
|
13
|
-
file: string;
|
|
14
|
-
updatedAt: string; // ISO
|
|
15
|
-
ageHours: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function assertSafeTeamId(teamId: string) {
|
|
19
|
-
// Conservative: matches OpenClaw team ids like "development-team".
|
|
20
|
-
if (!/^[a-z0-9][a-z0-9-]{1,63}$/.test(teamId)) {
|
|
21
|
-
throw new Error(`Invalid teamId "${teamId}"`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function isPathLike(s: string) {
|
|
26
|
-
return s.includes("/") || s.includes("\\");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function teamWorkspace(teamId: string) {
|
|
30
|
-
assertSafeTeamId(teamId);
|
|
31
|
-
return path.join(os.homedir(), ".openclaw", `workspace-${teamId}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Back-compat for older non-team-scoped routes.
|
|
36
|
-
* Prefer passing explicit teamId into APIs instead.
|
|
37
|
-
*/
|
|
38
|
-
export function getTeamWorkspaceDir(): string {
|
|
39
|
-
return process.env.CK_TEAM_WORKSPACE_DIR ?? teamWorkspace("development-team");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function stageDir(stage: TicketStage, teamOrDir: string = "development-team") {
|
|
43
|
-
const map: Record<TicketStage, string> = {
|
|
44
|
-
backlog: "work/backlog",
|
|
45
|
-
"in-progress": "work/in-progress",
|
|
46
|
-
testing: "work/testing",
|
|
47
|
-
done: "work/done",
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const base = isPathLike(teamOrDir) ? teamOrDir : teamWorkspace(teamOrDir);
|
|
51
|
-
return path.join(base, map[stage]);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function parseTitle(md: string) {
|
|
55
|
-
// Ticket markdown files typically start with: # 0033-some-slug
|
|
56
|
-
const firstLine = md.split("\n")[0] ?? "";
|
|
57
|
-
const header = firstLine.startsWith("# ") ? firstLine.slice(2).trim() : "";
|
|
58
|
-
|
|
59
|
-
// If header is like: "<id> <title...>" keep the explicit title portion.
|
|
60
|
-
const firstSpace = header.indexOf(" ");
|
|
61
|
-
if (firstSpace > 0) {
|
|
62
|
-
const afterSpace = header.slice(firstSpace + 1).trim();
|
|
63
|
-
if (afterSpace) return afterSpace;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Otherwise derive from the slug: strip leading number + hyphen, then de-kebab.
|
|
67
|
-
const derivedRaw = header
|
|
68
|
-
.replace(/^\d{4}-/, "")
|
|
69
|
-
.replace(/[-_]+/g, " ")
|
|
70
|
-
.replace(/\s+/g, " ")
|
|
71
|
-
.trim();
|
|
72
|
-
|
|
73
|
-
const ACRONYMS = new Set(["api", "cli", "ui", "ux", "gpu", "cpu", "npm", "pr", "ci", "cd", "json", "yaml", "md"]);
|
|
74
|
-
const titleCase = (s: string) =>
|
|
75
|
-
s
|
|
76
|
-
.split(" ")
|
|
77
|
-
.filter(Boolean)
|
|
78
|
-
.map((w) => {
|
|
79
|
-
const lower = w.toLowerCase();
|
|
80
|
-
if (ACRONYMS.has(lower)) return w.toUpperCase();
|
|
81
|
-
if (lower.startsWith("v") && /^\d/.test(lower.slice(1))) return w; // version-like
|
|
82
|
-
if (/^[\d.]+$/.test(w)) return w; // numbers/semver
|
|
83
|
-
return w.slice(0, 1).toUpperCase() + w.slice(1);
|
|
84
|
-
})
|
|
85
|
-
.join(" ");
|
|
86
|
-
|
|
87
|
-
const derived = derivedRaw ? titleCase(derivedRaw) : "";
|
|
88
|
-
|
|
89
|
-
if (derived) return derived;
|
|
90
|
-
return header || "(untitled)";
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function parseField(md: string, field: string): string | null {
|
|
94
|
-
const re = new RegExp(`^${field}:\\s*(.*)$`, "mi");
|
|
95
|
-
const m = md.match(re);
|
|
96
|
-
return m?.[1]?.trim() || null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function parseNumberFromFilename(filename: string): number | null {
|
|
100
|
-
const m = filename.match(/^(\d{4})-/);
|
|
101
|
-
if (!m) return null;
|
|
102
|
-
return Number(m[1]);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* List tickets.
|
|
107
|
-
* - Preferred: listTickets("development-team")
|
|
108
|
-
* - Back-compat: listTickets(getTeamWorkspaceDir())
|
|
109
|
-
*/
|
|
110
|
-
export async function listTickets(teamIdOrDir: string = "development-team"): Promise<TicketSummary[]> {
|
|
111
|
-
const stages: TicketStage[] = ["backlog", "in-progress", "testing", "done"];
|
|
112
|
-
const all: TicketSummary[] = [];
|
|
113
|
-
|
|
114
|
-
for (const stage of stages) {
|
|
115
|
-
let files: string[] = [];
|
|
116
|
-
try {
|
|
117
|
-
files = await fs.readdir(stageDir(stage, teamIdOrDir));
|
|
118
|
-
} catch {
|
|
119
|
-
files = [];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
for (const f of files) {
|
|
123
|
-
if (!f.endsWith(".md")) continue;
|
|
124
|
-
const number = parseNumberFromFilename(f);
|
|
125
|
-
if (number == null) continue;
|
|
126
|
-
|
|
127
|
-
const file = path.join(stageDir(stage, teamIdOrDir), f);
|
|
128
|
-
const [md, stat] = await Promise.all([fs.readFile(file, "utf8"), fs.stat(file)]);
|
|
129
|
-
|
|
130
|
-
const title = parseTitle(md);
|
|
131
|
-
const owner = parseField(md, "Owner");
|
|
132
|
-
const updatedAt = stat.mtime.toISOString();
|
|
133
|
-
const ageHours = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);
|
|
134
|
-
|
|
135
|
-
all.push({
|
|
136
|
-
number,
|
|
137
|
-
id: f.replace(/\.md$/, ""),
|
|
138
|
-
title,
|
|
139
|
-
owner,
|
|
140
|
-
stage,
|
|
141
|
-
file,
|
|
142
|
-
updatedAt,
|
|
143
|
-
ageHours,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
all.sort((a, b) => a.number - b.number);
|
|
149
|
-
return all;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Back-compat helper used by some API routes.
|
|
154
|
-
*/
|
|
155
|
-
export async function getTicketByIdOrNumber(
|
|
156
|
-
ticketIdOrNumber: string,
|
|
157
|
-
teamIdOrDir: string = "development-team",
|
|
158
|
-
): Promise<TicketSummary | null> {
|
|
159
|
-
const tickets = await listTickets(teamIdOrDir);
|
|
160
|
-
const normalized = ticketIdOrNumber.trim();
|
|
161
|
-
|
|
162
|
-
const byNumber = normalized.match(/^\d+$/) ? tickets.find((t) => t.number === Number(normalized)) : null;
|
|
163
|
-
const byId = tickets.find((t) => t.id === normalized);
|
|
164
|
-
return byId ?? byNumber ?? null;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export async function resolveTicket(teamId: string, ticketIdOrNumber: string): Promise<TicketSummary | null> {
|
|
168
|
-
return getTicketByIdOrNumber(ticketIdOrNumber, teamId);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* getTicketMarkdown(teamId, ticketIdOrNumber) OR getTicketMarkdown(ticketIdOrNumber, teamDir)
|
|
173
|
-
*/
|
|
174
|
-
export async function getTicketMarkdown(
|
|
175
|
-
a: string,
|
|
176
|
-
b: string,
|
|
177
|
-
): Promise<{ id: string; file: string; markdown: string; owner: string | null; stage: TicketStage } | null> {
|
|
178
|
-
// Detect call signature.
|
|
179
|
-
const ticketIdOrNumber = isPathLike(b) ? a : b;
|
|
180
|
-
const teamIdOrDir = isPathLike(b) ? b : a;
|
|
181
|
-
|
|
182
|
-
const hit = await getTicketByIdOrNumber(ticketIdOrNumber, teamIdOrDir);
|
|
183
|
-
if (!hit) return null;
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
id: hit.id,
|
|
187
|
-
file: hit.file,
|
|
188
|
-
markdown: await fs.readFile(hit.file, "utf8"),
|
|
189
|
-
owner: hit.owner,
|
|
190
|
-
stage: hit.stage,
|
|
191
|
-
};
|
|
192
|
-
}
|
package/src/lib/type-guards.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useMemo } from "react";
|
|
4
|
-
import { slugifyId } from "@/lib/slugify";
|
|
5
|
-
|
|
6
|
-
/** Manages name -> slugified id with touch tracking. Resets on modal close. */
|
|
7
|
-
export function useSlugifiedId(opts: {
|
|
8
|
-
open: boolean;
|
|
9
|
-
name: string;
|
|
10
|
-
setName: (v: string) => void;
|
|
11
|
-
id: string;
|
|
12
|
-
setId: (v: string) => void;
|
|
13
|
-
idTouched: boolean;
|
|
14
|
-
setIdTouched: (v: boolean) => void;
|
|
15
|
-
slugify?: (s: string, maxLen?: number) => string;
|
|
16
|
-
}): { derivedId: string; effectiveId: string } {
|
|
17
|
-
const { open, name, setName, id, setId, idTouched, setIdTouched, slugify = slugifyId } = opts;
|
|
18
|
-
|
|
19
|
-
const derivedId = useMemo(() => slugify(name), [name, slugify]);
|
|
20
|
-
const effectiveId = idTouched ? id : derivedId;
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (!open) return;
|
|
24
|
-
if (!idTouched) setId(derivedId);
|
|
25
|
-
}, [derivedId, open, idTouched, setId]);
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
if (open) return;
|
|
29
|
-
setIdTouched(false);
|
|
30
|
-
setName("");
|
|
31
|
-
setId("");
|
|
32
|
-
}, [open, setIdTouched, setName, setId]);
|
|
33
|
-
|
|
34
|
-
return { derivedId, effectiveId };
|
|
35
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
This folder contains the (early) file-first workflow model for ClawKitchen's Visual Workflows MVP.
|
|
2
|
-
|
|
3
|
-
Source of truth lives in each team workspace:
|
|
4
|
-
|
|
5
|
-
- shared-context/workflows/<id>.workflow.json
|
|
6
|
-
|
|
7
|
-
API (temporary):
|
|
8
|
-
|
|
9
|
-
- GET /api/teams/workflows?teamId=... (list)
|
|
10
|
-
- GET /api/teams/workflows?teamId=...&id=... (read)
|
|
11
|
-
- POST /api/teams/workflows { teamId, workflow } (write)
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
workflowFileName,
|
|
4
|
-
assertSafeWorkflowId,
|
|
5
|
-
listWorkflows,
|
|
6
|
-
readWorkflow,
|
|
7
|
-
writeWorkflow,
|
|
8
|
-
deleteWorkflow,
|
|
9
|
-
} from "../storage";
|
|
10
|
-
|
|
11
|
-
vi.mock("@/lib/paths", () => ({
|
|
12
|
-
getTeamWorkspaceDir: vi.fn().mockResolvedValue("/home/test/workspace/team1"),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
vi.mock("node:fs/promises", () => ({
|
|
16
|
-
default: {
|
|
17
|
-
readdir: vi.fn(),
|
|
18
|
-
readFile: vi.fn(),
|
|
19
|
-
writeFile: vi.fn(),
|
|
20
|
-
mkdir: vi.fn(),
|
|
21
|
-
unlink: vi.fn(),
|
|
22
|
-
},
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
import fs from "node:fs/promises";
|
|
26
|
-
|
|
27
|
-
describe("lib/workflows storage", () => {
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
vi.mocked(fs.readdir).mockReset();
|
|
30
|
-
vi.mocked(fs.readFile).mockReset();
|
|
31
|
-
vi.mocked(fs.writeFile).mockReset();
|
|
32
|
-
vi.mocked(fs.mkdir).mockReset();
|
|
33
|
-
vi.mocked(fs.unlink).mockReset();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe("workflowFileName", () => {
|
|
37
|
-
it("returns id.workflow.json", () => {
|
|
38
|
-
expect(workflowFileName("my-workflow")).toBe("my-workflow.workflow.json");
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe("assertSafeWorkflowId", () => {
|
|
43
|
-
it("returns trimmed id when valid", () => {
|
|
44
|
-
expect(assertSafeWorkflowId(" abc-123 ")).toBe("abc-123");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("throws when empty", () => {
|
|
48
|
-
expect(() => assertSafeWorkflowId("")).toThrow(/workflow id is required/i);
|
|
49
|
-
expect(() => assertSafeWorkflowId(" ")).toThrow(/workflow id is required/i);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("throws when invalid chars", () => {
|
|
53
|
-
expect(() => assertSafeWorkflowId("UPPERCASE")).toThrow(/Invalid workflow id/i);
|
|
54
|
-
expect(() => assertSafeWorkflowId("has_underscore")).toThrow(/Invalid workflow id/i);
|
|
55
|
-
expect(() => assertSafeWorkflowId("has space")).toThrow(/Invalid workflow id/i);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("accepts valid ids", () => {
|
|
59
|
-
expect(assertSafeWorkflowId("a")).toBe("a");
|
|
60
|
-
expect(assertSafeWorkflowId("abc-123")).toBe("abc-123");
|
|
61
|
-
expect(assertSafeWorkflowId("a1b2c3")).toBe("a1b2c3");
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe("listWorkflows", () => {
|
|
66
|
-
it("returns empty list when dir does not exist", async () => {
|
|
67
|
-
vi.mocked(fs.readdir).mockRejectedValue({ code: "ENOENT" });
|
|
68
|
-
const r = await listWorkflows("team1");
|
|
69
|
-
expect(r.ok).toBe(true);
|
|
70
|
-
expect(r.files).toEqual([]);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("returns sorted workflow files", async () => {
|
|
74
|
-
vi.mocked(fs.readdir).mockResolvedValue([
|
|
75
|
-
{ name: "b.workflow.json", isFile: () => true },
|
|
76
|
-
{ name: "a.workflow.json", isFile: () => true },
|
|
77
|
-
{ name: "other.txt", isFile: () => true },
|
|
78
|
-
] as unknown as Awaited<ReturnType<typeof fs.readdir>>);
|
|
79
|
-
const r = await listWorkflows("team1");
|
|
80
|
-
expect(r.ok).toBe(true);
|
|
81
|
-
expect(r.files).toEqual(["a.workflow.json", "b.workflow.json"]);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe("readWorkflow", () => {
|
|
86
|
-
it("returns parsed workflow", async () => {
|
|
87
|
-
const wf = { schema: "clawkitchen.workflow.v1", id: "wf1", name: "Test", nodes: [], edges: [] };
|
|
88
|
-
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(wf));
|
|
89
|
-
const r = await readWorkflow("team1", "wf1");
|
|
90
|
-
expect(r.ok).toBe(true);
|
|
91
|
-
expect(r.workflow).toEqual(wf);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("throws when id invalid", async () => {
|
|
95
|
-
await expect(readWorkflow("team1", "")).rejects.toThrow(/workflow id is required/i);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("writeWorkflow", () => {
|
|
100
|
-
it("writes workflow with schema", async () => {
|
|
101
|
-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
102
|
-
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
103
|
-
const wf = { schema: "clawkitchen.workflow.v1", id: "wf1", name: "Test", nodes: [], edges: [] };
|
|
104
|
-
const r = await writeWorkflow("team1", wf);
|
|
105
|
-
expect(r.ok).toBe(true);
|
|
106
|
-
expect(fs.writeFile).toHaveBeenCalledWith(
|
|
107
|
-
expect.stringContaining("wf1.workflow.json"),
|
|
108
|
-
expect.stringContaining('"schema": "clawkitchen.workflow.v1"'),
|
|
109
|
-
"utf8"
|
|
110
|
-
);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe("deleteWorkflow", () => {
|
|
115
|
-
it("returns existed:true when file deleted", async () => {
|
|
116
|
-
vi.mocked(fs.unlink).mockResolvedValue(undefined);
|
|
117
|
-
const r = await deleteWorkflow("team1", "wf1");
|
|
118
|
-
expect(r.ok).toBe(true);
|
|
119
|
-
expect((r as { existed?: boolean }).existed).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("returns existed:false when file not found", async () => {
|
|
123
|
-
vi.mocked(fs.unlink).mockRejectedValue({ code: "ENOENT" });
|
|
124
|
-
const r = await deleteWorkflow("team1", "wf1");
|
|
125
|
-
expect(r.ok).toBe(true);
|
|
126
|
-
expect((r as { existed?: boolean }).existed).toBe(false);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
});
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { validateWorkflowFileV1 } from "../validate";
|
|
3
|
-
|
|
4
|
-
describe("lib/workflows validate", () => {
|
|
5
|
-
const validWorkflow = {
|
|
6
|
-
schema: "clawkitchen.workflow.v1",
|
|
7
|
-
id: "test-wf",
|
|
8
|
-
name: "Test workflow",
|
|
9
|
-
nodes: [
|
|
10
|
-
{ id: "start", type: "start" as const, x: 80, y: 80 },
|
|
11
|
-
{ id: "end", type: "end" as const, x: 320, y: 80 },
|
|
12
|
-
],
|
|
13
|
-
edges: [{ id: "e1", from: "start", to: "end" }],
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
it("returns no errors for valid workflow", () => {
|
|
17
|
-
const r = validateWorkflowFileV1(validWorkflow);
|
|
18
|
-
expect(r.errors).toEqual([]);
|
|
19
|
-
expect(r.warnings).toEqual([]);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("errors when schema wrong", () => {
|
|
23
|
-
const r = validateWorkflowFileV1({ ...validWorkflow, schema: "wrong" as "clawkitchen.workflow.v1" });
|
|
24
|
-
expect(r.errors.some((e) => e.includes("schema"))).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("errors when id missing", () => {
|
|
28
|
-
const r = validateWorkflowFileV1({ ...validWorkflow, id: "" });
|
|
29
|
-
expect(r.errors.some((e) => e.includes("id is required"))).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("errors when name missing", () => {
|
|
33
|
-
const r = validateWorkflowFileV1({ ...validWorkflow, name: "" });
|
|
34
|
-
expect(r.errors.some((e) => e.includes("name is required"))).toBe(true);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("errors when node ids not unique", () => {
|
|
38
|
-
const r = validateWorkflowFileV1({
|
|
39
|
-
...validWorkflow,
|
|
40
|
-
nodes: [
|
|
41
|
-
{ id: "dup", type: "start" as const },
|
|
42
|
-
{ id: "dup", type: "end" as const },
|
|
43
|
-
],
|
|
44
|
-
edges: [],
|
|
45
|
-
});
|
|
46
|
-
expect(r.errors.some((e) => e.includes("unique"))).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("errors when edge references missing node", () => {
|
|
50
|
-
const r = validateWorkflowFileV1({
|
|
51
|
-
...validWorkflow,
|
|
52
|
-
edges: [{ id: "e1", from: "start", to: "nonexistent" }],
|
|
53
|
-
});
|
|
54
|
-
expect(r.errors.some((e) => e.includes("missing to node"))).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("warns when no start node", () => {
|
|
58
|
-
const r = validateWorkflowFileV1({
|
|
59
|
-
...validWorkflow,
|
|
60
|
-
nodes: [{ id: "end", type: "end" as const }],
|
|
61
|
-
edges: [],
|
|
62
|
-
});
|
|
63
|
-
expect(r.warnings.some((w) => w.includes("no start node"))).toBe(true);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("warns when no end node", () => {
|
|
67
|
-
const r = validateWorkflowFileV1({
|
|
68
|
-
...validWorkflow,
|
|
69
|
-
nodes: [{ id: "start", type: "start" as const }],
|
|
70
|
-
edges: [],
|
|
71
|
-
});
|
|
72
|
-
expect(r.warnings.some((w) => w.includes("no end node"))).toBe(true);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("validates cron triggers", () => {
|
|
76
|
-
const r = validateWorkflowFileV1({
|
|
77
|
-
...validWorkflow,
|
|
78
|
-
triggers: [
|
|
79
|
-
{ kind: "cron" as const, id: "t1", expr: "0 9 * * 1-5", enabled: true },
|
|
80
|
-
],
|
|
81
|
-
});
|
|
82
|
-
expect(r.errors).toEqual([]);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("errors when cron trigger missing expr", () => {
|
|
86
|
-
const r = validateWorkflowFileV1({
|
|
87
|
-
...validWorkflow,
|
|
88
|
-
triggers: [{ kind: "cron" as const, id: "t1", expr: "", enabled: true }],
|
|
89
|
-
});
|
|
90
|
-
expect(r.errors.some((e) => e.includes("missing expr"))).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
});
|