@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.
Files changed (42) hide show
  1. package/bin/lib/flow-dsl/codegen.mjs +23 -2
  2. package/bin/lib/flow-dsl/lint.mjs +44 -0
  3. package/bin/lib/flow-dsl/parser.mjs +68 -1
  4. package/bin/lib/marketplace-usage.mjs +218 -0
  5. package/bin/lib/marketplace.mjs +183 -3
  6. package/bin/lib/node-package-manifest.mjs +1 -1
  7. package/bin/lib/spaces.mjs +200 -0
  8. package/bin/lib/ui-server.mjs +314 -76
  9. package/bin/lib/workspace-routes.mjs +396 -5
  10. package/bin/lib/workspace-run-logs.mjs +2 -0
  11. package/bin/lib/workspace-server.mjs +698 -48
  12. package/bin/lib/workspace-state.mjs +2 -1
  13. package/builtin/nodes/agent_subAgent.md +5 -1
  14. package/builtin/nodes/context_bundle.md +44 -0
  15. package/builtin/nodes/context_knowledge.md +29 -0
  16. package/builtin/nodes/context_skills.md +29 -0
  17. package/builtin/nodes/context_workspace.md +36 -0
  18. package/builtin/nodes/control_while.md +7 -0
  19. package/builtin/nodes/tool_git_worktree_load.md +4 -3
  20. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-CTrXOZ00.js → WorkflowAssistantThread-CffIx5BY.js} +1 -1
  21. package/builtin/web-ui/dist/assets/index-CDItaRfX.css +1 -0
  22. package/builtin/web-ui/dist/assets/index-CWIcfWHO.js +873 -0
  23. package/builtin/web-ui/dist/index.html +2 -2
  24. package/package.json +1 -1
  25. package/shared/slot-types.js +1 -0
  26. package/skills/agentflow-cli/SKILL.md +50 -3
  27. package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +120 -6
  28. package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +5 -1
  29. package/skills/agentflow-cli/runtime/builtin/nodes/context_bundle.md +44 -0
  30. package/skills/agentflow-cli/runtime/builtin/nodes/context_knowledge.md +29 -0
  31. package/skills/agentflow-cli/runtime/builtin/nodes/context_skills.md +29 -0
  32. package/skills/agentflow-cli/runtime/builtin/nodes/context_workspace.md +36 -0
  33. package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +7 -0
  34. package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +4 -3
  35. package/skills/agentflow-cli/runtime/package.json +1 -1
  36. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +64 -0
  37. package/skills/agentflow-flow-dsl/SKILL.md +48 -2
  38. package/skills/agentflow-flow-dsl/references/node-calls.md +6 -2
  39. package/skills/agentflow-flow-dsl/references/subflow-authoring.md +34 -7
  40. package/skills/agentflow-node-reference/references/builtin-nodes.md +37 -5
  41. package/builtin/web-ui/dist/assets/index-5uJFccdX.css +0 -1
  42. package/builtin/web-ui/dist/assets/index-BeUfNQRL.js +0 -873
@@ -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
- const workspaceSchedules = listWorkspaceScheduleStatuses(root, userCtx);
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 result = setWorkspaceScheduleEnabled(root, payload, authUser, userCtx);
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;