@fieldwangai/agentflow 0.1.52 → 0.1.54

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 CHANGED
@@ -152,6 +152,7 @@ function userAllowlistMatchSet(users) {
152
152
  }
153
153
 
154
154
  export function isAuthUserAllowed(user) {
155
+ if (user?.isAdmin) return true;
155
156
  const allowlist = readUserAllowlist();
156
157
  if (!allowlist.enabled) return true;
157
158
  const allowed = userAllowlistMatchSet(allowlist.users);
@@ -266,15 +267,15 @@ export function loginOrCreateUser(username, password) {
266
267
  if (!userId) {
267
268
  return { ok: false, error: "用户名须以字母开头,仅可使用字母、数字、下划线与连字符,最多 64 字符" };
268
269
  }
269
- if (!isAuthUserAllowed({ userId, username: String(username || "").trim() })) {
270
- return { ok: false, forbidden: true, error: "用户不在白名单中,请联系管理员开通访问权限" };
271
- }
272
270
  const pwd = String(password || "");
273
271
  if (pwd.length < 4) return { ok: false, error: "密码至少 4 位" };
274
272
 
275
273
  const users = readAuthUsers();
276
274
  const firstUser = Object.keys(users).length === 0;
277
275
  let user = users[userId];
276
+ if (!isAuthUserAllowed({ userId, username: String(username || "").trim(), isAdmin: Boolean(user?.isAdmin) })) {
277
+ return { ok: false, forbidden: true, error: "用户不在白名单中,请联系管理员开通访问权限" };
278
+ }
278
279
  if (!user) {
279
280
  const hashed = hashPassword(pwd);
280
281
  user = {
@@ -392,10 +392,11 @@ export function listMarketplacePackages(workspaceRoot, opts = {}) {
392
392
  return { nodes, collections };
393
393
  }
394
394
 
395
- export function listMarketplaceFlowSnippets(workspaceRoot) {
395
+ export function listMarketplaceFlowSnippets(workspaceRoot, opts = {}) {
396
396
  const root = workspacePackageRoot(workspaceRoot);
397
397
  const snippetsRoot = path.join(root, "flow-snippets");
398
398
  const snippets = [];
399
+ const requestedUserId = String(opts.userId || "").trim();
399
400
  if (!fs.existsSync(snippetsRoot)) return { snippets };
400
401
  for (const entry of fs.readdirSync(snippetsRoot, { withFileTypes: true })) {
401
402
  if (!entry.isDirectory()) continue;
@@ -405,6 +406,8 @@ export function listMarketplaceFlowSnippets(workspaceRoot) {
405
406
  const manifest = readYamlObject(path.join(dir, FLOW_SNIPPET_MANIFEST));
406
407
  if (!manifest) continue;
407
408
  const snippet = manifest.snippet && typeof manifest.snippet === "object" ? manifest.snippet : {};
409
+ const ownerUserId = String(manifest.ownerUserId || manifest.createdBy || "").trim();
410
+ if (requestedUserId && ownerUserId !== requestedUserId) continue;
408
411
  snippets.push({
409
412
  id: manifest.id || entry.name,
410
413
  version: manifest.version || version,
@@ -415,6 +418,7 @@ export function listMarketplaceFlowSnippets(workspaceRoot) {
415
418
  edgeCount: Number(manifest.edgeCount) || (Array.isArray(snippet.edges) ? snippet.edges.length : 0),
416
419
  createdAt: manifest.createdAt || "",
417
420
  updatedAt: manifest.updatedAt || "",
421
+ ownerUserId,
418
422
  packageDir: dir,
419
423
  snippet,
420
424
  });
@@ -449,12 +453,19 @@ export function deleteMarketplaceNodePackage(workspaceRoot, id, version, opts =
449
453
  return { ok: true, id, version, packageDir };
450
454
  }
451
455
 
452
- export function deleteMarketplaceFlowSnippetPackage(workspaceRoot, id, version) {
456
+ export function deleteMarketplaceFlowSnippetPackage(workspaceRoot, id, version, opts = {}) {
453
457
  const packageDir = resolveWorkspaceFlowSnippetPackageDir(workspaceRoot, id, version);
454
458
  if (!packageDir) return { ok: false, error: "Invalid flow snippet id or version" };
455
- if (!fs.existsSync(path.join(packageDir, FLOW_SNIPPET_MANIFEST))) {
459
+ const manifestPath = path.join(packageDir, FLOW_SNIPPET_MANIFEST);
460
+ if (!fs.existsSync(manifestPath)) {
456
461
  return { ok: false, error: `Flow snippet package not found: ${id}@${version}` };
457
462
  }
463
+ const manifest = readYamlObject(manifestPath) || {};
464
+ const ownerUserId = String(manifest.ownerUserId || manifest.createdBy || "").trim();
465
+ const requestedUserId = String(opts.userId || "").trim();
466
+ if (!requestedUserId || ownerUserId !== requestedUserId) {
467
+ return { ok: false, error: "Flow snippet permission denied" };
468
+ }
458
469
  fs.rmSync(packageDir, { recursive: true, force: true });
459
470
  const versionRoot = path.dirname(packageDir);
460
471
  try {
@@ -732,11 +743,13 @@ export function publishNodeFromInstance(workspaceRoot, payload = {}, options = {
732
743
  };
733
744
  }
734
745
 
735
- export function publishFlowSnippet(workspaceRoot, payload = {}) {
746
+ export function publishFlowSnippet(workspaceRoot, payload = {}, opts = {}) {
736
747
  const label = String(payload.displayName || payload.name || payload.id || "flow snippet").trim();
737
748
  const id = safePackageId(payload.id || payload.packageId || label);
738
749
  const version = normalizeVersion(payload.version || "1.0.0");
739
750
  if (!id) return { ok: false, error: "Invalid snippet id" };
751
+ const ownerUserId = String(opts.userId || "").trim();
752
+ if (!ownerUserId) return { ok: false, error: "Authentication required" };
740
753
 
741
754
  const rawSnippet = payload.snippet && typeof payload.snippet === "object" ? payload.snippet : {};
742
755
  const instances = rawSnippet.instances && typeof rawSnippet.instances === "object" ? rawSnippet.instances : {};
@@ -748,6 +761,11 @@ export function publishFlowSnippet(workspaceRoot, payload = {}) {
748
761
  const now = new Date().toISOString();
749
762
  const dest = resolveWorkspaceFlowSnippetPackageDir(workspaceRoot, id, version);
750
763
  if (!dest) return { ok: false, error: "Invalid snippet id or version" };
764
+ const existingManifest = readYamlObject(path.join(dest, FLOW_SNIPPET_MANIFEST));
765
+ if (existingManifest) {
766
+ const existingOwner = String(existingManifest.ownerUserId || existingManifest.createdBy || "").trim();
767
+ if (existingOwner !== ownerUserId) return { ok: false, error: "Flow snippet permission denied" };
768
+ }
751
769
  fs.mkdirSync(path.dirname(dest), { recursive: true });
752
770
  fs.rmSync(dest, { recursive: true, force: true });
753
771
  fs.mkdirSync(dest, { recursive: true });
@@ -759,6 +777,8 @@ export function publishFlowSnippet(workspaceRoot, payload = {}) {
759
777
  displayName: label,
760
778
  description: String(payload.description || "").trim(),
761
779
  tags: Array.isArray(payload.tags) ? payload.tags.map((x) => String(x).trim()).filter(Boolean) : [],
780
+ ownerUserId,
781
+ createdBy: ownerUserId,
762
782
  nodeCount,
763
783
  edgeCount: edges.length,
764
784
  createdAt: now,
@@ -1601,11 +1601,15 @@ function workspaceSourceSlotForEdge(graph, edge) {
1601
1601
  function workspaceOutputSlotValueForEdge(graph, outputs, edge) {
1602
1602
  const sourceId = String(edge?.source || "");
1603
1603
  const slot = workspaceSourceSlotForEdge(graph, edge);
1604
+ const out = outputs.get(sourceId);
1605
+ const sourceIndex = workspaceHandleIndex(edge?.sourceHandle, "output");
1606
+ const slotName = String(slot?.name || "").trim();
1607
+ const isPrimaryOutput = !slot || slotName === "result" || slotName === "content" || sourceIndex === 0;
1608
+ if (isPrimaryOutput && out != null && String(out).trim()) return String(out);
1604
1609
  if (slot && String(slot?.type || "") !== "node") {
1605
1610
  const value = workspaceSlotValue(slot);
1606
1611
  if (value.trim()) return value;
1607
1612
  }
1608
- const out = outputs.get(sourceId);
1609
1613
  if (out != null && String(out).trim()) return String(out);
1610
1614
  const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
1611
1615
  return workspaceInstanceText(instances[sourceId]);
@@ -2022,6 +2026,26 @@ function workspaceDownstreamDisplayRequirements(graph, nodeId) {
2022
2026
  ].join("\n");
2023
2027
  }
2024
2028
 
2029
+ function workspaceNodeScopeGuardrails(graph, nodeId, inputValues = {}) {
2030
+ const edges = Array.isArray(graph?.edges) ? graph.edges : [];
2031
+ const directInputEdges = edges
2032
+ .filter((edge) => String(edge?.target || "") === String(nodeId))
2033
+ .filter((edge) => !isWorkspaceSemanticInputSlot(workspaceTargetSlotForEdge(graph, edge)));
2034
+ const upstreamNodeIds = Array.from(new Set(directInputEdges.map((edge) => String(edge?.source || "").trim()).filter(Boolean)));
2035
+ const inputNames = Object.keys(inputValues || {}).filter(Boolean);
2036
+ return [
2037
+ "## 当前节点上下文边界",
2038
+ "",
2039
+ "你只负责执行当前节点;`上游上下文`、已解析输入槽和节点任务就是本节点的业务上下文边界。",
2040
+ upstreamNodeIds.length ? `当前直接上游节点:${upstreamNodeIds.map((id) => `\`${id}\``).join("、")}。` : "当前没有直接业务上游节点。",
2041
+ inputNames.length ? `当前已解析输入槽:${inputNames.map((name) => `\`${name}\``).join("、")}。` : "当前没有已解析的具名业务输入槽。",
2042
+ "不要为了理解本节点而读取或搜索整张 workspace、`workspace.graph.json`、正式 `flow.yaml`、历史 run/log 或其它未连接节点。",
2043
+ "不要把下游展示节点已有内容、下游错误信息、其它分支节点内容当作本节点输入;下游输出要求只用于决定最终 JSON 的字段和格式。",
2044
+ "只有当当前节点任务文本或上游输入明确要求读取/修改 `workspace.graph.json`、`flow.yaml` 或某个具体文件路径时,才可以打开对应文件。",
2045
+ "如果完成任务所需信息不在当前节点任务、输入槽或上游上下文中,应明确说明缺少哪个上游输入,而不是扫描整张 workspace 猜测。",
2046
+ ].join("\n");
2047
+ }
2048
+
2025
2049
  function workspaceOutputProtocolRequirements(graph, nodeId) {
2026
2050
  const instance = graph?.instances?.[nodeId] || {};
2027
2051
  const outputSlots = Array.isArray(instance.output) ? instance.output : [];
@@ -2355,11 +2379,13 @@ function workspaceNodePrompt(graph, nodeId, upstreamText, skillsBlock, mcpBlock
2355
2379
  const instance = graph.instances[nodeId] || {};
2356
2380
  const body = workspaceResolveBodyPlaceholders(instance.body || "", inputValues).trim();
2357
2381
  const label = String(instance.label || nodeId).trim();
2382
+ const scopeGuardrails = workspaceNodeScopeGuardrails(graph, nodeId, inputValues);
2358
2383
  const downstreamRequirements = workspaceDownstreamDisplayRequirements(graph, nodeId);
2359
2384
  const outputProtocolRequirements = workspaceOutputProtocolRequirements(graph, nodeId);
2360
2385
  return [
2361
2386
  "你正在执行 AgentFlow Workspace 画布中的一个临时节点。",
2362
2387
  "按 Workspace 输出协议返回该节点要传给下游展示/后续节点的数据。",
2388
+ scopeGuardrails,
2363
2389
  workspaceSearchGuardrailsBlock(),
2364
2390
  skillsBlock ? `\n## Available Skills\n\n${skillsBlock}` : "",
2365
2391
  mcpBlock ? `\n## Available MCP\n\n${mcpBlock}` : "",
@@ -4816,7 +4842,7 @@ export function startUiServer({
4816
4842
 
4817
4843
  if (req.method === "GET" && url.pathname === "/api/marketplace/flow-snippets") {
4818
4844
  try {
4819
- json(res, 200, listMarketplaceFlowSnippets(root));
4845
+ json(res, 200, listMarketplaceFlowSnippets(root, userCtx));
4820
4846
  } catch (e) {
4821
4847
  json(res, 500, { error: (e && e.message) || String(e) });
4822
4848
  }
@@ -4847,7 +4873,7 @@ export function startUiServer({
4847
4873
  return;
4848
4874
  }
4849
4875
  try {
4850
- const result = deleteMarketplaceFlowSnippetPackage(root, id, version);
4876
+ const result = deleteMarketplaceFlowSnippetPackage(root, id, version, userCtx);
4851
4877
  json(res, result.ok ? 200 : 400, result);
4852
4878
  } catch (e) {
4853
4879
  json(res, 500, { ok: false, error: (e && e.message) || String(e) });
@@ -4926,7 +4952,7 @@ export function startUiServer({
4926
4952
  return;
4927
4953
  }
4928
4954
  try {
4929
- const result = publishFlowSnippet(root, payload || {});
4955
+ const result = publishFlowSnippet(root, payload || {}, userCtx);
4930
4956
  json(res, result.ok ? 200 : 400, result);
4931
4957
  } catch (e) {
4932
4958
  json(res, 500, { ok: false, error: (e && e.message) || String(e) });