@fieldwangai/agentflow 0.1.25 → 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.
@@ -14,6 +14,7 @@ import yaml from "js-yaml";
14
14
 
15
15
  import { getRunDir, LEGACY_NODES_DIR, PIPELINES_DIR, PROJECT_NODES_DIR } from "../lib/paths.mjs";
16
16
  import { getFlowDir } from "../lib/workspace.mjs";
17
+ import { isMarketplaceDefinitionId, resolveMarketplaceNodePackage, writeFlowMarketplaceLock } from "../lib/marketplace.mjs";
17
18
  import { loadAllExecIds, latestResultExecId, intermediateResultBasename, intermediateDirForNode, outputDirForNode } from "./get-exec-id.mjs";
18
19
 
19
20
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -39,6 +40,7 @@ function loadFlowDefinition(flowDir) {
39
40
  instances: data.instances && typeof data.instances === "object" ? data.instances : {},
40
41
  edges,
41
42
  ui: data.ui && typeof data.ui === "object" ? data.ui : {},
43
+ dependencies: data.dependencies && typeof data.dependencies === "object" ? data.dependencies : {},
42
44
  };
43
45
  } catch {
44
46
  return null;
@@ -48,6 +50,7 @@ function loadFlowDefinition(flowDir) {
48
50
  /** 由 definitionId 前缀推导 type */
49
51
  function definitionIdToType(definitionId) {
50
52
  const id = (definitionId || "").toLowerCase();
53
+ if (id.startsWith("marketplace:")) return "agent";
51
54
  if (id.startsWith("control_")) return "control";
52
55
  if (id.startsWith("agent_")) return "agent";
53
56
  if (id.startsWith("provide_")) return "provide";
@@ -62,8 +65,14 @@ function definitionIdToType(definitionId) {
62
65
  * @param {string} definitionName - 实例中引用的定义名(如 user_confirm_scope)
63
66
  * @returns {{ definitionId: string, definitionName: string }}
64
67
  */
65
- function resolveDefinitionIdFromNodeClass(flowDir, definitionName) {
66
- const workspaceRoot = path.resolve(flowDir, "..", "..", "..", "..");
68
+ function resolveDefinitionIdFromNodeClass(flowDir, definitionName, workspaceRoot, flowData) {
69
+ if (isMarketplaceDefinitionId(definitionName)) {
70
+ const resolved = resolveMarketplaceNodePackage(workspaceRoot, flowDir, definitionName, flowData);
71
+ return {
72
+ definitionId: resolved?.resolvedDefinitionId || definitionName,
73
+ definitionName,
74
+ };
75
+ }
67
76
  const fileName = definitionName.endsWith(".md") ? definitionName : `${definitionName}.md`;
68
77
  const flowNodesPath = path.join(flowDir, "nodes", fileName);
69
78
  const projectNodesNew = path.join(workspaceRoot, PROJECT_NODES_DIR, fileName);
@@ -85,7 +94,7 @@ function resolveDefinitionIdFromNodeClass(flowDir, definitionName) {
85
94
  }
86
95
 
87
96
  /** 从 loadFlowDefinition 结果得到 nodes 和 edges(与 readFlowMd 输出形状一致) */
88
- function readFlowFromYaml(flowDir) {
97
+ function readFlowFromYaml(flowDir, workspaceRoot = path.resolve(flowDir, "..", "..", "..", "..")) {
89
98
  const def = loadFlowDefinition(flowDir);
90
99
  if (!def) return { nodes: [], edges: [] };
91
100
  const instances = def.instances;
@@ -98,7 +107,7 @@ function readFlowFromYaml(flowDir) {
98
107
  const nodes = Array.from(nodeIds).map((id) => {
99
108
  const inst = instances[id] || {};
100
109
  const definitionName = inst.definitionId ?? id;
101
- const { definitionId } = resolveDefinitionIdFromNodeClass(flowDir, definitionName);
110
+ const { definitionId } = resolveDefinitionIdFromNodeClass(flowDir, definitionName, workspaceRoot, def);
102
111
  const type = definitionIdToType(definitionId);
103
112
  const label = inst.label != null ? String(inst.label) : id;
104
113
  const role =
@@ -613,7 +622,8 @@ function main() {
613
622
  process.exit(1);
614
623
  }
615
624
  try {
616
- const { nodes, edges } = readFlowFromYaml(flowDir);
625
+ writeFlowMarketplaceLock(workspaceRoot || path.resolve(flowDir, "..", "..", "..", ".."), flowDir, flowData);
626
+ const { nodes, edges } = readFlowFromYaml(flowDir, workspaceRoot || path.resolve(flowDir, "..", "..", "..", ".."));
617
627
  const { order: topoOrder, hasCycle } = topoSort(nodes, edges);
618
628
  const order = hasCycle ? nodes.map((n) => n.id) : topoOrder;
619
629
  const cycleNodes = hasCycle ? Array.from(findCycleNodes(nodes, edges)) : [];
@@ -124,6 +124,153 @@ function bashSingleQuote(s) {
124
124
  return "'" + String(s).replace(/'/g, "'\\''") + "'";
125
125
  }
126
126
 
127
+ function parseDurationMs(raw) {
128
+ const text = String(raw || "").trim();
129
+ if (!text) throw new Error("duration is required");
130
+ const m = text.match(/^(\d+(?:\.\d+)?)\s*(ms|millisecond|milliseconds|s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hour|hours|d|day|days)$/i);
131
+ if (!m) throw new Error(`Invalid duration: ${text}`);
132
+ const n = Number(m[1]);
133
+ if (!Number.isFinite(n) || n < 0) throw new Error(`Invalid duration: ${text}`);
134
+ const unit = m[2].toLowerCase();
135
+ const mult =
136
+ unit === "ms" || unit.startsWith("millisecond") ? 1 :
137
+ unit === "s" || unit === "sec" || unit === "secs" || unit.startsWith("second") ? 1000 :
138
+ unit === "m" || unit === "min" || unit === "mins" || unit.startsWith("minute") ? 60_000 :
139
+ unit === "h" || unit === "hr" || unit.startsWith("hour") ? 3_600_000 :
140
+ 86_400_000;
141
+ return Math.round(n * mult);
142
+ }
143
+
144
+ function zonedDateTimeToUtc(year, month, day, hour, minute, second, timezone) {
145
+ const guess = Date.UTC(year, month - 1, day, hour, minute, second);
146
+ const parts = new Intl.DateTimeFormat("en-US", {
147
+ timeZone: timezone,
148
+ year: "numeric",
149
+ month: "2-digit",
150
+ day: "2-digit",
151
+ hour: "2-digit",
152
+ minute: "2-digit",
153
+ second: "2-digit",
154
+ hour12: false,
155
+ }).formatToParts(new Date(guess)).reduce((acc, p) => {
156
+ if (p.type !== "literal") acc[p.type] = Number(p.value);
157
+ return acc;
158
+ }, {});
159
+ if (parts.hour === 24) parts.hour = 0;
160
+ const asIfUtc = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
161
+ return new Date(guess - (asIfUtc - guess));
162
+ }
163
+
164
+ function parseDateTime(raw, timezone = "Asia/Shanghai") {
165
+ const text = String(raw || "").trim();
166
+ if (!text) throw new Error("until/deadlineAt is required");
167
+ const ymd = text.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
168
+ if (ymd) {
169
+ return zonedDateTimeToUtc(
170
+ Number(ymd[1]),
171
+ Number(ymd[2]),
172
+ Number(ymd[3]),
173
+ Number(ymd[4] || 0),
174
+ Number(ymd[5] || 0),
175
+ Number(ymd[6] || 0),
176
+ timezone,
177
+ );
178
+ }
179
+ const direct = Date.parse(text);
180
+ if (Number.isFinite(direct)) return new Date(direct);
181
+
182
+ const hm = text.match(/^(tomorrow\s+)?(\d{1,2}):(\d{2})(?::(\d{2}))?$/i);
183
+ if (hm) {
184
+ const now = new Date();
185
+ const parts = new Intl.DateTimeFormat("en-US", {
186
+ timeZone: timezone,
187
+ year: "numeric",
188
+ month: "2-digit",
189
+ day: "2-digit",
190
+ }).formatToParts(now).reduce((acc, p) => {
191
+ if (p.type !== "literal") acc[p.type] = Number(p.value);
192
+ return acc;
193
+ }, {});
194
+ const date = zonedDateTimeToUtc(parts.year, parts.month, parts.day, Number(hm[2]), Number(hm[3]), Number(hm[4] || 0), timezone);
195
+ if (hm[1]) date.setUTCDate(date.getUTCDate() + 1);
196
+ return date;
197
+ }
198
+
199
+ throw new Error(`Invalid datetime: ${text}`);
200
+ }
201
+
202
+ function outputPathAbs(runDir, instanceId, execId, slotName) {
203
+ return path.join(runDir, outputDirForNode(instanceId), outputNodeBasename(instanceId, execId, slotName));
204
+ }
205
+
206
+ function writeOutputSlot(runDir, instanceId, execId, slotName, value) {
207
+ const p = outputPathAbs(runDir, instanceId, execId, slotName);
208
+ fs.mkdirSync(path.dirname(p), { recursive: true });
209
+ fs.writeFileSync(p, String(value ?? "") + "\n", "utf-8");
210
+ }
211
+
212
+ function writeWaitState(runDir, state) {
213
+ const legacyPath = path.join(runDir, "wait-state.json");
214
+ const registryPath = path.join(runDir, "wait-states.json");
215
+ let registry = { version: 1, flowName: state.flowName, uuid: state.uuid, waits: [] };
216
+ if (fs.existsSync(registryPath)) {
217
+ try {
218
+ const parsed = JSON.parse(fs.readFileSync(registryPath, "utf-8"));
219
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.waits)) registry = parsed;
220
+ } catch (_) {}
221
+ }
222
+ const waits = registry.waits.filter((w) => w && w.instanceId !== state.instanceId);
223
+ const wait = { ...state, id: state.id || state.instanceId };
224
+ waits.push(wait);
225
+ registry = {
226
+ ...registry,
227
+ version: 1,
228
+ flowName: state.flowName,
229
+ uuid: state.uuid,
230
+ updatedAt: new Date().toISOString(),
231
+ waits,
232
+ };
233
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
234
+ fs.writeFileSync(legacyPath, JSON.stringify(wait, null, 2) + "\n", "utf-8");
235
+ }
236
+
237
+ function readCancelFlag(runDir) {
238
+ for (const name of ["cancelled", "cancelled.json", "wait-cancelled.json"]) {
239
+ const p = path.join(runDir, name);
240
+ if (!fs.existsSync(p)) continue;
241
+ if (name.endsWith(".json")) {
242
+ try {
243
+ const data = JSON.parse(fs.readFileSync(p, "utf-8"));
244
+ if (data && (data.cancelled === true || data.status === "cancelled")) return true;
245
+ } catch (_) {}
246
+ } else {
247
+ return true;
248
+ }
249
+ }
250
+ return false;
251
+ }
252
+
253
+ function boolish(v) {
254
+ if (v == null || String(v).trim() === "") return false;
255
+ const s = String(v).trim();
256
+ if (fs.existsSync(s)) {
257
+ try {
258
+ return parseBool(fs.readFileSync(s, "utf-8").trim());
259
+ } catch (_) {
260
+ return false;
261
+ }
262
+ }
263
+ return parseBool(s);
264
+ }
265
+
266
+ function emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, suffix, content) {
267
+ const nodeIntermediateDir = path.join(runDir, intermediateDirForNode(instanceId));
268
+ fs.mkdirSync(nodeIntermediateDir, { recursive: true });
269
+ const promptPath = path.join(nodeIntermediateDir, `${instanceId}.${suffix}.prompt.md`);
270
+ fs.writeFileSync(promptPath, content, "utf-8");
271
+ return path.relative(workspaceRoot, promptPath).replace(/\\/g, "/");
272
+ }
273
+
127
274
  /**
128
275
  * 若为 tool_load_key / tool_save_key / tool_get_env,写入「直接执行 agentflow apply -ai run-tool-nodejs + 对应脚本」的 prompt,
129
276
  * key/value 从 getResolvedValues 的 resolvedInputs 读取并拼入命令。
@@ -380,6 +527,194 @@ function main() {
380
527
  return;
381
528
  }
382
529
 
530
+ if (definitionId === "control_delay" || definitionId === "control_wait_until") {
531
+ const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
532
+ if (!data.ok) {
533
+ console.error(JSON.stringify({ ok: false, error: `${definitionId}: getResolvedValues failed` }));
534
+ process.exit(1);
535
+ }
536
+ const inputs = data.resolvedInputs || {};
537
+ let wakeAt;
538
+ try {
539
+ if (definitionId === "control_delay") {
540
+ const duration = inputs.duration ?? inputs.value ?? "";
541
+ wakeAt = new Date(Date.now() + parseDurationMs(duration)).toISOString();
542
+ } else {
543
+ const timezone = inputs.timezone || "Asia/Shanghai";
544
+ wakeAt = parseDateTime(inputs.until ?? inputs.wakeAt ?? "", timezone).toISOString();
545
+ }
546
+ } catch (e) {
547
+ console.error(JSON.stringify({ ok: false, error: `${definitionId}: ${e.message}` }));
548
+ process.exit(1);
549
+ }
550
+
551
+ writeOutputSlot(runDir, instanceId, execId, "wakeAt", wakeAt);
552
+ writeWaitState(runDir, {
553
+ status: "waiting",
554
+ reason: definitionId,
555
+ flowName,
556
+ uuid,
557
+ instanceId,
558
+ execId,
559
+ wakeAt,
560
+ createdAt: new Date().toISOString(),
561
+ });
562
+ writeResult(
563
+ workspaceRoot,
564
+ flowName,
565
+ uuid,
566
+ instanceId,
567
+ { status: "pending", message: `等待至 ${wakeAt}` },
568
+ { execId, preserveBody: false },
569
+ );
570
+ const promptPath = emitLocalNoopPrompt(
571
+ workspaceRoot,
572
+ runDir,
573
+ instanceId,
574
+ "waiting",
575
+ `此节点为 ${definitionId},已写入 wait-state.json,等待 scheduler 在 ${wakeAt} 唤醒。\n`,
576
+ );
577
+ writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
578
+ logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "waiting", instanceId, wakeAt, definitionId });
579
+ console.log(JSON.stringify({
580
+ ok: true,
581
+ promptPath,
582
+ resultPath: resultPathRel,
583
+ execId,
584
+ subagent: "agentflow-node-executor",
585
+ optionalPromptPath: promptPath,
586
+ definitionId,
587
+ }));
588
+ return;
589
+ }
590
+
591
+ if (definitionId === "control_interval_loop") {
592
+ const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
593
+ if (!data.ok) {
594
+ console.error(JSON.stringify({ ok: false, error: "control_interval_loop: getResolvedValues failed" }));
595
+ process.exit(1);
596
+ }
597
+ const inputs = data.resolvedInputs || {};
598
+ let branch = "continue";
599
+ let message = "";
600
+ let wakeAt = "";
601
+ let expired = false;
602
+ try {
603
+ const timezone = inputs.timezone || "Asia/Shanghai";
604
+ const cancelled = boolish(inputs.cancelled) || readCancelFlag(runDir);
605
+ const done = boolish(inputs.done);
606
+ if (cancelled) {
607
+ branch = "cancelled";
608
+ message = "已取消";
609
+ } else if (done) {
610
+ branch = "done";
611
+ message = "已完成";
612
+ } else {
613
+ let deadline = null;
614
+ if (inputs.deadlineAt) {
615
+ deadline = parseDateTime(inputs.deadlineAt, timezone);
616
+ } else if (inputs.duration) {
617
+ const start = inputs.startAt ? parseDateTime(inputs.startAt, timezone) : new Date();
618
+ deadline = new Date(start.getTime() + parseDurationMs(inputs.duration));
619
+ }
620
+ expired = deadline ? Date.now() >= deadline.getTime() : false;
621
+ if (deadline) writeOutputSlot(runDir, instanceId, execId, "deadlineAt", deadline.toISOString());
622
+ if (expired) {
623
+ branch = "timeout";
624
+ message = `已超过截止时间 ${deadline.toISOString()}`;
625
+ } else {
626
+ const interval = inputs.interval || "10m";
627
+ wakeAt = new Date(Date.now() + parseDurationMs(interval)).toISOString();
628
+ branch = "continue";
629
+ message = `等待至 ${wakeAt}`;
630
+ }
631
+ }
632
+ } catch (e) {
633
+ console.error(JSON.stringify({ ok: false, error: `control_interval_loop: ${e.message}` }));
634
+ process.exit(1);
635
+ }
636
+ writeOutputSlot(runDir, instanceId, execId, "expired", expired ? "true" : "false");
637
+ if (wakeAt) writeOutputSlot(runDir, instanceId, execId, "wakeAt", wakeAt);
638
+ const promptPath = emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "interval-loop", `此节点为 control_interval_loop,分支:${branch}。\n`);
639
+ writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
640
+ if (wakeAt) {
641
+ writeWaitState(runDir, {
642
+ status: "waiting",
643
+ reason: definitionId,
644
+ flowName,
645
+ uuid,
646
+ instanceId,
647
+ execId,
648
+ branch,
649
+ wakeAt,
650
+ createdAt: new Date().toISOString(),
651
+ });
652
+ writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "pending", message, branch }, { execId, preserveBody: false });
653
+ } else {
654
+ writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message, branch }, { execId, preserveBody: false });
655
+ }
656
+ logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "interval-loop", instanceId, branch, wakeAt: wakeAt || undefined });
657
+ console.log(JSON.stringify({
658
+ ok: true,
659
+ promptPath,
660
+ resultPath: resultPathRel,
661
+ execId,
662
+ subagent: "agentflow-node-executor",
663
+ optionalPromptPath: promptPath,
664
+ definitionId,
665
+ }));
666
+ return;
667
+ }
668
+
669
+ if (definitionId === "control_deadline" || definitionId === "control_cancelled") {
670
+ const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
671
+ if (!data.ok) {
672
+ console.error(JSON.stringify({ ok: false, error: `${definitionId}: getResolvedValues failed` }));
673
+ process.exit(1);
674
+ }
675
+ const inputs = data.resolvedInputs || {};
676
+ let boolValue = false;
677
+ let message = "";
678
+ try {
679
+ if (definitionId === "control_deadline") {
680
+ const timezone = inputs.timezone || "Asia/Shanghai";
681
+ let deadline;
682
+ if (inputs.deadlineAt) {
683
+ deadline = parseDateTime(inputs.deadlineAt, timezone);
684
+ } else {
685
+ const start = inputs.startAt ? parseDateTime(inputs.startAt, timezone) : new Date();
686
+ deadline = new Date(start.getTime() + parseDurationMs(inputs.duration || ""));
687
+ }
688
+ const deadlineAt = deadline.toISOString();
689
+ boolValue = Date.now() >= deadline.getTime();
690
+ writeOutputSlot(runDir, instanceId, execId, "deadlineAt", deadlineAt);
691
+ writeOutputSlot(runDir, instanceId, execId, "expired", boolValue ? "true" : "false");
692
+ message = boolValue ? `已超过截止时间 ${deadlineAt}` : `未超过截止时间 ${deadlineAt}`;
693
+ } else {
694
+ boolValue = readCancelFlag(runDir);
695
+ writeOutputSlot(runDir, instanceId, execId, "cancelled", boolValue ? "true" : "false");
696
+ message = boolValue ? "已取消" : "未取消";
697
+ }
698
+ } catch (e) {
699
+ console.error(JSON.stringify({ ok: false, error: `${definitionId}: ${e.message}` }));
700
+ process.exit(1);
701
+ }
702
+ writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message }, { execId, preserveBody: false });
703
+ const promptPath = emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "local", `此节点为 ${definitionId},已本地计算完成。\n`);
704
+ writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
705
+ logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "local-control", instanceId, definitionId, value: boolValue });
706
+ console.log(JSON.stringify({
707
+ ok: true,
708
+ promptPath,
709
+ resultPath: resultPathRel,
710
+ execId,
711
+ subagent: "agentflow-node-executor",
712
+ optionalPromptPath: promptPath,
713
+ definitionId,
714
+ }));
715
+ return;
716
+ }
717
+
383
718
  const data = buildNodePrompt(workspaceRoot, flowName, uuid, instanceId, execId);
384
719
  if (!data.ok) {
385
720
  console.error(JSON.stringify({ ok: false, error: data.error || "build-node-prompt failed" }));
@@ -425,7 +760,7 @@ function main() {
425
760
  output.optionalPromptPath = anyOneResult.optionalPromptPath;
426
761
  output.directCommand = anyOneResult.directCommand;
427
762
  }
428
- } else if ((definitionId === "tool_nodejs" || definitionId === "control_toBool") && data.script) {
763
+ } else if ((definitionId === "tool_nodejs" || definitionId === "control_toBool" || String(definitionId || "").startsWith("marketplace:")) && data.script) {
429
764
  const toolNodejsResult = emitToolNodejsDirectCommand(workspaceRoot, flowName, uuid, instanceId, data.script, execId);
430
765
  if (toolNodejsResult) {
431
766
  output.optionalPromptPath = toolNodejsResult.optionalPromptPath;
@@ -0,0 +1,20 @@
1
+ ---
2
+ # 内置节点:取消状态判断
3
+ description: Check whether the current run/watch has been cancelled. Use cancelled output with control_if.
4
+ displayName: Cancelled
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ - type: text
10
+ name: watchId
11
+ default: ""
12
+ output:
13
+ - type: node
14
+ name: next
15
+ default: ""
16
+ - type: bool
17
+ name: cancelled
18
+ default: ""
19
+ ---
20
+ ${USER_PROMPT}
@@ -0,0 +1,32 @@
1
+ ---
2
+ # 内置节点:截止时间判断
3
+ description: Compute whether a deadline has expired. Use expired output with control_if.
4
+ displayName: Deadline
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ - type: text
10
+ name: startAt
11
+ default: ""
12
+ - type: text
13
+ name: duration
14
+ default: ""
15
+ - type: text
16
+ name: deadlineAt
17
+ default: ""
18
+ - type: text
19
+ name: timezone
20
+ default: "Asia/Shanghai"
21
+ output:
22
+ - type: node
23
+ name: next
24
+ default: ""
25
+ - type: bool
26
+ name: expired
27
+ default: ""
28
+ - type: text
29
+ name: deadlineAt
30
+ default: ""
31
+ ---
32
+ ${USER_PROMPT}
@@ -0,0 +1,20 @@
1
+ ---
2
+ # 内置节点:延迟等待
3
+ description: Persistently wait for a relative duration, then continue when scheduler resumes this run.
4
+ displayName: Delay
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ - type: text
10
+ name: duration
11
+ default: "10m"
12
+ output:
13
+ - type: node
14
+ name: next
15
+ default: ""
16
+ - type: text
17
+ name: wakeAt
18
+ default: ""
19
+ ---
20
+ ${USER_PROMPT}
@@ -0,0 +1,53 @@
1
+ ---
2
+ # 内置节点:间隔循环
3
+ description: Wait by interval and branch to continue, done, timeout, or cancelled for watch-style flows.
4
+ displayName: IntervalLoop
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ - type: bool
10
+ name: done
11
+ default: ""
12
+ - type: bool
13
+ name: cancelled
14
+ default: ""
15
+ - type: text
16
+ name: interval
17
+ default: "10m"
18
+ - type: text
19
+ name: startAt
20
+ default: ""
21
+ - type: text
22
+ name: duration
23
+ default: ""
24
+ - type: text
25
+ name: deadlineAt
26
+ default: ""
27
+ - type: text
28
+ name: timezone
29
+ default: "Asia/Shanghai"
30
+ output:
31
+ - type: node
32
+ name: continue
33
+ default: ""
34
+ - type: node
35
+ name: done
36
+ default: ""
37
+ - type: node
38
+ name: timeout
39
+ default: ""
40
+ - type: node
41
+ name: cancelled
42
+ default: ""
43
+ - type: text
44
+ name: wakeAt
45
+ default: ""
46
+ - type: bool
47
+ name: expired
48
+ default: ""
49
+ - type: text
50
+ name: deadlineAt
51
+ default: ""
52
+ ---
53
+ ${USER_PROMPT}
@@ -0,0 +1,23 @@
1
+ ---
2
+ # 内置节点:等待到指定时间
3
+ description: Persistently wait until an absolute time, then continue when scheduler resumes this run.
4
+ displayName: WaitUntil
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ - type: text
10
+ name: until
11
+ default: ""
12
+ - type: text
13
+ name: timezone
14
+ default: "Asia/Shanghai"
15
+ output:
16
+ - type: node
17
+ name: next
18
+ default: ""
19
+ - type: text
20
+ name: wakeAt
21
+ default: ""
22
+ ---
23
+ ${USER_PROMPT}