@fieldwangai/agentflow 0.1.26 → 0.1.27

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.
@@ -0,0 +1,542 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import yaml from "js-yaml";
4
+
5
+ import { MARKETPLACE_PACKAGES_DIR } from "./paths.mjs";
6
+
7
+ const NODE_MANIFEST = "node.yaml";
8
+ const COLLECTION_MANIFEST = "collection.yaml";
9
+ const LOCK_FILENAME = "agentflow.lock.json";
10
+
11
+ function workspacePackageRoot(workspaceRoot) {
12
+ return path.join(path.resolve(workspaceRoot), MARKETPLACE_PACKAGES_DIR);
13
+ }
14
+
15
+ export function workspaceMarketplaceRoot(workspaceRoot) {
16
+ return workspacePackageRoot(workspaceRoot);
17
+ }
18
+
19
+ export function parseMarketplaceDefinitionId(definitionId) {
20
+ const raw = String(definitionId || "").trim();
21
+ if (!raw.startsWith("marketplace:")) return null;
22
+ const spec = raw.slice("marketplace:".length).trim();
23
+ if (!spec) return null;
24
+ const at = spec.lastIndexOf("@");
25
+ if (at > 0) {
26
+ return { id: spec.slice(0, at), version: spec.slice(at + 1) || null };
27
+ }
28
+ return { id: spec, version: null };
29
+ }
30
+
31
+ export function isMarketplaceDefinitionId(definitionId) {
32
+ return Boolean(parseMarketplaceDefinitionId(definitionId));
33
+ }
34
+
35
+ function readYamlObject(filePath) {
36
+ if (!fs.existsSync(filePath)) return null;
37
+ try {
38
+ const data = yaml.load(fs.readFileSync(filePath, "utf-8"));
39
+ return data && typeof data === "object" && !Array.isArray(data) ? data : null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ function readJsonObject(filePath) {
46
+ if (!fs.existsSync(filePath)) return null;
47
+ try {
48
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
49
+ return data && typeof data === "object" && !Array.isArray(data) ? data : null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ function normalizeSlotList(value) {
56
+ if (!Array.isArray(value)) return [];
57
+ return value.map((slot) => {
58
+ if (!slot || typeof slot !== "object") return { type: "text", name: "", default: "" };
59
+ const type = slot.type != null ? String(slot.type).trim() : "text";
60
+ const name = slot.name != null ? String(slot.name).trim() : "";
61
+ const def = slot.default !== undefined ? slot.default : slot.value;
62
+ return {
63
+ type,
64
+ name,
65
+ default: def == null ? "" : String(def),
66
+ };
67
+ });
68
+ }
69
+
70
+ function normalizeManifest(raw, packageDir, source = "workspace") {
71
+ if (!raw || typeof raw !== "object") return null;
72
+ const id = raw.id != null ? String(raw.id).trim() : path.basename(packageDir);
73
+ const version = raw.version != null ? String(raw.version).trim() : "";
74
+ if (!id || !version) return null;
75
+ const runtime = raw.runtime && typeof raw.runtime === "object" ? raw.runtime : {};
76
+ return {
77
+ ...raw,
78
+ id,
79
+ version,
80
+ packageDir,
81
+ definitionId: `marketplace:${id}@${version}`,
82
+ displayName: raw.displayName != null ? String(raw.displayName) : raw.name != null ? String(raw.name) : id,
83
+ description: raw.description != null ? String(raw.description) : "",
84
+ input: normalizeSlotList(raw.input || raw.inputs),
85
+ output: normalizeSlotList(raw.output || raw.outputs),
86
+ runtime,
87
+ source,
88
+ };
89
+ }
90
+
91
+ function sortVersionsDesc(versions) {
92
+ return [...versions].sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: "base" }));
93
+ }
94
+
95
+ function listVersionDirs(baseDir) {
96
+ if (!fs.existsSync(baseDir)) return [];
97
+ return fs.readdirSync(baseDir, { withFileTypes: true })
98
+ .filter((entry) => entry.isDirectory())
99
+ .map((entry) => entry.name)
100
+ .filter(Boolean);
101
+ }
102
+
103
+ function findNodePackageDir(workspaceRoot, id, version) {
104
+ const root = workspacePackageRoot(workspaceRoot);
105
+ const nodeBase = path.join(root, "nodes", id);
106
+ if (version) {
107
+ const direct = path.join(nodeBase, version);
108
+ if (fs.existsSync(path.join(direct, NODE_MANIFEST))) return direct;
109
+ } else {
110
+ for (const v of sortVersionsDesc(listVersionDirs(nodeBase))) {
111
+ const direct = path.join(nodeBase, v);
112
+ if (fs.existsSync(path.join(direct, NODE_MANIFEST))) return direct;
113
+ }
114
+ }
115
+ return null;
116
+ }
117
+
118
+ function iterCollectionNodeDirs(workspaceRoot, collectionDeps = []) {
119
+ const root = workspacePackageRoot(workspaceRoot);
120
+ const out = [];
121
+ const deps = Array.isArray(collectionDeps) ? collectionDeps : [];
122
+ for (const dep of deps) {
123
+ const collectionId = typeof dep === "string" ? dep : dep && dep.id;
124
+ const collectionVersion = typeof dep === "object" && dep ? dep.version : null;
125
+ if (!collectionId) continue;
126
+ const collectionBase = path.join(root, "collections", String(collectionId));
127
+ const versions = collectionVersion ? [String(collectionVersion)] : sortVersionsDesc(listVersionDirs(collectionBase));
128
+ for (const version of versions) {
129
+ const nodesRoot = path.join(collectionBase, version, "nodes");
130
+ if (!fs.existsSync(nodesRoot)) continue;
131
+ for (const entry of fs.readdirSync(nodesRoot, { withFileTypes: true })) {
132
+ if (!entry.isDirectory()) continue;
133
+ const direct = path.join(nodesRoot, entry.name);
134
+ if (fs.existsSync(path.join(direct, NODE_MANIFEST))) {
135
+ out.push(direct);
136
+ continue;
137
+ }
138
+ for (const nodeVersion of sortVersionsDesc(listVersionDirs(direct))) {
139
+ const versioned = path.join(direct, nodeVersion);
140
+ if (fs.existsSync(path.join(versioned, NODE_MANIFEST))) out.push(versioned);
141
+ }
142
+ }
143
+ }
144
+ }
145
+ return out;
146
+ }
147
+
148
+ function dependencyVersion(flowData, id) {
149
+ const deps = flowData && typeof flowData === "object" ? flowData.dependencies : null;
150
+ const nodeDeps = deps && Array.isArray(deps.nodes) ? deps.nodes : [];
151
+ for (const dep of nodeDeps) {
152
+ if (typeof dep === "string") {
153
+ const parsed = parseMarketplaceDefinitionId(dep.startsWith("marketplace:") ? dep : `marketplace:${dep}`);
154
+ if (parsed && parsed.id === id) return parsed.version;
155
+ } else if (dep && typeof dep === "object" && dep.id === id) {
156
+ return dep.version != null ? String(dep.version) : null;
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+
162
+ function lockVersion(flowDir, id) {
163
+ const lock = readJsonObject(path.join(flowDir, LOCK_FILENAME));
164
+ const entry = lock && lock.nodes && lock.nodes[id];
165
+ if (typeof entry === "string") return entry;
166
+ if (entry && typeof entry === "object" && entry.version) return String(entry.version);
167
+ return null;
168
+ }
169
+
170
+ function collectionDeps(flowData) {
171
+ const deps = flowData && typeof flowData === "object" ? flowData.dependencies : null;
172
+ return deps && Array.isArray(deps.collections) ? deps.collections : [];
173
+ }
174
+
175
+ export function resolveMarketplaceNodePackage(workspaceRoot, flowDir, definitionId, flowData = null) {
176
+ const parsed = parseMarketplaceDefinitionId(definitionId);
177
+ if (!parsed) return null;
178
+ const id = parsed.id;
179
+ const requestedVersion = parsed.version || dependencyVersion(flowData, id) || lockVersion(flowDir, id);
180
+ let packageDir = findNodePackageDir(workspaceRoot, id, requestedVersion);
181
+
182
+ if (!packageDir) {
183
+ for (const dir of iterCollectionNodeDirs(workspaceRoot, collectionDeps(flowData))) {
184
+ const raw = readYamlObject(path.join(dir, NODE_MANIFEST));
185
+ const manifest = normalizeManifest(raw, dir, "collection");
186
+ if (!manifest || manifest.id !== id) continue;
187
+ if (requestedVersion && manifest.version !== requestedVersion) continue;
188
+ packageDir = dir;
189
+ break;
190
+ }
191
+ }
192
+
193
+ if (!packageDir) return null;
194
+ const manifest = normalizeManifest(readYamlObject(path.join(packageDir, NODE_MANIFEST)), packageDir);
195
+ if (!manifest) return null;
196
+ return {
197
+ ...manifest,
198
+ requestedDefinitionId: definitionId,
199
+ resolvedDefinitionId: `marketplace:${manifest.id}@${manifest.version}`,
200
+ };
201
+ }
202
+
203
+ export function listMarketplaceNodes(workspaceRoot, flowData = null) {
204
+ const root = workspacePackageRoot(workspaceRoot);
205
+ const out = [];
206
+ const seen = new Set();
207
+ const addManifest = (dir, source = "marketplace") => {
208
+ const manifest = normalizeManifest(readYamlObject(path.join(dir, NODE_MANIFEST)), dir, source);
209
+ if (!manifest) return;
210
+ const key = `${manifest.id}@${manifest.version}`;
211
+ if (seen.has(key)) return;
212
+ seen.add(key);
213
+ out.push(manifest);
214
+ };
215
+
216
+ const nodesRoot = path.join(root, "nodes");
217
+ if (fs.existsSync(nodesRoot)) {
218
+ for (const nodeEntry of fs.readdirSync(nodesRoot, { withFileTypes: true })) {
219
+ if (!nodeEntry.isDirectory()) continue;
220
+ const nodeBase = path.join(nodesRoot, nodeEntry.name);
221
+ for (const version of listVersionDirs(nodeBase)) {
222
+ addManifest(path.join(nodeBase, version), "marketplace");
223
+ }
224
+ }
225
+ }
226
+
227
+ for (const dir of iterCollectionNodeDirs(workspaceRoot, collectionDeps(flowData))) {
228
+ addManifest(dir, "collection");
229
+ }
230
+ return out.sort((a, b) => a.id.localeCompare(b.id) || a.version.localeCompare(b.version));
231
+ }
232
+
233
+ export function listMarketplacePackages(workspaceRoot) {
234
+ const root = workspacePackageRoot(workspaceRoot);
235
+ const nodes = listMarketplaceNodes(workspaceRoot).map((n) => ({
236
+ id: n.id,
237
+ version: n.version,
238
+ definitionId: n.definitionId,
239
+ displayName: n.displayName,
240
+ description: n.description,
241
+ inputs: n.input,
242
+ outputs: n.output,
243
+ packagedFiles: Array.isArray(n.packagedFiles) ? n.packagedFiles : [],
244
+ packageDir: n.packageDir,
245
+ }));
246
+ const collections = [];
247
+ const collectionsRoot = path.join(root, "collections");
248
+ if (fs.existsSync(collectionsRoot)) {
249
+ for (const entry of fs.readdirSync(collectionsRoot, { withFileTypes: true })) {
250
+ if (!entry.isDirectory()) continue;
251
+ const base = path.join(collectionsRoot, entry.name);
252
+ for (const version of listVersionDirs(base)) {
253
+ const dir = path.join(base, version);
254
+ const manifest = readYamlObject(path.join(dir, COLLECTION_MANIFEST)) || {};
255
+ collections.push({
256
+ id: manifest.id || entry.name,
257
+ version: manifest.version || version,
258
+ displayName: manifest.displayName || manifest.name || entry.name,
259
+ description: manifest.description || "",
260
+ packageDir: dir,
261
+ });
262
+ }
263
+ }
264
+ }
265
+ return { nodes, collections };
266
+ }
267
+
268
+ export function writeFlowMarketplaceLock(workspaceRoot, flowDir, flowData) {
269
+ if (!flowData || !flowData.instances || typeof flowData.instances !== "object") return null;
270
+ const nodes = {};
271
+ for (const inst of Object.values(flowData.instances)) {
272
+ const defId = inst && inst.definitionId;
273
+ const resolved = resolveMarketplaceNodePackage(workspaceRoot, flowDir, defId, flowData);
274
+ if (!resolved) continue;
275
+ nodes[resolved.id] = {
276
+ version: resolved.version,
277
+ resolved: path.relative(flowDir, resolved.packageDir).replace(/\\/g, "/"),
278
+ definitionId: resolved.resolvedDefinitionId,
279
+ };
280
+ }
281
+ const lockPath = path.join(flowDir, LOCK_FILENAME);
282
+ if (Object.keys(nodes).length === 0) return null;
283
+ const lock = readJsonObject(lockPath) || {};
284
+ const next = {
285
+ ...lock,
286
+ version: 1,
287
+ updatedAt: new Date().toISOString(),
288
+ nodes: { ...(lock.nodes && typeof lock.nodes === "object" ? lock.nodes : {}), ...nodes },
289
+ };
290
+ fs.writeFileSync(lockPath, JSON.stringify(next, null, 2) + "\n", "utf-8");
291
+ return next;
292
+ }
293
+
294
+ export function publishNodePackage(workspaceRoot, sourceDir) {
295
+ const src = path.resolve(sourceDir);
296
+ const manifestPath = path.join(src, NODE_MANIFEST);
297
+ const manifest = normalizeManifest(readYamlObject(manifestPath), src);
298
+ if (!manifest) return { ok: false, error: `Invalid node package manifest: ${manifestPath}` };
299
+ const dest = path.join(workspacePackageRoot(workspaceRoot), "nodes", manifest.id, manifest.version);
300
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
301
+ fs.rmSync(dest, { recursive: true, force: true });
302
+ fs.cpSync(src, dest, { recursive: true });
303
+ return { ok: true, id: manifest.id, version: manifest.version, packageDir: dest, definitionId: manifest.definitionId };
304
+ }
305
+
306
+ function safePackageId(raw) {
307
+ return String(raw || "")
308
+ .trim()
309
+ .toLowerCase()
310
+ .replace(/[^a-z0-9_-]+/g, "-")
311
+ .replace(/^-+|-+$/g, "");
312
+ }
313
+
314
+ function normalizeVersion(raw) {
315
+ const text = String(raw || "").trim();
316
+ return /^\d+\.\d+\.\d+(?:[-+][a-zA-Z0-9_.-]+)?$/.test(text) ? text : "1.0.0";
317
+ }
318
+
319
+ function tokenizeShellLike(command) {
320
+ const tokens = [];
321
+ let cur = "";
322
+ let quote = "";
323
+ let escaped = false;
324
+ for (const ch of String(command || "")) {
325
+ if (escaped) {
326
+ cur += ch;
327
+ escaped = false;
328
+ continue;
329
+ }
330
+ if (ch === "\\" && quote !== "'") {
331
+ cur += ch;
332
+ escaped = true;
333
+ continue;
334
+ }
335
+ if ((ch === "'" || ch === '"') && !quote) {
336
+ quote = ch;
337
+ cur += ch;
338
+ continue;
339
+ }
340
+ if (quote && ch === quote) {
341
+ quote = "";
342
+ cur += ch;
343
+ continue;
344
+ }
345
+ if (!quote && /\s/.test(ch)) {
346
+ if (cur) {
347
+ tokens.push(cur);
348
+ cur = "";
349
+ }
350
+ continue;
351
+ }
352
+ cur += ch;
353
+ }
354
+ if (cur) tokens.push(cur);
355
+ return tokens;
356
+ }
357
+
358
+ function stripShellQuotes(text) {
359
+ const s = String(text || "");
360
+ if (s.length >= 2 && ((s.startsWith("'") && s.endsWith("'")) || (s.startsWith('"') && s.endsWith('"')))) {
361
+ return s.slice(1, -1);
362
+ }
363
+ return s;
364
+ }
365
+
366
+ function replaceKnownPathVars(text, vars) {
367
+ let out = String(text || "");
368
+ for (const [key, value] of Object.entries(vars || {})) {
369
+ if (!value) continue;
370
+ out = out.replaceAll(`\${${key}}`, String(value));
371
+ }
372
+ return out;
373
+ }
374
+
375
+ function isInsideDir(filePath, dir) {
376
+ if (!filePath || !dir) return false;
377
+ const file = path.resolve(filePath);
378
+ const base = path.resolve(dir);
379
+ const baseWithSep = base.endsWith(path.sep) ? base : base + path.sep;
380
+ return file === base || file.startsWith(baseWithSep);
381
+ }
382
+
383
+ function uniqueRelativeScriptPath(destDir, baseName) {
384
+ const safeBase = path.basename(baseName || "script.mjs").replace(/[^a-zA-Z0-9_.-]+/g, "-") || "script.mjs";
385
+ let rel = path.join("scripts", safeBase).replace(/\\/g, "/");
386
+ let i = 2;
387
+ while (fs.existsSync(path.join(destDir, rel))) {
388
+ const ext = path.extname(safeBase);
389
+ const stem = ext ? safeBase.slice(0, -ext.length) : safeBase;
390
+ rel = path.join("scripts", `${stem}-${i}${ext}`).replace(/\\/g, "/");
391
+ i += 1;
392
+ }
393
+ return rel;
394
+ }
395
+
396
+ function scriptLanguageFor(interpreter, scriptPath) {
397
+ const cmd = path.basename(stripShellQuotes(interpreter || "")).toLowerCase();
398
+ const ext = path.extname(stripShellQuotes(scriptPath || "")).toLowerCase();
399
+ if (cmd.startsWith("node") || [".mjs", ".cjs", ".js"].includes(ext)) return "nodejs";
400
+ if (cmd.startsWith("python") || ext === ".py") return "python";
401
+ if (cmd === "bash" || cmd === "sh" || ext === ".sh") return "bash";
402
+ return cmd || "script";
403
+ }
404
+
405
+ function interpreterForLanguage(language) {
406
+ if (language === "nodejs") return "node";
407
+ if (language === "python") return "python3";
408
+ if (language === "bash") return "bash";
409
+ return "";
410
+ }
411
+
412
+ function createScriptPackagePlan(command, opts = {}) {
413
+ const tokens = tokenizeShellLike(command);
414
+ if (tokens.length < 2) return null;
415
+ const interpreter = stripShellQuotes(tokens[0]);
416
+ const scriptToken = tokens[1];
417
+ const scriptRaw = stripShellQuotes(scriptToken);
418
+ if (!/\.(mjs|cjs|js|py|sh)$/i.test(scriptRaw)) return null;
419
+
420
+ const vars = {
421
+ flowDir: opts.flowDir || "",
422
+ workspaceRoot: opts.workspaceRoot || "",
423
+ };
424
+ const resolvedScript = path.resolve(replaceKnownPathVars(scriptRaw, vars));
425
+ if (!fs.existsSync(resolvedScript) || !fs.statSync(resolvedScript).isFile()) return null;
426
+ if (
427
+ (opts.flowDir && isInsideDir(resolvedScript, opts.flowDir)) ||
428
+ (opts.workspaceRoot && isInsideDir(resolvedScript, opts.workspaceRoot))
429
+ ) {
430
+ return {
431
+ sourcePath: resolvedScript,
432
+ sourceToken: scriptToken,
433
+ language: scriptLanguageFor(interpreter, scriptRaw),
434
+ args: tokens.slice(2),
435
+ };
436
+ }
437
+ return null;
438
+ }
439
+
440
+ function buildPackagedRuntimeFromScript(command, destDir, opts = {}) {
441
+ const plan = createScriptPackagePlan(command, opts);
442
+ if (!plan) return null;
443
+ const entry = uniqueRelativeScriptPath(destDir, path.basename(plan.sourcePath));
444
+ const entryAbs = path.join(destDir, entry);
445
+ fs.mkdirSync(path.dirname(entryAbs), { recursive: true });
446
+ fs.copyFileSync(plan.sourcePath, entryAbs);
447
+ return {
448
+ runtime: {
449
+ type: "tool_nodejs",
450
+ language: plan.language,
451
+ entry,
452
+ args: plan.args,
453
+ },
454
+ packagedFiles: [{ from: plan.sourcePath, to: entry }],
455
+ };
456
+ }
457
+
458
+ export function publishNodeFromInstance(workspaceRoot, payload = {}, options = {}) {
459
+ const label = String(payload.label || payload.instanceId || "node").trim();
460
+ const id = safePackageId(payload.id || payload.packageId || label);
461
+ const version = normalizeVersion(payload.version || "1.0.0");
462
+ if (!id) return { ok: false, error: "Invalid package id" };
463
+
464
+ const inputs = normalizeSlotList(payload.inputs || payload.input).map((slot) => ({
465
+ type: slot.type,
466
+ name: slot.name,
467
+ default: slot.default,
468
+ }));
469
+ const outputs = normalizeSlotList(payload.outputs || payload.output).map((slot) => ({
470
+ type: slot.type,
471
+ name: slot.name,
472
+ default: slot.default,
473
+ }));
474
+ const script = String(payload.script || "").trim();
475
+ const body = String(payload.body || "").trim();
476
+ const description = String(payload.description || body || `Published from node ${label}`).trim();
477
+ const dest = path.join(workspacePackageRoot(workspaceRoot), "nodes", id, version);
478
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
479
+ fs.rmSync(dest, { recursive: true, force: true });
480
+ fs.mkdirSync(dest, { recursive: true });
481
+ const packagedScript = script
482
+ ? buildPackagedRuntimeFromScript(script, dest, {
483
+ flowDir: options.flowDir || payload.flowDir,
484
+ workspaceRoot,
485
+ })
486
+ : null;
487
+ const runtime = packagedScript?.runtime || (
488
+ script
489
+ ? {
490
+ type: "tool_nodejs",
491
+ language: "nodejs",
492
+ command: script,
493
+ }
494
+ : {
495
+ type: "agent_subAgent",
496
+ }
497
+ );
498
+ const manifest = {
499
+ id,
500
+ version,
501
+ name: label,
502
+ description,
503
+ runtime,
504
+ inputs,
505
+ outputs,
506
+ };
507
+ if (packagedScript?.packagedFiles?.length) manifest.packagedFiles = packagedScript.packagedFiles;
508
+ fs.writeFileSync(path.join(dest, NODE_MANIFEST), yaml.dump(manifest, { lineWidth: -1 }), "utf-8");
509
+ fs.writeFileSync(
510
+ path.join(dest, "README.md"),
511
+ `# ${label}\n\n${description || "Published from AgentFlow node properties."}\n`,
512
+ "utf-8",
513
+ );
514
+ if (body) fs.writeFileSync(path.join(dest, "prompt.md"), body + "\n", "utf-8");
515
+ return {
516
+ ok: true,
517
+ id,
518
+ version,
519
+ packageDir: dest,
520
+ definitionId: `marketplace:${id}@${version}`,
521
+ packagedFiles: packagedScript?.packagedFiles || [],
522
+ };
523
+ }
524
+
525
+ export function installFlowDependency(workspaceRoot, flowDir, spec) {
526
+ const parsed = parseMarketplaceDefinitionId(spec.startsWith("marketplace:") ? spec : `marketplace:${spec}`);
527
+ if (!parsed) return { ok: false, error: `Invalid marketplace node spec: ${spec}` };
528
+ const resolved = resolveMarketplaceNodePackage(workspaceRoot, flowDir, `marketplace:${parsed.id}${parsed.version ? `@${parsed.version}` : ""}`, { dependencies: {} });
529
+ if (!resolved) return { ok: false, error: `Marketplace node not found: ${spec}` };
530
+
531
+ const flowYamlPath = path.join(flowDir, "flow.yaml");
532
+ const data = readYamlObject(flowYamlPath);
533
+ if (!data) return { ok: false, error: `Invalid flow.yaml: ${flowYamlPath}` };
534
+ const deps = data.dependencies && typeof data.dependencies === "object" ? data.dependencies : {};
535
+ const nodes = Array.isArray(deps.nodes) ? deps.nodes : [];
536
+ const exists = nodes.some((item) => (typeof item === "string" ? item === resolved.id : item && item.id === resolved.id));
537
+ if (!exists) nodes.push({ id: resolved.id, version: resolved.version });
538
+ data.dependencies = { ...deps, nodes };
539
+ fs.writeFileSync(flowYamlPath, yaml.dump(data, { lineWidth: -1 }), "utf-8");
540
+ writeFlowMarketplaceLock(workspaceRoot, flowDir, data);
541
+ return { ok: true, id: resolved.id, version: resolved.version, definitionId: `marketplace:${resolved.id}@${resolved.version}` };
542
+ }
package/bin/lib/paths.mjs CHANGED
@@ -170,6 +170,8 @@ export const LEGACY_PIPELINES_DIR = ".cursor/agentflow/pipelines";
170
170
  export const WORKSPACE_AGENTFLOW_ROOT = ".workspace/agentflow";
171
171
  /** 项目内自定义节点 .md 目录(主路径;与包内 builtin/nodes 区分) */
172
172
  export const PROJECT_NODES_DIR = ".workspace/agentflow/nodes";
173
+ /** Workspace local marketplace package store. */
174
+ export const MARKETPLACE_PACKAGES_DIR = ".workspace/agentflow/marketplace/packages";
173
175
  /** 旧版项目内节点目录;仅用于读取回退 */
174
176
  export const LEGACY_NODES_DIR = ".cursor/agentflow/nodes";
175
177
  /** Web UI 模型映射等项目内配置(主路径) */
@@ -197,6 +199,11 @@ export const CURSOR_NON_MODEL_PATTERNS = [
197
199
  /** 仅 pre+post、不执行任何命令的节点类型 */
198
200
  export const LOCAL_ONLY_DEFINITION_IDS = new Set([
199
201
  "control_if",
202
+ "control_delay",
203
+ "control_wait_until",
204
+ "control_deadline",
205
+ "control_cancelled",
206
+ "control_interval_loop",
200
207
  "control_start",
201
208
  "control_end",
202
209
  "tool_print",