@particle-academy/fancy-flow 0.65.2 → 0.66.0
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/README.md +100 -3
- package/dist/{FlowViewer-BI_AyyvU.d.ts → FlowViewer-BCc_BvuD.d.ts} +1 -1
- package/dist/{FlowViewer-DABx-6A-.d.cts → FlowViewer-CoAJ3o52.d.cts} +1 -1
- package/dist/{HumanPrompt-Bpp3JiVH.d.cts → HumanPrompt--pXDOyYC.d.cts} +2 -2
- package/dist/{HumanPrompt-BKThiyjB.d.ts → HumanPrompt-DXm_FP9z.d.ts} +2 -2
- package/dist/chunk-2QXKDLGT.js +58 -0
- package/dist/chunk-2QXKDLGT.js.map +1 -0
- package/dist/{chunk-77V4QC6Y.js → chunk-B7HKJJVV.js} +3 -3
- package/dist/{chunk-77V4QC6Y.js.map → chunk-B7HKJJVV.js.map} +1 -1
- package/dist/{chunk-MBVX4ZRB.js → chunk-FNYLKSFJ.js} +3 -3
- package/dist/{chunk-MBVX4ZRB.js.map → chunk-FNYLKSFJ.js.map} +1 -1
- package/dist/{chunk-JF6WCRBU.js → chunk-HQGOFGAL.js} +861 -40
- package/dist/chunk-HQGOFGAL.js.map +1 -0
- package/dist/{chunk-5H54OTKT.js → chunk-IEYCNFXZ.js} +3 -3
- package/dist/{chunk-5H54OTKT.js.map → chunk-IEYCNFXZ.js.map} +1 -1
- package/dist/{chunk-W5DPKJY4.js → chunk-MTONBM53.js} +4 -4
- package/dist/chunk-MTONBM53.js.map +1 -0
- package/dist/{chunk-RIAFHQT5.js → chunk-PNHZHEWE.js} +3 -3
- package/dist/{chunk-RIAFHQT5.js.map → chunk-PNHZHEWE.js.map} +1 -1
- package/dist/{chunk-EO6444T2.js → chunk-UCCTXJXL.js} +4 -4
- package/dist/{chunk-EO6444T2.js.map → chunk-UCCTXJXL.js.map} +1 -1
- package/dist/connectors.d.cts +2 -2
- package/dist/connectors.d.ts +2 -2
- package/dist/durable/index.d.cts +2 -2
- package/dist/durable/index.d.ts +2 -2
- package/dist/durable.cjs +837 -47
- package/dist/durable.cjs.map +1 -1
- package/dist/durable.js +2 -3
- package/dist/durable.js.map +1 -1
- package/dist/engine.cjs +843 -132
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.cts +6 -7
- package/dist/engine.d.ts +6 -7
- package/dist/engine.js +5 -6
- package/dist/engine.js.map +1 -1
- package/dist/fields/react-fancy.d.cts +3 -3
- package/dist/fields/react-fancy.d.ts +3 -3
- package/dist/index.cjs +853 -140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -36
- package/dist/index.d.ts +65 -36
- package/dist/index.js +19 -18
- package/dist/index.js.map +1 -1
- package/dist/layout/index.d.cts +1 -1
- package/dist/layout/index.d.ts +1 -1
- package/dist/llm/prism.cjs.map +1 -1
- package/dist/llm/prism.d.cts +1 -2
- package/dist/llm/prism.d.ts +1 -2
- package/dist/llm/prism.js +1 -1
- package/dist/llm/vercel-ai.cjs.map +1 -1
- package/dist/llm/vercel-ai.d.cts +1 -2
- package/dist/llm/vercel-ai.d.ts +1 -2
- package/dist/llm/vercel-ai.js +1 -1
- package/dist/registry/index.d.cts +5 -6
- package/dist/registry/index.d.ts +5 -6
- package/dist/{registry-CntAdUcY.d.cts → registry-BcgllSk2.d.cts} +1 -1
- package/dist/{registry-DYoV-MQi.d.ts → registry-h-2QswZ5.d.ts} +1 -1
- package/dist/registry.cjs +867 -37
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +3 -3
- package/dist/{run-cohort-C1nRh_mi.d.ts → run-cohort-CjUBWg6X.d.ts} +2 -2
- package/dist/{run-cohort-CBRAUj-z.d.cts → run-cohort-DL9JzzPU.d.cts} +2 -2
- package/dist/{run-flow-2XsHBwpd.d.cts → run-flow-B_8hgO5_.d.cts} +1 -1
- package/dist/{run-flow-D7AOCcfE.d.ts → run-flow-CxEGBOxd.d.ts} +1 -1
- package/dist/runtime/index.d.cts +5 -5
- package/dist/runtime/index.d.ts +5 -5
- package/dist/runtime.cjs +837 -36
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +4 -4
- package/dist/schema/index.d.cts +19 -6
- package/dist/schema/index.d.ts +19 -6
- package/dist/schema.cjs +837 -36
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +4 -4
- package/dist/screens.cjs +837 -36
- package/dist/screens.cjs.map +1 -1
- package/dist/screens.d.cts +2 -2
- package/dist/screens.d.ts +2 -2
- package/dist/screens.js +5 -5
- package/dist/terminal/fancy-term-host.cjs +99 -0
- package/dist/terminal/fancy-term-host.cjs.map +1 -0
- package/dist/terminal/fancy-term-host.d.cts +76 -0
- package/dist/terminal/fancy-term-host.d.ts +76 -0
- package/dist/terminal/fancy-term-host.js +92 -0
- package/dist/terminal/fancy-term-host.js.map +1 -0
- package/dist/types-B-Syk9-M.d.cts +620 -0
- package/dist/types-B-Syk9-M.d.ts +620 -0
- package/dist/{types-D71SKA5A.d.cts → types-C2ATTmjN.d.cts} +1 -1
- package/dist/{types-Ckhz-YwC.d.ts → types-VunLGqrn.d.ts} +1 -1
- package/dist/ux.cjs +837 -36
- package/dist/ux.cjs.map +1 -1
- package/dist/ux.d.cts +2 -2
- package/dist/ux.d.ts +2 -2
- package/dist/ux.js +2 -2
- package/package.json +11 -1
- package/dist/capabilities-BYa5p5jw.d.cts +0 -112
- package/dist/capabilities-COOXRiNL.d.ts +0 -112
- package/dist/chunk-JF6WCRBU.js.map +0 -1
- package/dist/chunk-UM4C46AF.js +0 -103
- package/dist/chunk-UM4C46AF.js.map +0 -1
- package/dist/chunk-USL4FMFU.js +0 -41
- package/dist/chunk-USL4FMFU.js.map +0 -1
- package/dist/chunk-W5DPKJY4.js.map +0 -1
- package/dist/types-JFYjPJAG.d.cts +0 -333
- package/dist/types-JFYjPJAG.d.ts +0 -333
package/dist/registry.cjs
CHANGED
|
@@ -2216,38 +2216,38 @@ function attrRemoveNS2(fullname) {
|
|
|
2216
2216
|
this.removeAttributeNS(fullname.space, fullname.local);
|
|
2217
2217
|
};
|
|
2218
2218
|
}
|
|
2219
|
-
function attrConstant2(name,
|
|
2219
|
+
function attrConstant2(name, interpolate2, value1) {
|
|
2220
2220
|
var string00, string1 = value1 + "", interpolate0;
|
|
2221
2221
|
return function() {
|
|
2222
2222
|
var string0 = this.getAttribute(name);
|
|
2223
|
-
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 =
|
|
2223
|
+
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 = interpolate2(string00 = string0, value1);
|
|
2224
2224
|
};
|
|
2225
2225
|
}
|
|
2226
|
-
function attrConstantNS2(fullname,
|
|
2226
|
+
function attrConstantNS2(fullname, interpolate2, value1) {
|
|
2227
2227
|
var string00, string1 = value1 + "", interpolate0;
|
|
2228
2228
|
return function() {
|
|
2229
2229
|
var string0 = this.getAttributeNS(fullname.space, fullname.local);
|
|
2230
|
-
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 =
|
|
2230
|
+
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 = interpolate2(string00 = string0, value1);
|
|
2231
2231
|
};
|
|
2232
2232
|
}
|
|
2233
|
-
function attrFunction2(name,
|
|
2233
|
+
function attrFunction2(name, interpolate2, value) {
|
|
2234
2234
|
var string00, string10, interpolate0;
|
|
2235
2235
|
return function() {
|
|
2236
2236
|
var string0, value1 = value(this), string1;
|
|
2237
2237
|
if (value1 == null) return void this.removeAttribute(name);
|
|
2238
2238
|
string0 = this.getAttribute(name);
|
|
2239
2239
|
string1 = value1 + "";
|
|
2240
|
-
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 =
|
|
2240
|
+
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 = interpolate2(string00 = string0, value1));
|
|
2241
2241
|
};
|
|
2242
2242
|
}
|
|
2243
|
-
function attrFunctionNS2(fullname,
|
|
2243
|
+
function attrFunctionNS2(fullname, interpolate2, value) {
|
|
2244
2244
|
var string00, string10, interpolate0;
|
|
2245
2245
|
return function() {
|
|
2246
2246
|
var string0, value1 = value(this), string1;
|
|
2247
2247
|
if (value1 == null) return void this.removeAttributeNS(fullname.space, fullname.local);
|
|
2248
2248
|
string0 = this.getAttributeNS(fullname.space, fullname.local);
|
|
2249
2249
|
string1 = value1 + "";
|
|
2250
|
-
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 =
|
|
2250
|
+
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 = interpolate2(string00 = string0, value1));
|
|
2251
2251
|
};
|
|
2252
2252
|
}
|
|
2253
2253
|
function attr_default2(name, value) {
|
|
@@ -2457,11 +2457,11 @@ function selection_default2() {
|
|
|
2457
2457
|
}
|
|
2458
2458
|
|
|
2459
2459
|
// node_modules/d3-transition/src/transition/style.js
|
|
2460
|
-
function styleNull(name,
|
|
2460
|
+
function styleNull(name, interpolate2) {
|
|
2461
2461
|
var string00, string10, interpolate0;
|
|
2462
2462
|
return function() {
|
|
2463
2463
|
var string0 = styleValue(this, name), string1 = (this.style.removeProperty(name), styleValue(this, name));
|
|
2464
|
-
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : interpolate0 =
|
|
2464
|
+
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : interpolate0 = interpolate2(string00 = string0, string10 = string1);
|
|
2465
2465
|
};
|
|
2466
2466
|
}
|
|
2467
2467
|
function styleRemove2(name) {
|
|
@@ -2469,19 +2469,19 @@ function styleRemove2(name) {
|
|
|
2469
2469
|
this.style.removeProperty(name);
|
|
2470
2470
|
};
|
|
2471
2471
|
}
|
|
2472
|
-
function styleConstant2(name,
|
|
2472
|
+
function styleConstant2(name, interpolate2, value1) {
|
|
2473
2473
|
var string00, string1 = value1 + "", interpolate0;
|
|
2474
2474
|
return function() {
|
|
2475
2475
|
var string0 = styleValue(this, name);
|
|
2476
|
-
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 =
|
|
2476
|
+
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 = interpolate2(string00 = string0, value1);
|
|
2477
2477
|
};
|
|
2478
2478
|
}
|
|
2479
|
-
function styleFunction2(name,
|
|
2479
|
+
function styleFunction2(name, interpolate2, value) {
|
|
2480
2480
|
var string00, string10, interpolate0;
|
|
2481
2481
|
return function() {
|
|
2482
2482
|
var string0 = styleValue(this, name), value1 = value(this), string1 = value1 + "";
|
|
2483
2483
|
if (value1 == null) string1 = value1 = (this.style.removeProperty(name), styleValue(this, name));
|
|
2484
|
-
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 =
|
|
2484
|
+
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 = interpolate2(string00 = string0, value1));
|
|
2485
2485
|
};
|
|
2486
2486
|
}
|
|
2487
2487
|
function styleMaybeRemove(id2, name) {
|
|
@@ -2583,9 +2583,9 @@ function transition_default() {
|
|
|
2583
2583
|
// node_modules/d3-transition/src/transition/end.js
|
|
2584
2584
|
function end_default() {
|
|
2585
2585
|
var on0, on1, that = this, id2 = that._id, size = that.size();
|
|
2586
|
-
return new Promise(function(
|
|
2586
|
+
return new Promise(function(resolve2, reject) {
|
|
2587
2587
|
var cancel = { value: reject }, end = { value: function() {
|
|
2588
|
-
if (--size === 0)
|
|
2588
|
+
if (--size === 0) resolve2();
|
|
2589
2589
|
} };
|
|
2590
2590
|
that.each(function() {
|
|
2591
2591
|
var schedule = set2(this, id2), on = schedule.on;
|
|
@@ -2597,7 +2597,7 @@ function end_default() {
|
|
|
2597
2597
|
}
|
|
2598
2598
|
schedule.on = on1;
|
|
2599
2599
|
});
|
|
2600
|
-
if (size === 0)
|
|
2600
|
+
if (size === 0) resolve2();
|
|
2601
2601
|
});
|
|
2602
2602
|
}
|
|
2603
2603
|
|
|
@@ -2799,7 +2799,7 @@ function defaultConstrain(transform2, extent, translateExtent) {
|
|
|
2799
2799
|
);
|
|
2800
2800
|
}
|
|
2801
2801
|
function zoom_default2() {
|
|
2802
|
-
var filter2 = defaultFilter2, extent = defaultExtent, constrain = defaultConstrain, wheelDelta2 = defaultWheelDelta, touchable = defaultTouchable2, scaleExtent = [0, Infinity], translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], duration = 250,
|
|
2802
|
+
var filter2 = defaultFilter2, extent = defaultExtent, constrain = defaultConstrain, wheelDelta2 = defaultWheelDelta, touchable = defaultTouchable2, scaleExtent = [0, Infinity], translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], duration = 250, interpolate2 = zoom_default, listeners3 = dispatch_default("start", "zoom", "end"), touchstarting, touchfirst, touchending, touchDelay = 500, wheelDelay = 150, clickDistance2 = 0, tapDistance = 10;
|
|
2803
2803
|
function zoom(selection2) {
|
|
2804
2804
|
selection2.property("__zoom", defaultTransform).on("wheel.zoom", wheeled, { passive: false }).on("mousedown.zoom", mousedowned).on("dblclick.zoom", dblclicked).filter(touchable).on("touchstart.zoom", touchstarted).on("touchmove.zoom", touchmoved).on("touchend.zoom touchcancel.zoom", touchended).style("-webkit-tap-highlight-color", "rgba(0,0,0,0)");
|
|
2805
2805
|
}
|
|
@@ -2860,7 +2860,7 @@ function zoom_default2() {
|
|
|
2860
2860
|
}).on("interrupt.zoom end.zoom", function() {
|
|
2861
2861
|
gesture(this, arguments).event(event).end();
|
|
2862
2862
|
}).tween("zoom", function() {
|
|
2863
|
-
var that = this, args = arguments, g = gesture(that, args).event(event), e = extent.apply(that, args), p = point == null ? centroid(e) : typeof point === "function" ? point.apply(that, args) : point, w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]), a = that.__zoom, b = typeof transform2 === "function" ? transform2.apply(that, args) : transform2, i =
|
|
2863
|
+
var that = this, args = arguments, g = gesture(that, args).event(event), e = extent.apply(that, args), p = point == null ? centroid(e) : typeof point === "function" ? point.apply(that, args) : point, w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]), a = that.__zoom, b = typeof transform2 === "function" ? transform2.apply(that, args) : transform2, i = interpolate2(a.invert(p).concat(w / a.k), b.invert(p).concat(w / b.k));
|
|
2864
2864
|
return function(t) {
|
|
2865
2865
|
if (t === 1) t = b;
|
|
2866
2866
|
else {
|
|
@@ -3065,7 +3065,7 @@ function zoom_default2() {
|
|
|
3065
3065
|
return arguments.length ? (duration = +_, zoom) : duration;
|
|
3066
3066
|
};
|
|
3067
3067
|
zoom.interpolate = function(_) {
|
|
3068
|
-
return arguments.length ? (
|
|
3068
|
+
return arguments.length ? (interpolate2 = _, zoom) : interpolate2;
|
|
3069
3069
|
};
|
|
3070
3070
|
zoom.on = function() {
|
|
3071
3071
|
var value = listeners3.on.apply(listeners3, arguments);
|
|
@@ -3558,13 +3558,13 @@ function areSetsEqual(a, b) {
|
|
|
3558
3558
|
return true;
|
|
3559
3559
|
}
|
|
3560
3560
|
function withResolvers() {
|
|
3561
|
-
let
|
|
3561
|
+
let resolve2;
|
|
3562
3562
|
let reject;
|
|
3563
3563
|
const promise = new Promise((res, rej) => {
|
|
3564
|
-
|
|
3564
|
+
resolve2 = res;
|
|
3565
3565
|
reject = rej;
|
|
3566
3566
|
});
|
|
3567
|
-
return { promise, resolve, reject };
|
|
3567
|
+
return { promise, resolve: resolve2, reject };
|
|
3568
3568
|
}
|
|
3569
3569
|
function mergeAriaLabelConfig(partial) {
|
|
3570
3570
|
return { ...defaultAriaLabelConfig, ...partial || {} };
|
|
@@ -5214,8 +5214,8 @@ function XYPanZoom({ domNode, minZoom, maxZoom, translateExtent, viewport, onPan
|
|
|
5214
5214
|
d3ZoomInstance.wheelDelta(wheelDelta);
|
|
5215
5215
|
async function setTransform(transform2, options) {
|
|
5216
5216
|
if (d3Selection) {
|
|
5217
|
-
return new Promise((
|
|
5218
|
-
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).transform(getD3Transition(d3Selection, options?.duration, options?.ease, () =>
|
|
5217
|
+
return new Promise((resolve2) => {
|
|
5218
|
+
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).transform(getD3Transition(d3Selection, options?.duration, options?.ease, () => resolve2(true)), transform2);
|
|
5219
5219
|
});
|
|
5220
5220
|
}
|
|
5221
5221
|
return false;
|
|
@@ -5317,16 +5317,16 @@ function XYPanZoom({ domNode, minZoom, maxZoom, translateExtent, viewport, onPan
|
|
|
5317
5317
|
}
|
|
5318
5318
|
async function scaleTo(zoom, options) {
|
|
5319
5319
|
if (d3Selection) {
|
|
5320
|
-
return new Promise((
|
|
5321
|
-
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).scaleTo(getD3Transition(d3Selection, options?.duration, options?.ease, () =>
|
|
5320
|
+
return new Promise((resolve2) => {
|
|
5321
|
+
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).scaleTo(getD3Transition(d3Selection, options?.duration, options?.ease, () => resolve2(true)), zoom);
|
|
5322
5322
|
});
|
|
5323
5323
|
}
|
|
5324
5324
|
return false;
|
|
5325
5325
|
}
|
|
5326
5326
|
async function scaleBy(factor, options) {
|
|
5327
5327
|
if (d3Selection) {
|
|
5328
|
-
return new Promise((
|
|
5329
|
-
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).scaleBy(getD3Transition(d3Selection, options?.duration, options?.ease, () =>
|
|
5328
|
+
return new Promise((resolve2) => {
|
|
5329
|
+
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).scaleBy(getD3Transition(d3Selection, options?.duration, options?.ease, () => resolve2(true)), factor);
|
|
5330
5330
|
});
|
|
5331
5331
|
}
|
|
5332
5332
|
return false;
|
|
@@ -9170,11 +9170,11 @@ function NoteNodeInner(props) {
|
|
|
9170
9170
|
const config = data.config ?? {};
|
|
9171
9171
|
const api = useFlowEditorOptional();
|
|
9172
9172
|
const hasConfigText = typeof config.text === "string" && config.text.length > 0;
|
|
9173
|
-
const
|
|
9173
|
+
const text3 = hasConfigText ? String(config.text) : typeof data.body === "string" ? data.body : typeof data.text === "string" ? data.text : "";
|
|
9174
9174
|
const title = typeof config.title === "string" && config.title || (!hasConfigText && typeof data.body === "string" && typeof data.label === "string" ? data.label : "");
|
|
9175
9175
|
const skin = NOTE_COLORS[coerceColor(config.color)];
|
|
9176
9176
|
const [editing, setEditing] = ReactExports.useState(false);
|
|
9177
|
-
const [draft, setDraft] = ReactExports.useState(
|
|
9177
|
+
const [draft, setDraft] = ReactExports.useState(text3);
|
|
9178
9178
|
const areaRef = ReactExports.useRef(null);
|
|
9179
9179
|
ReactExports.useEffect(() => {
|
|
9180
9180
|
if (editing && areaRef.current) {
|
|
@@ -9185,7 +9185,7 @@ function NoteNodeInner(props) {
|
|
|
9185
9185
|
}, [editing]);
|
|
9186
9186
|
const commit = () => {
|
|
9187
9187
|
setEditing(false);
|
|
9188
|
-
if (!api || draft ===
|
|
9188
|
+
if (!api || draft === text3) return;
|
|
9189
9189
|
const current = api.nodes.find((n) => n.id === props.id);
|
|
9190
9190
|
if (!current) return;
|
|
9191
9191
|
api.updateNode({
|
|
@@ -9199,7 +9199,7 @@ function NoteNodeInner(props) {
|
|
|
9199
9199
|
className: ["ff-note", props.selected ? "ff-note--selected" : ""].filter(Boolean).join(" "),
|
|
9200
9200
|
style: { background: skin.bg, borderColor: skin.border },
|
|
9201
9201
|
onDoubleClick: api ? () => {
|
|
9202
|
-
setDraft(
|
|
9202
|
+
setDraft(text3);
|
|
9203
9203
|
setEditing(true);
|
|
9204
9204
|
} : void 0,
|
|
9205
9205
|
children: [
|
|
@@ -9218,7 +9218,7 @@ function NoteNodeInner(props) {
|
|
|
9218
9218
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) commit();
|
|
9219
9219
|
}
|
|
9220
9220
|
}
|
|
9221
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-note__body", children:
|
|
9221
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-note__body", children: text3 || /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-note__placeholder", children: api ? "Double-click to write a note\u2026" : "Note" }) })
|
|
9222
9222
|
]
|
|
9223
9223
|
}
|
|
9224
9224
|
);
|
|
@@ -9236,6 +9236,16 @@ function registerLlmClient(client) {
|
|
|
9236
9236
|
function getLlmClient() {
|
|
9237
9237
|
return llmClient;
|
|
9238
9238
|
}
|
|
9239
|
+
var terminalHost = null;
|
|
9240
|
+
function registerTerminalHost(host) {
|
|
9241
|
+
terminalHost = host;
|
|
9242
|
+
return () => {
|
|
9243
|
+
if (terminalHost === host) terminalHost = null;
|
|
9244
|
+
};
|
|
9245
|
+
}
|
|
9246
|
+
function getTerminalHost() {
|
|
9247
|
+
return terminalHost;
|
|
9248
|
+
}
|
|
9239
9249
|
function isResolutionFailure(value) {
|
|
9240
9250
|
return typeof value === "object" && value !== null && "reason" in value && value.reason !== void 0;
|
|
9241
9251
|
}
|
|
@@ -9259,7 +9269,14 @@ function capabilityStatus() {
|
|
|
9259
9269
|
return {
|
|
9260
9270
|
llm: llmClient !== null,
|
|
9261
9271
|
workflow_resolver: workflowResolver !== null,
|
|
9262
|
-
document: documentReady
|
|
9272
|
+
document: documentReady,
|
|
9273
|
+
// Listed here for the reason this function exists at all. A graph with a
|
|
9274
|
+
// terminal lane and no host registered fails PART WAY THROUGH — after the
|
|
9275
|
+
// nodes before it have already run and possibly written somewhere — and the
|
|
9276
|
+
// whole point of asking up front is to learn that before starting. Leaving
|
|
9277
|
+
// it out would have made the one check designed to catch this report
|
|
9278
|
+
// all-clear for exactly the case it was built for.
|
|
9279
|
+
terminal: terminalHost !== null
|
|
9263
9280
|
};
|
|
9264
9281
|
}
|
|
9265
9282
|
|
|
@@ -9421,6 +9438,249 @@ var RunIdentity = class _RunIdentity {
|
|
|
9421
9438
|
}
|
|
9422
9439
|
};
|
|
9423
9440
|
|
|
9441
|
+
// src/runtime/terminal-transcript.ts
|
|
9442
|
+
var ESC = String.fromCharCode(27);
|
|
9443
|
+
var CSI = String.fromCharCode(155);
|
|
9444
|
+
var BEL = String.fromCharCode(7);
|
|
9445
|
+
var ANSI = new RegExp(
|
|
9446
|
+
`[${ESC}${CSI}](?:\\[[0-?]*[ -/]*[@-~]|\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)|[@-Z\\\\-_])`,
|
|
9447
|
+
"g"
|
|
9448
|
+
);
|
|
9449
|
+
var MAX_PENDING_ESCAPE = 1024;
|
|
9450
|
+
var MAX_BUFFER = 1e6;
|
|
9451
|
+
var TerminalTranscript = class {
|
|
9452
|
+
constructor() {
|
|
9453
|
+
/** Stripped, complete text not yet consumed by a wait. */
|
|
9454
|
+
this.text = "";
|
|
9455
|
+
/** Raw bytes held back because they may be the start of an escape sequence. */
|
|
9456
|
+
this.tail = "";
|
|
9457
|
+
this.waiters = /* @__PURE__ */ new Set();
|
|
9458
|
+
}
|
|
9459
|
+
/** Feed raw terminal output in. Safe to call with any chunking. */
|
|
9460
|
+
append(chunk) {
|
|
9461
|
+
this.tail += chunk;
|
|
9462
|
+
const safe = this.safeLength();
|
|
9463
|
+
if (safe > 0) {
|
|
9464
|
+
this.text += this.tail.slice(0, safe).replace(ANSI, "");
|
|
9465
|
+
this.tail = this.tail.slice(safe);
|
|
9466
|
+
}
|
|
9467
|
+
if (this.text.length > MAX_BUFFER) {
|
|
9468
|
+
this.text = this.text.slice(this.text.length - MAX_BUFFER);
|
|
9469
|
+
}
|
|
9470
|
+
for (const wake2 of [...this.waiters]) wake2();
|
|
9471
|
+
}
|
|
9472
|
+
/**
|
|
9473
|
+
* How much of `tail` can be stripped now.
|
|
9474
|
+
*
|
|
9475
|
+
* Everything, unless the final `ESC` has not yet been terminated — in which
|
|
9476
|
+
* case processing stops there and resumes when the rest arrives.
|
|
9477
|
+
*/
|
|
9478
|
+
safeLength() {
|
|
9479
|
+
const lastEsc = Math.max(this.tail.lastIndexOf(ESC), this.tail.lastIndexOf(CSI));
|
|
9480
|
+
if (lastEsc === -1) return this.tail.length;
|
|
9481
|
+
ANSI.lastIndex = lastEsc;
|
|
9482
|
+
const match = ANSI.exec(this.tail);
|
|
9483
|
+
ANSI.lastIndex = 0;
|
|
9484
|
+
if (match && match.index === lastEsc) return this.tail.length;
|
|
9485
|
+
if (this.tail.length - lastEsc > MAX_PENDING_ESCAPE) return this.tail.length;
|
|
9486
|
+
return lastEsc;
|
|
9487
|
+
}
|
|
9488
|
+
/** Unconsumed output, escape sequences removed. */
|
|
9489
|
+
peek() {
|
|
9490
|
+
return this.text;
|
|
9491
|
+
}
|
|
9492
|
+
/** Drop everything currently buffered — used before typing a new command. */
|
|
9493
|
+
clear() {
|
|
9494
|
+
this.text = "";
|
|
9495
|
+
this.tail = "";
|
|
9496
|
+
}
|
|
9497
|
+
/**
|
|
9498
|
+
* Wait until `pattern` matches the unconsumed text.
|
|
9499
|
+
*
|
|
9500
|
+
* Checks what has ALREADY arrived before subscribing, because the common case
|
|
9501
|
+
* is that the process answered while the previous node was still finishing.
|
|
9502
|
+
*
|
|
9503
|
+
* `exited` is raced deliberately. Without it, a shell that dies reports as
|
|
9504
|
+
* "timed out waiting for X" — sending whoever reads it to lengthen a timeout
|
|
9505
|
+
* for a process that is not running. Naming the exit is the difference
|
|
9506
|
+
* between a diagnosis and a wrong lead.
|
|
9507
|
+
*/
|
|
9508
|
+
waitFor(pattern, options) {
|
|
9509
|
+
return new Promise((resolve2) => {
|
|
9510
|
+
let settled = false;
|
|
9511
|
+
let timer2;
|
|
9512
|
+
const finish = (result) => {
|
|
9513
|
+
if (settled) return;
|
|
9514
|
+
settled = true;
|
|
9515
|
+
this.waiters.delete(check);
|
|
9516
|
+
if (timer2) clearTimeout(timer2);
|
|
9517
|
+
resolve2(result);
|
|
9518
|
+
};
|
|
9519
|
+
const check = () => {
|
|
9520
|
+
pattern.lastIndex = 0;
|
|
9521
|
+
const match = pattern.exec(this.text);
|
|
9522
|
+
if (!match) return;
|
|
9523
|
+
const through = match.index + (match[0].length || 1);
|
|
9524
|
+
const text3 = this.text.slice(0, through);
|
|
9525
|
+
this.text = this.text.slice(through);
|
|
9526
|
+
finish({ status: "matched", text: text3, match });
|
|
9527
|
+
};
|
|
9528
|
+
this.waiters.add(check);
|
|
9529
|
+
check();
|
|
9530
|
+
if (settled) return;
|
|
9531
|
+
if (options.timeoutMs > 0) {
|
|
9532
|
+
timer2 = setTimeout(() => {
|
|
9533
|
+
const text3 = this.text;
|
|
9534
|
+
this.text = "";
|
|
9535
|
+
finish({ status: "timeout", text: text3 });
|
|
9536
|
+
}, options.timeoutMs);
|
|
9537
|
+
timer2.unref?.();
|
|
9538
|
+
}
|
|
9539
|
+
options.exited?.then(
|
|
9540
|
+
(exit) => {
|
|
9541
|
+
if (settled) return;
|
|
9542
|
+
const text3 = this.text;
|
|
9543
|
+
this.text = "";
|
|
9544
|
+
finish({ status: "exited", text: text3, exitCode: exit.exitCode, signal: exit.signal });
|
|
9545
|
+
},
|
|
9546
|
+
() => {
|
|
9547
|
+
}
|
|
9548
|
+
);
|
|
9549
|
+
});
|
|
9550
|
+
}
|
|
9551
|
+
};
|
|
9552
|
+
|
|
9553
|
+
// src/runtime/terminal-sessions.ts
|
|
9554
|
+
var TerminalSessions = class {
|
|
9555
|
+
constructor(graph) {
|
|
9556
|
+
this.graph = graph;
|
|
9557
|
+
this.open = /* @__PURE__ */ new Map();
|
|
9558
|
+
this.laneOf = /* @__PURE__ */ new Map();
|
|
9559
|
+
this.transcripts = /* @__PURE__ */ new Map();
|
|
9560
|
+
this.listening = /* @__PURE__ */ new Map();
|
|
9561
|
+
}
|
|
9562
|
+
/**
|
|
9563
|
+
* The terminal lane a node belongs to, or null.
|
|
9564
|
+
*
|
|
9565
|
+
* Walks up `parentId` rather than checking only the immediate parent, and
|
|
9566
|
+
* caches per node id — a graph is walked once per run, and the answer cannot
|
|
9567
|
+
* change while it runs.
|
|
9568
|
+
*/
|
|
9569
|
+
laneFor(nodeId, isTerminalLane) {
|
|
9570
|
+
const cached = this.laneOf.get(nodeId);
|
|
9571
|
+
if (cached !== void 0) return cached;
|
|
9572
|
+
const byId = new Map(this.graph.nodes.map((n) => [n.id, n]));
|
|
9573
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9574
|
+
let current = byId.get(nodeId);
|
|
9575
|
+
let answer = null;
|
|
9576
|
+
while (current) {
|
|
9577
|
+
if (seen.has(current.id)) break;
|
|
9578
|
+
seen.add(current.id);
|
|
9579
|
+
if (current.id !== nodeId && isTerminalLane(current)) {
|
|
9580
|
+
answer = current.id;
|
|
9581
|
+
break;
|
|
9582
|
+
}
|
|
9583
|
+
const parentId = current.parentId;
|
|
9584
|
+
current = parentId ? byId.get(parentId) : void 0;
|
|
9585
|
+
}
|
|
9586
|
+
this.laneOf.set(nodeId, answer);
|
|
9587
|
+
return answer;
|
|
9588
|
+
}
|
|
9589
|
+
/**
|
|
9590
|
+
* The session for a lane, opening it on first use.
|
|
9591
|
+
*
|
|
9592
|
+
* The PROMISE is cached, not the resolved session. Two nodes cannot run
|
|
9593
|
+
* concurrently in the current engine, but a caller that awaits a
|
|
9594
|
+
* half-finished open would otherwise start a second process — and two shells
|
|
9595
|
+
* where a graph says one is the failure this class exists to prevent, arriving
|
|
9596
|
+
* only under concurrency and therefore only sometimes.
|
|
9597
|
+
*/
|
|
9598
|
+
session(laneId, spec) {
|
|
9599
|
+
const existing = this.open.get(laneId);
|
|
9600
|
+
if (existing) return existing;
|
|
9601
|
+
const host = getTerminalHost();
|
|
9602
|
+
if (!host) {
|
|
9603
|
+
const failed = Promise.reject(
|
|
9604
|
+
new Error(
|
|
9605
|
+
"No terminal host is registered. A terminal lane needs one \u2014 call registerTerminalHost() from the desktop app, or install @particle-academy/fancy-flow/terminal/fancy-term-host."
|
|
9606
|
+
)
|
|
9607
|
+
);
|
|
9608
|
+
this.open.set(laneId, failed);
|
|
9609
|
+
failed.catch(() => {
|
|
9610
|
+
});
|
|
9611
|
+
return failed;
|
|
9612
|
+
}
|
|
9613
|
+
const opening = Promise.resolve(host.open(spec)).then((session) => {
|
|
9614
|
+
const transcript = this.transcriptFor(laneId);
|
|
9615
|
+
this.listening.set(laneId, session.onData((chunk) => transcript.append(chunk)));
|
|
9616
|
+
return session;
|
|
9617
|
+
});
|
|
9618
|
+
this.open.set(laneId, opening);
|
|
9619
|
+
opening.catch(() => {
|
|
9620
|
+
});
|
|
9621
|
+
return opening;
|
|
9622
|
+
}
|
|
9623
|
+
/**
|
|
9624
|
+
* The accumulated output for a lane.
|
|
9625
|
+
*
|
|
9626
|
+
* Created on demand and kept for the run, so it exists before the session
|
|
9627
|
+
* resolves and survives every node that reads it. Consuming is the reader's
|
|
9628
|
+
* job — see `TerminalTranscript.waitFor`.
|
|
9629
|
+
*/
|
|
9630
|
+
transcriptFor(laneId) {
|
|
9631
|
+
const existing = this.transcripts.get(laneId);
|
|
9632
|
+
if (existing) return existing;
|
|
9633
|
+
const created = new TerminalTranscript();
|
|
9634
|
+
this.transcripts.set(laneId, created);
|
|
9635
|
+
return created;
|
|
9636
|
+
}
|
|
9637
|
+
/** True once a lane has a session — used to avoid opening one during teardown. */
|
|
9638
|
+
isOpen(laneId) {
|
|
9639
|
+
return this.open.has(laneId);
|
|
9640
|
+
}
|
|
9641
|
+
/**
|
|
9642
|
+
* Close every session this run opened.
|
|
9643
|
+
*
|
|
9644
|
+
* Every close is attempted even if one throws: a host that fails to close one
|
|
9645
|
+
* PTY must not strand the others, which would leave processes alive after the
|
|
9646
|
+
* run reported that it had finished. Errors are returned rather than thrown,
|
|
9647
|
+
* because teardown runs in a `finally` and throwing there would replace the
|
|
9648
|
+
* run's real error with a cleanup error.
|
|
9649
|
+
*/
|
|
9650
|
+
async closeAll() {
|
|
9651
|
+
const errors = [];
|
|
9652
|
+
for (const [laneId, pending] of this.open) {
|
|
9653
|
+
try {
|
|
9654
|
+
this.listening.get(laneId)?.();
|
|
9655
|
+
this.listening.delete(laneId);
|
|
9656
|
+
const session = await pending;
|
|
9657
|
+
await session.close();
|
|
9658
|
+
} catch (e) {
|
|
9659
|
+
errors.push(e instanceof Error ? e : new Error(`${laneId}: ${String(e)}`));
|
|
9660
|
+
}
|
|
9661
|
+
}
|
|
9662
|
+
this.open.clear();
|
|
9663
|
+
this.listening.clear();
|
|
9664
|
+
this.transcripts.clear();
|
|
9665
|
+
return errors;
|
|
9666
|
+
}
|
|
9667
|
+
};
|
|
9668
|
+
function specForLane(lane) {
|
|
9669
|
+
const config = lane.data?.config ?? {};
|
|
9670
|
+
const text3 = (key) => {
|
|
9671
|
+
const value = config[key];
|
|
9672
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
9673
|
+
};
|
|
9674
|
+
const env = config.env;
|
|
9675
|
+
return {
|
|
9676
|
+
command: text3("command"),
|
|
9677
|
+
cwd: text3("cwd"),
|
|
9678
|
+
env: env && typeof env === "object" && !Array.isArray(env) ? Object.fromEntries(
|
|
9679
|
+
Object.entries(env).map(([k, v]) => [k, String(v)])
|
|
9680
|
+
) : void 0
|
|
9681
|
+
};
|
|
9682
|
+
}
|
|
9683
|
+
|
|
9424
9684
|
// src/runtime/workflow-props.ts
|
|
9425
9685
|
function typeOf(value) {
|
|
9426
9686
|
if (value === null) return "null";
|
|
@@ -9499,6 +9759,25 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
9499
9759
|
const props = propsCheck.props;
|
|
9500
9760
|
const declaresProps = (graph.inputs?.length ?? 0) > 0;
|
|
9501
9761
|
const incomingByNode = indexIncoming(graph.edges);
|
|
9762
|
+
const sessions = new TerminalSessions(graph);
|
|
9763
|
+
const isTerminalLane = (candidate) => getNodeKind(candidate.type ?? "")?.name === "@particle-academy/terminal_lane";
|
|
9764
|
+
const terminalAccessorFor = (node) => {
|
|
9765
|
+
const laneId = sessions.laneFor(node.id, isTerminalLane);
|
|
9766
|
+
if (laneId === null) return void 0;
|
|
9767
|
+
const lane = graph.nodes.find((n) => n.id === laneId);
|
|
9768
|
+
if (!lane) return void 0;
|
|
9769
|
+
return {
|
|
9770
|
+
session: () => sessions.session(laneId, specForLane(lane)),
|
|
9771
|
+
// Awaits the session first on purpose. A transcript handed out before
|
|
9772
|
+
// anything is feeding it would look perfectly healthy and match nothing,
|
|
9773
|
+
// and "waited and nothing came" is the hardest failure here to tell apart
|
|
9774
|
+
// from a process that is simply slow.
|
|
9775
|
+
transcript: async () => {
|
|
9776
|
+
await sessions.session(laneId, specForLane(lane));
|
|
9777
|
+
return sessions.transcriptFor(laneId);
|
|
9778
|
+
}
|
|
9779
|
+
};
|
|
9780
|
+
};
|
|
9502
9781
|
const timer2 = timeoutMs ? setTimeout(() => errors.push(`Run timed out after ${timeoutMs}ms`), timeoutMs) : null;
|
|
9503
9782
|
onEvent({ type: "run-start" });
|
|
9504
9783
|
try {
|
|
@@ -9564,7 +9843,8 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
9564
9843
|
emit: onEvent,
|
|
9565
9844
|
executors,
|
|
9566
9845
|
depth,
|
|
9567
|
-
run
|
|
9846
|
+
run,
|
|
9847
|
+
terminal: terminalAccessorFor(node)
|
|
9568
9848
|
})
|
|
9569
9849
|
);
|
|
9570
9850
|
outputs[node.id] = result;
|
|
@@ -9586,6 +9866,9 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
9586
9866
|
}
|
|
9587
9867
|
} finally {
|
|
9588
9868
|
if (timer2) clearTimeout(timer2);
|
|
9869
|
+
for (const error of await sessions.closeAll()) {
|
|
9870
|
+
onEvent({ type: "log", level: "warn", message: `terminal close failed: ${error.message}` });
|
|
9871
|
+
}
|
|
9589
9872
|
}
|
|
9590
9873
|
const ok = errors.length === 0;
|
|
9591
9874
|
onEvent({ type: "run-end", ok });
|
|
@@ -9664,7 +9947,7 @@ function pickExecutor(executors, node) {
|
|
|
9664
9947
|
for (const id2 of executorLookupIds(node)) {
|
|
9665
9948
|
if (executors[id2]) return executors[id2];
|
|
9666
9949
|
}
|
|
9667
|
-
return
|
|
9950
|
+
return getNodeKind(node.type ?? "")?.executor;
|
|
9668
9951
|
}
|
|
9669
9952
|
function activatedPorts(node, result) {
|
|
9670
9953
|
if (result && typeof result === "object") {
|
|
@@ -9774,6 +10057,366 @@ var subflowExecutor = async (ctx) => {
|
|
|
9774
10057
|
return { __port: "out", value: result.outputs };
|
|
9775
10058
|
};
|
|
9776
10059
|
|
|
10060
|
+
// src/expressions/expr.ts
|
|
10061
|
+
function wholeExpression(trimmed) {
|
|
10062
|
+
if (trimmed.length < 4) return null;
|
|
10063
|
+
if (!trimmed.startsWith("{{") || !trimmed.endsWith("}}")) return null;
|
|
10064
|
+
return trimmed.slice(2, -2);
|
|
10065
|
+
}
|
|
10066
|
+
function interpolate(template, resolve2) {
|
|
10067
|
+
let out = "";
|
|
10068
|
+
let i = 0;
|
|
10069
|
+
for (; ; ) {
|
|
10070
|
+
const open = template.indexOf("{{", i);
|
|
10071
|
+
if (open === -1) return out + template.slice(i);
|
|
10072
|
+
const close = template.indexOf("}}", open + 2);
|
|
10073
|
+
if (close === -1) return out + template.slice(i);
|
|
10074
|
+
out += template.slice(i, open) + resolve2(template.slice(open + 2, close));
|
|
10075
|
+
i = close + 2;
|
|
10076
|
+
}
|
|
10077
|
+
}
|
|
10078
|
+
var FALSY_STRINGS = /* @__PURE__ */ new Set(["", "0", "false", "no", "off", "null"]);
|
|
10079
|
+
var UnresolvedPathError = class extends Error {
|
|
10080
|
+
constructor(path) {
|
|
10081
|
+
super(
|
|
10082
|
+
`Expression path "${path}" did not resolve. Under the "throw" policy an unresolvable path is an error rather than an empty string.`
|
|
10083
|
+
);
|
|
10084
|
+
this.path = path;
|
|
10085
|
+
this.name = "UnresolvedPathError";
|
|
10086
|
+
}
|
|
10087
|
+
};
|
|
10088
|
+
function tryResolvePath(path, context) {
|
|
10089
|
+
const unresolved = { resolved: false, value: null };
|
|
10090
|
+
const trimmed = path.trim();
|
|
10091
|
+
if (trimmed === "") return unresolved;
|
|
10092
|
+
const segments = trimmed.split(".");
|
|
10093
|
+
let cursor;
|
|
10094
|
+
const head = segments[0];
|
|
10095
|
+
if (head === "$json" || head === "$input") {
|
|
10096
|
+
cursor = context !== null && typeof context === "object" && "in" in context ? context.in : context;
|
|
10097
|
+
segments.shift();
|
|
10098
|
+
} else {
|
|
10099
|
+
cursor = context;
|
|
10100
|
+
}
|
|
10101
|
+
for (const segment of segments) {
|
|
10102
|
+
if (cursor === null || cursor === void 0) return unresolved;
|
|
10103
|
+
if (typeof cursor !== "object") return unresolved;
|
|
10104
|
+
const next = cursor[segment];
|
|
10105
|
+
if (next === void 0) return unresolved;
|
|
10106
|
+
cursor = next;
|
|
10107
|
+
}
|
|
10108
|
+
return { resolved: true, value: cursor === void 0 ? null : cursor };
|
|
10109
|
+
}
|
|
10110
|
+
function evaluateExpression(template, context, options = {}) {
|
|
10111
|
+
if (typeof template !== "string") return template;
|
|
10112
|
+
const policy = options.onUnresolved ?? "empty";
|
|
10113
|
+
const whole = wholeExpression(template.trim());
|
|
10114
|
+
if (whole !== null) {
|
|
10115
|
+
const r = tryResolvePath(whole, context);
|
|
10116
|
+
if (r.resolved) return r.value;
|
|
10117
|
+
if (policy === "throw") throw new UnresolvedPathError(whole);
|
|
10118
|
+
return policy === "keep" ? template : null;
|
|
10119
|
+
}
|
|
10120
|
+
return interpolate(template, (path) => {
|
|
10121
|
+
const r = tryResolvePath(path, context);
|
|
10122
|
+
if (r.resolved) return stringify(r.value);
|
|
10123
|
+
if (policy === "throw") throw new UnresolvedPathError(path);
|
|
10124
|
+
return policy === "keep" ? `{{${path}}}` : "";
|
|
10125
|
+
});
|
|
10126
|
+
}
|
|
10127
|
+
function truthy(value) {
|
|
10128
|
+
if (typeof value === "boolean") return value;
|
|
10129
|
+
if (value === null || value === void 0) return false;
|
|
10130
|
+
if (typeof value === "string") return !FALSY_STRINGS.has(value.trim().toLowerCase());
|
|
10131
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
10132
|
+
if (typeof value === "number") return value !== 0;
|
|
10133
|
+
return Boolean(value);
|
|
10134
|
+
}
|
|
10135
|
+
function text(value) {
|
|
10136
|
+
return stringify(value);
|
|
10137
|
+
}
|
|
10138
|
+
function stringify(value) {
|
|
10139
|
+
if (typeof value === "string") return value;
|
|
10140
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
10141
|
+
if (value === null || value === void 0) return "";
|
|
10142
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
10143
|
+
try {
|
|
10144
|
+
return JSON.stringify(value) ?? "";
|
|
10145
|
+
} catch {
|
|
10146
|
+
return "";
|
|
10147
|
+
}
|
|
10148
|
+
}
|
|
10149
|
+
|
|
10150
|
+
// src/registry/logic.ts
|
|
10151
|
+
function configOf(node) {
|
|
10152
|
+
return node?.data?.config ?? {};
|
|
10153
|
+
}
|
|
10154
|
+
function contextOf(inputs) {
|
|
10155
|
+
return inputs;
|
|
10156
|
+
}
|
|
10157
|
+
function resolve(value, inputs) {
|
|
10158
|
+
if (typeof value !== "string") return value;
|
|
10159
|
+
return evaluateExpression(value, contextOf(inputs));
|
|
10160
|
+
}
|
|
10161
|
+
function conditionHolds(row, inputs) {
|
|
10162
|
+
const left = resolve(row.left, inputs);
|
|
10163
|
+
const right = resolve(row.right, inputs);
|
|
10164
|
+
const operator = typeof row.operator === "string" ? row.operator : "eq";
|
|
10165
|
+
const asText = (v) => text(v);
|
|
10166
|
+
const asNumber = (v) => {
|
|
10167
|
+
const n = typeof v === "number" ? v : Number(asText(v));
|
|
10168
|
+
return Number.isFinite(n) ? n : NaN;
|
|
10169
|
+
};
|
|
10170
|
+
const numeric = (fn) => {
|
|
10171
|
+
const a = asNumber(left);
|
|
10172
|
+
const b = asNumber(right);
|
|
10173
|
+
return Number.isFinite(a) && Number.isFinite(b) ? fn(a, b) : false;
|
|
10174
|
+
};
|
|
10175
|
+
const isEmpty = (v) => v === null || v === void 0 || v === "" || Array.isArray(v) && v.length === 0 || typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0;
|
|
10176
|
+
switch (operator) {
|
|
10177
|
+
// Compared as TEXT, deliberately. `right` is a text field in the schema, so
|
|
10178
|
+
// `status == "2"` must match the number 2 an upstream node produced —
|
|
10179
|
+
// strict equality there would fail on a difference the author cannot see.
|
|
10180
|
+
case "eq":
|
|
10181
|
+
return asText(left) === asText(right);
|
|
10182
|
+
case "neq":
|
|
10183
|
+
return asText(left) !== asText(right);
|
|
10184
|
+
case "contains":
|
|
10185
|
+
return asText(left).includes(asText(right));
|
|
10186
|
+
case "not_contains":
|
|
10187
|
+
return !asText(left).includes(asText(right));
|
|
10188
|
+
case "gt":
|
|
10189
|
+
return numeric((a, b) => a > b);
|
|
10190
|
+
case "gte":
|
|
10191
|
+
return numeric((a, b) => a >= b);
|
|
10192
|
+
case "lt":
|
|
10193
|
+
return numeric((a, b) => a < b);
|
|
10194
|
+
case "lte":
|
|
10195
|
+
return numeric((a, b) => a <= b);
|
|
10196
|
+
case "truthy":
|
|
10197
|
+
return truthy(left);
|
|
10198
|
+
case "falsy":
|
|
10199
|
+
return !truthy(left);
|
|
10200
|
+
case "empty":
|
|
10201
|
+
return isEmpty(left);
|
|
10202
|
+
case "not_empty":
|
|
10203
|
+
return !isEmpty(left);
|
|
10204
|
+
default:
|
|
10205
|
+
return false;
|
|
10206
|
+
}
|
|
10207
|
+
}
|
|
10208
|
+
var branchExecutor = (ctx) => {
|
|
10209
|
+
const config = configOf(ctx.node);
|
|
10210
|
+
const inputs = ctx.inputs;
|
|
10211
|
+
let taken;
|
|
10212
|
+
const raw = config.condition;
|
|
10213
|
+
if (typeof raw === "string" && raw.trim() !== "") {
|
|
10214
|
+
taken = truthy(resolve(raw, inputs));
|
|
10215
|
+
} else {
|
|
10216
|
+
const rows = Array.isArray(config.conditions) ? config.conditions : [];
|
|
10217
|
+
if (rows.length === 0) {
|
|
10218
|
+
taken = false;
|
|
10219
|
+
} else {
|
|
10220
|
+
const results = rows.map((row) => conditionHolds(row, inputs));
|
|
10221
|
+
taken = config.match === "any" ? results.some(Boolean) : results.every(Boolean);
|
|
10222
|
+
}
|
|
10223
|
+
}
|
|
10224
|
+
return { __port: taken ? "true" : "false", value: ctx.inputs.in ?? ctx.inputs };
|
|
10225
|
+
};
|
|
10226
|
+
var transformExecutor = (ctx) => {
|
|
10227
|
+
const config = configOf(ctx.node);
|
|
10228
|
+
const inputs = ctx.inputs;
|
|
10229
|
+
const passthrough = ctx.inputs.in ?? ctx.inputs;
|
|
10230
|
+
if (config.mode === "expression") {
|
|
10231
|
+
const expression = config.expression;
|
|
10232
|
+
if (typeof expression !== "string" || expression.trim() === "") return passthrough;
|
|
10233
|
+
return resolve(expression, inputs);
|
|
10234
|
+
}
|
|
10235
|
+
const rows = Array.isArray(config.fields) ? config.fields : [];
|
|
10236
|
+
if (rows.length === 0) return passthrough;
|
|
10237
|
+
const out = {};
|
|
10238
|
+
let wrote = false;
|
|
10239
|
+
for (const row of rows) {
|
|
10240
|
+
const key = typeof row.key === "string" ? row.key.trim() : "";
|
|
10241
|
+
if (key === "") continue;
|
|
10242
|
+
out[key] = resolve(row.value, inputs);
|
|
10243
|
+
wrote = true;
|
|
10244
|
+
}
|
|
10245
|
+
return wrote ? out : passthrough;
|
|
10246
|
+
};
|
|
10247
|
+
var mergeExecutor = (ctx) => {
|
|
10248
|
+
const entries = Object.entries(ctx.inputs);
|
|
10249
|
+
if (configOf(ctx.node).mode === "concat") {
|
|
10250
|
+
const out = [];
|
|
10251
|
+
for (const [, value] of entries) {
|
|
10252
|
+
if (value === null || value === void 0) continue;
|
|
10253
|
+
if (Array.isArray(value)) out.push(...value);
|
|
10254
|
+
else out.push(value);
|
|
10255
|
+
}
|
|
10256
|
+
return out;
|
|
10257
|
+
}
|
|
10258
|
+
const merged = {};
|
|
10259
|
+
for (const [port, value] of entries) {
|
|
10260
|
+
if (value === null || value === void 0) continue;
|
|
10261
|
+
if (typeof value === "object" && !Array.isArray(value)) Object.assign(merged, value);
|
|
10262
|
+
else merged[port] = value;
|
|
10263
|
+
}
|
|
10264
|
+
return merged;
|
|
10265
|
+
};
|
|
10266
|
+
var forEachExecutor = (ctx) => {
|
|
10267
|
+
const source = resolve(configOf(ctx.node).source, ctx.inputs);
|
|
10268
|
+
let items;
|
|
10269
|
+
if (Array.isArray(source)) items = source;
|
|
10270
|
+
else if (source === null || source === void 0) items = [];
|
|
10271
|
+
else if (typeof source === "object") items = Object.values(source);
|
|
10272
|
+
else items = [source];
|
|
10273
|
+
return { items, count: items.length };
|
|
10274
|
+
};
|
|
10275
|
+
|
|
10276
|
+
// src/registry/terminal-nodes.ts
|
|
10277
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
10278
|
+
var ENTER = "\r";
|
|
10279
|
+
function configOf2(node) {
|
|
10280
|
+
return node?.data?.config ?? {};
|
|
10281
|
+
}
|
|
10282
|
+
function text2(config, key) {
|
|
10283
|
+
const value = config[key];
|
|
10284
|
+
return typeof value === "string" ? value : "";
|
|
10285
|
+
}
|
|
10286
|
+
function millis(config, key, fallback) {
|
|
10287
|
+
const value = Number(config[key]);
|
|
10288
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
10289
|
+
}
|
|
10290
|
+
function exitMarker(nonce) {
|
|
10291
|
+
const token = `__fancy_flow_exit_${nonce}__`;
|
|
10292
|
+
return {
|
|
10293
|
+
token,
|
|
10294
|
+
// The expanded status is required to be digits. The shell ECHOES the typed
|
|
10295
|
+
// command back first, and that echo contains the marker with `$?`
|
|
10296
|
+
// unexpanded — so requiring digits is what stops the node matching its own
|
|
10297
|
+
// command line and reporting success before anything has run.
|
|
10298
|
+
pattern: new RegExp(`${token}:(\\d+)`)
|
|
10299
|
+
};
|
|
10300
|
+
}
|
|
10301
|
+
var counter = 0;
|
|
10302
|
+
function newNonce() {
|
|
10303
|
+
counter += 1;
|
|
10304
|
+
return `${Date.now().toString(36)}${counter.toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
10305
|
+
}
|
|
10306
|
+
function withoutMarker(output, token) {
|
|
10307
|
+
return output.split("\n").filter((line) => !line.includes(token)).join("\n").trim();
|
|
10308
|
+
}
|
|
10309
|
+
function fail(ctx, message) {
|
|
10310
|
+
ctx.abort(message);
|
|
10311
|
+
throw new Error(message);
|
|
10312
|
+
}
|
|
10313
|
+
function requireTerminal(ctx) {
|
|
10314
|
+
if (!ctx.terminal) {
|
|
10315
|
+
fail(
|
|
10316
|
+
ctx,
|
|
10317
|
+
`"${String(ctx.node.data?.label ?? ctx.node.id)}" is a terminal node but is not inside a terminal lane. Drag it into one \u2014 a terminal node outside a lane has no session to talk to, and opening a private shell for it would defeat the point of the lane.`
|
|
10318
|
+
);
|
|
10319
|
+
}
|
|
10320
|
+
return ctx.terminal;
|
|
10321
|
+
}
|
|
10322
|
+
function compilePattern(ctx, raw, config) {
|
|
10323
|
+
const flags = typeof config.flags === "string" ? config.flags.replace(/[gy]/g, "") : "";
|
|
10324
|
+
try {
|
|
10325
|
+
return config.mode === "regex" ? new RegExp(raw, flags) : new RegExp(raw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags);
|
|
10326
|
+
} catch (e) {
|
|
10327
|
+
return fail(ctx, `terminal_await has an invalid regex: ${e instanceof Error ? e.message : String(e)}`);
|
|
10328
|
+
}
|
|
10329
|
+
}
|
|
10330
|
+
var terminalRunExecutor = async (ctx) => {
|
|
10331
|
+
const config = configOf2(ctx.node);
|
|
10332
|
+
const command = text2(config, "command").trim();
|
|
10333
|
+
if (!command) fail(ctx, "terminal_run has no command configured");
|
|
10334
|
+
const terminal = requireTerminal(ctx);
|
|
10335
|
+
const session = await terminal.session();
|
|
10336
|
+
const transcript = await terminal.transcript();
|
|
10337
|
+
const { token, pattern } = exitMarker(newNonce());
|
|
10338
|
+
const timeoutMs = millis(config, "timeoutMs", DEFAULT_TIMEOUT_MS);
|
|
10339
|
+
transcript.clear();
|
|
10340
|
+
await session.write(`${command}; printf '${token}:%s\\n' "$?"${ENTER}`);
|
|
10341
|
+
const result = await transcript.waitFor(pattern, { timeoutMs, exited: session.exited });
|
|
10342
|
+
if (result.status === "exited") {
|
|
10343
|
+
fail(
|
|
10344
|
+
ctx,
|
|
10345
|
+
`The terminal exited (code ${result.exitCode}${result.signal ? `, signal ${result.signal}` : ""}) while running "${command}". The lane's session is gone, so every later node in this lane would fail too.`
|
|
10346
|
+
);
|
|
10347
|
+
}
|
|
10348
|
+
if (result.status === "timeout") {
|
|
10349
|
+
fail(
|
|
10350
|
+
ctx,
|
|
10351
|
+
`"${command}" did not finish within ${timeoutMs}ms. Raise the node's timeout if it is genuinely slow \u2014 but a command that waits for input never finishes at all, and needs terminal_send + terminal_await.`
|
|
10352
|
+
);
|
|
10353
|
+
}
|
|
10354
|
+
const exitCode = Number(result.match[1]);
|
|
10355
|
+
const output = withoutMarker(result.text, token);
|
|
10356
|
+
const failOnNonZero = config.failOnNonZero !== false;
|
|
10357
|
+
if (failOnNonZero && exitCode !== 0) {
|
|
10358
|
+
ctx.abort(`"${command}" exited ${exitCode}.
|
|
10359
|
+
${output}`);
|
|
10360
|
+
}
|
|
10361
|
+
return { output, exitCode, command };
|
|
10362
|
+
};
|
|
10363
|
+
var terminalSendExecutor = async (ctx) => {
|
|
10364
|
+
const config = configOf2(ctx.node);
|
|
10365
|
+
const terminal = requireTerminal(ctx);
|
|
10366
|
+
const session = await terminal.session();
|
|
10367
|
+
const body = text2(config, "text");
|
|
10368
|
+
const submit = config.submit !== false;
|
|
10369
|
+
if (config.clearFirst === true) {
|
|
10370
|
+
(await terminal.transcript()).clear();
|
|
10371
|
+
}
|
|
10372
|
+
await session.write(submit ? `${body}${ENTER}` : body);
|
|
10373
|
+
return { sent: body, submitted: submit };
|
|
10374
|
+
};
|
|
10375
|
+
var terminalAwaitExecutor = async (ctx) => {
|
|
10376
|
+
const config = configOf2(ctx.node);
|
|
10377
|
+
const terminal = requireTerminal(ctx);
|
|
10378
|
+
const session = await terminal.session();
|
|
10379
|
+
const transcript = await terminal.transcript();
|
|
10380
|
+
const raw = text2(config, "pattern").trim();
|
|
10381
|
+
if (!raw) fail(ctx, "terminal_await has no pattern configured \u2014 there is nothing for it to wait for");
|
|
10382
|
+
const pattern = compilePattern(ctx, raw, config);
|
|
10383
|
+
const timeoutMs = millis(config, "timeoutMs", DEFAULT_TIMEOUT_MS);
|
|
10384
|
+
const result = await transcript.waitFor(pattern, {
|
|
10385
|
+
timeoutMs,
|
|
10386
|
+
exited: session.exited
|
|
10387
|
+
});
|
|
10388
|
+
if (result.status === "exited") {
|
|
10389
|
+
fail(
|
|
10390
|
+
ctx,
|
|
10391
|
+
`The terminal exited (code ${result.exitCode}${result.signal ? `, signal ${result.signal}` : ""}) while waiting for ${JSON.stringify(raw)}. It never appeared, and the lane's session is gone.`
|
|
10392
|
+
);
|
|
10393
|
+
}
|
|
10394
|
+
if (result.status === "timeout") {
|
|
10395
|
+
if (config.onTimeout === "continue") {
|
|
10396
|
+
ctx.emit({
|
|
10397
|
+
type: "log",
|
|
10398
|
+
nodeId: ctx.node.id,
|
|
10399
|
+
level: "warn",
|
|
10400
|
+
message: `terminal_await: ${JSON.stringify(raw)} did not appear within ${timeoutMs}ms; continuing as configured.`
|
|
10401
|
+
});
|
|
10402
|
+
return { matched: false, output: result.text, groups: [] };
|
|
10403
|
+
}
|
|
10404
|
+
fail(
|
|
10405
|
+
ctx,
|
|
10406
|
+
`${JSON.stringify(raw)} did not appear within ${timeoutMs}ms. Last output:
|
|
10407
|
+
${result.text.slice(-2e3)}`
|
|
10408
|
+
);
|
|
10409
|
+
}
|
|
10410
|
+
return {
|
|
10411
|
+
matched: true,
|
|
10412
|
+
output: result.text,
|
|
10413
|
+
matchedText: result.match[0],
|
|
10414
|
+
// Capture groups are the reason to use regex mode at all — a prompt that
|
|
10415
|
+
// reports a session id or a file path is only useful if the value comes out.
|
|
10416
|
+
groups: result.match.slice(1)
|
|
10417
|
+
};
|
|
10418
|
+
};
|
|
10419
|
+
|
|
9777
10420
|
// src/registry/builtin.ts
|
|
9778
10421
|
var HUMAN_FIELD_OUTPUT_TYPE = {
|
|
9779
10422
|
text: "string",
|
|
@@ -10063,6 +10706,9 @@ var KINDS = [
|
|
|
10063
10706
|
// returns its input on the chosen port
|
|
10064
10707
|
emits: "input",
|
|
10065
10708
|
aliases: ["branch", "@fancy/branch"],
|
|
10709
|
+
// Pure logic: no I/O to choose, so a default is safe. A host
|
|
10710
|
+
// executor still wins -- pickExecutor consults the registry first.
|
|
10711
|
+
executor: branchExecutor,
|
|
10066
10712
|
category: "logic",
|
|
10067
10713
|
label: "Branch",
|
|
10068
10714
|
description: "Multi-way branch on a condition or value.",
|
|
@@ -10160,6 +10806,9 @@ var KINDS = [
|
|
|
10160
10806
|
{ path: "count", type: "number" }
|
|
10161
10807
|
],
|
|
10162
10808
|
aliases: ["for_each", "@fancy/for_each"],
|
|
10809
|
+
// Pure logic: no I/O to choose, so a default is safe. A host
|
|
10810
|
+
// executor still wins -- pickExecutor consults the registry first.
|
|
10811
|
+
executor: forEachExecutor,
|
|
10163
10812
|
category: "logic",
|
|
10164
10813
|
label: "For Each",
|
|
10165
10814
|
description: "Iterate over a list, emitting each item on `item`.",
|
|
@@ -10180,6 +10829,9 @@ var KINDS = [
|
|
|
10180
10829
|
// reference.
|
|
10181
10830
|
emits: (config) => (config?.mode ?? "merge") === "concat" ? null : "inputs-merged",
|
|
10182
10831
|
aliases: ["merge", "@fancy/merge"],
|
|
10832
|
+
// Pure logic: no I/O to choose, so a default is safe. A host
|
|
10833
|
+
// executor still wins -- pickExecutor consults the registry first.
|
|
10834
|
+
executor: mergeExecutor,
|
|
10183
10835
|
category: "logic",
|
|
10184
10836
|
label: "Merge",
|
|
10185
10837
|
description: "Combine multiple inputs into one object or array.",
|
|
@@ -10225,6 +10877,9 @@ var KINDS = [
|
|
|
10225
10877
|
// else the shape that expression names. So the RELATION is config-dependent.
|
|
10226
10878
|
emits: (config) => (config?.expression ?? "") === "" ? "input" : "expression:expression",
|
|
10227
10879
|
aliases: ["transform", "@fancy/transform"],
|
|
10880
|
+
// Pure logic: no I/O to choose, so a default is safe. A host
|
|
10881
|
+
// executor still wins -- pickExecutor consults the registry first.
|
|
10882
|
+
executor: transformExecutor,
|
|
10228
10883
|
category: "logic",
|
|
10229
10884
|
label: "Transform",
|
|
10230
10885
|
description: "Reshape data with an expression.",
|
|
@@ -10683,6 +11338,179 @@ var KINDS = [
|
|
|
10683
11338
|
resizable: { minWidth: 160, minHeight: 72 },
|
|
10684
11339
|
component: LaneNode
|
|
10685
11340
|
},
|
|
11341
|
+
{
|
|
11342
|
+
// Terminal lane — a lane that OWNS a terminal for the length of the run.
|
|
11343
|
+
//
|
|
11344
|
+
// Visually a swimlane, and `layout` for the same reason the plain lane is:
|
|
11345
|
+
// a lane is not a step, so it never executes and never appears in topo
|
|
11346
|
+
// order. What is new is that it DECLARES a resource. The runtime opens its
|
|
11347
|
+
// terminal lazily, when the first terminal node inside it runs, and closes
|
|
11348
|
+
// it in the run's `finally` — so a graph that never reaches a terminal node
|
|
11349
|
+
// never spawns a process, and one that does gets exactly one session no
|
|
11350
|
+
// matter how many nodes use it.
|
|
11351
|
+
//
|
|
11352
|
+
// Membership is `parentId`, which already persists into the WorkflowSchema.
|
|
11353
|
+
// That is what lets a headless runtime resolve the same grouping the canvas
|
|
11354
|
+
// shows, without a second association to keep in sync.
|
|
11355
|
+
name: "@particle-academy/terminal_lane",
|
|
11356
|
+
aliases: ["terminal_lane", "@fancy/terminal_lane"],
|
|
11357
|
+
category: "layout",
|
|
11358
|
+
label: "Terminal lane",
|
|
11359
|
+
description: "A lane that owns one terminal. Opens at the first terminal node, closes when the run ends.",
|
|
11360
|
+
icon: "\u25A3",
|
|
11361
|
+
inputs: [],
|
|
11362
|
+
outputs: [],
|
|
11363
|
+
configSchema: [
|
|
11364
|
+
{ type: "text", key: "title", label: "Title", default: "Terminal" },
|
|
11365
|
+
{
|
|
11366
|
+
type: "text",
|
|
11367
|
+
key: "command",
|
|
11368
|
+
label: "Command",
|
|
11369
|
+
placeholder: "Leave empty for the default shell"
|
|
11370
|
+
},
|
|
11371
|
+
{ type: "text", key: "cwd", label: "Working directory", placeholder: "Host default" },
|
|
11372
|
+
{ type: "keyvalue", key: "env", label: "Environment" },
|
|
11373
|
+
{
|
|
11374
|
+
type: "select",
|
|
11375
|
+
key: "orientation",
|
|
11376
|
+
label: "Orientation",
|
|
11377
|
+
default: "horizontal",
|
|
11378
|
+
options: [
|
|
11379
|
+
{ value: "horizontal", label: "Row" },
|
|
11380
|
+
{ value: "vertical", label: "Column" }
|
|
11381
|
+
]
|
|
11382
|
+
}
|
|
11383
|
+
],
|
|
11384
|
+
defaultConfig: { title: "Terminal", orientation: "horizontal" },
|
|
11385
|
+
resizable: { minWidth: 220, minHeight: 120 },
|
|
11386
|
+
component: LaneNode
|
|
11387
|
+
},
|
|
11388
|
+
{
|
|
11389
|
+
// Terminal nodes. All three live inside a terminal lane and talk to the
|
|
11390
|
+
// session that lane owns; outside one they abort by name rather than
|
|
11391
|
+
// opening a shell of their own.
|
|
11392
|
+
name: "@particle-academy/terminal_run",
|
|
11393
|
+
aliases: ["terminal_run", "@fancy/terminal_run"],
|
|
11394
|
+
category: "io",
|
|
11395
|
+
label: "Run in terminal",
|
|
11396
|
+
description: "Run a shell command in the lane's terminal and wait for its exit code.",
|
|
11397
|
+
icon: "$",
|
|
11398
|
+
inputs: [{ id: "in" }],
|
|
11399
|
+
outputs: [{ id: "out", label: "output" }],
|
|
11400
|
+
executor: terminalRunExecutor,
|
|
11401
|
+
configSchema: [
|
|
11402
|
+
{
|
|
11403
|
+
type: "textarea",
|
|
11404
|
+
key: "command",
|
|
11405
|
+
label: "Command",
|
|
11406
|
+
rows: 3,
|
|
11407
|
+
required: true,
|
|
11408
|
+
placeholder: "npm test",
|
|
11409
|
+
description: "Runs in the lane's shell, so `cd` and exported variables persist between nodes. SHELL ONLY \u2014 a TUI never returns to a prompt, so use Send + Await for one."
|
|
11410
|
+
},
|
|
11411
|
+
{
|
|
11412
|
+
type: "number",
|
|
11413
|
+
key: "timeoutMs",
|
|
11414
|
+
label: "Timeout (ms)",
|
|
11415
|
+
default: 12e4,
|
|
11416
|
+
description: "How long to wait for the command to finish before failing the run."
|
|
11417
|
+
},
|
|
11418
|
+
{
|
|
11419
|
+
type: "switch",
|
|
11420
|
+
key: "failOnNonZero",
|
|
11421
|
+
label: "Fail the run on a non-zero exit",
|
|
11422
|
+
default: true,
|
|
11423
|
+
description: "On by default. Turning it off means a failed command lets the run report success \u2014 read the exit code on the output port and branch on it instead."
|
|
11424
|
+
}
|
|
11425
|
+
],
|
|
11426
|
+
defaultConfig: { command: "", timeoutMs: 12e4, failOnNonZero: true }
|
|
11427
|
+
},
|
|
11428
|
+
{
|
|
11429
|
+
name: "@particle-academy/terminal_send",
|
|
11430
|
+
aliases: ["terminal_send", "@fancy/terminal_send"],
|
|
11431
|
+
category: "io",
|
|
11432
|
+
label: "Send to terminal",
|
|
11433
|
+
description: "Type text at whatever is running in the lane's terminal, without waiting.",
|
|
11434
|
+
icon: "\u2328",
|
|
11435
|
+
inputs: [{ id: "in" }],
|
|
11436
|
+
outputs: [{ id: "out", label: "sent" }],
|
|
11437
|
+
executor: terminalSendExecutor,
|
|
11438
|
+
configSchema: [
|
|
11439
|
+
{
|
|
11440
|
+
type: "textarea",
|
|
11441
|
+
key: "text",
|
|
11442
|
+
label: "Text",
|
|
11443
|
+
rows: 4,
|
|
11444
|
+
placeholder: "Summarise the failing test and propose a fix.",
|
|
11445
|
+
description: "What to type. This is how a graph prompts an agent TUI such as Claude Code or Codex."
|
|
11446
|
+
},
|
|
11447
|
+
{
|
|
11448
|
+
type: "switch",
|
|
11449
|
+
key: "submit",
|
|
11450
|
+
label: "Press Enter",
|
|
11451
|
+
default: true,
|
|
11452
|
+
description: "Off leaves the text on the input line \u2014 useful for building one up across several nodes."
|
|
11453
|
+
},
|
|
11454
|
+
{
|
|
11455
|
+
type: "switch",
|
|
11456
|
+
key: "clearFirst",
|
|
11457
|
+
label: "Forget earlier output first",
|
|
11458
|
+
default: false,
|
|
11459
|
+
description: "Discards anything the terminal said before this send, so a following Await cannot match a prompt left over from the previous exchange."
|
|
11460
|
+
}
|
|
11461
|
+
],
|
|
11462
|
+
defaultConfig: { text: "", submit: true, clearFirst: false }
|
|
11463
|
+
},
|
|
11464
|
+
{
|
|
11465
|
+
name: "@particle-academy/terminal_await",
|
|
11466
|
+
aliases: ["terminal_await", "@fancy/terminal_await"],
|
|
11467
|
+
category: "io",
|
|
11468
|
+
label: "Await terminal output",
|
|
11469
|
+
description: "Wait until the lane's terminal prints something that matches.",
|
|
11470
|
+
icon: "\u23F1",
|
|
11471
|
+
inputs: [{ id: "in" }],
|
|
11472
|
+
outputs: [{ id: "out", label: "output" }],
|
|
11473
|
+
executor: terminalAwaitExecutor,
|
|
11474
|
+
configSchema: [
|
|
11475
|
+
{
|
|
11476
|
+
type: "text",
|
|
11477
|
+
key: "pattern",
|
|
11478
|
+
label: "Wait for",
|
|
11479
|
+
required: true,
|
|
11480
|
+
placeholder: "esc to interrupt",
|
|
11481
|
+
description: "Matched against the output with colour codes already stripped, so match what you SEE."
|
|
11482
|
+
},
|
|
11483
|
+
{
|
|
11484
|
+
type: "select",
|
|
11485
|
+
key: "mode",
|
|
11486
|
+
label: "Match as",
|
|
11487
|
+
default: "text",
|
|
11488
|
+
options: [
|
|
11489
|
+
{ value: "text", label: "Plain text" },
|
|
11490
|
+
{ value: "regex", label: "Regular expression" }
|
|
11491
|
+
],
|
|
11492
|
+
description: "Regex mode also returns capture groups, which is how a value gets out of a prompt."
|
|
11493
|
+
},
|
|
11494
|
+
{
|
|
11495
|
+
type: "number",
|
|
11496
|
+
key: "timeoutMs",
|
|
11497
|
+
label: "Timeout (ms)",
|
|
11498
|
+
default: 12e4
|
|
11499
|
+
},
|
|
11500
|
+
{
|
|
11501
|
+
type: "select",
|
|
11502
|
+
key: "onTimeout",
|
|
11503
|
+
label: "If it never appears",
|
|
11504
|
+
default: "fail",
|
|
11505
|
+
options: [
|
|
11506
|
+
{ value: "fail", label: "Fail the run" },
|
|
11507
|
+
{ value: "continue", label: "Continue with matched: false" }
|
|
11508
|
+
],
|
|
11509
|
+
description: "Failing is the default. Continuing lets the next node type at a process that never became ready while the run still reports success, so it has to be asked for."
|
|
11510
|
+
}
|
|
11511
|
+
],
|
|
11512
|
+
defaultConfig: { pattern: "", mode: "text", timeoutMs: 12e4, onTimeout: "fail" }
|
|
11513
|
+
},
|
|
10686
11514
|
{
|
|
10687
11515
|
// Note — a sticky-note annotation. Portless + visual-only: the runtime skips
|
|
10688
11516
|
// the `annotation` category, so a note's text NEVER reaches a runner — it
|
|
@@ -11157,6 +11985,7 @@ exports.ensureBuiltinKinds = ensureBuiltinKinds;
|
|
|
11157
11985
|
exports.getLlmClient = getLlmClient;
|
|
11158
11986
|
exports.getNodeKind = getNodeKind;
|
|
11159
11987
|
exports.getRichInputAdapter = getRichInputAdapter;
|
|
11988
|
+
exports.getTerminalHost = getTerminalHost;
|
|
11160
11989
|
exports.getWorkflowResolver = getWorkflowResolver;
|
|
11161
11990
|
exports.isPause = isPause;
|
|
11162
11991
|
exports.isResolutionFailure = isResolutionFailure;
|
|
@@ -11174,6 +12003,7 @@ exports.registerBuiltinKinds = registerBuiltinKinds;
|
|
|
11174
12003
|
exports.registerLlmClient = registerLlmClient;
|
|
11175
12004
|
exports.registerNodeKind = registerNodeKind;
|
|
11176
12005
|
exports.registerRichInputAdapter = registerRichInputAdapter;
|
|
12006
|
+
exports.registerTerminalHost = registerTerminalHost;
|
|
11177
12007
|
exports.registerWorkflowResolver = registerWorkflowResolver;
|
|
11178
12008
|
exports.resolveFallbackPort = resolveFallbackPort;
|
|
11179
12009
|
exports.resolveKindId = resolveKindId;
|