@fieldwangai/agentflow 0.1.160 → 0.1.163
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 +2 -0
- package/bin/lib/cli-auth-page.mjs +103 -0
- 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/skill-runtime.mjs +6 -0
- package/bin/lib/spaces.mjs +200 -0
- package/bin/lib/table.mjs +2 -3
- package/bin/lib/ui-server.mjs +471 -84
- package/bin/lib/workspace-draft.mjs +74 -0
- package/bin/lib/workspace-routes.mjs +484 -4
- package/bin/lib/workspace-server.mjs +373 -30
- 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 +12 -5
- package/builtin/nodes/tool_git_worktree_load.md +4 -3
- package/builtin/pipelines/new/workspace.flow.js +1 -1
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-Bfo9Ythw.js → WorkflowAssistantThread-DmhIgoD7.js} +1 -1
- package/builtin/web-ui/dist/assets/index-CI9J6Unt.js +873 -0
- package/builtin/web-ui/dist/assets/index-DY5vE7v1.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +5 -1
- package/shared/slot-types.js +1 -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 +179 -27
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +14879 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +38 -0
- 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_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 +131 -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 +74 -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 +457 -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 +73 -8
- package/skills/agentflow-flow-dsl/agents/openai.yaml +2 -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-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 +5 -13
- package/skills/agentflow-node-reference/references/builtin-nodes.md +37 -5
- package/skills/agentflow-workspace-markdown/SKILL.md +1 -1
- package/builtin/web-ui/dist/assets/index-XbeI5foV.js +0 -872
- package/builtin/web-ui/dist/assets/index-e1omCEau.css +0 -1
- package/skills/agentflow-workspace-graph/SKILL.md +0 -85
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -52,6 +52,7 @@ import { packageResolverFor, scanAvailableNodePackages } from "./flow-dsl/packag
|
|
|
52
52
|
import { runStartupStorageMigrations } from "./startup-storage-migrations.mjs";
|
|
53
53
|
import { getPipelineFiles } from "./workspace-tree.mjs";
|
|
54
54
|
import { listExpiredWorkspacePreviews } from "./workspace-preview.mjs";
|
|
55
|
+
import { listExpiredWorkspaceDrafts } from "./workspace-draft.mjs";
|
|
55
56
|
import { LEGACY_FLOW_EXECUTION_DISABLED, LEGACY_FLOW_EXECUTION_MESSAGE } from "./legacy-flow-execution.mjs";
|
|
56
57
|
import {
|
|
57
58
|
listRecentComposerSessions,
|
|
@@ -71,7 +72,11 @@ import {
|
|
|
71
72
|
authSetupRequired,
|
|
72
73
|
buildClearSessionCookie,
|
|
73
74
|
buildSessionCookie,
|
|
75
|
+
createCliAuthorization,
|
|
76
|
+
decideCliAuthorization,
|
|
77
|
+
exchangeCliAuthorization,
|
|
74
78
|
getAuthUserFromRequest,
|
|
79
|
+
getCliAuthorization,
|
|
75
80
|
getSessionTokenFromRequest,
|
|
76
81
|
isAuthUserAllowed,
|
|
77
82
|
listAuthUsers,
|
|
@@ -80,8 +85,13 @@ import {
|
|
|
80
85
|
readAuthUsers,
|
|
81
86
|
readUserAllowlist,
|
|
82
87
|
resetAuthUserPassword,
|
|
88
|
+
revokeSessionToken,
|
|
83
89
|
writeUserAllowlist,
|
|
84
90
|
} from "./auth.mjs";
|
|
91
|
+
import {
|
|
92
|
+
renderCliAuthorizationPage,
|
|
93
|
+
renderCliAuthorizationResult,
|
|
94
|
+
} from "./cli-auth-page.mjs";
|
|
85
95
|
import { readGlobalEnvRows, readUserEnvRows, writeGlobalEnvRows, writeUserEnvRows } from "./user-env.mjs";
|
|
86
96
|
import {
|
|
87
97
|
readAdminBuiltinPipelineConfig,
|
|
@@ -120,7 +130,6 @@ import { json, readBody } from "./http-util.mjs";
|
|
|
120
130
|
|
|
121
131
|
// 从 ui-server 拆出去的 Workspace 子系统;路由仍在下面的 startUiServer 里
|
|
122
132
|
import {
|
|
123
|
-
USER_WORKSPACES_FILENAME,
|
|
124
133
|
WORKSPACE_DEFERRED_RUN_POLL_MS,
|
|
125
134
|
WORKSPACE_SCHEDULE_POLL_MS,
|
|
126
135
|
activeWorkspaceRunUsageRecords,
|
|
@@ -131,6 +140,7 @@ import {
|
|
|
131
140
|
displayShareOutputUrl,
|
|
132
141
|
isReadonlyBuiltinFlowSource,
|
|
133
142
|
isValidFlowSourceRead,
|
|
143
|
+
listConfiguredWorkspaces,
|
|
134
144
|
listWorkspaceScheduleStatuses,
|
|
135
145
|
normalizeDisplayShareExpiry,
|
|
136
146
|
normalizeDisplayShareNodeIds,
|
|
@@ -141,11 +151,11 @@ import {
|
|
|
141
151
|
readCursorMcpConfig,
|
|
142
152
|
readCursorMcpServers,
|
|
143
153
|
readDisplayShares,
|
|
154
|
+
readUserWorkspaces,
|
|
144
155
|
readUserMcpPrivate,
|
|
145
156
|
readWorkspaceGraph,
|
|
146
157
|
readWorkspaceRunUsageRecords,
|
|
147
158
|
readWorkspaceScheduleRegistry,
|
|
148
|
-
readWorkspacesFromPath,
|
|
149
159
|
resolveWorkspaceFilePath,
|
|
150
160
|
resolveWorkspaceScopeRoot,
|
|
151
161
|
runStatusBucket,
|
|
@@ -163,11 +173,22 @@ import {
|
|
|
163
173
|
workspaceDownloadContentDisposition,
|
|
164
174
|
workspaceFlowCollaborationGuard,
|
|
165
175
|
workspaceScheduleNextRunAt,
|
|
166
|
-
workspacesPath,
|
|
167
176
|
writeDisplayShares,
|
|
168
177
|
writeWorkspaceGraph,
|
|
169
178
|
} from "./workspace-server.mjs";
|
|
170
|
-
import { handleWorkspaceRoutes } from "./workspace-routes.mjs";
|
|
179
|
+
import { handleWorkspaceRoutes, workspaceGraphWithScheduleMode } from "./workspace-routes.mjs";
|
|
180
|
+
import {
|
|
181
|
+
createSpace,
|
|
182
|
+
deleteSpacePage,
|
|
183
|
+
getSpaceById,
|
|
184
|
+
getSpaceByRoute,
|
|
185
|
+
listSpacesForUser,
|
|
186
|
+
readSpaces,
|
|
187
|
+
normalizeSpacePagePath,
|
|
188
|
+
normalizeSpaceVisibility,
|
|
189
|
+
updateSpace,
|
|
190
|
+
upsertSpacePage,
|
|
191
|
+
} from "./spaces.mjs";
|
|
171
192
|
|
|
172
193
|
const MIME = {
|
|
173
194
|
".html": "text/html; charset=utf-8",
|
|
@@ -206,10 +227,8 @@ const BUILTIN_SKILL_COLLECTIONS = [
|
|
|
206
227
|
id: "pipeline",
|
|
207
228
|
name: "Pipeline",
|
|
208
229
|
defaultKeys: [
|
|
209
|
-
"agentflow-flow-
|
|
210
|
-
"agentflow-flow-edit-node-fields",
|
|
230
|
+
"agentflow-flow-dsl",
|
|
211
231
|
"agentflow-flow-recipes",
|
|
212
|
-
"agentflow-flow-sync-ui",
|
|
213
232
|
"agentflow-node-reference",
|
|
214
233
|
"agentflow-placeholder-reference",
|
|
215
234
|
"agentflow-runtime-reference",
|
|
@@ -219,7 +238,7 @@ const BUILTIN_SKILL_COLLECTIONS = [
|
|
|
219
238
|
id: "workspace",
|
|
220
239
|
name: "Workspace",
|
|
221
240
|
defaultKeys: [
|
|
222
|
-
"agentflow-
|
|
241
|
+
"agentflow-flow-dsl",
|
|
223
242
|
"agentflow-workspace-markdown",
|
|
224
243
|
"agentflow-workspace-mermaid",
|
|
225
244
|
"agentflow-workspace-ascii",
|
|
@@ -233,7 +252,18 @@ const BUILTIN_SKILL_COLLECTIONS = [
|
|
|
233
252
|
],
|
|
234
253
|
legacyDefaultKeys: [
|
|
235
254
|
[
|
|
236
|
-
"agentflow-workspace-
|
|
255
|
+
"agentflow-workspace-markdown",
|
|
256
|
+
"agentflow-workspace-mermaid",
|
|
257
|
+
"agentflow-workspace-ascii",
|
|
258
|
+
"agentflow-workspace-chart",
|
|
259
|
+
"agentflow-workspace-table",
|
|
260
|
+
"agentflow-workspace-html",
|
|
261
|
+
"agentflow-workspace-image",
|
|
262
|
+
"agentflow-node-reference",
|
|
263
|
+
"agentflow-placeholder-reference",
|
|
264
|
+
"agentflow-runtime-reference",
|
|
265
|
+
],
|
|
266
|
+
[
|
|
237
267
|
"agentflow-workspace-markdown",
|
|
238
268
|
"agentflow-workspace-mermaid",
|
|
239
269
|
"agentflow-workspace-ascii",
|
|
@@ -244,7 +274,6 @@ const BUILTIN_SKILL_COLLECTIONS = [
|
|
|
244
274
|
"agentflow-runtime-reference",
|
|
245
275
|
],
|
|
246
276
|
[
|
|
247
|
-
"agentflow-workspace-graph",
|
|
248
277
|
"agentflow-workspace-markdown",
|
|
249
278
|
"agentflow-workspace-mermaid",
|
|
250
279
|
"agentflow-workspace-ascii",
|
|
@@ -917,70 +946,6 @@ function skillhubInstallArgs(payload, { uninstall = false } = {}) {
|
|
|
917
946
|
}
|
|
918
947
|
|
|
919
948
|
const DISPLAY_SHARE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
920
|
-
function legacyUserWorkspacesPath(userCtx = {}) {
|
|
921
|
-
return path.join(getAgentflowUserDataRoot(userCtx.userId || ""), USER_WORKSPACES_FILENAME);
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
function readLegacyAdminWorkspaces(userCtx = {}) {
|
|
925
|
-
const users = readAuthUsers();
|
|
926
|
-
const candidates = [];
|
|
927
|
-
for (const [userId, user] of Object.entries(users || {})) {
|
|
928
|
-
if (user?.isAdmin) candidates.push(String(userId || ""));
|
|
929
|
-
}
|
|
930
|
-
if (userCtx?.isAdmin && userCtx.userId) candidates.unshift(String(userCtx.userId));
|
|
931
|
-
const seenPaths = new Set();
|
|
932
|
-
const seenEntries = new Set();
|
|
933
|
-
const out = [];
|
|
934
|
-
for (const userId of candidates) {
|
|
935
|
-
const p = legacyUserWorkspacesPath({ userId });
|
|
936
|
-
const resolved = path.resolve(p);
|
|
937
|
-
if (seenPaths.has(resolved) || resolved === path.resolve(workspacesPath())) continue;
|
|
938
|
-
seenPaths.add(resolved);
|
|
939
|
-
for (const entry of readWorkspacesFromPath(p, { userId })) {
|
|
940
|
-
const key = entry.id || entry.path || entry.repoUrl;
|
|
941
|
-
if (seenEntries.has(key)) continue;
|
|
942
|
-
seenEntries.add(key);
|
|
943
|
-
out.push(entry);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
return out;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
function readUserWorkspaces(userCtx = {}) {
|
|
950
|
-
const globalPath = workspacesPath();
|
|
951
|
-
const globalWorkspaces = fs.existsSync(globalPath) ? readWorkspacesFromPath(globalPath, userCtx) : [];
|
|
952
|
-
const adminLegacy = readLegacyAdminWorkspaces(userCtx);
|
|
953
|
-
if (globalWorkspaces.length || adminLegacy.length) {
|
|
954
|
-
const seen = new Set();
|
|
955
|
-
const out = [];
|
|
956
|
-
for (const entry of [...globalWorkspaces, ...adminLegacy]) {
|
|
957
|
-
const key = entry.id || entry.path || entry.repoUrl;
|
|
958
|
-
if (seen.has(key)) continue;
|
|
959
|
-
seen.add(key);
|
|
960
|
-
out.push(entry);
|
|
961
|
-
}
|
|
962
|
-
return out;
|
|
963
|
-
}
|
|
964
|
-
return readWorkspacesFromPath(legacyUserWorkspacesPath(userCtx), userCtx);
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
function listConfiguredWorkspaces(root, scopedRoot, userCtx = {}) {
|
|
968
|
-
const currentRoot = path.resolve(scopedRoot || root);
|
|
969
|
-
const homeRoot = path.resolve(os.homedir());
|
|
970
|
-
const builtins = [
|
|
971
|
-
{ id: "current", label: "当前流程工作区", kind: "local", path: currentRoot, builtin: true, exists: fs.existsSync(currentRoot) && fs.statSync(currentRoot).isDirectory(), type: "flow", enabled: true },
|
|
972
|
-
{ id: "home", label: "用户 Home", kind: "local", path: homeRoot, builtin: true, exists: fs.existsSync(homeRoot) && fs.statSync(homeRoot).isDirectory(), type: "local", enabled: true },
|
|
973
|
-
];
|
|
974
|
-
const custom = readUserWorkspaces(userCtx).filter((entry) => entry.enabled !== false).map((entry) => ({ ...entry, builtin: false }));
|
|
975
|
-
const seen = new Set();
|
|
976
|
-
return [...builtins, ...custom].filter((entry) => {
|
|
977
|
-
const key = path.resolve(entry.path);
|
|
978
|
-
if (seen.has(key)) return false;
|
|
979
|
-
seen.add(key);
|
|
980
|
-
return true;
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
|
|
984
949
|
function workflowBindableWorkspaces(userCtx = {}) {
|
|
985
950
|
return readUserWorkspaces(userCtx)
|
|
986
951
|
.filter((entry) => entry.enabled !== false && entry.exists)
|
|
@@ -1333,6 +1298,12 @@ function isDisplayShareExpired(share) {
|
|
|
1333
1298
|
return Number.isFinite(expiresAt) && expiresAt <= Date.now();
|
|
1334
1299
|
}
|
|
1335
1300
|
|
|
1301
|
+
function canReadDisplayShare(share, userCtx = {}) {
|
|
1302
|
+
if (!share) return false;
|
|
1303
|
+
if (String(share.visibility || "public") !== "private") return true;
|
|
1304
|
+
return userCtx?.isAdmin === true || String(share.userId || "") === String(userCtx?.userId || "");
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1336
1307
|
function getDisplayShareOrExpired(id) {
|
|
1337
1308
|
const shares = readDisplayShares();
|
|
1338
1309
|
const share = shares[id];
|
|
@@ -1358,10 +1329,78 @@ function displayShareSummary(share, baseUrl = "") {
|
|
|
1358
1329
|
expiresAt: String(share?.expiresAt || ""),
|
|
1359
1330
|
expiresMode: String(share?.expiresMode || (share?.expiresAt ? "days" : "permanent")),
|
|
1360
1331
|
expiresInDays: share?.expiresInDays == null ? null : Number(share.expiresInDays),
|
|
1332
|
+
visibility: String(share?.visibility || "public") === "private" ? "private" : "public",
|
|
1361
1333
|
url: displayShareOutputUrl(share?.id || "", baseUrl),
|
|
1362
1334
|
};
|
|
1363
1335
|
}
|
|
1364
1336
|
|
|
1337
|
+
function spacePublicSummary(space = {}, baseUrl = "") {
|
|
1338
|
+
const ownerId = String(space.ownerId || "");
|
|
1339
|
+
const slug = String(space.slug || "");
|
|
1340
|
+
const rootPath = `/s/${encodeURIComponent(ownerId)}/${encodeURIComponent(slug)}`;
|
|
1341
|
+
const base = normalizePublicBaseUrl(baseUrl);
|
|
1342
|
+
const absoluteRoot = base ? new URL(rootPath, base.endsWith("/") ? base : `${base}/`).href : rootPath;
|
|
1343
|
+
return {
|
|
1344
|
+
id: String(space.id || ""),
|
|
1345
|
+
ownerId,
|
|
1346
|
+
slug,
|
|
1347
|
+
title: String(space.title || slug),
|
|
1348
|
+
description: String(space.description || ""),
|
|
1349
|
+
visibility: normalizeSpaceVisibility(space.visibility),
|
|
1350
|
+
status: String(space.status || "active"),
|
|
1351
|
+
pages: (Array.isArray(space.pages) ? space.pages : []).map((page) => ({
|
|
1352
|
+
id: String(page.id || ""),
|
|
1353
|
+
title: String(page.title || ""),
|
|
1354
|
+
path: normalizeSpacePagePath(page.path || "/"),
|
|
1355
|
+
shareId: String(page.shareId || ""),
|
|
1356
|
+
hidden: page.hidden === true,
|
|
1357
|
+
order: Number(page.order || 0),
|
|
1358
|
+
})),
|
|
1359
|
+
url: absoluteRoot,
|
|
1360
|
+
createdAt: String(space.createdAt || ""),
|
|
1361
|
+
updatedAt: String(space.updatedAt || ""),
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function canManageSpace(space, userCtx = {}) {
|
|
1366
|
+
return Boolean(space) && (userCtx?.isAdmin === true || String(space.ownerId || "") === String(userCtx?.userId || ""));
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function displayShareSpaceReferences(shareId = "") {
|
|
1370
|
+
const wanted = String(shareId || "").trim();
|
|
1371
|
+
if (!wanted) return [];
|
|
1372
|
+
const refs = [];
|
|
1373
|
+
for (const space of readSpaces()) {
|
|
1374
|
+
for (const page of Array.isArray(space.pages) ? space.pages : []) {
|
|
1375
|
+
if (String(page.shareId || "") !== wanted) continue;
|
|
1376
|
+
refs.push({ spaceId: space.id, ownerId: space.ownerId, slug: space.slug, pageId: page.id, path: page.path });
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
return refs;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function syncSpaceShareVisibility(space) {
|
|
1383
|
+
if (!space) return;
|
|
1384
|
+
const shares = readDisplayShares();
|
|
1385
|
+
let changed = false;
|
|
1386
|
+
for (const page of Array.isArray(space.pages) ? space.pages : []) {
|
|
1387
|
+
const share = shares[String(page.shareId || "")];
|
|
1388
|
+
if (!share || String(share.userId || "") !== String(space.ownerId || "")) continue;
|
|
1389
|
+
const visibility = normalizeSpaceVisibility(space.visibility);
|
|
1390
|
+
if (share.visibility === visibility && !share.expiresAt) continue;
|
|
1391
|
+
shares[page.shareId] = {
|
|
1392
|
+
...share,
|
|
1393
|
+
visibility,
|
|
1394
|
+
expiresAt: "",
|
|
1395
|
+
expiresMode: "permanent",
|
|
1396
|
+
expiresInDays: null,
|
|
1397
|
+
updatedAt: new Date().toISOString(),
|
|
1398
|
+
};
|
|
1399
|
+
changed = true;
|
|
1400
|
+
}
|
|
1401
|
+
if (changed) writeDisplayShares(shares);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1365
1404
|
function listDisplaySharesForUser(userCtx = {}, baseUrl = "") {
|
|
1366
1405
|
const userId = String(userCtx?.userId || "");
|
|
1367
1406
|
const isAdmin = userCtx?.isAdmin === true;
|
|
@@ -1389,6 +1428,9 @@ function updateDisplayShareExpiryForUser(id, userCtx = {}, patch = {}) {
|
|
|
1389
1428
|
const userId = String(userCtx?.userId || "");
|
|
1390
1429
|
if (userCtx?.isAdmin !== true && String(share.userId || "") !== userId) return { status: 403, error: "Forbidden" };
|
|
1391
1430
|
const expiry = normalizeDisplayShareExpiry(patch, new Date());
|
|
1431
|
+
if (displayShareSpaceReferences(id).length > 0 && expiry.expiresMode !== "permanent") {
|
|
1432
|
+
return { status: 409, error: "Space 页面使用中的展示内容必须永久有效;请先从空间移除页面" };
|
|
1433
|
+
}
|
|
1392
1434
|
const updated = {
|
|
1393
1435
|
...share,
|
|
1394
1436
|
expiresAt: expiry.expiresAt,
|
|
@@ -1407,6 +1449,9 @@ function deleteDisplayShareForUser(id, userCtx = {}) {
|
|
|
1407
1449
|
if (!share) return { status: 404, error: "Display share not found" };
|
|
1408
1450
|
const userId = String(userCtx?.userId || "");
|
|
1409
1451
|
if (userCtx?.isAdmin !== true && String(share.userId || "") !== userId) return { status: 403, error: "Forbidden" };
|
|
1452
|
+
if (displayShareSpaceReferences(id).length > 0) {
|
|
1453
|
+
return { status: 409, error: "展示内容仍被 Space 页面引用;请先从空间移除页面" };
|
|
1454
|
+
}
|
|
1410
1455
|
delete shares[id];
|
|
1411
1456
|
writeDisplayShares(shares);
|
|
1412
1457
|
return { status: 200 };
|
|
@@ -1450,6 +1495,25 @@ function serverPublicBaseUrl(req, host, port, payload = null) {
|
|
|
1450
1495
|
return configuredPublicBaseUrl(payload) || requestPublicBaseUrl(req) || normalizePublicBaseUrl(`http://${host}:${port}`);
|
|
1451
1496
|
}
|
|
1452
1497
|
|
|
1498
|
+
function html(res, status, content, headers = {}) {
|
|
1499
|
+
const body = String(content || "");
|
|
1500
|
+
res.writeHead(status, {
|
|
1501
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
1502
|
+
"Content-Length": Buffer.byteLength(body),
|
|
1503
|
+
"Cache-Control": "no-store, max-age=0",
|
|
1504
|
+
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'",
|
|
1505
|
+
"X-Content-Type-Options": "nosniff",
|
|
1506
|
+
"Referrer-Policy": "no-referrer",
|
|
1507
|
+
...headers,
|
|
1508
|
+
});
|
|
1509
|
+
res.end(body);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
async function readUrlEncodedBody(req) {
|
|
1513
|
+
const body = await readBody(req);
|
|
1514
|
+
return new URLSearchParams(String(body || ""));
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1453
1517
|
function publicDisplayPayloadFromShare(root, share) {
|
|
1454
1518
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
1455
1519
|
flowId: share.flowId || "",
|
|
@@ -1684,7 +1748,7 @@ function bufferLooksLikeZip(buf) {
|
|
|
1684
1748
|
|
|
1685
1749
|
/**
|
|
1686
1750
|
* @param {import('http').IncomingMessage} req
|
|
1687
|
-
* @returns {Promise<{ targetSpace: string, flowIdField: string, file: Buffer, filename: string, gotFile: boolean }>}
|
|
1751
|
+
* @returns {Promise<{ targetSpace: string, flowIdField: string, scheduleMode: string, file: Buffer, filename: string, gotFile: boolean }>}
|
|
1688
1752
|
*/
|
|
1689
1753
|
function parseFlowsImportForm(req) {
|
|
1690
1754
|
return new Promise((resolve, reject) => {
|
|
@@ -1694,6 +1758,7 @@ function parseFlowsImportForm(req) {
|
|
|
1694
1758
|
});
|
|
1695
1759
|
let targetSpace = "user";
|
|
1696
1760
|
let flowIdField = "";
|
|
1761
|
+
let scheduleMode = "disabled";
|
|
1697
1762
|
/** @type {Buffer[]} */
|
|
1698
1763
|
const chunks = [];
|
|
1699
1764
|
let filename = "";
|
|
@@ -1706,6 +1771,9 @@ function parseFlowsImportForm(req) {
|
|
|
1706
1771
|
if (name === "flowId" && typeof val === "string") {
|
|
1707
1772
|
flowIdField = val;
|
|
1708
1773
|
}
|
|
1774
|
+
if (name === "scheduleMode" && typeof val === "string") {
|
|
1775
|
+
scheduleMode = val.trim().toLowerCase();
|
|
1776
|
+
}
|
|
1709
1777
|
});
|
|
1710
1778
|
|
|
1711
1779
|
bb.on("file", (name, file, info) => {
|
|
@@ -1725,6 +1793,7 @@ function parseFlowsImportForm(req) {
|
|
|
1725
1793
|
resolve({
|
|
1726
1794
|
targetSpace,
|
|
1727
1795
|
flowIdField: flowIdField.trim(),
|
|
1796
|
+
scheduleMode,
|
|
1728
1797
|
file: Buffer.concat(chunks),
|
|
1729
1798
|
filename,
|
|
1730
1799
|
gotFile,
|
|
@@ -1748,12 +1817,16 @@ function cleanupExpiredWorkspacePreviews(workspaceRoot = "") {
|
|
|
1748
1817
|
}
|
|
1749
1818
|
let removed = 0;
|
|
1750
1819
|
for (const pipelinesRoot of roots) {
|
|
1751
|
-
|
|
1820
|
+
const expired = [
|
|
1821
|
+
...listExpiredWorkspacePreviews(pipelinesRoot),
|
|
1822
|
+
...listExpiredWorkspaceDrafts(pipelinesRoot),
|
|
1823
|
+
];
|
|
1824
|
+
for (const item of expired) {
|
|
1752
1825
|
try {
|
|
1753
1826
|
fs.rmSync(item.flowDir, { recursive: true, force: true });
|
|
1754
1827
|
removed += 1;
|
|
1755
1828
|
} catch (e) {
|
|
1756
|
-
log.debug(`[workspace-
|
|
1829
|
+
log.debug(`[workspace-temporary] cleanup failed: ${(e && e.message) || String(e)}`);
|
|
1757
1830
|
}
|
|
1758
1831
|
}
|
|
1759
1832
|
}
|
|
@@ -2013,12 +2086,12 @@ function buildComposerPromptWithFlowContext(p) {
|
|
|
2013
2086
|
`- flowSource:${p.flowSource}`,
|
|
2014
2087
|
...builtinExtra,
|
|
2015
2088
|
`- 当前关联的节点实例 ID(顺序:画布选中优先,再输入框 @提及):${idsLine}`,
|
|
2016
|
-
"- 像普通 agent 请求一样处理用户说明:可能只是问问题,也可能要求编辑文件。不要因为存在 flowId
|
|
2089
|
+
"- 像普通 agent 请求一样处理用户说明:可能只是问问题,也可能要求编辑文件。不要因为存在 flowId 就默认修改流程。",
|
|
2017
2090
|
"- 按需使用当前环境可用的 skills;如果用户点名某个 skill,遵循该 skill 的 SKILL.md。",
|
|
2018
2091
|
"- 如果你判断需要编辑 AgentFlow 流程,可按需读取这些本地 skills:",
|
|
2019
|
-
" - `skills/agentflow-flow-
|
|
2020
|
-
" - `skills/agentflow-
|
|
2021
|
-
" - `skills/agentflow-flow
|
|
2092
|
+
" - `skills/agentflow-flow-dsl/SKILL.md`:新建或修改 workspace.flow.js、节点、字段、连线、子流程与受控循环",
|
|
2093
|
+
" - `skills/agentflow-node-dsl/SKILL.md`:仅在需要确定性自定义代码节点时使用",
|
|
2094
|
+
" - `skills/agentflow-author-flow/SKILL.md`:需要 Draft、试运行、动态修改、发布或定时时使用完整生命周期",
|
|
2022
2095
|
"- 如果只是回答问题,不要修改文件。",
|
|
2023
2096
|
"",
|
|
2024
2097
|
...(p.selectedSkillBlock ? [p.selectedSkillBlock, ""] : []),
|
|
@@ -2091,6 +2164,132 @@ export function startUiServer({
|
|
|
2091
2164
|
return;
|
|
2092
2165
|
}
|
|
2093
2166
|
|
|
2167
|
+
if (url.pathname === "/api/auth/cli/device" && req.method === "POST") {
|
|
2168
|
+
let payload = {};
|
|
2169
|
+
try {
|
|
2170
|
+
const raw = await readBody(req);
|
|
2171
|
+
payload = raw ? JSON.parse(raw) : {};
|
|
2172
|
+
} catch {
|
|
2173
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
const authorization = createCliAuthorization({
|
|
2177
|
+
publicBaseUrl: serverPublicBaseUrl(req, host, uiPort),
|
|
2178
|
+
clientName: payload?.clientName,
|
|
2179
|
+
});
|
|
2180
|
+
json(res, 201, {
|
|
2181
|
+
requestId: authorization.requestId,
|
|
2182
|
+
deviceCode: authorization.deviceCode,
|
|
2183
|
+
userCode: authorization.userCode,
|
|
2184
|
+
verificationUrl: authorization.verificationUrl,
|
|
2185
|
+
expiresAt: authorization.expiresAt,
|
|
2186
|
+
pollInterval: authorization.pollInterval,
|
|
2187
|
+
});
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
if (url.pathname === "/api/auth/cli/token" && req.method === "POST") {
|
|
2192
|
+
let payload = {};
|
|
2193
|
+
try {
|
|
2194
|
+
const raw = await readBody(req);
|
|
2195
|
+
payload = raw ? JSON.parse(raw) : {};
|
|
2196
|
+
} catch {
|
|
2197
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
const result = exchangeCliAuthorization(payload?.deviceCode);
|
|
2201
|
+
if (!result.ok) {
|
|
2202
|
+
json(res, result.status || 400, {
|
|
2203
|
+
error: result.error,
|
|
2204
|
+
code: result.code || "invalid_request",
|
|
2205
|
+
authorization: result.authorization || undefined,
|
|
2206
|
+
});
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
json(res, 200, result);
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
if (url.pathname === "/api/auth/cli/revoke" && req.method === "POST") {
|
|
2214
|
+
const token = getSessionTokenFromRequest(req);
|
|
2215
|
+
if (!token || !getAuthUserFromRequest(req)) {
|
|
2216
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
revokeSessionToken(token);
|
|
2220
|
+
json(res, 200, { ok: true });
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
if (url.pathname === "/cli/authorize" && req.method === "GET") {
|
|
2225
|
+
const requestId = String(url.searchParams.get("request") || "").trim();
|
|
2226
|
+
const user = getAuthUserFromRequest(req);
|
|
2227
|
+
const result = getCliAuthorization(requestId, { includeApprovalNonce: Boolean(user) });
|
|
2228
|
+
if (result.ok && result.authorization.status !== "pending") {
|
|
2229
|
+
html(res, 200, renderCliAuthorizationResult({
|
|
2230
|
+
approved: result.authorization.status === "approved",
|
|
2231
|
+
clientName: result.authorization.clientName,
|
|
2232
|
+
}));
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
html(res, result.ok ? 200 : result.status || 404, renderCliAuthorizationPage({
|
|
2236
|
+
authorization: result.authorization || null,
|
|
2237
|
+
approvalNonce: result.approvalNonce || "",
|
|
2238
|
+
user,
|
|
2239
|
+
error: result.ok ? "" : result.error,
|
|
2240
|
+
}));
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
if (url.pathname === "/cli/authorize/login" && req.method === "POST") {
|
|
2245
|
+
const form = await readUrlEncodedBody(req);
|
|
2246
|
+
const requestId = String(form.get("request") || "").trim();
|
|
2247
|
+
const pending = getCliAuthorization(requestId);
|
|
2248
|
+
if (!pending.ok) {
|
|
2249
|
+
html(res, pending.status || 404, renderCliAuthorizationPage({ error: pending.error }));
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
const result = loginOrCreateUser(form.get("username"), form.get("password"));
|
|
2253
|
+
if (!result.ok) {
|
|
2254
|
+
html(res, result.forbidden ? 403 : 401, renderCliAuthorizationPage({
|
|
2255
|
+
authorization: pending.authorization,
|
|
2256
|
+
error: result.error || "Login failed",
|
|
2257
|
+
}));
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
res.writeHead(303, {
|
|
2261
|
+
Location: `/cli/authorize?request=${encodeURIComponent(requestId)}`,
|
|
2262
|
+
"Set-Cookie": buildSessionCookie(result.token),
|
|
2263
|
+
"Cache-Control": "no-store, max-age=0",
|
|
2264
|
+
});
|
|
2265
|
+
res.end();
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
if (url.pathname === "/cli/authorize/decision" && req.method === "POST") {
|
|
2270
|
+
const user = getAuthUserFromRequest(req);
|
|
2271
|
+
if (!user) {
|
|
2272
|
+
html(res, 401, renderCliAuthorizationResult({ error: "登录状态已失效,请重新打开授权链接。" }));
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
const form = await readUrlEncodedBody(req);
|
|
2276
|
+
const requestId = String(form.get("request") || "").trim();
|
|
2277
|
+
const current = getCliAuthorization(requestId);
|
|
2278
|
+
const approved = form.get("decision") === "approve";
|
|
2279
|
+
const result = decideCliAuthorization({
|
|
2280
|
+
requestId,
|
|
2281
|
+
approvalNonce: form.get("approvalNonce"),
|
|
2282
|
+
userId: user.userId,
|
|
2283
|
+
approved,
|
|
2284
|
+
});
|
|
2285
|
+
html(res, result.ok ? 200 : result.status || 400, renderCliAuthorizationResult({
|
|
2286
|
+
approved,
|
|
2287
|
+
clientName: current.authorization?.clientName,
|
|
2288
|
+
error: result.ok ? "" : result.error,
|
|
2289
|
+
}));
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2094
2293
|
if (url.pathname === "/api/app-version" && req.method === "GET") {
|
|
2095
2294
|
res.setHeader("Cache-Control", "no-store, max-age=0");
|
|
2096
2295
|
json(res, 200, {
|
|
@@ -2167,6 +2366,154 @@ export function startUiServer({
|
|
|
2167
2366
|
json(res, 200, { token: getSessionTokenFromRequest(req) || "" });
|
|
2168
2367
|
return;
|
|
2169
2368
|
}
|
|
2369
|
+
|
|
2370
|
+
if (req.method === "GET" && url.pathname === "/api/spaces/public") {
|
|
2371
|
+
try {
|
|
2372
|
+
const ownerId = String(url.searchParams.get("owner") || "").trim();
|
|
2373
|
+
const slug = String(url.searchParams.get("slug") || "").trim();
|
|
2374
|
+
const space = getSpaceByRoute(ownerId, slug);
|
|
2375
|
+
if (!space || (!canManageSpace(space, userCtx) && normalizeSpaceVisibility(space.visibility) === "private")) {
|
|
2376
|
+
json(res, 404, { error: "Space not found" });
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
if (space.status === "paused" && !canManageSpace(space, userCtx)) {
|
|
2380
|
+
json(res, 410, { error: "Space is paused" });
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
json(res, 200, {
|
|
2384
|
+
space: spacePublicSummary(space, requestPublicBaseUrl(req)),
|
|
2385
|
+
manageable: canManageSpace(space, userCtx),
|
|
2386
|
+
});
|
|
2387
|
+
} catch (e) {
|
|
2388
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
2389
|
+
}
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
if (req.method === "GET" && url.pathname === "/api/spaces") {
|
|
2394
|
+
if (!authUser?.userId) {
|
|
2395
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
const spaces = listSpacesForUser(userCtx.userId, userCtx.isAdmin)
|
|
2399
|
+
.map((space) => spacePublicSummary(space, requestPublicBaseUrl(req)));
|
|
2400
|
+
json(res, 200, { spaces });
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
if (req.method === "POST" && url.pathname === "/api/spaces") {
|
|
2405
|
+
if (!authUser?.userId) {
|
|
2406
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
try {
|
|
2410
|
+
const payload = JSON.parse(await readBody(req));
|
|
2411
|
+
const result = createSpace({
|
|
2412
|
+
ownerId: userCtx.userId,
|
|
2413
|
+
slug: payload.slug,
|
|
2414
|
+
title: payload.title,
|
|
2415
|
+
description: payload.description,
|
|
2416
|
+
visibility: payload.visibility,
|
|
2417
|
+
});
|
|
2418
|
+
if (result.error) {
|
|
2419
|
+
json(res, 400, { error: result.error });
|
|
2420
|
+
return;
|
|
2421
|
+
}
|
|
2422
|
+
json(res, 200, { ok: true, space: spacePublicSummary(result.space, requestPublicBaseUrl(req)) });
|
|
2423
|
+
} catch (e) {
|
|
2424
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
2425
|
+
}
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
if (req.method === "PATCH" && url.pathname === "/api/spaces") {
|
|
2430
|
+
if (!authUser?.userId) {
|
|
2431
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
try {
|
|
2435
|
+
const payload = JSON.parse(await readBody(req));
|
|
2436
|
+
const current = getSpaceById(payload.id);
|
|
2437
|
+
if (!canManageSpace(current, userCtx)) {
|
|
2438
|
+
json(res, current ? 403 : 404, { error: current ? "Forbidden" : "Space not found" });
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
const result = updateSpace(payload.id, payload);
|
|
2442
|
+
if (result.error) {
|
|
2443
|
+
json(res, 400, { error: result.error });
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
syncSpaceShareVisibility(result.space);
|
|
2447
|
+
json(res, 200, { ok: true, space: spacePublicSummary(result.space, requestPublicBaseUrl(req)) });
|
|
2448
|
+
} catch (e) {
|
|
2449
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
2450
|
+
}
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
if (req.method === "POST" && url.pathname === "/api/spaces/page") {
|
|
2455
|
+
if (!authUser?.userId) {
|
|
2456
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
try {
|
|
2460
|
+
const payload = JSON.parse(await readBody(req));
|
|
2461
|
+
const space = getSpaceById(payload.spaceId);
|
|
2462
|
+
if (!canManageSpace(space, userCtx)) {
|
|
2463
|
+
json(res, space ? 403 : 404, { error: space ? "Forbidden" : "Space not found" });
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
const { share } = getDisplayShareOrExpired(String(payload.shareId || "").trim());
|
|
2467
|
+
if (!share || (userCtx.isAdmin !== true && String(share.userId || "") !== String(userCtx.userId || ""))) {
|
|
2468
|
+
json(res, 400, { error: "Display share not found or not owned by current user" });
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
const foreignSpaceRef = displayShareSpaceReferences(share.id).find((ref) => ref.spaceId !== space.id);
|
|
2472
|
+
if (foreignSpaceRef) {
|
|
2473
|
+
json(res, 409, { error: "同一展示内容不能绑定到多个空间;请从 Workspace 重新生成一次展示" });
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const result = upsertSpacePage(space.id, {
|
|
2477
|
+
title: payload.title,
|
|
2478
|
+
path: payload.path,
|
|
2479
|
+
shareId: share.id,
|
|
2480
|
+
hidden: payload.hidden === true,
|
|
2481
|
+
});
|
|
2482
|
+
if (result.error) {
|
|
2483
|
+
json(res, 400, { error: result.error });
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
syncSpaceShareVisibility(result.space);
|
|
2487
|
+
json(res, 200, {
|
|
2488
|
+
ok: true,
|
|
2489
|
+
page: result.page,
|
|
2490
|
+
space: spacePublicSummary(result.space, requestPublicBaseUrl(req)),
|
|
2491
|
+
});
|
|
2492
|
+
} catch (e) {
|
|
2493
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
2494
|
+
}
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
if (req.method === "DELETE" && url.pathname === "/api/spaces/page") {
|
|
2499
|
+
if (!authUser?.userId) {
|
|
2500
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
const space = getSpaceById(url.searchParams.get("spaceId") || "");
|
|
2504
|
+
if (!canManageSpace(space, userCtx)) {
|
|
2505
|
+
json(res, space ? 403 : 404, { error: space ? "Forbidden" : "Space not found" });
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
const result = deleteSpacePage(space.id, url.searchParams.get("pageId") || "");
|
|
2509
|
+
if (result.error) {
|
|
2510
|
+
json(res, 404, { error: result.error });
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
json(res, 200, { ok: true, space: spacePublicSummary(result.space, requestPublicBaseUrl(req)) });
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2170
2517
|
if (req.method === "GET" && url.pathname === "/api/display/shares") {
|
|
2171
2518
|
try {
|
|
2172
2519
|
if (!authUser?.userId) {
|
|
@@ -2194,6 +2541,10 @@ export function startUiServer({
|
|
|
2194
2541
|
json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
|
|
2195
2542
|
return;
|
|
2196
2543
|
}
|
|
2544
|
+
if (!canReadDisplayShare(share, userCtx)) {
|
|
2545
|
+
json(res, 404, { error: "Display share not found" });
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2197
2548
|
const payload = publicDisplayPayloadFromShare(root, share);
|
|
2198
2549
|
if (payload.error) {
|
|
2199
2550
|
json(res, 404, { error: payload.error });
|
|
@@ -2274,6 +2625,10 @@ export function startUiServer({
|
|
|
2274
2625
|
json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
|
|
2275
2626
|
return;
|
|
2276
2627
|
}
|
|
2628
|
+
if (!canReadDisplayShare(share, userCtx)) {
|
|
2629
|
+
json(res, 404, { error: "Display share not found" });
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2277
2632
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
2278
2633
|
flowId: share.flowId || "",
|
|
2279
2634
|
flowSource: share.flowSource || "user",
|
|
@@ -2706,6 +3061,10 @@ export function startUiServer({
|
|
|
2706
3061
|
}
|
|
2707
3062
|
const flowId = idCheck.flowId;
|
|
2708
3063
|
const targetSpace = parsed.targetSpace === "workspace" ? "workspace" : "user";
|
|
3064
|
+
if (!["enabled", "disabled", "preserve"].includes(parsed.scheduleMode)) {
|
|
3065
|
+
json(res, 400, { error: "scheduleMode must be enabled, disabled, or preserve" });
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
2709
3068
|
const existing = listFlowsJson(root, {
|
|
2710
3069
|
...userCtx,
|
|
2711
3070
|
includeWorkspaceFlows: targetSpace === "workspace",
|
|
@@ -2747,10 +3106,37 @@ export function startUiServer({
|
|
|
2747
3106
|
json(res, 400, { error: w.error });
|
|
2748
3107
|
return;
|
|
2749
3108
|
}
|
|
2750
|
-
|
|
2751
|
-
|
|
3109
|
+
const targetDir = targetSpace === "workspace"
|
|
3110
|
+
? path.join(path.resolve(root), PIPELINES_DIR, flowId)
|
|
3111
|
+
: path.join(getUserPipelinesRoot(userCtx.userId), flowId);
|
|
3112
|
+
let collaborationCreated = false;
|
|
3113
|
+
try {
|
|
3114
|
+
if (targetSpace === "workspace") {
|
|
3115
|
+
ensureWorkspaceCollaboration({ flowId, userId: userCtx.userId });
|
|
3116
|
+
collaborationCreated = true;
|
|
3117
|
+
}
|
|
3118
|
+
const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource: targetSpace }, userCtx);
|
|
3119
|
+
if (scoped.error) throw new Error(scoped.error);
|
|
3120
|
+
const importedGraph = readWorkspaceGraph(scoped.root, root).graph;
|
|
3121
|
+
const graph = workspaceGraphWithScheduleMode(importedGraph, parsed.scheduleMode);
|
|
3122
|
+
if (graph !== importedGraph) writeWorkspaceGraph(scoped.root, graph, root);
|
|
3123
|
+
const persisted = readWorkspaceGraph(scoped.root, root).graph;
|
|
3124
|
+
const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, persisted, authUser, userCtx);
|
|
3125
|
+
json(res, 200, {
|
|
3126
|
+
success: true,
|
|
3127
|
+
flowId,
|
|
3128
|
+
flowSource: targetSpace,
|
|
3129
|
+
scheduleMode: parsed.scheduleMode,
|
|
3130
|
+
workspaceSchedules,
|
|
3131
|
+
});
|
|
3132
|
+
} catch (e) {
|
|
3133
|
+
try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (_) {}
|
|
3134
|
+
if (collaborationCreated) deleteWorkspaceCollaborationForFlow(flowId, false);
|
|
3135
|
+
json(res, 500, {
|
|
3136
|
+
error: `Flow publication rolled back: ${(e && e.message) || String(e)}`,
|
|
3137
|
+
rolledBack: true,
|
|
3138
|
+
});
|
|
2752
3139
|
}
|
|
2753
|
-
json(res, 200, { success: true, flowId, flowSource: targetSpace });
|
|
2754
3140
|
return;
|
|
2755
3141
|
}
|
|
2756
3142
|
|
|
@@ -2893,6 +3279,7 @@ export function startUiServer({
|
|
|
2893
3279
|
expiresInDays: payload.expiresInDays,
|
|
2894
3280
|
permanent: payload.permanent,
|
|
2895
3281
|
expiresAt: payload.expiresAt,
|
|
3282
|
+
visibility: payload.visibility,
|
|
2896
3283
|
});
|
|
2897
3284
|
json(res, 200, { ok: true, share, url: `/display/${encodeURIComponent(share.id)}` });
|
|
2898
3285
|
} catch (e) {
|