@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
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { NextResponse } from "next/server";
|
|
4
|
-
import { errorMessage } from "@/lib/errors";
|
|
5
|
-
import { getTeamContextFromQuery } from "@/lib/api-route-helpers";
|
|
6
|
-
|
|
7
|
-
export async function GET(req: Request) {
|
|
8
|
-
const ctx = await getTeamContextFromQuery(req);
|
|
9
|
-
if (ctx instanceof NextResponse) return ctx;
|
|
10
|
-
const { teamId, teamDir } = ctx;
|
|
11
|
-
const skillsDir = path.join(teamDir, "skills");
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
15
|
-
const skills = entries
|
|
16
|
-
.filter((e) => e.isDirectory())
|
|
17
|
-
.map((e) => e.name)
|
|
18
|
-
.sort((a, b) => a.localeCompare(b));
|
|
19
|
-
|
|
20
|
-
return NextResponse.json({ ok: true, teamId, skillsDir, skills });
|
|
21
|
-
} catch (e: unknown) {
|
|
22
|
-
// skills dir may not exist
|
|
23
|
-
return NextResponse.json({ ok: true, teamId, skillsDir, skills: [], note: errorMessage(e) });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,534 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import crypto from "node:crypto";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { jsonOkRest, parseJsonBody } from "@/lib/api-route-helpers";
|
|
6
|
-
import { handleWorkflowRunsGet } from "@/lib/workflows/api-handlers";
|
|
7
|
-
import { errorMessage } from "@/lib/errors";
|
|
8
|
-
import { toolsInvoke } from "@/lib/gateway";
|
|
9
|
-
import { listWorkflowRuns, readWorkflowRun, writeWorkflowRun } from "@/lib/workflows/runs-storage";
|
|
10
|
-
import type { WorkflowRunFileV1, WorkflowRunNodeResultV1 } from "@/lib/workflows/runs-types";
|
|
11
|
-
import { readWorkflow } from "@/lib/workflows/storage";
|
|
12
|
-
import { assertSafeRelativeFileName, getTeamWorkspaceDir } from "@/lib/paths";
|
|
13
|
-
import type { WorkflowFileV1 } from "@/lib/workflows/types";
|
|
14
|
-
|
|
15
|
-
function nowIso() {
|
|
16
|
-
return new Date().toISOString();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
20
|
-
return Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function appendTeamFile(teamId: string, relPath: string, content: string) {
|
|
24
|
-
const safe = assertSafeRelativeFileName(relPath);
|
|
25
|
-
const teamDir = await getTeamWorkspaceDir(teamId);
|
|
26
|
-
const full = path.join(teamDir, safe);
|
|
27
|
-
await fs.mkdir(path.dirname(full), { recursive: true });
|
|
28
|
-
await fs.appendFile(full, content, "utf8");
|
|
29
|
-
return { full };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function templateReplace(input: string, vars: Record<string, string>) {
|
|
33
|
-
let out = input;
|
|
34
|
-
for (const [k, v] of Object.entries(vars)) {
|
|
35
|
-
out = out.replaceAll(`{{${k}}}`, v);
|
|
36
|
-
}
|
|
37
|
-
return out;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function maybeExecutePendingNodesAfterApproval({
|
|
41
|
-
teamId,
|
|
42
|
-
workflow,
|
|
43
|
-
run,
|
|
44
|
-
approvalNodeId,
|
|
45
|
-
decidedAt,
|
|
46
|
-
}: {
|
|
47
|
-
teamId: string;
|
|
48
|
-
workflow: WorkflowFileV1;
|
|
49
|
-
run: WorkflowRunFileV1;
|
|
50
|
-
approvalNodeId: string;
|
|
51
|
-
decidedAt: string;
|
|
52
|
-
}) {
|
|
53
|
-
const wfNodes = Array.isArray(workflow.nodes) ? workflow.nodes : [];
|
|
54
|
-
const approvalIdx = wfNodes.findIndex((n) => n.id === approvalNodeId);
|
|
55
|
-
if (approvalIdx < 0) return run;
|
|
56
|
-
|
|
57
|
-
const vars = {
|
|
58
|
-
date: decidedAt,
|
|
59
|
-
"run.id": run.id,
|
|
60
|
-
"workflow.id": workflow.id,
|
|
61
|
-
"workflow.name": workflow.name || workflow.id,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const nextNodes: WorkflowRunNodeResultV1[] = Array.isArray(run.nodes)
|
|
65
|
-
? await Promise.all(
|
|
66
|
-
run.nodes.map(async (n) => {
|
|
67
|
-
const wfIdx = wfNodes.findIndex((wfn) => wfn.id === n.nodeId);
|
|
68
|
-
const afterApproval = wfIdx >= 0 && wfIdx > approvalIdx;
|
|
69
|
-
|
|
70
|
-
if (!afterApproval || n.status !== "pending") return n;
|
|
71
|
-
|
|
72
|
-
const wfNode = wfIdx >= 0 ? wfNodes[wfIdx] : undefined;
|
|
73
|
-
const startedAt = n.startedAt ?? decidedAt;
|
|
74
|
-
|
|
75
|
-
if (wfNode?.type === "tool") {
|
|
76
|
-
const cfg = wfNode.config && typeof wfNode.config === "object" ? (wfNode.config as Record<string, unknown>) : {};
|
|
77
|
-
const tool = typeof cfg.tool === "string" && cfg.tool.trim() ? cfg.tool.trim() : "(unknown)";
|
|
78
|
-
|
|
79
|
-
if (tool === "fs.append") {
|
|
80
|
-
const args = cfg.args && typeof cfg.args === "object" ? (cfg.args as Record<string, unknown>) : {};
|
|
81
|
-
const pVal = typeof args.path === "string" ? args.path : "";
|
|
82
|
-
const cVal = typeof args.content === "string" ? args.content : "";
|
|
83
|
-
if (pVal && cVal) {
|
|
84
|
-
const content = templateReplace(cVal, vars);
|
|
85
|
-
const { full } = await appendTeamFile(teamId, pVal, content);
|
|
86
|
-
return {
|
|
87
|
-
...n,
|
|
88
|
-
status: "success",
|
|
89
|
-
startedAt,
|
|
90
|
-
endedAt: decidedAt,
|
|
91
|
-
output: { tool, appendedTo: full, bytes: content.length },
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
...n,
|
|
98
|
-
status: "success",
|
|
99
|
-
startedAt,
|
|
100
|
-
endedAt: decidedAt,
|
|
101
|
-
output: { tool, result: "(sample execution after approval)" },
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
...n,
|
|
107
|
-
status: "success",
|
|
108
|
-
startedAt,
|
|
109
|
-
endedAt: decidedAt,
|
|
110
|
-
output: n.output ?? { note: "(sample execution after approval)" },
|
|
111
|
-
};
|
|
112
|
-
})
|
|
113
|
-
)
|
|
114
|
-
: [];
|
|
115
|
-
|
|
116
|
-
return { ...run, nodes: nextNodes };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function formatApprovalPacketMessage(workflow: WorkflowFileV1, run: WorkflowRunFileV1, approvalNodeId: string): string {
|
|
120
|
-
const title = `${workflow.name || workflow.id} — Approval needed`;
|
|
121
|
-
const runLine = `Run: ${run.id}`;
|
|
122
|
-
|
|
123
|
-
const approvalNode = Array.isArray(run.nodes) ? run.nodes.find((n) => n.nodeId === approvalNodeId) : undefined;
|
|
124
|
-
const out = isRecord(approvalNode?.output) ? approvalNode.output : {};
|
|
125
|
-
const packet = isRecord(out.packet) ? out.packet : null;
|
|
126
|
-
const platforms = packet && isRecord(packet.platforms) ? (packet.platforms as Record<string, unknown>) : null;
|
|
127
|
-
|
|
128
|
-
let body = `${title}\n${runLine}\n\n`;
|
|
129
|
-
|
|
130
|
-
if (packet && typeof packet.note === "string" && packet.note.trim()) {
|
|
131
|
-
body += `${packet.note.trim()}\n\n`;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (platforms) {
|
|
135
|
-
body += "Drafts:\n";
|
|
136
|
-
for (const [k, v] of Object.entries(platforms)) {
|
|
137
|
-
if (!v) continue;
|
|
138
|
-
const p = isRecord(v) ? v : { value: v };
|
|
139
|
-
const hook = typeof p.hook === "string" ? p.hook.trim() : "";
|
|
140
|
-
const text = typeof p.body === "string" ? p.body.trim() : "";
|
|
141
|
-
const script = typeof p.script === "string" ? p.script.trim() : "";
|
|
142
|
-
const notes = typeof p.assetNotes === "string" ? p.assetNotes.trim() : "";
|
|
143
|
-
|
|
144
|
-
body += `\n— ${k.toUpperCase()} —\n`;
|
|
145
|
-
if (hook) body += `Hook: ${hook}\n`;
|
|
146
|
-
if (text) body += `Body: ${text}\n`;
|
|
147
|
-
if (script) body += `Script: ${script}\n`;
|
|
148
|
-
if (notes) body += `Notes: ${notes}\n`;
|
|
149
|
-
}
|
|
150
|
-
body += "\n";
|
|
151
|
-
} else {
|
|
152
|
-
body += "(No structured approval packet found in run file.)\n\n";
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
body += "Reply in ClawKitchen: Approve / Request changes / Cancel.";
|
|
156
|
-
return body;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function applyTemplate(template: string, vars: Record<string, string>) {
|
|
160
|
-
let out = template;
|
|
161
|
-
for (const [k, v] of Object.entries(vars)) {
|
|
162
|
-
out = out.replaceAll(`{{${k}}}`, v);
|
|
163
|
-
}
|
|
164
|
-
return out;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function getApprovalSendConfig(workflow: WorkflowFileV1, approvalNodeId: string) {
|
|
168
|
-
const meta = isRecord(workflow.meta) ? workflow.meta : {};
|
|
169
|
-
const node = Array.isArray(workflow.nodes) ? workflow.nodes.find((n) => n.id === approvalNodeId) : undefined;
|
|
170
|
-
const cfg = node && isRecord(node.config) ? node.config : {};
|
|
171
|
-
|
|
172
|
-
const provider = String(cfg.provider ?? cfg.channel ?? meta.approvalProvider ?? "telegram").trim() || "telegram";
|
|
173
|
-
const target = String(cfg.target ?? cfg.chatId ?? meta.approvalTarget ?? "").trim();
|
|
174
|
-
const messageTemplate = String(cfg.messageTemplate ?? cfg.template ?? "").trim();
|
|
175
|
-
|
|
176
|
-
return { provider, target, messageTemplate };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async function maybeSendApprovalRequest({
|
|
180
|
-
teamId,
|
|
181
|
-
workflow,
|
|
182
|
-
run,
|
|
183
|
-
approvalNodeId,
|
|
184
|
-
}: {
|
|
185
|
-
teamId: string;
|
|
186
|
-
workflow: WorkflowFileV1;
|
|
187
|
-
run: WorkflowRunFileV1;
|
|
188
|
-
approvalNodeId: string;
|
|
189
|
-
}) {
|
|
190
|
-
const { provider, target, messageTemplate } = getApprovalSendConfig(workflow, approvalNodeId);
|
|
191
|
-
if (!target) return;
|
|
192
|
-
|
|
193
|
-
const base = formatApprovalPacketMessage(workflow, run, approvalNodeId);
|
|
194
|
-
const message = messageTemplate
|
|
195
|
-
? `${applyTemplate(messageTemplate, {
|
|
196
|
-
workflowName: workflow.name || workflow.id,
|
|
197
|
-
workflowId: workflow.id,
|
|
198
|
-
runId: run.id,
|
|
199
|
-
nodeId: approvalNodeId,
|
|
200
|
-
})}\n\n${base}`
|
|
201
|
-
: base;
|
|
202
|
-
|
|
203
|
-
// Best-effort: message delivery failures should not block file-first persistence.
|
|
204
|
-
await toolsInvoke({
|
|
205
|
-
tool: "message",
|
|
206
|
-
args: {
|
|
207
|
-
action: "send",
|
|
208
|
-
channel: provider,
|
|
209
|
-
target,
|
|
210
|
-
message,
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Writeback of delivery info happens in the caller (so we can record errors too).
|
|
215
|
-
// eslint-disable-next-line sonarjs/void-use -- intentional no-op to satisfy param
|
|
216
|
-
void teamId;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export async function GET(req: Request) {
|
|
220
|
-
return handleWorkflowRunsGet(req, readWorkflowRun, listWorkflowRuns);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export async function POST(req: Request) {
|
|
224
|
-
const parsed = await parseJsonBody(req);
|
|
225
|
-
if (parsed instanceof NextResponse) return parsed;
|
|
226
|
-
const { body: o } = parsed;
|
|
227
|
-
|
|
228
|
-
const teamId = String(o.teamId ?? "").trim();
|
|
229
|
-
const workflowId = String(o.workflowId ?? "").trim();
|
|
230
|
-
const mode = String(o.mode ?? "").trim();
|
|
231
|
-
const action = String(o.action ?? "").trim();
|
|
232
|
-
const runIdFromBody = String(o.runId ?? "").trim();
|
|
233
|
-
const note = typeof o.note === "string" ? o.note : undefined;
|
|
234
|
-
const decidedBy = typeof o.decidedBy === "string" ? o.decidedBy : undefined;
|
|
235
|
-
|
|
236
|
-
if (!teamId) return NextResponse.json({ ok: false, error: "teamId is required" }, { status: 400 });
|
|
237
|
-
if (!workflowId) return NextResponse.json({ ok: false, error: "workflowId is required" }, { status: 400 });
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
// Action mode: approve/request_changes/cancel (file-first) updates an existing run.
|
|
241
|
-
if (action) {
|
|
242
|
-
if (!runIdFromBody) return NextResponse.json({ ok: false, error: "runId is required for action" }, { status: 400 });
|
|
243
|
-
if (!["approve", "request_changes", "cancel"].includes(action)) {
|
|
244
|
-
return NextResponse.json({ ok: false, error: `Unsupported action: ${action}` }, { status: 400 });
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const existing = await readWorkflowRun(teamId, workflowId, runIdFromBody);
|
|
248
|
-
const run = existing.run;
|
|
249
|
-
|
|
250
|
-
const approvalNodeId = run.approval?.nodeId || (Array.isArray(run.nodes) ? run.nodes.find((n) => n.status === "waiting")?.nodeId : undefined);
|
|
251
|
-
if (!approvalNodeId) {
|
|
252
|
-
return NextResponse.json({ ok: false, error: "Run is not awaiting approval" }, { status: 400 });
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const decidedAt = nowIso();
|
|
256
|
-
const nextState = action === "approve" ? "approved" : action === "request_changes" ? "changes_requested" : "canceled";
|
|
257
|
-
|
|
258
|
-
const nextStatus: WorkflowRunFileV1["status"] =
|
|
259
|
-
nextState === "approved" ? "success" : nextState === "canceled" ? "canceled" : "waiting_for_approval";
|
|
260
|
-
|
|
261
|
-
const nextNodes: WorkflowRunNodeResultV1[] = Array.isArray(run.nodes)
|
|
262
|
-
? run.nodes.map((n) => {
|
|
263
|
-
if (n.nodeId === approvalNodeId) {
|
|
264
|
-
const existingOutput = typeof n.output === "object" && n.output ? (n.output as Record<string, unknown>) : {};
|
|
265
|
-
return {
|
|
266
|
-
...n,
|
|
267
|
-
status: nextState === "approved" ? "success" : nextState === "canceled" ? "error" : "waiting",
|
|
268
|
-
endedAt: nextState === "changes_requested" ? n.endedAt : decidedAt,
|
|
269
|
-
output: {
|
|
270
|
-
...existingOutput,
|
|
271
|
-
decision: nextState,
|
|
272
|
-
note,
|
|
273
|
-
decidedBy,
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (nextState === "canceled" && n.status === "pending") {
|
|
279
|
-
return {
|
|
280
|
-
...n,
|
|
281
|
-
status: "skipped",
|
|
282
|
-
startedAt: n.startedAt ?? decidedAt,
|
|
283
|
-
endedAt: decidedAt,
|
|
284
|
-
output: n.output ?? { note: "skipped due to cancel" },
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return n;
|
|
289
|
-
})
|
|
290
|
-
: [];
|
|
291
|
-
|
|
292
|
-
const nextRun: WorkflowRunFileV1 = {
|
|
293
|
-
...run,
|
|
294
|
-
status: nextStatus,
|
|
295
|
-
endedAt: nextStatus === "success" || nextStatus === "canceled" ? decidedAt : run.endedAt,
|
|
296
|
-
approval: {
|
|
297
|
-
nodeId: approvalNodeId,
|
|
298
|
-
state: nextState,
|
|
299
|
-
requestedAt: run.approval?.requestedAt,
|
|
300
|
-
decidedAt: nextState === "changes_requested" ? undefined : decidedAt,
|
|
301
|
-
note,
|
|
302
|
-
decidedBy,
|
|
303
|
-
},
|
|
304
|
-
nodes: nextNodes,
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
let finalRun: WorkflowRunFileV1 = nextRun;
|
|
309
|
-
|
|
310
|
-
// Best-effort: for sample runs, simulate resuming execution after approval by resolving
|
|
311
|
-
// pending nodes and performing file-first writeback steps (fs.append).
|
|
312
|
-
if (action === "approve") {
|
|
313
|
-
try {
|
|
314
|
-
const wf = (await readWorkflow(teamId, workflowId)).workflow;
|
|
315
|
-
finalRun = await maybeExecutePendingNodesAfterApproval({
|
|
316
|
-
teamId,
|
|
317
|
-
workflow: wf,
|
|
318
|
-
run: nextRun,
|
|
319
|
-
approvalNodeId,
|
|
320
|
-
decidedAt,
|
|
321
|
-
});
|
|
322
|
-
} catch {
|
|
323
|
-
// ignore; keep file-first decision recorded
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return jsonOkRest({ ...(await writeWorkflowRun(teamId, workflowId, finalRun)), runId: run.id });
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Create mode
|
|
331
|
-
const runId = `run-${nowIso().replace(/[:.]/g, "-")}-${crypto.randomBytes(3).toString("hex")}`.toLowerCase();
|
|
332
|
-
|
|
333
|
-
const run: WorkflowRunFileV1 =
|
|
334
|
-
mode === "sample"
|
|
335
|
-
? await (async () => {
|
|
336
|
-
const wf = (await readWorkflow(teamId, workflowId)).workflow;
|
|
337
|
-
const t0 = Date.now();
|
|
338
|
-
|
|
339
|
-
const templateId =
|
|
340
|
-
wf.meta && typeof wf.meta === "object" && "templateId" in wf.meta ? (wf.meta as Record<string, unknown>).templateId : undefined;
|
|
341
|
-
const isMarketingCadence = templateId === "marketing-cadence-v1";
|
|
342
|
-
|
|
343
|
-
const marketingDrafts = isMarketingCadence
|
|
344
|
-
? {
|
|
345
|
-
x: {
|
|
346
|
-
hook: "Stop losing hours to repetitive agent setup.",
|
|
347
|
-
body: "ClawRecipes scaffolds entire teams of agents in one command — workflows, roles, conventions, and a human-approval gate before posting.",
|
|
348
|
-
},
|
|
349
|
-
instagram: {
|
|
350
|
-
hook: "Ship agent workflows faster.",
|
|
351
|
-
body: "From idea → drafted assets → brand QC → approval → posting. File-first workflows you can export and version.",
|
|
352
|
-
assetNotes: "Square image: diagram of workflow nodes + approval gate.",
|
|
353
|
-
},
|
|
354
|
-
tiktok: {
|
|
355
|
-
hook: "POV: you stop copy/pasting prompts.",
|
|
356
|
-
script: "Today I’m building a marketing cadence workflow that researches, drafts, QC’s, then waits for human approval before it posts. File-first. Portable. No magic.",
|
|
357
|
-
assetNotes: "15–25s screen recording of the canvas + approval buttons.",
|
|
358
|
-
},
|
|
359
|
-
youtube: {
|
|
360
|
-
hook: "Build a marketing cadence workflow (with human approval) in 2 minutes.",
|
|
361
|
-
script: "We’ll wire research → drafts → QC → approval → post nodes, and persist the whole thing to shared-context/workflows/*.workflow.json so it’s portable.",
|
|
362
|
-
assetNotes: "Thumbnail: workflow canvas with 'Approve & Post' highlighted.",
|
|
363
|
-
},
|
|
364
|
-
}
|
|
365
|
-
: null;
|
|
366
|
-
|
|
367
|
-
const approvalIdx = wf.nodes.findIndex((n) => n.type === "human_approval");
|
|
368
|
-
const approvalNodeId = approvalIdx >= 0 ? wf.nodes[approvalIdx]?.id : undefined;
|
|
369
|
-
|
|
370
|
-
const nodeResults: WorkflowRunNodeResultV1[] = wf.nodes.map((n, idx) => {
|
|
371
|
-
const startedAt = new Date(t0 + idx * 350).toISOString();
|
|
372
|
-
const endedAt = new Date(t0 + idx * 350 + 200).toISOString();
|
|
373
|
-
|
|
374
|
-
const beforeApproval = approvalIdx < 0 ? true : idx < approvalIdx;
|
|
375
|
-
const isApproval = approvalNodeId ? n.id === approvalNodeId : false;
|
|
376
|
-
const afterApproval = approvalIdx >= 0 && idx > approvalIdx;
|
|
377
|
-
|
|
378
|
-
const base: WorkflowRunNodeResultV1 = {
|
|
379
|
-
nodeId: n.id,
|
|
380
|
-
status: beforeApproval ? "success" : afterApproval ? "pending" : isApproval ? "waiting" : "success",
|
|
381
|
-
startedAt,
|
|
382
|
-
endedAt: beforeApproval ? endedAt : undefined,
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
if (n.type === "llm") {
|
|
386
|
-
const marketingOutput =
|
|
387
|
-
beforeApproval && isMarketingCadence
|
|
388
|
-
? n.id === "research"
|
|
389
|
-
? {
|
|
390
|
-
model: "(sample)",
|
|
391
|
-
kind: "research",
|
|
392
|
-
bullets: [
|
|
393
|
-
"New agent teams are compelling when they’re portable + file-first.",
|
|
394
|
-
"Human approval gates are mandatory for auto-post workflows.",
|
|
395
|
-
"Cron triggers need timezone + preset suggestions.",
|
|
396
|
-
],
|
|
397
|
-
}
|
|
398
|
-
: n.id === "draft_assets"
|
|
399
|
-
? {
|
|
400
|
-
model: "(sample)",
|
|
401
|
-
kind: "draft_assets",
|
|
402
|
-
drafts: marketingDrafts,
|
|
403
|
-
}
|
|
404
|
-
: n.id === "qc_brand"
|
|
405
|
-
? {
|
|
406
|
-
model: "(sample)",
|
|
407
|
-
kind: "qc_brand",
|
|
408
|
-
notes: [
|
|
409
|
-
"Keep claims concrete (no ‘magic’).",
|
|
410
|
-
"Mention ClawRecipes before OpenClaw.",
|
|
411
|
-
"Explicitly state: no posting without approval.",
|
|
412
|
-
],
|
|
413
|
-
}
|
|
414
|
-
: {
|
|
415
|
-
model: "(sample)",
|
|
416
|
-
text: `Sample output for ${n.id}`,
|
|
417
|
-
}
|
|
418
|
-
: null;
|
|
419
|
-
|
|
420
|
-
return {
|
|
421
|
-
...base,
|
|
422
|
-
output: beforeApproval
|
|
423
|
-
? marketingOutput ?? {
|
|
424
|
-
model: "(sample)",
|
|
425
|
-
text: `Sample output for ${n.id}`,
|
|
426
|
-
}
|
|
427
|
-
: undefined,
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (n.type === "tool") {
|
|
432
|
-
const toolVal = n.config && typeof n.config === "object" ? (n.config as Record<string, unknown>).tool : undefined;
|
|
433
|
-
const tool = typeof toolVal === "string" && toolVal.trim() ? toolVal.trim() : "(unknown)";
|
|
434
|
-
return {
|
|
435
|
-
...base,
|
|
436
|
-
output: beforeApproval
|
|
437
|
-
? {
|
|
438
|
-
tool,
|
|
439
|
-
result: "(sample tool result)",
|
|
440
|
-
}
|
|
441
|
-
: undefined,
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (n.type === "human_approval") {
|
|
446
|
-
const approvalPacket = isMarketingCadence
|
|
447
|
-
? {
|
|
448
|
-
channel: "(sample)",
|
|
449
|
-
decision: "pending",
|
|
450
|
-
options: ["approve", "request_changes", "cancel"],
|
|
451
|
-
packet: {
|
|
452
|
-
templateId: "marketing-cadence-v1",
|
|
453
|
-
note: "Per-platform drafts (sample) — approve to post, request changes to loop, or cancel.",
|
|
454
|
-
platforms: {
|
|
455
|
-
x: marketingDrafts?.x,
|
|
456
|
-
instagram: marketingDrafts?.instagram,
|
|
457
|
-
tiktok: marketingDrafts?.tiktok,
|
|
458
|
-
youtube: marketingDrafts?.youtube,
|
|
459
|
-
},
|
|
460
|
-
},
|
|
461
|
-
}
|
|
462
|
-
: {
|
|
463
|
-
channel: "(sample)",
|
|
464
|
-
decision: "pending",
|
|
465
|
-
options: ["approve", "request_changes", "cancel"],
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
return {
|
|
469
|
-
...base,
|
|
470
|
-
output: approvalPacket,
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return base;
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
const status: WorkflowRunFileV1["status"] = approvalNodeId ? "waiting_for_approval" : "success";
|
|
478
|
-
|
|
479
|
-
const baseRun: WorkflowRunFileV1 = {
|
|
480
|
-
schema: "clawkitchen.workflow-run.v1",
|
|
481
|
-
id: runId,
|
|
482
|
-
workflowId,
|
|
483
|
-
startedAt: new Date(t0).toISOString(),
|
|
484
|
-
endedAt: approvalNodeId ? undefined : new Date(t0 + wf.nodes.length * 350 + 200).toISOString(),
|
|
485
|
-
status,
|
|
486
|
-
summary: approvalNodeId
|
|
487
|
-
? "Sample run (awaiting approval)"
|
|
488
|
-
: "Sample run (generated by ClawKitchen UI)",
|
|
489
|
-
nodes: nodeResults,
|
|
490
|
-
approval: approvalNodeId
|
|
491
|
-
? {
|
|
492
|
-
nodeId: approvalNodeId,
|
|
493
|
-
state: "pending",
|
|
494
|
-
requestedAt: new Date(t0 + approvalIdx * 350).toISOString(),
|
|
495
|
-
}
|
|
496
|
-
: undefined,
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
if (approvalNodeId) {
|
|
500
|
-
const { provider, target } = getApprovalSendConfig(wf, approvalNodeId);
|
|
501
|
-
|
|
502
|
-
if (target) {
|
|
503
|
-
try {
|
|
504
|
-
await maybeSendApprovalRequest({ teamId, workflow: wf, run: baseRun, approvalNodeId });
|
|
505
|
-
baseRun.approval = {
|
|
506
|
-
...baseRun.approval,
|
|
507
|
-
outbound: { provider, target, sentAt: nowIso() },
|
|
508
|
-
} as WorkflowRunFileV1["approval"];
|
|
509
|
-
} catch (e: unknown) {
|
|
510
|
-
baseRun.approval = {
|
|
511
|
-
...baseRun.approval,
|
|
512
|
-
outbound: { provider, target, error: errorMessage(e), attemptedAt: nowIso() },
|
|
513
|
-
} as WorkflowRunFileV1["approval"];
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
return baseRun satisfies WorkflowRunFileV1;
|
|
519
|
-
})()
|
|
520
|
-
: {
|
|
521
|
-
schema: "clawkitchen.workflow-run.v1",
|
|
522
|
-
id: runId,
|
|
523
|
-
workflowId,
|
|
524
|
-
startedAt: nowIso(),
|
|
525
|
-
status: "running",
|
|
526
|
-
summary: "Run created (execution engine not yet wired)",
|
|
527
|
-
nodes: [],
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
return jsonOkRest({ ...(await writeWorkflowRun(teamId, workflowId, run)), runId });
|
|
531
|
-
} catch (err: unknown) {
|
|
532
|
-
return NextResponse.json({ ok: false, error: errorMessage(err) }, { status: 500 });
|
|
533
|
-
}
|
|
534
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { NextResponse } from "next/server";
|
|
4
|
-
import { jsonOkRest, parseJsonBody } from "@/lib/api-route-helpers";
|
|
5
|
-
import { errorMessage } from "@/lib/errors";
|
|
6
|
-
import { getTeamWorkspaceDir } from "@/lib/paths";
|
|
7
|
-
import { listWorkflows, writeWorkflow } from "@/lib/workflows/storage";
|
|
8
|
-
import { marketingCadenceWorkflowV1 } from "@/lib/workflows/templates";
|
|
9
|
-
|
|
10
|
-
async function exists(p: string) {
|
|
11
|
-
try {
|
|
12
|
-
await fs.access(p);
|
|
13
|
-
return true;
|
|
14
|
-
} catch {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function nextAvailableId(baseId: string, existing: Set<string>) {
|
|
20
|
-
if (!existing.has(baseId)) return baseId;
|
|
21
|
-
for (let i = 2; i < 1000; i++) {
|
|
22
|
-
const id = `${baseId}-${i}`;
|
|
23
|
-
if (!existing.has(id)) return id;
|
|
24
|
-
}
|
|
25
|
-
return `${baseId}-${Date.now()}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function ensureFile(p: string, content: string) {
|
|
29
|
-
if (await exists(p)) return;
|
|
30
|
-
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
31
|
-
await fs.writeFile(p, content, "utf8");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function POST(req: Request) {
|
|
35
|
-
const parsed = await parseJsonBody(req);
|
|
36
|
-
if (parsed instanceof NextResponse) return parsed;
|
|
37
|
-
const { body: o } = parsed;
|
|
38
|
-
|
|
39
|
-
const teamId = String(o.teamId ?? "").trim();
|
|
40
|
-
const templateId = String(o.templateId ?? "").trim();
|
|
41
|
-
|
|
42
|
-
if (!teamId) return NextResponse.json({ ok: false, error: "teamId is required" }, { status: 400 });
|
|
43
|
-
if (!templateId) return NextResponse.json({ ok: false, error: "templateId is required" }, { status: 400 });
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
if (templateId !== "marketing-cadence-v1") {
|
|
47
|
-
return NextResponse.json({ ok: false, error: `Unknown templateId: ${templateId}` }, { status: 400 });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const listed = await listWorkflows(teamId);
|
|
51
|
-
const existingIds = new Set(
|
|
52
|
-
listed.files
|
|
53
|
-
.map((f) => (typeof f === "string" && f.endsWith(".workflow.json") ? f.slice(0, -".workflow.json".length) : null))
|
|
54
|
-
.filter((x): x is string => Boolean(x))
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const id = nextAvailableId("marketing-cadence-v1", existingIds);
|
|
58
|
-
|
|
59
|
-
const wf = marketingCadenceWorkflowV1({ id, approvalProvider: "telegram", approvalTarget: "" });
|
|
60
|
-
const writeRes = await writeWorkflow(teamId, wf);
|
|
61
|
-
|
|
62
|
-
// Ensure canonical file-first destinations exist so demo doesn't 404.
|
|
63
|
-
const teamDir = await getTeamWorkspaceDir(teamId);
|
|
64
|
-
await ensureFile(path.join(teamDir, "shared-context", "marketing", "POST_LOG.md"), "");
|
|
65
|
-
await ensureFile(path.join(teamDir, "shared-context", "memory", "marketing_learnings.jsonl"), "");
|
|
66
|
-
|
|
67
|
-
return jsonOkRest({ ...writeRes, workflowId: wf.id, templateId });
|
|
68
|
-
} catch (err: unknown) {
|
|
69
|
-
return NextResponse.json({ ok: false, error: errorMessage(err) }, { status: 500 });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import { jsonOkRest, parseJsonBody } from "@/lib/api-route-helpers";
|
|
3
|
-
import { errorMessage } from "@/lib/errors";
|
|
4
|
-
import { handleWorkflowsGet } from "@/lib/workflows/api-handlers";
|
|
5
|
-
import { deleteWorkflow, listWorkflows, readWorkflow, writeWorkflow } from "@/lib/workflows/storage";
|
|
6
|
-
import type { WorkflowFileV1 } from "@/lib/workflows/types";
|
|
7
|
-
|
|
8
|
-
function isWorkflowFileV1(v: unknown): v is WorkflowFileV1 {
|
|
9
|
-
if (!v || typeof v !== "object") return false;
|
|
10
|
-
const o = v as Record<string, unknown>;
|
|
11
|
-
if (typeof o.id !== "string" || !o.id.trim()) return false;
|
|
12
|
-
if (typeof o.name !== "string" || !o.name.trim()) return false;
|
|
13
|
-
if (!Array.isArray(o.nodes)) return false;
|
|
14
|
-
if (!Array.isArray(o.edges)) return false;
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function GET(req: Request) {
|
|
19
|
-
return handleWorkflowsGet(req, readWorkflow, listWorkflows);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function POST(req: Request) {
|
|
23
|
-
const parsed = await parseJsonBody(req);
|
|
24
|
-
if (parsed instanceof NextResponse) return parsed;
|
|
25
|
-
const { body: o } = parsed;
|
|
26
|
-
|
|
27
|
-
const teamId = String(o.teamId ?? "").trim();
|
|
28
|
-
const workflowRaw = o.workflow;
|
|
29
|
-
|
|
30
|
-
if (!teamId) return NextResponse.json({ ok: false, error: "teamId is required" }, { status: 400 });
|
|
31
|
-
if (!isWorkflowFileV1(workflowRaw)) {
|
|
32
|
-
return NextResponse.json({ ok: false, error: "workflow is required (must include id, name, nodes[], edges[])" }, { status: 400 });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
return jsonOkRest(await writeWorkflow(teamId, workflowRaw));
|
|
37
|
-
} catch (err: unknown) {
|
|
38
|
-
return NextResponse.json({ ok: false, error: errorMessage(err) }, { status: 500 });
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function DELETE(req: Request) {
|
|
43
|
-
const { searchParams } = new URL(req.url);
|
|
44
|
-
const teamId = String(searchParams.get("teamId") ?? "").trim();
|
|
45
|
-
const id = String(searchParams.get("id") ?? "").trim();
|
|
46
|
-
|
|
47
|
-
if (!teamId) return NextResponse.json({ ok: false, error: "teamId is required" }, { status: 400 });
|
|
48
|
-
if (!id) return NextResponse.json({ ok: false, error: "id is required" }, { status: 400 });
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
return jsonOkRest(await deleteWorkflow(teamId, id));
|
|
52
|
-
} catch (err: unknown) {
|
|
53
|
-
return NextResponse.json({ ok: false, error: errorMessage(err) }, { status: 500 });
|
|
54
|
-
}
|
|
55
|
-
}
|