@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
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -130,7 +130,6 @@ import { json, readBody } from "./http-util.mjs";
|
|
|
130
130
|
|
|
131
131
|
// 从 ui-server 拆出去的 Workspace 子系统;路由仍在下面的 startUiServer 里
|
|
132
132
|
import {
|
|
133
|
-
USER_WORKSPACES_FILENAME,
|
|
134
133
|
WORKSPACE_DEFERRED_RUN_POLL_MS,
|
|
135
134
|
WORKSPACE_SCHEDULE_POLL_MS,
|
|
136
135
|
activeWorkspaceRunUsageRecords,
|
|
@@ -141,6 +140,7 @@ import {
|
|
|
141
140
|
displayShareOutputUrl,
|
|
142
141
|
isReadonlyBuiltinFlowSource,
|
|
143
142
|
isValidFlowSourceRead,
|
|
143
|
+
listConfiguredWorkspaces,
|
|
144
144
|
listWorkspaceScheduleStatuses,
|
|
145
145
|
normalizeDisplayShareExpiry,
|
|
146
146
|
normalizeDisplayShareNodeIds,
|
|
@@ -151,11 +151,11 @@ import {
|
|
|
151
151
|
readCursorMcpConfig,
|
|
152
152
|
readCursorMcpServers,
|
|
153
153
|
readDisplayShares,
|
|
154
|
+
readUserWorkspaces,
|
|
154
155
|
readUserMcpPrivate,
|
|
155
156
|
readWorkspaceGraph,
|
|
156
157
|
readWorkspaceRunUsageRecords,
|
|
157
158
|
readWorkspaceScheduleRegistry,
|
|
158
|
-
readWorkspacesFromPath,
|
|
159
159
|
resolveWorkspaceFilePath,
|
|
160
160
|
resolveWorkspaceScopeRoot,
|
|
161
161
|
runStatusBucket,
|
|
@@ -173,11 +173,22 @@ import {
|
|
|
173
173
|
workspaceDownloadContentDisposition,
|
|
174
174
|
workspaceFlowCollaborationGuard,
|
|
175
175
|
workspaceScheduleNextRunAt,
|
|
176
|
-
workspacesPath,
|
|
177
176
|
writeDisplayShares,
|
|
178
177
|
writeWorkspaceGraph,
|
|
179
178
|
} from "./workspace-server.mjs";
|
|
180
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";
|
|
181
192
|
|
|
182
193
|
const MIME = {
|
|
183
194
|
".html": "text/html; charset=utf-8",
|
|
@@ -935,70 +946,6 @@ function skillhubInstallArgs(payload, { uninstall = false } = {}) {
|
|
|
935
946
|
}
|
|
936
947
|
|
|
937
948
|
const DISPLAY_SHARE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
938
|
-
function legacyUserWorkspacesPath(userCtx = {}) {
|
|
939
|
-
return path.join(getAgentflowUserDataRoot(userCtx.userId || ""), USER_WORKSPACES_FILENAME);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
function readLegacyAdminWorkspaces(userCtx = {}) {
|
|
943
|
-
const users = readAuthUsers();
|
|
944
|
-
const candidates = [];
|
|
945
|
-
for (const [userId, user] of Object.entries(users || {})) {
|
|
946
|
-
if (user?.isAdmin) candidates.push(String(userId || ""));
|
|
947
|
-
}
|
|
948
|
-
if (userCtx?.isAdmin && userCtx.userId) candidates.unshift(String(userCtx.userId));
|
|
949
|
-
const seenPaths = new Set();
|
|
950
|
-
const seenEntries = new Set();
|
|
951
|
-
const out = [];
|
|
952
|
-
for (const userId of candidates) {
|
|
953
|
-
const p = legacyUserWorkspacesPath({ userId });
|
|
954
|
-
const resolved = path.resolve(p);
|
|
955
|
-
if (seenPaths.has(resolved) || resolved === path.resolve(workspacesPath())) continue;
|
|
956
|
-
seenPaths.add(resolved);
|
|
957
|
-
for (const entry of readWorkspacesFromPath(p, { userId })) {
|
|
958
|
-
const key = entry.id || entry.path || entry.repoUrl;
|
|
959
|
-
if (seenEntries.has(key)) continue;
|
|
960
|
-
seenEntries.add(key);
|
|
961
|
-
out.push(entry);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
return out;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
function readUserWorkspaces(userCtx = {}) {
|
|
968
|
-
const globalPath = workspacesPath();
|
|
969
|
-
const globalWorkspaces = fs.existsSync(globalPath) ? readWorkspacesFromPath(globalPath, userCtx) : [];
|
|
970
|
-
const adminLegacy = readLegacyAdminWorkspaces(userCtx);
|
|
971
|
-
if (globalWorkspaces.length || adminLegacy.length) {
|
|
972
|
-
const seen = new Set();
|
|
973
|
-
const out = [];
|
|
974
|
-
for (const entry of [...globalWorkspaces, ...adminLegacy]) {
|
|
975
|
-
const key = entry.id || entry.path || entry.repoUrl;
|
|
976
|
-
if (seen.has(key)) continue;
|
|
977
|
-
seen.add(key);
|
|
978
|
-
out.push(entry);
|
|
979
|
-
}
|
|
980
|
-
return out;
|
|
981
|
-
}
|
|
982
|
-
return readWorkspacesFromPath(legacyUserWorkspacesPath(userCtx), userCtx);
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
function listConfiguredWorkspaces(root, scopedRoot, userCtx = {}) {
|
|
986
|
-
const currentRoot = path.resolve(scopedRoot || root);
|
|
987
|
-
const homeRoot = path.resolve(os.homedir());
|
|
988
|
-
const builtins = [
|
|
989
|
-
{ id: "current", label: "当前流程工作区", kind: "local", path: currentRoot, builtin: true, exists: fs.existsSync(currentRoot) && fs.statSync(currentRoot).isDirectory(), type: "flow", enabled: true },
|
|
990
|
-
{ id: "home", label: "用户 Home", kind: "local", path: homeRoot, builtin: true, exists: fs.existsSync(homeRoot) && fs.statSync(homeRoot).isDirectory(), type: "local", enabled: true },
|
|
991
|
-
];
|
|
992
|
-
const custom = readUserWorkspaces(userCtx).filter((entry) => entry.enabled !== false).map((entry) => ({ ...entry, builtin: false }));
|
|
993
|
-
const seen = new Set();
|
|
994
|
-
return [...builtins, ...custom].filter((entry) => {
|
|
995
|
-
const key = path.resolve(entry.path);
|
|
996
|
-
if (seen.has(key)) return false;
|
|
997
|
-
seen.add(key);
|
|
998
|
-
return true;
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
949
|
function workflowBindableWorkspaces(userCtx = {}) {
|
|
1003
950
|
return readUserWorkspaces(userCtx)
|
|
1004
951
|
.filter((entry) => entry.enabled !== false && entry.exists)
|
|
@@ -1325,6 +1272,7 @@ function buildAdminUsageDashboard(workspaceRoot) {
|
|
|
1325
1272
|
username: String(run.username || run.userId || ""),
|
|
1326
1273
|
flowId: String(run.flowId || ""),
|
|
1327
1274
|
flowSource: String(run.flowSource || "user"),
|
|
1275
|
+
archived: run.archived === true,
|
|
1328
1276
|
runId: String(run.runId || ""),
|
|
1329
1277
|
at: Number(run.at || 0),
|
|
1330
1278
|
endedAt: run.endedAt == null ? null : Number(run.endedAt),
|
|
@@ -1351,6 +1299,12 @@ function isDisplayShareExpired(share) {
|
|
|
1351
1299
|
return Number.isFinite(expiresAt) && expiresAt <= Date.now();
|
|
1352
1300
|
}
|
|
1353
1301
|
|
|
1302
|
+
function canReadDisplayShare(share, userCtx = {}) {
|
|
1303
|
+
if (!share) return false;
|
|
1304
|
+
if (String(share.visibility || "public") !== "private") return true;
|
|
1305
|
+
return userCtx?.isAdmin === true || String(share.userId || "") === String(userCtx?.userId || "");
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1354
1308
|
function getDisplayShareOrExpired(id) {
|
|
1355
1309
|
const shares = readDisplayShares();
|
|
1356
1310
|
const share = shares[id];
|
|
@@ -1376,10 +1330,78 @@ function displayShareSummary(share, baseUrl = "") {
|
|
|
1376
1330
|
expiresAt: String(share?.expiresAt || ""),
|
|
1377
1331
|
expiresMode: String(share?.expiresMode || (share?.expiresAt ? "days" : "permanent")),
|
|
1378
1332
|
expiresInDays: share?.expiresInDays == null ? null : Number(share.expiresInDays),
|
|
1333
|
+
visibility: String(share?.visibility || "public") === "private" ? "private" : "public",
|
|
1379
1334
|
url: displayShareOutputUrl(share?.id || "", baseUrl),
|
|
1380
1335
|
};
|
|
1381
1336
|
}
|
|
1382
1337
|
|
|
1338
|
+
function spacePublicSummary(space = {}, baseUrl = "") {
|
|
1339
|
+
const ownerId = String(space.ownerId || "");
|
|
1340
|
+
const slug = String(space.slug || "");
|
|
1341
|
+
const rootPath = `/s/${encodeURIComponent(ownerId)}/${encodeURIComponent(slug)}`;
|
|
1342
|
+
const base = normalizePublicBaseUrl(baseUrl);
|
|
1343
|
+
const absoluteRoot = base ? new URL(rootPath, base.endsWith("/") ? base : `${base}/`).href : rootPath;
|
|
1344
|
+
return {
|
|
1345
|
+
id: String(space.id || ""),
|
|
1346
|
+
ownerId,
|
|
1347
|
+
slug,
|
|
1348
|
+
title: String(space.title || slug),
|
|
1349
|
+
description: String(space.description || ""),
|
|
1350
|
+
visibility: normalizeSpaceVisibility(space.visibility),
|
|
1351
|
+
status: String(space.status || "active"),
|
|
1352
|
+
pages: (Array.isArray(space.pages) ? space.pages : []).map((page) => ({
|
|
1353
|
+
id: String(page.id || ""),
|
|
1354
|
+
title: String(page.title || ""),
|
|
1355
|
+
path: normalizeSpacePagePath(page.path || "/"),
|
|
1356
|
+
shareId: String(page.shareId || ""),
|
|
1357
|
+
hidden: page.hidden === true,
|
|
1358
|
+
order: Number(page.order || 0),
|
|
1359
|
+
})),
|
|
1360
|
+
url: absoluteRoot,
|
|
1361
|
+
createdAt: String(space.createdAt || ""),
|
|
1362
|
+
updatedAt: String(space.updatedAt || ""),
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function canManageSpace(space, userCtx = {}) {
|
|
1367
|
+
return Boolean(space) && (userCtx?.isAdmin === true || String(space.ownerId || "") === String(userCtx?.userId || ""));
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function displayShareSpaceReferences(shareId = "") {
|
|
1371
|
+
const wanted = String(shareId || "").trim();
|
|
1372
|
+
if (!wanted) return [];
|
|
1373
|
+
const refs = [];
|
|
1374
|
+
for (const space of readSpaces()) {
|
|
1375
|
+
for (const page of Array.isArray(space.pages) ? space.pages : []) {
|
|
1376
|
+
if (String(page.shareId || "") !== wanted) continue;
|
|
1377
|
+
refs.push({ spaceId: space.id, ownerId: space.ownerId, slug: space.slug, pageId: page.id, path: page.path });
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return refs;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function syncSpaceShareVisibility(space) {
|
|
1384
|
+
if (!space) return;
|
|
1385
|
+
const shares = readDisplayShares();
|
|
1386
|
+
let changed = false;
|
|
1387
|
+
for (const page of Array.isArray(space.pages) ? space.pages : []) {
|
|
1388
|
+
const share = shares[String(page.shareId || "")];
|
|
1389
|
+
if (!share || String(share.userId || "") !== String(space.ownerId || "")) continue;
|
|
1390
|
+
const visibility = normalizeSpaceVisibility(space.visibility);
|
|
1391
|
+
if (share.visibility === visibility && !share.expiresAt) continue;
|
|
1392
|
+
shares[page.shareId] = {
|
|
1393
|
+
...share,
|
|
1394
|
+
visibility,
|
|
1395
|
+
expiresAt: "",
|
|
1396
|
+
expiresMode: "permanent",
|
|
1397
|
+
expiresInDays: null,
|
|
1398
|
+
updatedAt: new Date().toISOString(),
|
|
1399
|
+
};
|
|
1400
|
+
changed = true;
|
|
1401
|
+
}
|
|
1402
|
+
if (changed) writeDisplayShares(shares);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1383
1405
|
function listDisplaySharesForUser(userCtx = {}, baseUrl = "") {
|
|
1384
1406
|
const userId = String(userCtx?.userId || "");
|
|
1385
1407
|
const isAdmin = userCtx?.isAdmin === true;
|
|
@@ -1407,6 +1429,9 @@ function updateDisplayShareExpiryForUser(id, userCtx = {}, patch = {}) {
|
|
|
1407
1429
|
const userId = String(userCtx?.userId || "");
|
|
1408
1430
|
if (userCtx?.isAdmin !== true && String(share.userId || "") !== userId) return { status: 403, error: "Forbidden" };
|
|
1409
1431
|
const expiry = normalizeDisplayShareExpiry(patch, new Date());
|
|
1432
|
+
if (displayShareSpaceReferences(id).length > 0 && expiry.expiresMode !== "permanent") {
|
|
1433
|
+
return { status: 409, error: "Space 页面使用中的展示内容必须永久有效;请先从空间移除页面" };
|
|
1434
|
+
}
|
|
1410
1435
|
const updated = {
|
|
1411
1436
|
...share,
|
|
1412
1437
|
expiresAt: expiry.expiresAt,
|
|
@@ -1425,6 +1450,9 @@ function deleteDisplayShareForUser(id, userCtx = {}) {
|
|
|
1425
1450
|
if (!share) return { status: 404, error: "Display share not found" };
|
|
1426
1451
|
const userId = String(userCtx?.userId || "");
|
|
1427
1452
|
if (userCtx?.isAdmin !== true && String(share.userId || "") !== userId) return { status: 403, error: "Forbidden" };
|
|
1453
|
+
if (displayShareSpaceReferences(id).length > 0) {
|
|
1454
|
+
return { status: 409, error: "展示内容仍被 Space 页面引用;请先从空间移除页面" };
|
|
1455
|
+
}
|
|
1428
1456
|
delete shares[id];
|
|
1429
1457
|
writeDisplayShares(shares);
|
|
1430
1458
|
return { status: 200 };
|
|
@@ -1966,6 +1994,50 @@ function setWorkspaceScheduleEnabled(root, payload = {}, authUser = {}, userCtx
|
|
|
1966
1994
|
return { success: true, workspaceSchedules };
|
|
1967
1995
|
}
|
|
1968
1996
|
|
|
1997
|
+
function schedulesForRequest(root, authUser = {}, userCtx = {}) {
|
|
1998
|
+
if (!authUser?.isAdmin) {
|
|
1999
|
+
return {
|
|
2000
|
+
adminView: false,
|
|
2001
|
+
users: [],
|
|
2002
|
+
schedules: listWorkspaceScheduleStatuses(root, userCtx),
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
const users = listAuthUsers();
|
|
2006
|
+
const usernames = new Map(users.map((user) => [user.userId, user.username || user.userId]));
|
|
2007
|
+
const byIdentity = new Map();
|
|
2008
|
+
for (const user of users) {
|
|
2009
|
+
const rows = listWorkspaceScheduleStatuses(root, { ...userCtx, userId: user.userId });
|
|
2010
|
+
for (const row of rows) {
|
|
2011
|
+
const ownerUserId = String(row.ownerUserId || row.userId || user.userId);
|
|
2012
|
+
const key = String(row.key || `${ownerUserId}:${row.flowSource || "user"}:${row.flowId || ""}:${row.scheduleNodeId || ""}`);
|
|
2013
|
+
const identity = String(row.flowSource || "user") === "workspace"
|
|
2014
|
+
? `workspace:${row.workspaceId || row.flowId || ""}:${row.scheduleNodeId || ""}`
|
|
2015
|
+
: key;
|
|
2016
|
+
const existing = byIdentity.get(identity);
|
|
2017
|
+
if (existing && (existing.registered || !row.registered)) continue;
|
|
2018
|
+
byIdentity.set(identity, {
|
|
2019
|
+
...row,
|
|
2020
|
+
ownerUserId,
|
|
2021
|
+
ownerUsername: usernames.get(ownerUserId) || row.ownerUsername || ownerUserId,
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
const schedules = [...byIdentity.values()].sort((a, b) => {
|
|
2026
|
+
const ea = a.enabled ? 0 : 1;
|
|
2027
|
+
const eb = b.enabled ? 0 : 1;
|
|
2028
|
+
return ea - eb
|
|
2029
|
+
|| String(a.nextRunAt || "").localeCompare(String(b.nextRunAt || ""))
|
|
2030
|
+
|| String(a.ownerUsername || a.ownerUserId || "").localeCompare(String(b.ownerUsername || b.ownerUserId || ""))
|
|
2031
|
+
|| String(a.flowId || "").localeCompare(String(b.flowId || ""));
|
|
2032
|
+
});
|
|
2033
|
+
const ownerIds = new Set(schedules.map((schedule) => String(schedule.ownerUserId || "")).filter(Boolean));
|
|
2034
|
+
return {
|
|
2035
|
+
adminView: true,
|
|
2036
|
+
users: users.filter((user) => ownerIds.has(user.userId)),
|
|
2037
|
+
schedules,
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
|
|
1969
2041
|
function pollWorkspaceSchedules(root) {
|
|
1970
2042
|
const now = Date.now();
|
|
1971
2043
|
const registry = readWorkspaceScheduleRegistry();
|
|
@@ -2339,6 +2411,154 @@ export function startUiServer({
|
|
|
2339
2411
|
json(res, 200, { token: getSessionTokenFromRequest(req) || "" });
|
|
2340
2412
|
return;
|
|
2341
2413
|
}
|
|
2414
|
+
|
|
2415
|
+
if (req.method === "GET" && url.pathname === "/api/spaces/public") {
|
|
2416
|
+
try {
|
|
2417
|
+
const ownerId = String(url.searchParams.get("owner") || "").trim();
|
|
2418
|
+
const slug = String(url.searchParams.get("slug") || "").trim();
|
|
2419
|
+
const space = getSpaceByRoute(ownerId, slug);
|
|
2420
|
+
if (!space || (!canManageSpace(space, userCtx) && normalizeSpaceVisibility(space.visibility) === "private")) {
|
|
2421
|
+
json(res, 404, { error: "Space not found" });
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
if (space.status === "paused" && !canManageSpace(space, userCtx)) {
|
|
2425
|
+
json(res, 410, { error: "Space is paused" });
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
json(res, 200, {
|
|
2429
|
+
space: spacePublicSummary(space, requestPublicBaseUrl(req)),
|
|
2430
|
+
manageable: canManageSpace(space, userCtx),
|
|
2431
|
+
});
|
|
2432
|
+
} catch (e) {
|
|
2433
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
2434
|
+
}
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
if (req.method === "GET" && url.pathname === "/api/spaces") {
|
|
2439
|
+
if (!authUser?.userId) {
|
|
2440
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
const spaces = listSpacesForUser(userCtx.userId, userCtx.isAdmin)
|
|
2444
|
+
.map((space) => spacePublicSummary(space, requestPublicBaseUrl(req)));
|
|
2445
|
+
json(res, 200, { spaces });
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
if (req.method === "POST" && url.pathname === "/api/spaces") {
|
|
2450
|
+
if (!authUser?.userId) {
|
|
2451
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
try {
|
|
2455
|
+
const payload = JSON.parse(await readBody(req));
|
|
2456
|
+
const result = createSpace({
|
|
2457
|
+
ownerId: userCtx.userId,
|
|
2458
|
+
slug: payload.slug,
|
|
2459
|
+
title: payload.title,
|
|
2460
|
+
description: payload.description,
|
|
2461
|
+
visibility: payload.visibility,
|
|
2462
|
+
});
|
|
2463
|
+
if (result.error) {
|
|
2464
|
+
json(res, 400, { error: result.error });
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
json(res, 200, { ok: true, space: spacePublicSummary(result.space, requestPublicBaseUrl(req)) });
|
|
2468
|
+
} catch (e) {
|
|
2469
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
2470
|
+
}
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
if (req.method === "PATCH" && url.pathname === "/api/spaces") {
|
|
2475
|
+
if (!authUser?.userId) {
|
|
2476
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
try {
|
|
2480
|
+
const payload = JSON.parse(await readBody(req));
|
|
2481
|
+
const current = getSpaceById(payload.id);
|
|
2482
|
+
if (!canManageSpace(current, userCtx)) {
|
|
2483
|
+
json(res, current ? 403 : 404, { error: current ? "Forbidden" : "Space not found" });
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
const result = updateSpace(payload.id, payload);
|
|
2487
|
+
if (result.error) {
|
|
2488
|
+
json(res, 400, { error: result.error });
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
syncSpaceShareVisibility(result.space);
|
|
2492
|
+
json(res, 200, { ok: true, space: spacePublicSummary(result.space, requestPublicBaseUrl(req)) });
|
|
2493
|
+
} catch (e) {
|
|
2494
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
2495
|
+
}
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
if (req.method === "POST" && url.pathname === "/api/spaces/page") {
|
|
2500
|
+
if (!authUser?.userId) {
|
|
2501
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
try {
|
|
2505
|
+
const payload = JSON.parse(await readBody(req));
|
|
2506
|
+
const space = getSpaceById(payload.spaceId);
|
|
2507
|
+
if (!canManageSpace(space, userCtx)) {
|
|
2508
|
+
json(res, space ? 403 : 404, { error: space ? "Forbidden" : "Space not found" });
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
const { share } = getDisplayShareOrExpired(String(payload.shareId || "").trim());
|
|
2512
|
+
if (!share || (userCtx.isAdmin !== true && String(share.userId || "") !== String(userCtx.userId || ""))) {
|
|
2513
|
+
json(res, 400, { error: "Display share not found or not owned by current user" });
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
const foreignSpaceRef = displayShareSpaceReferences(share.id).find((ref) => ref.spaceId !== space.id);
|
|
2517
|
+
if (foreignSpaceRef) {
|
|
2518
|
+
json(res, 409, { error: "同一展示内容不能绑定到多个空间;请从 Workspace 重新生成一次展示" });
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
const result = upsertSpacePage(space.id, {
|
|
2522
|
+
title: payload.title,
|
|
2523
|
+
path: payload.path,
|
|
2524
|
+
shareId: share.id,
|
|
2525
|
+
hidden: payload.hidden === true,
|
|
2526
|
+
});
|
|
2527
|
+
if (result.error) {
|
|
2528
|
+
json(res, 400, { error: result.error });
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
syncSpaceShareVisibility(result.space);
|
|
2532
|
+
json(res, 200, {
|
|
2533
|
+
ok: true,
|
|
2534
|
+
page: result.page,
|
|
2535
|
+
space: spacePublicSummary(result.space, requestPublicBaseUrl(req)),
|
|
2536
|
+
});
|
|
2537
|
+
} catch (e) {
|
|
2538
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
2539
|
+
}
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
if (req.method === "DELETE" && url.pathname === "/api/spaces/page") {
|
|
2544
|
+
if (!authUser?.userId) {
|
|
2545
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
const space = getSpaceById(url.searchParams.get("spaceId") || "");
|
|
2549
|
+
if (!canManageSpace(space, userCtx)) {
|
|
2550
|
+
json(res, space ? 403 : 404, { error: space ? "Forbidden" : "Space not found" });
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
const result = deleteSpacePage(space.id, url.searchParams.get("pageId") || "");
|
|
2554
|
+
if (result.error) {
|
|
2555
|
+
json(res, 404, { error: result.error });
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
json(res, 200, { ok: true, space: spacePublicSummary(result.space, requestPublicBaseUrl(req)) });
|
|
2559
|
+
return;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2342
2562
|
if (req.method === "GET" && url.pathname === "/api/display/shares") {
|
|
2343
2563
|
try {
|
|
2344
2564
|
if (!authUser?.userId) {
|
|
@@ -2366,6 +2586,10 @@ export function startUiServer({
|
|
|
2366
2586
|
json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
|
|
2367
2587
|
return;
|
|
2368
2588
|
}
|
|
2589
|
+
if (!canReadDisplayShare(share, userCtx)) {
|
|
2590
|
+
json(res, 404, { error: "Display share not found" });
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2369
2593
|
const payload = publicDisplayPayloadFromShare(root, share);
|
|
2370
2594
|
if (payload.error) {
|
|
2371
2595
|
json(res, 404, { error: payload.error });
|
|
@@ -2446,6 +2670,10 @@ export function startUiServer({
|
|
|
2446
2670
|
json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
|
|
2447
2671
|
return;
|
|
2448
2672
|
}
|
|
2673
|
+
if (!canReadDisplayShare(share, userCtx)) {
|
|
2674
|
+
json(res, 404, { error: "Display share not found" });
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2449
2677
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
2450
2678
|
flowId: share.flowId || "",
|
|
2451
2679
|
flowSource: share.flowSource || "user",
|
|
@@ -3096,6 +3324,7 @@ export function startUiServer({
|
|
|
3096
3324
|
expiresInDays: payload.expiresInDays,
|
|
3097
3325
|
permanent: payload.permanent,
|
|
3098
3326
|
expiresAt: payload.expiresAt,
|
|
3327
|
+
visibility: payload.visibility,
|
|
3099
3328
|
});
|
|
3100
3329
|
json(res, 200, { ok: true, share, url: `/display/${encodeURIComponent(share.id)}` });
|
|
3101
3330
|
} catch (e) {
|
|
@@ -4394,14 +4623,7 @@ finishedAt: "${new Date().toISOString()}"
|
|
|
4394
4623
|
try {
|
|
4395
4624
|
// 旧 Pipeline schedule 随 Start/End 执行一并下线:它们只会驱动已废弃的
|
|
4396
4625
|
// `agentflow apply`,列出来只会给用户永远不会触发的条目。
|
|
4397
|
-
|
|
4398
|
-
json(res, 200, {
|
|
4399
|
-
schedules: [...workspaceSchedules].sort((a, b) => {
|
|
4400
|
-
const ea = a.enabled ? 0 : 1;
|
|
4401
|
-
const eb = b.enabled ? 0 : 1;
|
|
4402
|
-
return ea - eb || String(a.nextRunAt || "").localeCompare(String(b.nextRunAt || "")) || String(a.flowId || "").localeCompare(String(b.flowId || ""));
|
|
4403
|
-
}),
|
|
4404
|
-
});
|
|
4626
|
+
json(res, 200, schedulesForRequest(root, authUser, userCtx));
|
|
4405
4627
|
} catch (e) {
|
|
4406
4628
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
4407
4629
|
}
|
|
@@ -4419,7 +4641,23 @@ finishedAt: "${new Date().toISOString()}"
|
|
|
4419
4641
|
const kind = String(payload.kind || "").trim();
|
|
4420
4642
|
if (kind === "workspace") {
|
|
4421
4643
|
try {
|
|
4422
|
-
const
|
|
4644
|
+
const requestedOwnerId = String(payload.ownerUserId || userCtx.userId || "").trim();
|
|
4645
|
+
if (requestedOwnerId !== String(userCtx.userId || "") && !authUser?.isAdmin) {
|
|
4646
|
+
json(res, 403, { error: "Admin permission required to manage another user's schedule" });
|
|
4647
|
+
return;
|
|
4648
|
+
}
|
|
4649
|
+
const targetRecord = readAuthUsers()[requestedOwnerId];
|
|
4650
|
+
if (!targetRecord) {
|
|
4651
|
+
json(res, 404, { error: "Schedule owner not found" });
|
|
4652
|
+
return;
|
|
4653
|
+
}
|
|
4654
|
+
const targetAuthUser = {
|
|
4655
|
+
userId: requestedOwnerId,
|
|
4656
|
+
username: String(targetRecord.username || requestedOwnerId),
|
|
4657
|
+
isAdmin: Boolean(targetRecord.isAdmin),
|
|
4658
|
+
};
|
|
4659
|
+
const targetUserCtx = { ...userCtx, userId: requestedOwnerId };
|
|
4660
|
+
const result = setWorkspaceScheduleEnabled(root, payload, targetAuthUser, targetUserCtx);
|
|
4423
4661
|
if (!result.success) {
|
|
4424
4662
|
json(res, 400, { error: result.error || "Could not update workspace schedule" });
|
|
4425
4663
|
return;
|