@fieldwangai/agentflow 0.1.31 → 0.1.32

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.
@@ -656,16 +656,38 @@ function resolveMarkdownNodeFile(workspaceRoot, nodeId, flowId, flowSource, opts
656
656
 
657
657
  function readNodeUsage(workspaceRoot, nodeId, opts = {}) {
658
658
  const usage = [];
659
+ const marketSpec = parseMarketplaceDefinitionId(nodeId);
659
660
  for (const flow of listFlowsJson(workspaceRoot, opts)) {
660
661
  const flowPath = getFlowYamlAbs(workspaceRoot, flow.id, flow.source || "user", { archived: Boolean(flow.archived), userId: opts.userId });
661
662
  if (!flowPath.path) continue;
662
663
  try {
663
664
  const data = yaml.load(fs.readFileSync(flowPath.path, "utf-8"));
664
665
  const instances = data && typeof data === "object" ? data.instances : null;
665
- if (!instances || typeof instances !== "object") continue;
666
- const hits = Object.entries(instances)
667
- .filter(([, inst]) => inst && inst.definitionId === nodeId)
668
- .map(([instanceId, inst]) => ({ instanceId, label: inst.label || instanceId }));
666
+ const hits = [];
667
+ if (marketSpec) {
668
+ const deps = data && typeof data === "object" && data.dependencies && typeof data.dependencies === "object" ? data.dependencies : {};
669
+ const nodeDeps = Array.isArray(deps.nodes) ? deps.nodes : [];
670
+ if (nodeDeps.some((dep) => {
671
+ const parsed = typeof dep === "string"
672
+ ? parseMarketplaceDefinitionId(dep.startsWith("marketplace:") ? dep : `marketplace:${dep}`)
673
+ : dep && typeof dep === "object"
674
+ ? { id: dep.id, version: dep.version != null ? String(dep.version) : null }
675
+ : null;
676
+ return parsed && parsed.id === marketSpec.id && (!parsed.version || !marketSpec.version || parsed.version === marketSpec.version);
677
+ })) {
678
+ hits.push({ instanceId: "dependencies.nodes", label: "dependency" });
679
+ }
680
+ }
681
+ if (instances && typeof instances === "object") {
682
+ hits.push(...Object.entries(instances)
683
+ .filter(([, inst]) => {
684
+ if (!inst) return false;
685
+ if (!marketSpec) return inst.definitionId === nodeId;
686
+ const parsed = parseMarketplaceDefinitionId(inst.definitionId);
687
+ return parsed && parsed.id === marketSpec.id && (!parsed.version || !marketSpec.version || parsed.version === marketSpec.version);
688
+ })
689
+ .map(([instanceId, inst]) => ({ instanceId, label: inst.label || instanceId })));
690
+ }
669
691
  if (hits.length > 0) {
670
692
  usage.push({ flowId: flow.id, flowSource: flow.source || "user", archived: Boolean(flow.archived), instances: hits });
671
693
  }
@@ -2,7 +2,13 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import yaml from "js-yaml";
4
4
 
5
- import { MARKETPLACE_PACKAGES_DIR } from "./paths.mjs";
5
+ import {
6
+ ARCHIVED_PIPELINES_DIR_NAME,
7
+ LEGACY_PIPELINES_DIR,
8
+ MARKETPLACE_PACKAGES_DIR,
9
+ PIPELINES_DIR,
10
+ getUserPipelinesRoot,
11
+ } from "./paths.mjs";
6
12
 
7
13
  const NODE_MANIFEST = "node.yaml";
8
14
  const COLLECTION_MANIFEST = "collection.yaml";
@@ -100,6 +106,99 @@ function listVersionDirs(baseDir) {
100
106
  .filter(Boolean);
101
107
  }
102
108
 
109
+ function isSafePathSegment(value) {
110
+ const text = String(value || "").trim();
111
+ return Boolean(text) && !text.includes("\0") && !path.isAbsolute(text) && !text.split(/[\\/]+/).includes("..");
112
+ }
113
+
114
+ function resolveWorkspaceNodePackageDir(workspaceRoot, id, version) {
115
+ if (!isSafePathSegment(id) || !isSafePathSegment(version)) return null;
116
+ const base = path.resolve(workspacePackageRoot(workspaceRoot), "nodes");
117
+ const target = path.resolve(base, id, version);
118
+ if (target !== base && !target.startsWith(base + path.sep)) return null;
119
+ return target;
120
+ }
121
+
122
+ function collectFlowDirs(rootDir, source, archived = false) {
123
+ const out = [];
124
+ if (!fs.existsSync(rootDir)) return out;
125
+ let entries = [];
126
+ try {
127
+ entries = fs.readdirSync(rootDir, { withFileTypes: true });
128
+ } catch {
129
+ return out;
130
+ }
131
+ for (const entry of entries) {
132
+ if (!entry.isDirectory() || entry.name === ARCHIVED_PIPELINES_DIR_NAME) continue;
133
+ const dir = path.join(rootDir, entry.name);
134
+ if (!fs.existsSync(path.join(dir, "flow.yaml"))) continue;
135
+ out.push({ flowId: entry.name, flowSource: source, archived, flowDir: dir });
136
+ }
137
+ return out;
138
+ }
139
+
140
+ function listWritableFlowDirs(workspaceRoot, opts = {}) {
141
+ const root = path.resolve(workspaceRoot);
142
+ const userRoot = getUserPipelinesRoot(opts.userId);
143
+ const wsRoot = path.join(root, PIPELINES_DIR);
144
+ const legacyRoot = path.join(root, LEGACY_PIPELINES_DIR);
145
+ return [
146
+ ...collectFlowDirs(userRoot, "user", false),
147
+ ...collectFlowDirs(path.join(userRoot, ARCHIVED_PIPELINES_DIR_NAME), "user", true),
148
+ ...collectFlowDirs(wsRoot, "workspace", false),
149
+ ...collectFlowDirs(path.join(wsRoot, ARCHIVED_PIPELINES_DIR_NAME), "workspace", true),
150
+ ...collectFlowDirs(legacyRoot, "workspace", false),
151
+ ...collectFlowDirs(path.join(legacyRoot, ARCHIVED_PIPELINES_DIR_NAME), "workspace", true),
152
+ ];
153
+ }
154
+
155
+ function depMatchesNode(dep, id, version) {
156
+ if (typeof dep === "string") {
157
+ const parsed = parseMarketplaceDefinitionId(dep.startsWith("marketplace:") ? dep : `marketplace:${dep}`);
158
+ return Boolean(parsed && parsed.id === id && (!parsed.version || parsed.version === version));
159
+ }
160
+ if (!dep || typeof dep !== "object") return false;
161
+ return dep.id === id && (dep.version == null || String(dep.version) === version);
162
+ }
163
+
164
+ function instanceMatchesNode(inst, id, version) {
165
+ const parsed = parseMarketplaceDefinitionId(inst?.definitionId);
166
+ return Boolean(parsed && parsed.id === id && (!parsed.version || parsed.version === version));
167
+ }
168
+
169
+ export function listMarketplaceNodeUsages(workspaceRoot, id, version, opts = {}) {
170
+ const usages = [];
171
+ if (!id || !version) return usages;
172
+ for (const flow of listWritableFlowDirs(workspaceRoot, opts)) {
173
+ const flowYamlPath = path.join(flow.flowDir, "flow.yaml");
174
+ const data = readYamlObject(flowYamlPath);
175
+ if (!data) continue;
176
+ const hits = [];
177
+ const deps = data.dependencies && typeof data.dependencies === "object" ? data.dependencies : {};
178
+ const nodeDeps = Array.isArray(deps.nodes) ? deps.nodes : [];
179
+ for (const dep of nodeDeps) {
180
+ if (depMatchesNode(dep, id, version)) {
181
+ hits.push({ instanceId: "dependencies.nodes", label: "dependency" });
182
+ }
183
+ }
184
+ const instances = data.instances && typeof data.instances === "object" ? data.instances : {};
185
+ for (const [instanceId, inst] of Object.entries(instances)) {
186
+ if (instanceMatchesNode(inst, id, version)) {
187
+ hits.push({ instanceId, label: inst?.label || instanceId });
188
+ }
189
+ }
190
+ if (hits.length > 0) {
191
+ usages.push({
192
+ flowId: flow.flowId,
193
+ flowSource: flow.flowSource,
194
+ archived: flow.archived,
195
+ instances: hits,
196
+ });
197
+ }
198
+ }
199
+ return usages;
200
+ }
201
+
103
202
  function findNodePackageDir(workspaceRoot, id, version) {
104
203
  const root = workspacePackageRoot(workspaceRoot);
105
204
  const nodeBase = path.join(root, "nodes", id);
@@ -230,7 +329,7 @@ export function listMarketplaceNodes(workspaceRoot, flowData = null) {
230
329
  return out.sort((a, b) => a.id.localeCompare(b.id) || a.version.localeCompare(b.version));
231
330
  }
232
331
 
233
- export function listMarketplacePackages(workspaceRoot) {
332
+ export function listMarketplacePackages(workspaceRoot, opts = {}) {
234
333
  const root = workspacePackageRoot(workspaceRoot);
235
334
  const nodes = listMarketplaceNodes(workspaceRoot).map((n) => ({
236
335
  id: n.id,
@@ -242,6 +341,7 @@ export function listMarketplacePackages(workspaceRoot) {
242
341
  outputs: n.output,
243
342
  packagedFiles: Array.isArray(n.packagedFiles) ? n.packagedFiles : [],
244
343
  packageDir: n.packageDir,
344
+ usage: listMarketplaceNodeUsages(workspaceRoot, n.id, n.version, opts),
245
345
  }));
246
346
  const collections = [];
247
347
  const collectionsRoot = path.join(root, "collections");
@@ -265,6 +365,28 @@ export function listMarketplacePackages(workspaceRoot) {
265
365
  return { nodes, collections };
266
366
  }
267
367
 
368
+ export function deleteMarketplaceNodePackage(workspaceRoot, id, version, opts = {}) {
369
+ const packageDir = resolveWorkspaceNodePackageDir(workspaceRoot, id, version);
370
+ if (!packageDir) return { ok: false, error: "Invalid marketplace node id or version" };
371
+ if (!fs.existsSync(path.join(packageDir, NODE_MANIFEST))) {
372
+ return { ok: false, error: `Marketplace node package not found: ${id}@${version}` };
373
+ }
374
+ const usage = listMarketplaceNodeUsages(workspaceRoot, id, version, opts);
375
+ if (usage.length > 0) {
376
+ return { ok: false, error: "Marketplace node is still used by flows", usage };
377
+ }
378
+ fs.rmSync(packageDir, { recursive: true, force: true });
379
+ const versionRoot = path.dirname(packageDir);
380
+ try {
381
+ if (fs.existsSync(versionRoot) && fs.readdirSync(versionRoot).length === 0) {
382
+ fs.rmdirSync(versionRoot);
383
+ }
384
+ } catch {
385
+ /* keep non-empty or unreadable parent */
386
+ }
387
+ return { ok: true, id, version, packageDir };
388
+ }
389
+
268
390
  export function writeFlowMarketplaceLock(workspaceRoot, flowDir, flowData) {
269
391
  if (!flowData || !flowData.instances || typeof flowData.instances !== "object") return null;
270
392
  const nodes = {};
@@ -74,7 +74,7 @@ import {
74
74
  import { runNodeScript } from "./pipeline-scripts.mjs";
75
75
  import { readFlowSchedule, writeFlowSchedule } from "./schedule-config.mjs";
76
76
  import { listScheduleStatuses } from "./scheduler.mjs";
77
- import { installFlowDependency, listMarketplacePackages, publishNodeFromInstance } from "./marketplace.mjs";
77
+ import { deleteMarketplaceNodePackage, installFlowDependency, listMarketplacePackages, publishNodeFromInstance } from "./marketplace.mjs";
78
78
  import {
79
79
  authSetupRequired,
80
80
  buildClearSessionCookie,
@@ -2280,13 +2280,29 @@ export function startUiServer({
2280
2280
 
2281
2281
  if (req.method === "GET" && url.pathname === "/api/marketplace/nodes") {
2282
2282
  try {
2283
- json(res, 200, listMarketplacePackages(root));
2283
+ json(res, 200, listMarketplacePackages(root, userCtx));
2284
2284
  } catch (e) {
2285
2285
  json(res, 500, { error: (e && e.message) || String(e) });
2286
2286
  }
2287
2287
  return;
2288
2288
  }
2289
2289
 
2290
+ if (req.method === "DELETE" && url.pathname === "/api/marketplace/node") {
2291
+ const id = url.searchParams.get("id") || "";
2292
+ const version = url.searchParams.get("version") || "";
2293
+ if (!id || !version) {
2294
+ json(res, 400, { ok: false, error: "Missing marketplace node id or version" });
2295
+ return;
2296
+ }
2297
+ try {
2298
+ const result = deleteMarketplaceNodePackage(root, id, version, userCtx);
2299
+ json(res, result.ok ? 200 : 400, result);
2300
+ } catch (e) {
2301
+ json(res, 500, { ok: false, error: (e && e.message) || String(e) });
2302
+ }
2303
+ return;
2304
+ }
2305
+
2290
2306
  if (req.method === "POST" && url.pathname === "/api/marketplace/install-node") {
2291
2307
  let payload;
2292
2308
  try {