@fieldwangai/agentflow 0.1.159 → 0.1.162
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/auth.mjs +231 -14
- package/bin/lib/catalog-flows.mjs +12 -1
- package/bin/lib/cli-auth-page.mjs +103 -0
- package/bin/lib/control-while.mjs +336 -0
- package/bin/lib/flow-dsl/codegen.mjs +74 -11
- package/bin/lib/flow-dsl/ir.mjs +33 -2
- package/bin/lib/flow-dsl/lint.mjs +128 -3
- package/bin/lib/flow-dsl/parser.mjs +168 -4
- package/bin/lib/marketplace.mjs +10 -2
- package/bin/lib/node-package-manifest.mjs +7 -2
- package/bin/lib/node-ui-kit.mjs +156 -0
- package/bin/lib/paths.mjs +2 -0
- package/bin/lib/skill-runtime.mjs +6 -0
- package/bin/lib/table.mjs +2 -3
- package/bin/lib/ui-server.mjs +220 -17
- package/bin/lib/workspace-draft.mjs +74 -0
- package/bin/lib/workspace-flow-store.mjs +11 -2
- package/bin/lib/workspace-graph-merge.mjs +3 -0
- package/bin/lib/workspace-routes.mjs +307 -5
- package/bin/lib/workspace-server.mjs +598 -11
- package/bin/pipeline/validate-flow.mjs +3 -3
- package/builtin/nodes/control_parse_json.md +25 -0
- package/builtin/nodes/control_subflow_call.md +27 -0
- package/builtin/nodes/control_while.md +124 -0
- package/builtin/nodes/provide_json.md +14 -0
- package/builtin/nodes/workspace_subflow_input.md +14 -0
- package/builtin/pipelines/new/workspace.flow.js +1 -1
- package/builtin/pipelines/subflow-preview/workspace.flow.js +29 -0
- package/builtin/pipelines/subflow-preview/workspace.layout.json +38 -0
- package/builtin/pipelines/subflow-preview/workspace.nodes.json +19 -0
- package/builtin/pipelines/subflow-preview/workspace.state.json +93 -0
- package/builtin/pipelines/while-subflow-preview/workspace.flow.js +55 -0
- package/builtin/pipelines/while-subflow-preview/workspace.layout.json +66 -0
- package/builtin/pipelines/while-subflow-preview/workspace.nodes.json +49 -0
- package/builtin/pipelines/while-subflow-preview/workspace.state.json +194 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-CY19DsYq.js → WorkflowAssistantThread-CTrXOZ00.js} +1 -1
- package/builtin/web-ui/dist/assets/index-5uJFccdX.css +1 -0
- package/builtin/web-ui/dist/assets/index-BeUfNQRL.js +873 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +6 -1
- package/reference/flow-control-capabilities.md +41 -16
- package/shared/slot-types.js +58 -0
- package/skills/agentflow-author-flow/SKILL.md +81 -8
- package/skills/agentflow-author-flow/agents/openai.yaml +3 -3
- package/skills/agentflow-cli/SKILL.md +130 -25
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +14765 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +34 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_agent_toBool.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_anyOne.md +19 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_cancelled.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_cd_workspace.md +50 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_delay.md +31 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_end.md +13 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_if.md +21 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_interval_loop.md +55 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_load_mcp.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_load_skills.md +35 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_parse_json.md +25 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_start.md +13 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_subflow_call.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_toBool.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_user_workspace.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_wait_until.md +34 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +124 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_ascii.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_chart.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_code.md +39 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_html.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_image.md +36 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_markdown.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_mermaid.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_react_app.md +53 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_table.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_bool.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_file.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_json.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_password.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_str.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_display_share_link.md +47 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_get_env.md +20 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_checkout.md +70 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +73 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_unload.md +52 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_gitlab_create_mr.md +114 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_jenkins_build.md +67 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_load_key.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_nodejs.md +52 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_print.md +21 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_save_key.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_set_run_env.md +38 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_ask.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_check.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_app_markdown.md +57 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_group_markdown.md +45 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_one_click_task.md +44 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_run.md +16 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_subflow_input.md +14 -0
- package/skills/agentflow-cli/runtime/package.json +6 -0
- package/skills/agentflow-cli/scripts/agentflow-auth-store.mjs +113 -0
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +393 -28
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +11 -4
- package/skills/agentflow-flow-add-instances/SKILL.md +2 -2
- package/skills/agentflow-flow-dsl/SKILL.md +83 -10
- package/skills/agentflow-flow-dsl/agents/openai.yaml +2 -2
- package/skills/agentflow-flow-dsl/references/node-calls.md +3 -0
- package/skills/agentflow-flow-dsl/references/subflow-authoring.md +230 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +3 -3
- package/skills/agentflow-flow-recipes/SKILL.md +1 -1
- package/skills/agentflow-flow-recipes/references/recipes.md +1 -2
- package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
- package/skills/agentflow-node-dsl/SKILL.md +44 -13
- package/skills/agentflow-node-reference/references/builtin-nodes.md +25 -0
- package/skills/agentflow-workspace-markdown/SKILL.md +1 -1
- package/builtin/web-ui/dist/assets/index-B9ppXv7e.css +0 -1
- package/builtin/web-ui/dist/assets/index-CdEQWRrp.js +0 -872
- package/skills/agentflow-workspace-graph/SKILL.md +0 -85
|
@@ -27,16 +27,19 @@ import { NODE_PACKAGE_ENTRY, nodePackageExportsRun, readNodePackageManifest } fr
|
|
|
27
27
|
import { inspectNodePackageDirectory } from "./node-package-archive.mjs";
|
|
28
28
|
import { json, readBody } from "./http-util.mjs";
|
|
29
29
|
import { log } from "./log.mjs";
|
|
30
|
-
import { PACKAGE_ROOT, getAgentflowUserDataRoot } from "./paths.mjs";
|
|
30
|
+
import { PACKAGE_ROOT, PIPELINES_DIR, getAgentflowUserDataRoot, getUserPipelinesRoot } from "./paths.mjs";
|
|
31
|
+
import { validateUserPipelineId } from "./flow-write.mjs";
|
|
31
32
|
import { runLedgerId } from "./run-ledger.mjs";
|
|
32
33
|
import { getTeamById, getTeamForUser } from "./teams.mjs";
|
|
33
34
|
import { readMergedEnvObject, runtimeEnvForUser } from "./user-env.mjs";
|
|
34
|
-
import { acceptWorkspaceCollaborationInvite, addWorkspaceCollaborationMember, ensureWorkspaceCollaboration, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, removeWorkspaceCollaborationMember, removeWorkspaceCollaborationTeamShare, setWorkspaceCollaborationTeamShare, workspaceCollaborationAccess } from "./workspace-collaboration.mjs";
|
|
35
|
+
import { acceptWorkspaceCollaborationInvite, addWorkspaceCollaborationMember, deleteWorkspaceCollaborationForFlow, ensureWorkspaceCollaboration, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, removeWorkspaceCollaborationMember, removeWorkspaceCollaborationTeamShare, setWorkspaceCollaborationTeamShare, workspaceCollaborationAccess } from "./workspace-collaboration.mjs";
|
|
35
36
|
import { WorkspaceFlowParseError } from "./workspace-flow-store.mjs";
|
|
36
37
|
import { mergeWorkspaceGraphs, workspaceDesignRevision, workspaceRuntimeRevision } from "./workspace-graph-merge.mjs";
|
|
37
38
|
import { DEFAULT_WORKSPACE_PREVIEW_TTL_MS, createWorkspacePreviewId, normalizeWorkspacePreviewTtlMs, readWorkspacePreviewMetadata, workspaceSharedPreviewFlowDir, writeWorkspacePreviewMetadata } from "./workspace-preview.mjs";
|
|
39
|
+
import { DEFAULT_WORKSPACE_DRAFT_TTL_MS, createWorkspaceDraftId, normalizeWorkspaceDraftTtlMs, readWorkspaceDraftMetadata, workspaceDraftFlowDir, writeWorkspaceDraftMetadata } from "./workspace-draft.mjs";
|
|
38
40
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession, listWorkspaceRunLogs, readWorkspaceRunLogEvents } from "./workspace-run-logs.mjs";
|
|
39
|
-
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, 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, workspaceScopedUserContext, workspaceSearchGuardrailsBlock, workspaceUnwrapOutputEnvelopeForDisplay, workspacesPath, writeWorkspaceConversations, writeWorkspaceGraph } from "./workspace-server.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";
|
|
40
43
|
import { getWorkspaceTree } from "./workspace-tree.mjs";
|
|
41
44
|
import busboy from "busboy";
|
|
42
45
|
import crypto from "crypto";
|
|
@@ -192,6 +195,29 @@ function commitWorkspaceGraph(workspaceRoot, scoped, graph, userCtx) {
|
|
|
192
195
|
};
|
|
193
196
|
}
|
|
194
197
|
|
|
198
|
+
export function workspaceGraphWithScheduleMode(graph, mode = "disabled") {
|
|
199
|
+
const normalizedMode = String(mode || "disabled").trim().toLowerCase();
|
|
200
|
+
if (!["enabled", "disabled", "preserve"].includes(normalizedMode)) {
|
|
201
|
+
throw new Error("scheduleMode must be enabled, disabled, or preserve");
|
|
202
|
+
}
|
|
203
|
+
if (normalizedMode === "preserve") return graph;
|
|
204
|
+
const enabled = normalizedMode === "enabled";
|
|
205
|
+
const instances = { ...(graph?.instances || {}) };
|
|
206
|
+
let scheduleCount = 0;
|
|
207
|
+
for (const [nodeId, instance] of Object.entries(instances)) {
|
|
208
|
+
if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
|
|
209
|
+
scheduleCount += 1;
|
|
210
|
+
const config = normalizeWorkspaceScheduledRunConfig(instance.body || "");
|
|
211
|
+
const nextConfig = { ...config, enabled };
|
|
212
|
+
if (enabled) workspaceScheduleNextRunAt(nextConfig, new Date());
|
|
213
|
+
instances[nodeId] = { ...instance, body: JSON.stringify(nextConfig) };
|
|
214
|
+
}
|
|
215
|
+
if (enabled && scheduleCount === 0) {
|
|
216
|
+
throw new Error("Cannot enable scheduling: the draft has no Scheduled Run node");
|
|
217
|
+
}
|
|
218
|
+
return { ...graph, instances };
|
|
219
|
+
}
|
|
220
|
+
|
|
195
221
|
function missingWorkspaceGraphNodePackages(workspaceRoot, scoped, graph, userCtx) {
|
|
196
222
|
const missing = new Set();
|
|
197
223
|
for (const instance of Object.values(graph?.instances || {})) {
|
|
@@ -538,6 +564,13 @@ function buildNodeStudioPrompt({ requirement, currentSource, parseError, history
|
|
|
538
564
|
" 真实运行时位于会被清理的临时目录里。",
|
|
539
565
|
"3. 槽位类型只能是 `text` `file` `bool` `node` `image` `json`。声明顺序 = 画布上的引脚顺序。",
|
|
540
566
|
"",
|
|
567
|
+
"## 可选 Node UI Kit",
|
|
568
|
+
"",
|
|
569
|
+
"需要让画布直接解释节点时,在 export default 里声明 `ui.card`。只能组合安全组件:",
|
|
570
|
+
"`binding`、`code`、`decision`、`metrics`、`summary`、`history`、`subflow`;不能写 React、HTML 或事件处理器。",
|
|
571
|
+
"组件里的 input/output 必须引用已声明槽位。template 可用 details / state-machine,tone 可用",
|
|
572
|
+
"neutral / blue / purple / green / amber / red。没有可解释内容时可以不声明 UI。",
|
|
573
|
+
"",
|
|
541
574
|
"失败用抛异常或非零退出表示,不要把 stdout 包成 JSON。",
|
|
542
575
|
currentSource ? `\n## 当前 ${NODE_PACKAGE_ENTRY}\n\n\`\`\`js\n${currentSource}\n\`\`\`` : "",
|
|
543
576
|
parseError ? `\n## 上一版解析失败,必须修掉\n\n${parseError}` : "",
|
|
@@ -568,7 +601,7 @@ function emptyNodeStudioDraft(userCtx = {}, draftId = "") {
|
|
|
568
601
|
inputs: [],
|
|
569
602
|
outputs: [],
|
|
570
603
|
configSchema: { fields: [] },
|
|
571
|
-
ui: { card: { icon: "extension",
|
|
604
|
+
ui: { card: { template: "details", icon: "extension", tone: "neutral", sections: [] } },
|
|
572
605
|
},
|
|
573
606
|
config: {},
|
|
574
607
|
test: { inputs: {}, log: [], status: "not run" },
|
|
@@ -1302,6 +1335,185 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1302
1335
|
return;
|
|
1303
1336
|
}
|
|
1304
1337
|
|
|
1338
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/draft") {
|
|
1339
|
+
if (!authUser?.userId) {
|
|
1340
|
+
json(res, 401, { error: "Authentication required" });
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
let payload;
|
|
1344
|
+
try {
|
|
1345
|
+
payload = JSON.parse(await readBody(req, 4 * 1024 * 1024));
|
|
1346
|
+
} catch {
|
|
1347
|
+
json(res, 400, { error: "Invalid JSON" });
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
const graph = payload?.graph;
|
|
1351
|
+
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
|
1352
|
+
json(res, 400, { error: "graph must be an object" });
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
const rawRequestedId = String(payload.draftId || "").trim();
|
|
1356
|
+
const flowId = rawRequestedId || createWorkspaceDraftId();
|
|
1357
|
+
const flowDir = workspaceDraftFlowDir(flowId, authUser.userId);
|
|
1358
|
+
if (!flowDir) {
|
|
1359
|
+
json(res, 400, { error: "Invalid draftId" });
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const existing = readWorkspaceDraftMetadata(flowDir);
|
|
1363
|
+
if (existing && existing.ownerId !== authUser.userId) {
|
|
1364
|
+
json(res, 403, { error: "Draft ownership denied" });
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
if (rawRequestedId && !existing && fs.existsSync(flowDir)) {
|
|
1368
|
+
json(res, 409, { error: "Draft project already exists but is not a draft" });
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
if (existing) {
|
|
1372
|
+
const currentGraph = readWorkspaceGraph(flowDir, root).graph;
|
|
1373
|
+
const currentRevision = workspaceDesignRevision(currentGraph);
|
|
1374
|
+
const baseRevision = String(payload.baseRevision || "").trim();
|
|
1375
|
+
if (!baseRevision) {
|
|
1376
|
+
json(res, 428, {
|
|
1377
|
+
error: "Draft update requires baseRevision",
|
|
1378
|
+
conflict: "missing-base-revision",
|
|
1379
|
+
currentRevision,
|
|
1380
|
+
});
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if (baseRevision !== currentRevision) {
|
|
1384
|
+
json(res, 409, {
|
|
1385
|
+
error: "Draft 已被更新,请先拉取最新版本后再修改",
|
|
1386
|
+
conflict: "revision-mismatch",
|
|
1387
|
+
expectedRevision: baseRevision,
|
|
1388
|
+
currentRevision,
|
|
1389
|
+
});
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
const now = Date.now();
|
|
1394
|
+
const ttlInput = payload.ttlMs != null
|
|
1395
|
+
? Number(payload.ttlMs)
|
|
1396
|
+
: payload.ttlSeconds != null
|
|
1397
|
+
? Number(payload.ttlSeconds) * 1000
|
|
1398
|
+
: DEFAULT_WORKSPACE_DRAFT_TTL_MS;
|
|
1399
|
+
const ttlMs = normalizeWorkspaceDraftTtlMs(ttlInput);
|
|
1400
|
+
const metadata = {
|
|
1401
|
+
version: 1,
|
|
1402
|
+
flowId,
|
|
1403
|
+
ownerId: authUser.userId,
|
|
1404
|
+
title: String(payload.title || "Workspace Draft").trim().slice(0, 200),
|
|
1405
|
+
createdAt: existing?.createdAt || new Date(now).toISOString(),
|
|
1406
|
+
updatedAt: new Date(now).toISOString(),
|
|
1407
|
+
expiresAt: new Date(now + ttlMs).toISOString(),
|
|
1408
|
+
};
|
|
1409
|
+
const created = !fs.existsSync(flowDir);
|
|
1410
|
+
try {
|
|
1411
|
+
fs.mkdirSync(flowDir, { recursive: true });
|
|
1412
|
+
if (payload.resetRuntime !== false) {
|
|
1413
|
+
const statePath = path.join(flowDir, WORKSPACE_STATE_FILENAME);
|
|
1414
|
+
if (fs.existsSync(statePath)) fs.rmSync(statePath);
|
|
1415
|
+
}
|
|
1416
|
+
const written = writeWorkspaceGraph(flowDir, graph, root);
|
|
1417
|
+
writeWorkspaceDraftMetadata(flowDir, metadata);
|
|
1418
|
+
const persisted = readWorkspaceGraph(flowDir, root).graph;
|
|
1419
|
+
const baseUrl = `${url.protocol}//${url.host}`;
|
|
1420
|
+
json(res, 200, {
|
|
1421
|
+
ok: true,
|
|
1422
|
+
flowId,
|
|
1423
|
+
draftId: flowId,
|
|
1424
|
+
flowSource: "user",
|
|
1425
|
+
draft: true,
|
|
1426
|
+
writable: true,
|
|
1427
|
+
runnable: true,
|
|
1428
|
+
schedulesSuppressed: true,
|
|
1429
|
+
revision: workspaceDesignRevision(persisted),
|
|
1430
|
+
format: written.format,
|
|
1431
|
+
expiresAt: metadata.expiresAt,
|
|
1432
|
+
url: `${baseUrl}/workspace?flowId=${encodeURIComponent(flowId)}&flowSource=user`,
|
|
1433
|
+
});
|
|
1434
|
+
} catch (e) {
|
|
1435
|
+
if (created) {
|
|
1436
|
+
try { fs.rmSync(flowDir, { recursive: true, force: true }); } catch (_) {}
|
|
1437
|
+
}
|
|
1438
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1439
|
+
}
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/draft/publish") {
|
|
1444
|
+
if (!authUser?.userId) {
|
|
1445
|
+
json(res, 401, { error: "Authentication required" });
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
let payload;
|
|
1449
|
+
try {
|
|
1450
|
+
payload = JSON.parse(await readBody(req));
|
|
1451
|
+
} catch {
|
|
1452
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
const draftId = String(payload.draftId || "").trim();
|
|
1456
|
+
const draftDir = workspaceDraftFlowDir(draftId, authUser.userId);
|
|
1457
|
+
const draftMetadata = draftDir ? readWorkspaceDraftMetadata(draftDir) : null;
|
|
1458
|
+
if (!draftMetadata || draftMetadata.ownerId !== authUser.userId) {
|
|
1459
|
+
json(res, 404, { error: "Workspace draft not found" });
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
const idCheck = validateUserPipelineId(String(payload.flowId || ""));
|
|
1463
|
+
if (!idCheck.ok) {
|
|
1464
|
+
json(res, 400, { error: idCheck.error });
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
const flowId = idCheck.flowId;
|
|
1468
|
+
const requestedTarget = String(payload.targetSpace || "personal").trim().toLowerCase();
|
|
1469
|
+
const flowSource = requestedTarget === "workspace" || requestedTarget === "team" ? "workspace" : "user";
|
|
1470
|
+
const targetDir = flowSource === "workspace"
|
|
1471
|
+
? path.join(path.resolve(root), PIPELINES_DIR, flowId)
|
|
1472
|
+
: path.join(getUserPipelinesRoot(authUser.userId), flowId);
|
|
1473
|
+
if (fs.existsSync(targetDir)) {
|
|
1474
|
+
json(res, 409, { error: `已存在同名流水线 ${flowId}` });
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
try {
|
|
1478
|
+
const draftGraph = readWorkspaceGraph(draftDir, root).graph;
|
|
1479
|
+
const graph = workspaceGraphWithScheduleMode(draftGraph, payload.scheduleMode || "disabled");
|
|
1480
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
1481
|
+
writeWorkspaceGraph(targetDir, graph, root);
|
|
1482
|
+
let collaborationCreated = false;
|
|
1483
|
+
if (flowSource === "workspace") {
|
|
1484
|
+
ensureWorkspaceCollaboration({ flowId, userId: authUser.userId });
|
|
1485
|
+
collaborationCreated = true;
|
|
1486
|
+
}
|
|
1487
|
+
try {
|
|
1488
|
+
const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource }, userCtx);
|
|
1489
|
+
if (scoped.error) throw new Error(scoped.error);
|
|
1490
|
+
const persisted = readWorkspaceGraph(scoped.root, root).graph;
|
|
1491
|
+
const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, persisted, authUser, userCtx);
|
|
1492
|
+
const baseUrl = `${url.protocol}//${url.host}`;
|
|
1493
|
+
json(res, 200, {
|
|
1494
|
+
ok: true,
|
|
1495
|
+
success: true,
|
|
1496
|
+
action: "created",
|
|
1497
|
+
draftId,
|
|
1498
|
+
flowId,
|
|
1499
|
+
flowSource,
|
|
1500
|
+
targetSpace: requestedTarget === "team" ? "team" : flowSource === "user" ? "personal" : "workspace",
|
|
1501
|
+
scheduleMode: String(payload.scheduleMode || "disabled").toLowerCase(),
|
|
1502
|
+
workspaceSchedules,
|
|
1503
|
+
revision: workspaceDesignRevision(persisted),
|
|
1504
|
+
url: `${baseUrl}/workspace?flowId=${encodeURIComponent(flowId)}&flowSource=${encodeURIComponent(flowSource)}`,
|
|
1505
|
+
});
|
|
1506
|
+
} catch (e) {
|
|
1507
|
+
if (collaborationCreated) deleteWorkspaceCollaborationForFlow(flowId, false);
|
|
1508
|
+
throw e;
|
|
1509
|
+
}
|
|
1510
|
+
} catch (e) {
|
|
1511
|
+
try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (_) {}
|
|
1512
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1513
|
+
}
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1305
1517
|
if (req.method === "POST" && url.pathname === "/api/workspace/preview") {
|
|
1306
1518
|
if (!authUser?.userId) {
|
|
1307
1519
|
json(res, 401, { error: "Authentication required" });
|
|
@@ -1468,6 +1680,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1468
1680
|
flowId: scoped.flowId,
|
|
1469
1681
|
flowSource: scoped.flowSource,
|
|
1470
1682
|
archived: scoped.archived,
|
|
1683
|
+
draft: scoped.draft === true,
|
|
1471
1684
|
writable: !(scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource))
|
|
1472
1685
|
&& scoped.adminReadonly !== true
|
|
1473
1686
|
&& collaborationAccess.writable !== false,
|
|
@@ -1589,7 +1802,20 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1589
1802
|
const graph = mergeWorkspacePersistentNodeRefs(nextGraph, currentGraph);
|
|
1590
1803
|
const committed = commitWorkspaceGraph(root, scoped, graph, userCtx);
|
|
1591
1804
|
const { path: graphPath, revision, runtimeRevision } = committed;
|
|
1592
|
-
|
|
1805
|
+
let workspaceSchedules;
|
|
1806
|
+
try {
|
|
1807
|
+
workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, committed.graph, authUser, userCtx);
|
|
1808
|
+
} catch (scheduleError) {
|
|
1809
|
+
try {
|
|
1810
|
+
const rolledBack = commitWorkspaceGraph(root, scoped, currentStoredGraph, userCtx);
|
|
1811
|
+
syncWorkspaceSchedulesForGraph(root, scoped, rolledBack.graph, authUser, userCtx);
|
|
1812
|
+
} catch (rollbackError) {
|
|
1813
|
+
throw new Error(
|
|
1814
|
+
`Workspace schedule sync failed and graph rollback also failed: ${(scheduleError && scheduleError.message) || String(scheduleError)}; rollback: ${(rollbackError && rollbackError.message) || String(rollbackError)}`,
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
throw new Error(`Workspace graph save rolled back: ${(scheduleError && scheduleError.message) || String(scheduleError)}`);
|
|
1818
|
+
}
|
|
1593
1819
|
broadcastWorkspaceCollaborationEvent(
|
|
1594
1820
|
userCtx,
|
|
1595
1821
|
scoped.flowSource,
|
|
@@ -1648,6 +1874,80 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1648
1874
|
return;
|
|
1649
1875
|
}
|
|
1650
1876
|
|
|
1877
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/schedule/config") {
|
|
1878
|
+
let payload;
|
|
1879
|
+
try {
|
|
1880
|
+
payload = JSON.parse(await readBody(req));
|
|
1881
|
+
} catch {
|
|
1882
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
try {
|
|
1886
|
+
const flowId = String(payload.flowId || "").trim();
|
|
1887
|
+
const flowSource = String(payload.flowSource || "user").trim() || "user";
|
|
1888
|
+
const scheduleNodeId = String(payload.scheduleNodeId || "").trim();
|
|
1889
|
+
if (!flowId || !scheduleNodeId) {
|
|
1890
|
+
json(res, 400, { error: "flowId and scheduleNodeId are required" });
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource }, userCtx);
|
|
1894
|
+
if (scoped.error) {
|
|
1895
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
if (
|
|
1899
|
+
scoped.archived
|
|
1900
|
+
|| isReadonlyBuiltinFlowSource(scoped.flowSource)
|
|
1901
|
+
|| scoped.collaborationAccess?.writable === false
|
|
1902
|
+
) {
|
|
1903
|
+
json(res, 403, { error: "Workspace schedule edit permission denied" });
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
const graph = readWorkspaceGraph(scoped.root, root).graph;
|
|
1907
|
+
const instance = graph.instances?.[scheduleNodeId];
|
|
1908
|
+
if (!instance || String(instance.definitionId || "") !== "workspace_scheduled_run") {
|
|
1909
|
+
json(res, 404, { error: "Workspace schedule node not found" });
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
const current = normalizeWorkspaceScheduledRunConfig(instance.body || "");
|
|
1913
|
+
const config = {
|
|
1914
|
+
...current,
|
|
1915
|
+
...(typeof payload.enabled === "boolean" ? { enabled: payload.enabled } : {}),
|
|
1916
|
+
...(payload.cron != null ? { cron: String(payload.cron).trim() } : {}),
|
|
1917
|
+
...(payload.timezone != null ? { timezone: String(payload.timezone).trim() } : {}),
|
|
1918
|
+
...(payload.overlapPolicy != null ? { overlapPolicy: String(payload.overlapPolicy).trim() } : {}),
|
|
1919
|
+
};
|
|
1920
|
+
if (config.overlapPolicy !== "skip") {
|
|
1921
|
+
json(res, 400, { error: "Only overlapPolicy=skip is supported" });
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
if (config.enabled) workspaceScheduleNextRunAt(config, new Date());
|
|
1925
|
+
graph.instances = { ...(graph.instances || {}) };
|
|
1926
|
+
graph.instances[scheduleNodeId] = { ...instance, body: JSON.stringify(config) };
|
|
1927
|
+
const committed = commitWorkspaceGraph(root, scoped, graph, userCtx);
|
|
1928
|
+
const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, committed.graph, authUser, userCtx);
|
|
1929
|
+
const schedule = workspaceSchedules.find((item) => item.scheduleNodeId === scheduleNodeId) || {
|
|
1930
|
+
flowId,
|
|
1931
|
+
flowSource,
|
|
1932
|
+
scheduleNodeId,
|
|
1933
|
+
...config,
|
|
1934
|
+
suppressed: scoped.draft === true,
|
|
1935
|
+
};
|
|
1936
|
+
json(res, 200, {
|
|
1937
|
+
ok: true,
|
|
1938
|
+
flowId,
|
|
1939
|
+
flowSource,
|
|
1940
|
+
draft: scoped.draft === true,
|
|
1941
|
+
revision: committed.revision,
|
|
1942
|
+
schedule,
|
|
1943
|
+
workspaceSchedules,
|
|
1944
|
+
});
|
|
1945
|
+
} catch (e) {
|
|
1946
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
1947
|
+
}
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1651
1951
|
if (req.method === "POST" && url.pathname === "/api/workspace/run/plan") {
|
|
1652
1952
|
let payload;
|
|
1653
1953
|
try {
|
|
@@ -2917,6 +3217,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2917
3217
|
const flowSource = url.searchParams.get("flowSource") || "user";
|
|
2918
3218
|
const lang = url.searchParams.get("lang") || "en";
|
|
2919
3219
|
const marketplaceScope = url.searchParams.get("scope") === "owned" ? "owned" : "all";
|
|
3220
|
+
const includeHidden = url.searchParams.get("includeHidden") === "1";
|
|
2920
3221
|
if (flowId && !isValidFlowSourceRead(flowSource)) {
|
|
2921
3222
|
json(res, 400, { error: "Invalid flowSource" });
|
|
2922
3223
|
return;
|
|
@@ -2934,6 +3235,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2934
3235
|
archived: nodesArchived,
|
|
2935
3236
|
...requestedContext.userCtx,
|
|
2936
3237
|
marketplaceScope,
|
|
3238
|
+
includeHidden,
|
|
2937
3239
|
}));
|
|
2938
3240
|
} catch (e) {
|
|
2939
3241
|
json(res, 500, { error: (e && e.message) || String(e) });
|