@fieldwangai/agentflow 0.1.162 → 0.1.164
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/bin/lib/flow-dsl/codegen.mjs +23 -2
- package/bin/lib/flow-dsl/lint.mjs +44 -0
- package/bin/lib/flow-dsl/parser.mjs +68 -1
- package/bin/lib/marketplace-usage.mjs +218 -0
- package/bin/lib/marketplace.mjs +183 -3
- package/bin/lib/node-package-manifest.mjs +1 -1
- package/bin/lib/spaces.mjs +200 -0
- package/bin/lib/ui-server.mjs +314 -76
- package/bin/lib/workspace-routes.mjs +396 -5
- package/bin/lib/workspace-run-logs.mjs +2 -0
- package/bin/lib/workspace-server.mjs +698 -48
- package/bin/lib/workspace-state.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +5 -1
- package/builtin/nodes/context_bundle.md +44 -0
- package/builtin/nodes/context_knowledge.md +29 -0
- package/builtin/nodes/context_skills.md +29 -0
- package/builtin/nodes/context_workspace.md +36 -0
- package/builtin/nodes/control_while.md +7 -0
- package/builtin/nodes/tool_git_worktree_load.md +4 -3
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-CTrXOZ00.js → WorkflowAssistantThread-CffIx5BY.js} +1 -1
- package/builtin/web-ui/dist/assets/index-CDItaRfX.css +1 -0
- package/builtin/web-ui/dist/assets/index-CWIcfWHO.js +873 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/shared/slot-types.js +1 -0
- package/skills/agentflow-cli/SKILL.md +50 -3
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +120 -6
- package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +5 -1
- package/skills/agentflow-cli/runtime/builtin/nodes/context_bundle.md +44 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_knowledge.md +29 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_skills.md +29 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_workspace.md +36 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +7 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +4 -3
- package/skills/agentflow-cli/runtime/package.json +1 -1
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +64 -0
- package/skills/agentflow-flow-dsl/SKILL.md +48 -2
- package/skills/agentflow-flow-dsl/references/node-calls.md +6 -2
- package/skills/agentflow-flow-dsl/references/subflow-authoring.md +34 -7
- package/skills/agentflow-node-reference/references/builtin-nodes.md +37 -5
- package/builtin/web-ui/dist/assets/index-5uJFccdX.css +0 -1
- package/builtin/web-ui/dist/assets/index-BeUfNQRL.js +0 -873
|
@@ -17,12 +17,23 @@ import { buildSkillCompactInjectionBlock, loadResourcesForSkillKeys } from "./co
|
|
|
17
17
|
import { execFileBuffered } from "./exec-buffered.mjs";
|
|
18
18
|
import { runGit } from "./git-worktree.mjs";
|
|
19
19
|
import {
|
|
20
|
+
listMarketplaceFlowSnippets,
|
|
21
|
+
listMarketplaceFlows,
|
|
20
22
|
listMarketplacePackages,
|
|
21
23
|
nodePackageArchive,
|
|
24
|
+
publishMarketplaceFlow,
|
|
22
25
|
publishNodePackage,
|
|
23
26
|
publishNodePackageArchive,
|
|
27
|
+
readMarketplaceFlow,
|
|
24
28
|
resolveMarketplaceNodePackage,
|
|
29
|
+
setMarketplaceVisibility,
|
|
25
30
|
} from "./marketplace.mjs";
|
|
31
|
+
import {
|
|
32
|
+
appendMarketplaceUsageEvent,
|
|
33
|
+
marketplaceResourcesForRun,
|
|
34
|
+
readMarketplaceFlowOrigin,
|
|
35
|
+
writeMarketplaceFlowOrigin,
|
|
36
|
+
} from "./marketplace-usage.mjs";
|
|
26
37
|
import { NODE_PACKAGE_ENTRY, nodePackageExportsRun, readNodePackageManifest } from "./node-package-manifest.mjs";
|
|
27
38
|
import { inspectNodePackageDirectory } from "./node-package-archive.mjs";
|
|
28
39
|
import { json, readBody } from "./http-util.mjs";
|
|
@@ -38,8 +49,8 @@ import { mergeWorkspaceGraphs, workspaceDesignRevision, workspaceRuntimeRevision
|
|
|
38
49
|
import { DEFAULT_WORKSPACE_PREVIEW_TTL_MS, createWorkspacePreviewId, normalizeWorkspacePreviewTtlMs, readWorkspacePreviewMetadata, workspaceSharedPreviewFlowDir, writeWorkspacePreviewMetadata } from "./workspace-preview.mjs";
|
|
39
50
|
import { DEFAULT_WORKSPACE_DRAFT_TTL_MS, createWorkspaceDraftId, normalizeWorkspaceDraftTtlMs, readWorkspaceDraftMetadata, workspaceDraftFlowDir, writeWorkspaceDraftMetadata } from "./workspace-draft.mjs";
|
|
40
51
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession, listWorkspaceRunLogs, readWorkspaceRunLogEvents } from "./workspace-run-logs.mjs";
|
|
41
|
-
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, removeWorkspaceDeferredRun, resolveWorkspaceFilePath, resolveWorkspaceScopeRoot, runWorkspaceGraph, sleepMs, syncWorkspaceSchedulesForGraph, upsertWorkspaceDeferredRun, workspaceActiveRunsForScope, workspaceCollaborationEventKey, workspaceCollaborationSequences, workspaceCollaborationSubscribers, workspaceCollaborationSummaryWithUsers, workspaceDeferredRunsForScope, workspaceDesignPath, workspaceDownloadContentDisposition, workspaceFindActiveRunConflict, workspaceGraphAsSource, workspaceOptimizeRunImplementations, workspaceRepoUrlWithCredential, workspaceRunControl, workspaceRunEntryKey, workspaceRunKey, workspaceRunPlan, workspaceRunPlanNodeIds, workspaceRunTouchedNodeIds, workspaceRuntimeNodeLabel, workspaceScheduleNextRunAt, workspaceScopedUserContext, workspaceSearchGuardrailsBlock, workspaceUnwrapOutputEnvelopeForDisplay, workspacesPath, writeWorkspaceConversations, writeWorkspaceGraph } from "./workspace-server.mjs";
|
|
42
|
-
import { WORKSPACE_STATE_FILENAME } from "./workspace-state.mjs";
|
|
52
|
+
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, cleanupWorkspaceRunResources, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, publishWorkspaceRelease, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, readWorkspaceReleaseStatus, removeWorkspaceDeferredRun, resolveWorkspaceFilePath, resolveWorkspaceScopeRoot, rollbackWorkspaceRelease, runWorkspaceGraph, sleepMs, syncWorkspaceSchedulesForGraph, upsertWorkspaceDeferredRun, workspaceActiveRunsForScope, workspaceCollaborationEventKey, workspaceCollaborationSequences, workspaceCollaborationSubscribers, workspaceCollaborationSummaryWithUsers, workspaceDeferredRunsForScope, workspaceDesignPath, workspaceDownloadContentDisposition, workspaceFindActiveRunConflict, workspaceGraphAsSource, workspaceOptimizeRunImplementations, workspaceRepoUrlWithCredential, workspaceRunControl, workspaceRunEntryKey, workspaceRunKey, workspaceRunPlan, workspaceRunPlanNodeIds, workspaceRunTouchedNodeIds, workspaceRuntimeNodeLabel, workspaceScheduleNextRunAt, workspaceScopedUserContext, workspaceSearchGuardrailsBlock, workspaceUnwrapOutputEnvelopeForDisplay, workspacesPath, writeWorkspaceConversations, writeWorkspaceGraph } from "./workspace-server.mjs";
|
|
53
|
+
import { splitWorkspaceGraph, WORKSPACE_STATE_FILENAME } from "./workspace-state.mjs";
|
|
43
54
|
import { getWorkspaceTree } from "./workspace-tree.mjs";
|
|
44
55
|
import busboy from "busboy";
|
|
45
56
|
import crypto from "crypto";
|
|
@@ -899,6 +910,45 @@ function workspaceBufferLooksLikeZip(buf) {
|
|
|
899
910
|
);
|
|
900
911
|
}
|
|
901
912
|
|
|
913
|
+
function installedMarketplaceFlowCopies(userId) {
|
|
914
|
+
const copies = new Map();
|
|
915
|
+
const pipelinesRoot = getUserPipelinesRoot(userId);
|
|
916
|
+
if (!fs.existsSync(pipelinesRoot)) return copies;
|
|
917
|
+
for (const entry of fs.readdirSync(pipelinesRoot, { withFileTypes: true })) {
|
|
918
|
+
if (!entry.isDirectory()) continue;
|
|
919
|
+
const origin = readMarketplaceFlowOrigin(path.join(pipelinesRoot, entry.name));
|
|
920
|
+
if (!origin) continue;
|
|
921
|
+
const key = `${origin.id}@${origin.version}`;
|
|
922
|
+
const flowIds = copies.get(key) || [];
|
|
923
|
+
flowIds.push(entry.name);
|
|
924
|
+
copies.set(key, flowIds);
|
|
925
|
+
}
|
|
926
|
+
return copies;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function marketplaceResourceMatches(item, queryText) {
|
|
930
|
+
if (!queryText) return true;
|
|
931
|
+
return [
|
|
932
|
+
item.id,
|
|
933
|
+
item.definitionId,
|
|
934
|
+
item.displayName,
|
|
935
|
+
item.description,
|
|
936
|
+
item.ownerUserId,
|
|
937
|
+
item.source,
|
|
938
|
+
...(Array.isArray(item.tags) ? item.tags : []),
|
|
939
|
+
].filter(Boolean).join("\n").toLowerCase().includes(queryText);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function sortMarketplaceResources(items) {
|
|
943
|
+
return items.sort((a, b) => (
|
|
944
|
+
Number(b.useCount || 0) - Number(a.useCount || 0)
|
|
945
|
+
|| Number(b.installCount || 0) - Number(a.installCount || 0)
|
|
946
|
+
|| String(b.updatedAt || b.createdAt || "").localeCompare(String(a.updatedAt || a.createdAt || ""))
|
|
947
|
+
|| String(a.displayName || a.id).localeCompare(String(b.displayName || b.id))
|
|
948
|
+
|| String(b.version || "").localeCompare(String(a.version || ""), undefined, { numeric: true, sensitivity: "base" })
|
|
949
|
+
));
|
|
950
|
+
}
|
|
951
|
+
|
|
902
952
|
/**
|
|
903
953
|
* @param {import('http').IncomingMessage} req
|
|
904
954
|
* @param {import('http').ServerResponse} res
|
|
@@ -921,6 +971,11 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
921
971
|
inputs: node.inputs,
|
|
922
972
|
outputs: node.outputs,
|
|
923
973
|
ownerUserId: node.ownerUserId || node.createdBy || "",
|
|
974
|
+
visibility: node.visibility || "public",
|
|
975
|
+
useCount: Number(node.useCount || 0),
|
|
976
|
+
installCount: Number(node.installCount || 0),
|
|
977
|
+
uniqueUserCount: Number(node.uniqueUserCount || 0),
|
|
978
|
+
lastUsedAt: node.lastUsedAt || "",
|
|
924
979
|
fileList: node.fileList || [],
|
|
925
980
|
fileCount: node.fileCount || 0,
|
|
926
981
|
totalBytes: node.totalBytes || 0,
|
|
@@ -980,6 +1035,14 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
980
1035
|
json(res, 404, { error: result.error || "Node package not found" });
|
|
981
1036
|
return;
|
|
982
1037
|
}
|
|
1038
|
+
appendMarketplaceUsageEvent(root, {
|
|
1039
|
+
kind: "node",
|
|
1040
|
+
id,
|
|
1041
|
+
version,
|
|
1042
|
+
action: "install",
|
|
1043
|
+
actorUserId: userCtx.userId || "",
|
|
1044
|
+
eventId: `install:node:${id}@${version}:${userCtx.userId || "anonymous"}`,
|
|
1045
|
+
});
|
|
983
1046
|
res.writeHead(200, {
|
|
984
1047
|
"Content-Type": "application/zip",
|
|
985
1048
|
"Content-Length": result.archive.length,
|
|
@@ -995,6 +1058,199 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
995
1058
|
return;
|
|
996
1059
|
}
|
|
997
1060
|
|
|
1061
|
+
if (req.method === "GET" && url.pathname === "/api/marketplace/resources") {
|
|
1062
|
+
try {
|
|
1063
|
+
const kind = String(url.searchParams.get("kind") || "flow").trim();
|
|
1064
|
+
const requestedScope = String(url.searchParams.get("scope") || "all").trim();
|
|
1065
|
+
const scope = ["all", "owned", "installed"].includes(requestedScope) ? requestedScope : "all";
|
|
1066
|
+
const marketplaceScope = scope === "owned" ? "owned" : "all";
|
|
1067
|
+
const queryText = String(url.searchParams.get("q") || "").trim().toLowerCase();
|
|
1068
|
+
if (kind === "node") {
|
|
1069
|
+
const marketplaceNodes = listMarketplacePackages(root, { ...userCtx, marketplaceScope }).nodes
|
|
1070
|
+
.map((node) => ({ ...node, resourceType: "node", installed: true }));
|
|
1071
|
+
let nodes = marketplaceNodes;
|
|
1072
|
+
if (scope === "installed") {
|
|
1073
|
+
const marketplaceByDefinition = new Map(marketplaceNodes.map((node) => [node.definitionId, node]));
|
|
1074
|
+
nodes = listNodesJson(root, "", "", { ...userCtx, marketplaceScope: "all" }).nodes.map((node) => {
|
|
1075
|
+
const marketplaceNode = marketplaceByDefinition.get(node.id);
|
|
1076
|
+
if (marketplaceNode) {
|
|
1077
|
+
return {
|
|
1078
|
+
...marketplaceNode,
|
|
1079
|
+
definitionId: node.id,
|
|
1080
|
+
source: node.source || "marketplace",
|
|
1081
|
+
installed: true,
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
return {
|
|
1085
|
+
id: node.packageId || node.id,
|
|
1086
|
+
version: node.version || "",
|
|
1087
|
+
definitionId: node.id,
|
|
1088
|
+
displayName: node.displayName || node.label || node.id,
|
|
1089
|
+
description: node.description || "",
|
|
1090
|
+
inputs: node.inputs || {},
|
|
1091
|
+
outputs: node.outputs || {},
|
|
1092
|
+
source: node.source || "project",
|
|
1093
|
+
resourceType: "node",
|
|
1094
|
+
localCatalog: true,
|
|
1095
|
+
installed: true,
|
|
1096
|
+
};
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
nodes = sortMarketplaceResources(nodes.filter((item) => marketplaceResourceMatches(item, queryText)));
|
|
1100
|
+
json(res, 200, { kind, scope, sort: "useCount", order: "desc", items: nodes });
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
if (kind !== "flow") {
|
|
1104
|
+
json(res, 400, { error: "kind must be flow or node" });
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const installedCopies = installedMarketplaceFlowCopies(userCtx.userId);
|
|
1108
|
+
const flows = listMarketplaceFlows(root, { ...userCtx, marketplaceScope }).flows.map((flow) => {
|
|
1109
|
+
const installedFlowIds = installedCopies.get(`${flow.id}@${flow.version}`) || [];
|
|
1110
|
+
return {
|
|
1111
|
+
...flow,
|
|
1112
|
+
resourceType: "flow",
|
|
1113
|
+
installed: installedFlowIds.length > 0,
|
|
1114
|
+
installedFlowIds,
|
|
1115
|
+
};
|
|
1116
|
+
});
|
|
1117
|
+
const snippets = scope === "installed" ? [] : listMarketplaceFlowSnippets(root, { ...userCtx, marketplaceScope }).snippets
|
|
1118
|
+
.map((snippet) => ({ ...snippet, resourceType: "flow-snippet", installed: false }));
|
|
1119
|
+
const items = sortMarketplaceResources(
|
|
1120
|
+
[...flows, ...snippets]
|
|
1121
|
+
.filter((item) => scope !== "installed" || item.installed)
|
|
1122
|
+
.filter((item) => marketplaceResourceMatches(item, queryText)),
|
|
1123
|
+
);
|
|
1124
|
+
json(res, 200, { kind, scope, sort: "useCount", order: "desc", items });
|
|
1125
|
+
} catch (e) {
|
|
1126
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1127
|
+
}
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
if (req.method === "POST" && url.pathname === "/api/marketplace/flows/publish") {
|
|
1132
|
+
let payload;
|
|
1133
|
+
try {
|
|
1134
|
+
payload = JSON.parse(await readBody(req));
|
|
1135
|
+
} catch {
|
|
1136
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
try {
|
|
1140
|
+
const flowId = String(payload?.flowId || "").trim();
|
|
1141
|
+
const flowSource = String(payload?.flowSource || "user").trim();
|
|
1142
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
1143
|
+
flowId,
|
|
1144
|
+
flowSource,
|
|
1145
|
+
archived: payload?.archived === true,
|
|
1146
|
+
}, userCtx);
|
|
1147
|
+
if (scoped.error) {
|
|
1148
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)) {
|
|
1152
|
+
json(res, 400, { error: "Only active editable flows can be published" });
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const current = readWorkspaceGraph(scoped.root, root).graph;
|
|
1156
|
+
const { design } = splitWorkspaceGraph(current);
|
|
1157
|
+
const graph = workspaceGraphWithScheduleMode(design, "disabled");
|
|
1158
|
+
const result = publishMarketplaceFlow(root, {
|
|
1159
|
+
id: payload?.id || flowId,
|
|
1160
|
+
version: payload?.version || "1.0.0",
|
|
1161
|
+
displayName: payload?.displayName || flowId,
|
|
1162
|
+
description: payload?.description || "",
|
|
1163
|
+
tags: payload?.tags || [],
|
|
1164
|
+
visibility: payload?.visibility || "public",
|
|
1165
|
+
graph,
|
|
1166
|
+
}, userCtx);
|
|
1167
|
+
json(res, result.ok ? (result.alreadyExists ? 200 : 201) : (result.conflict ? 409 : 400), result);
|
|
1168
|
+
} catch (e) {
|
|
1169
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1170
|
+
}
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
if (req.method === "POST" && url.pathname === "/api/marketplace/flows/install") {
|
|
1175
|
+
let payload;
|
|
1176
|
+
try {
|
|
1177
|
+
payload = JSON.parse(await readBody(req));
|
|
1178
|
+
} catch {
|
|
1179
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const id = String(payload?.id || "").trim();
|
|
1183
|
+
const version = String(payload?.version || "").trim();
|
|
1184
|
+
const idCheck = validateUserPipelineId(String(payload?.flowId || id));
|
|
1185
|
+
if (!id || !version || !idCheck.ok) {
|
|
1186
|
+
json(res, 400, { error: idCheck.error || "Missing marketplace flow id or version" });
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
const flowId = idCheck.flowId;
|
|
1190
|
+
const targetDir = path.join(getUserPipelinesRoot(userCtx.userId), flowId);
|
|
1191
|
+
if (fs.existsSync(targetDir)) {
|
|
1192
|
+
json(res, 409, { error: `已存在同名流水线 ${flowId}` });
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
try {
|
|
1196
|
+
const source = readMarketplaceFlow(root, id, version, { ...userCtx, marketplaceScope: "all" });
|
|
1197
|
+
if (!source.ok) {
|
|
1198
|
+
json(res, 404, { error: source.error || "Marketplace flow not found" });
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
1202
|
+
const graph = workspaceGraphWithScheduleMode(source.graph, "disabled");
|
|
1203
|
+
writeWorkspaceGraph(targetDir, graph, root);
|
|
1204
|
+
writeMarketplaceFlowOrigin(targetDir, { id, version });
|
|
1205
|
+
appendMarketplaceUsageEvent(root, {
|
|
1206
|
+
kind: "flow",
|
|
1207
|
+
id,
|
|
1208
|
+
version,
|
|
1209
|
+
action: "install",
|
|
1210
|
+
actorUserId: userCtx.userId || "",
|
|
1211
|
+
eventId: `install:flow:${id}@${version}:${userCtx.userId || "anonymous"}`,
|
|
1212
|
+
});
|
|
1213
|
+
json(res, 201, {
|
|
1214
|
+
ok: true,
|
|
1215
|
+
id,
|
|
1216
|
+
version,
|
|
1217
|
+
flowId,
|
|
1218
|
+
flowSource: "user",
|
|
1219
|
+
url: `/workspace?flowId=${encodeURIComponent(flowId)}&flowSource=user`,
|
|
1220
|
+
});
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
try {
|
|
1223
|
+
if (fs.existsSync(targetDir)) fs.rmSync(targetDir, { recursive: true, force: true });
|
|
1224
|
+
} catch {}
|
|
1225
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1226
|
+
}
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
if (req.method === "PATCH" && url.pathname === "/api/marketplace/visibility") {
|
|
1231
|
+
let payload;
|
|
1232
|
+
try {
|
|
1233
|
+
payload = JSON.parse(await readBody(req));
|
|
1234
|
+
} catch {
|
|
1235
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
try {
|
|
1239
|
+
const result = setMarketplaceVisibility(
|
|
1240
|
+
root,
|
|
1241
|
+
String(payload?.kind || ""),
|
|
1242
|
+
String(payload?.id || ""),
|
|
1243
|
+
String(payload?.version || ""),
|
|
1244
|
+
String(payload?.visibility || "public"),
|
|
1245
|
+
userCtx,
|
|
1246
|
+
);
|
|
1247
|
+
json(res, result.ok ? 200 : 400, result);
|
|
1248
|
+
} catch (e) {
|
|
1249
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1250
|
+
}
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
998
1254
|
if (req.method === "GET" && url.pathname === "/api/workspace-tree") {
|
|
999
1255
|
try {
|
|
1000
1256
|
json(res, 200, getWorkspaceTree(root));
|
|
@@ -1416,7 +1672,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1416
1672
|
const written = writeWorkspaceGraph(flowDir, graph, root);
|
|
1417
1673
|
writeWorkspaceDraftMetadata(flowDir, metadata);
|
|
1418
1674
|
const persisted = readWorkspaceGraph(flowDir, root).graph;
|
|
1419
|
-
const baseUrl =
|
|
1675
|
+
const baseUrl = requestPublicBaseUrl(req);
|
|
1420
1676
|
json(res, 200, {
|
|
1421
1677
|
ok: true,
|
|
1422
1678
|
flowId,
|
|
@@ -1489,7 +1745,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1489
1745
|
if (scoped.error) throw new Error(scoped.error);
|
|
1490
1746
|
const persisted = readWorkspaceGraph(scoped.root, root).graph;
|
|
1491
1747
|
const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, persisted, authUser, userCtx);
|
|
1492
|
-
const baseUrl =
|
|
1748
|
+
const baseUrl = requestPublicBaseUrl(req);
|
|
1493
1749
|
json(res, 200, {
|
|
1494
1750
|
ok: true,
|
|
1495
1751
|
success: true,
|
|
@@ -1578,7 +1834,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1578
1834
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1579
1835
|
return;
|
|
1580
1836
|
}
|
|
1581
|
-
const baseUrl =
|
|
1837
|
+
const baseUrl = requestPublicBaseUrl(req);
|
|
1582
1838
|
const workspaceUrl = `${baseUrl}/workspace?flowId=${encodeURIComponent(flowId)}&flowSource=workspace&archived=1`;
|
|
1583
1839
|
json(res, 200, { ok: true, flowId, flowSource: "workspace", archived: true, preview: true, expiresAt: metadata.expiresAt, url: workspaceUrl });
|
|
1584
1840
|
return;
|
|
@@ -1690,6 +1946,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1690
1946
|
ownerUserId: scoped.ownerUserId,
|
|
1691
1947
|
ownerUsername: scoped.ownerUsername,
|
|
1692
1948
|
} : null,
|
|
1949
|
+
release: readWorkspaceReleaseStatus(scoped.root, root, hydratedGraph),
|
|
1693
1950
|
workspaceSchedules: listWorkspaceScheduleStatusesForFlow(scopedUserCtx, scoped.flowSource || "user", scoped.flowId || ""),
|
|
1694
1951
|
});
|
|
1695
1952
|
} catch (e) {
|
|
@@ -1704,6 +1961,126 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1704
1961
|
return;
|
|
1705
1962
|
}
|
|
1706
1963
|
|
|
1964
|
+
if (req.method === "GET" && url.pathname === "/api/workspace/releases") {
|
|
1965
|
+
try {
|
|
1966
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
1967
|
+
flowId: url.searchParams.get("flowId") || "",
|
|
1968
|
+
flowSource: url.searchParams.get("flowSource") || "user",
|
|
1969
|
+
workspaceId: url.searchParams.get("workspaceId") || "",
|
|
1970
|
+
adminOwnerId: url.searchParams.get("adminOwnerId") || "",
|
|
1971
|
+
archived: url.searchParams.get("archived") === "1",
|
|
1972
|
+
}, userCtx);
|
|
1973
|
+
if (scoped.error) {
|
|
1974
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
json(res, 200, { ok: true, release: readWorkspaceReleaseStatus(scoped.root, root) });
|
|
1978
|
+
} catch (e) {
|
|
1979
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1980
|
+
}
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/releases/publish") {
|
|
1985
|
+
let payload;
|
|
1986
|
+
try {
|
|
1987
|
+
payload = JSON.parse(await readBody(req));
|
|
1988
|
+
} catch {
|
|
1989
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
try {
|
|
1993
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
1994
|
+
flowId: payload.flowId || "",
|
|
1995
|
+
flowSource: payload.flowSource || "user",
|
|
1996
|
+
workspaceId: payload.workspaceId || "",
|
|
1997
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
1998
|
+
}, userCtx);
|
|
1999
|
+
if (scoped.error) {
|
|
2000
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
if (
|
|
2004
|
+
scoped.archived
|
|
2005
|
+
|| scoped.draft === true
|
|
2006
|
+
|| isReadonlyBuiltinFlowSource(scoped.flowSource)
|
|
2007
|
+
|| scoped.collaborationAccess?.writable === false
|
|
2008
|
+
) {
|
|
2009
|
+
json(res, 403, { error: "Workspace release publish permission denied" });
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
const result = publishWorkspaceRelease(scoped.root, root, {
|
|
2013
|
+
expectedRevision: payload.expectedRevision || "",
|
|
2014
|
+
createdBy: userCtx.userId || authUser?.userId || "",
|
|
2015
|
+
notes: payload.notes || "",
|
|
2016
|
+
});
|
|
2017
|
+
if (result.error) {
|
|
2018
|
+
json(res, result.conflict ? 409 : 400, result);
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
const graph = readWorkspaceGraph(scoped.root, root).graph;
|
|
2022
|
+
const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx);
|
|
2023
|
+
broadcastWorkspaceCollaborationEvent(userCtx, scoped.flowSource, scoped.flowId, scoped.archived, {
|
|
2024
|
+
type: "release.published",
|
|
2025
|
+
releaseId: result.release.id,
|
|
2026
|
+
revision: result.release.designRevision,
|
|
2027
|
+
actorId: userCtx.userId || "",
|
|
2028
|
+
});
|
|
2029
|
+
json(res, 200, { ...result, workspaceSchedules });
|
|
2030
|
+
} catch (e) {
|
|
2031
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
2032
|
+
}
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/releases/rollback") {
|
|
2037
|
+
let payload;
|
|
2038
|
+
try {
|
|
2039
|
+
payload = JSON.parse(await readBody(req));
|
|
2040
|
+
} catch {
|
|
2041
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
try {
|
|
2045
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
2046
|
+
flowId: payload.flowId || "",
|
|
2047
|
+
flowSource: payload.flowSource || "user",
|
|
2048
|
+
workspaceId: payload.workspaceId || "",
|
|
2049
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
2050
|
+
}, userCtx);
|
|
2051
|
+
if (scoped.error) {
|
|
2052
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
if (
|
|
2056
|
+
scoped.archived
|
|
2057
|
+
|| scoped.draft === true
|
|
2058
|
+
|| isReadonlyBuiltinFlowSource(scoped.flowSource)
|
|
2059
|
+
|| scoped.collaborationAccess?.writable === false
|
|
2060
|
+
) {
|
|
2061
|
+
json(res, 403, { error: "Workspace release rollback permission denied" });
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
const result = rollbackWorkspaceRelease(scoped.root, payload.releaseId || "", root);
|
|
2065
|
+
if (result.error) {
|
|
2066
|
+
json(res, 404, result);
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
const graph = readWorkspaceGraph(scoped.root, root).graph;
|
|
2070
|
+
const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx);
|
|
2071
|
+
broadcastWorkspaceCollaborationEvent(userCtx, scoped.flowSource, scoped.flowId, scoped.archived, {
|
|
2072
|
+
type: "release.rollback",
|
|
2073
|
+
releaseId: result.release.id,
|
|
2074
|
+
revision: result.release.designRevision,
|
|
2075
|
+
actorId: userCtx.userId || "",
|
|
2076
|
+
});
|
|
2077
|
+
json(res, 200, { ...result, workspaceSchedules });
|
|
2078
|
+
} catch (e) {
|
|
2079
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
2080
|
+
}
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
1707
2084
|
if (req.method === "POST" && url.pathname === "/api/workspace/graph") {
|
|
1708
2085
|
let payload;
|
|
1709
2086
|
try {
|
|
@@ -1836,6 +2213,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1836
2213
|
designRevision: revision,
|
|
1837
2214
|
runtimeRevision,
|
|
1838
2215
|
merged,
|
|
2216
|
+
release: readWorkspaceReleaseStatus(scoped.root, root, committed.graph),
|
|
1839
2217
|
workspaceSchedules,
|
|
1840
2218
|
});
|
|
1841
2219
|
} catch (e) {
|
|
@@ -2158,6 +2536,8 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2158
2536
|
flowSource: scoped.flowSource || payload.flowSource || "user",
|
|
2159
2537
|
plannedNodeIds,
|
|
2160
2538
|
startedAt: Date.now(),
|
|
2539
|
+
workspaceRoot: root,
|
|
2540
|
+
marketplaceResources: marketplaceResourcesForRun(scoped.root, runtimeGraph, plannedNodeIds),
|
|
2161
2541
|
};
|
|
2162
2542
|
const runLog = createWorkspaceRunLogSession({
|
|
2163
2543
|
runId,
|
|
@@ -2255,6 +2635,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2255
2635
|
...runEntry,
|
|
2256
2636
|
endedAt,
|
|
2257
2637
|
durationMs: endedAt - runEntry.startedAt,
|
|
2638
|
+
marketplaceResources: marketplaceResourcesForRun(scoped.root, runtimeGraph, result.order || Array.from(touchedIds)),
|
|
2258
2639
|
}, "success");
|
|
2259
2640
|
finishWorkspaceRunLogSession(runLog.runId, "success", {
|
|
2260
2641
|
endedAt,
|
|
@@ -2350,6 +2731,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2350
2731
|
...runEntry,
|
|
2351
2732
|
endedAt,
|
|
2352
2733
|
durationMs: endedAt - runEntry.startedAt,
|
|
2734
|
+
marketplaceResources: marketplaceResourcesForRun(scoped.root, runtimeGraph, result.order || Array.from(touchedIds)),
|
|
2353
2735
|
}, "success");
|
|
2354
2736
|
finishWorkspaceRunLogSession(runLog.runId, "success", {
|
|
2355
2737
|
endedAt,
|
|
@@ -2574,6 +2956,11 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2574
2956
|
return;
|
|
2575
2957
|
}
|
|
2576
2958
|
removeWorkspaceDeferredRun(deferred.key);
|
|
2959
|
+
const resourceCleanup = cleanupWorkspaceRunResources(scoped.root, deferred.runId, {
|
|
2960
|
+
force: false,
|
|
2961
|
+
status: "stopped",
|
|
2962
|
+
emit: (event) => appendWorkspaceRunLogEvent(deferred.runId, event),
|
|
2963
|
+
});
|
|
2577
2964
|
const endedAt = Date.now();
|
|
2578
2965
|
appendWorkspaceRunLogEvent(deferred.runId, {
|
|
2579
2966
|
type: "stop-completed",
|
|
@@ -2602,6 +2989,10 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2602
2989
|
ok: true,
|
|
2603
2990
|
stopped: true,
|
|
2604
2991
|
monitoringOnly: true,
|
|
2992
|
+
resources: {
|
|
2993
|
+
cleaned: resourceCleanup.cleaned,
|
|
2994
|
+
preserved: resourceCleanup.preserved,
|
|
2995
|
+
},
|
|
2605
2996
|
});
|
|
2606
2997
|
return;
|
|
2607
2998
|
}
|
|
@@ -90,6 +90,8 @@ function indexRecord(meta = {}, patch = {}) {
|
|
|
90
90
|
flowSource: String(meta.flowSource || "user"),
|
|
91
91
|
scheduleNodeId: String(meta.scheduleNodeId || ""),
|
|
92
92
|
runNodeId: String(meta.runNodeId || ""),
|
|
93
|
+
releaseId: String(meta.releaseId || ""),
|
|
94
|
+
designRevision: String(meta.designRevision || ""),
|
|
93
95
|
scheduled: meta.scheduled === true,
|
|
94
96
|
trigger: String(meta.trigger || (meta.scheduled === true ? "scheduled" : "manual")),
|
|
95
97
|
label: String(meta.label || ""),
|