@fieldwangai/agentflow 0.1.67 → 0.1.68

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.
@@ -97,6 +97,7 @@ import {
97
97
  isAuthUserAllowed,
98
98
  loginOrCreateUser,
99
99
  logoutRequest,
100
+ readAuthUsers,
100
101
  readUserAllowlist,
101
102
  writeUserAllowlist,
102
103
  } from "./auth.mjs";
@@ -257,8 +258,38 @@ function createFeedbackItem(payload, user) {
257
258
  };
258
259
  }
259
260
 
260
- function skillCollectionsAbs(userCtx = {}) {
261
- return path.join(getAgentflowUserDataRoot(userCtx.userId), SKILL_COLLECTIONS_FILENAME);
261
+ function skillCollectionsAbs() {
262
+ return path.join(getAgentflowDataRoot(), "admin", SKILL_COLLECTIONS_FILENAME);
263
+ }
264
+
265
+ function legacyAdminSkillCollectionPaths() {
266
+ const users = readAuthUsers();
267
+ return Object.entries(users || {})
268
+ .filter(([, user]) => user?.isAdmin)
269
+ .map(([userId, user]) => path.join(getAgentflowUserDataRoot(user.userId || userId), SKILL_COLLECTIONS_FILENAME));
270
+ }
271
+
272
+ function readSkillCollectionFile(filePath) {
273
+ try {
274
+ if (!fs.existsSync(filePath)) return null;
275
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
276
+ return data && typeof data === "object" && !Array.isArray(data) ? data : null;
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+
282
+ function mergeSkillCollectionConfigs(configs = []) {
283
+ const merged = [];
284
+ const seen = new Set();
285
+ for (const config of configs) {
286
+ for (const collection of normalizeSkillCollectionConfig(config).collections) {
287
+ if (!collection.id || seen.has(collection.id)) continue;
288
+ seen.add(collection.id);
289
+ merged.push(collection);
290
+ }
291
+ }
292
+ return { version: 1, collections: merged };
262
293
  }
263
294
 
264
295
  function slugifySkillCollectionId(name, fallback = "collection") {
@@ -382,8 +413,12 @@ function withBuiltinSkillCollections(config, availableSkills = []) {
382
413
  function readSkillCollectionConfig(userCtx = {}, availableSkills = []) {
383
414
  const p = skillCollectionsAbs(userCtx);
384
415
  try {
385
- if (!fs.existsSync(p)) return withBuiltinSkillCollections({}, availableSkills);
386
- return withBuiltinSkillCollections(JSON.parse(fs.readFileSync(p, "utf-8")), availableSkills);
416
+ const globalConfig = readSkillCollectionFile(p);
417
+ if (globalConfig) return withBuiltinSkillCollections(globalConfig, availableSkills);
418
+ const legacyConfigs = legacyAdminSkillCollectionPaths()
419
+ .map((legacyPath) => readSkillCollectionFile(legacyPath))
420
+ .filter(Boolean);
421
+ return withBuiltinSkillCollections(mergeSkillCollectionConfigs(legacyConfigs), availableSkills);
387
422
  } catch {
388
423
  return withBuiltinSkillCollections({}, availableSkills);
389
424
  }
@@ -1408,6 +1443,7 @@ function readWorkspaceGraph(workspaceRoot) {
1408
1443
  }
1409
1444
 
1410
1445
  const DISPLAY_SHARE_FILENAME = "display-shares.json";
1446
+ const DISPLAY_SHARE_TTL_MS = 24 * 60 * 60 * 1000;
1411
1447
 
1412
1448
  function displaySharesPath() {
1413
1449
  return path.join(getAgentflowDataRoot(), DISPLAY_SHARE_FILENAME);
@@ -1432,6 +1468,26 @@ function createDisplayShareId() {
1432
1468
  return crypto.randomBytes(12).toString("base64url");
1433
1469
  }
1434
1470
 
1471
+ function displayShareExpiresAt(now = new Date()) {
1472
+ const time = now instanceof Date ? now.getTime() : Date.now();
1473
+ return new Date(time + DISPLAY_SHARE_TTL_MS).toISOString();
1474
+ }
1475
+
1476
+ function isDisplayShareExpired(share) {
1477
+ const expiresAt = Date.parse(String(share?.expiresAt || ""));
1478
+ return Number.isFinite(expiresAt) && expiresAt <= Date.now();
1479
+ }
1480
+
1481
+ function getDisplayShareOrExpired(id) {
1482
+ const shares = readDisplayShares();
1483
+ const share = shares[id];
1484
+ if (!share) return { shares, share: null, expired: false };
1485
+ if (!isDisplayShareExpired(share)) return { shares, share, expired: false };
1486
+ delete shares[id];
1487
+ writeDisplayShares(shares);
1488
+ return { shares, share: null, expired: true };
1489
+ }
1490
+
1435
1491
  function normalizeDisplayShareNodeIds(ids, graph) {
1436
1492
  const out = [];
1437
1493
  const seen = new Set();
@@ -1519,6 +1575,7 @@ function publicDisplayPayloadFromShare(root, share) {
1519
1575
  : null,
1520
1576
  createdAt: share.createdAt || "",
1521
1577
  updatedAt: share.updatedAt || "",
1578
+ expiresAt: share.expiresAt || "",
1522
1579
  },
1523
1580
  nodes,
1524
1581
  };
@@ -3927,9 +3984,9 @@ export function startUiServer({
3927
3984
  json(res, 400, { error: "Missing display share id" });
3928
3985
  return;
3929
3986
  }
3930
- const share = readDisplayShares()[id];
3987
+ const { share, expired } = getDisplayShareOrExpired(id);
3931
3988
  if (!share) {
3932
- json(res, 404, { error: "Display share not found" });
3989
+ json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
3933
3990
  return;
3934
3991
  }
3935
3992
  const payload = publicDisplayPayloadFromShare(root, share);
@@ -3951,9 +4008,9 @@ export function startUiServer({
3951
4008
  json(res, 400, { error: "Missing display share id" });
3952
4009
  return;
3953
4010
  }
3954
- const share = readDisplayShares()[id];
4011
+ const { share, expired } = getDisplayShareOrExpired(id);
3955
4012
  if (!share) {
3956
- json(res, 404, { error: "Display share not found" });
4013
+ json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
3957
4014
  return;
3958
4015
  }
3959
4016
  const scoped = resolveWorkspaceScopeRoot(root, {
@@ -4394,7 +4451,8 @@ export function startUiServer({
4394
4451
  const shares = readDisplayShares();
4395
4452
  let id = createDisplayShareId();
4396
4453
  while (shares[id]) id = createDisplayShareId();
4397
- const now = new Date().toISOString();
4454
+ const nowDate = new Date();
4455
+ const now = nowDate.toISOString();
4398
4456
  const share = {
4399
4457
  id,
4400
4458
  userId: authUser.userId,
@@ -4402,10 +4460,11 @@ export function startUiServer({
4402
4460
  flowSource: scoped.flowSource || "user",
4403
4461
  archived: scoped.archived === true,
4404
4462
  title: String(payload.title || "").trim() || "AgentFlow Display",
4405
- layout: ["canvas", "gallery", "slides", "document"].includes(String(payload.layout || "")) ? String(payload.layout) : "canvas",
4463
+ layout: ["canvas", "gallery", "slides", "document", "single"].includes(String(payload.layout || "")) ? String(payload.layout) : "canvas",
4406
4464
  nodeIds,
4407
4465
  createdAt: now,
4408
4466
  updatedAt: now,
4467
+ expiresAt: displayShareExpiresAt(nowDate),
4409
4468
  };
4410
4469
  shares[id] = share;
4411
4470
  writeDisplayShares(shares);
@@ -5481,6 +5540,10 @@ export function startUiServer({
5481
5540
  json(res, 400, { error: "Missing skill slug or collection" });
5482
5541
  return;
5483
5542
  }
5543
+ if (payload?.collection && !authUser?.isAdmin) {
5544
+ json(res, 403, { error: "Admin required" });
5545
+ return;
5546
+ }
5484
5547
  const beforeSkills = payload?.collection ? listComposerSkills(PACKAGE_ROOT, root) : [];
5485
5548
  const result = await runSkillhub(args, { cwd: root, timeoutMs: 180_000, maxBuffer: 4 * 1024 * 1024 });
5486
5549
  if (!result.ok) {
@@ -5510,6 +5573,10 @@ export function startUiServer({
5510
5573
  json(res, 400, { error: "Missing skill slug or collection" });
5511
5574
  return;
5512
5575
  }
5576
+ if (payload?.collection && !authUser?.isAdmin) {
5577
+ json(res, 403, { error: "Admin required" });
5578
+ return;
5579
+ }
5513
5580
  const result = await runSkillhub(args, { cwd: root, timeoutMs: 120_000, maxBuffer: 4 * 1024 * 1024 });
5514
5581
  if (!result.ok) {
5515
5582
  json(res, 500, { error: result.error, stdout: result.stdout });
@@ -6499,6 +6566,10 @@ finishedAt: "${new Date().toISOString()}"
6499
6566
  }
6500
6567
 
6501
6568
  if (req.method === "POST" && url.pathname === "/api/skill-collections") {
6569
+ if (!authUser?.isAdmin) {
6570
+ json(res, 403, { error: "Admin required" });
6571
+ return;
6572
+ }
6502
6573
  let payload;
6503
6574
  try {
6504
6575
  payload = JSON.parse(await readBody(req));