@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RichInputPreview } from './chunk-F5RPRB7A.js';
|
|
2
|
-
import { getLlmClient, getWorkflowResolver, isResolutionFailure } from './chunk-
|
|
2
|
+
import { getLlmClient, getWorkflowResolver, isResolutionFailure, getTerminalHost } from './chunk-2QXKDLGT.js';
|
|
3
3
|
import ReactExports, { createContext, forwardRef, memo, useMemo, useState, useRef, useEffect, useCallback, useContext, createElement, useSyncExternalStore, useDebugValue, useLayoutEffect } from 'react';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
@@ -2087,38 +2087,38 @@ function attrRemoveNS2(fullname) {
|
|
|
2087
2087
|
this.removeAttributeNS(fullname.space, fullname.local);
|
|
2088
2088
|
};
|
|
2089
2089
|
}
|
|
2090
|
-
function attrConstant2(name,
|
|
2090
|
+
function attrConstant2(name, interpolate2, value1) {
|
|
2091
2091
|
var string00, string1 = value1 + "", interpolate0;
|
|
2092
2092
|
return function() {
|
|
2093
2093
|
var string0 = this.getAttribute(name);
|
|
2094
|
-
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 =
|
|
2094
|
+
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 = interpolate2(string00 = string0, value1);
|
|
2095
2095
|
};
|
|
2096
2096
|
}
|
|
2097
|
-
function attrConstantNS2(fullname,
|
|
2097
|
+
function attrConstantNS2(fullname, interpolate2, value1) {
|
|
2098
2098
|
var string00, string1 = value1 + "", interpolate0;
|
|
2099
2099
|
return function() {
|
|
2100
2100
|
var string0 = this.getAttributeNS(fullname.space, fullname.local);
|
|
2101
|
-
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 =
|
|
2101
|
+
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 = interpolate2(string00 = string0, value1);
|
|
2102
2102
|
};
|
|
2103
2103
|
}
|
|
2104
|
-
function attrFunction2(name,
|
|
2104
|
+
function attrFunction2(name, interpolate2, value) {
|
|
2105
2105
|
var string00, string10, interpolate0;
|
|
2106
2106
|
return function() {
|
|
2107
2107
|
var string0, value1 = value(this), string1;
|
|
2108
2108
|
if (value1 == null) return void this.removeAttribute(name);
|
|
2109
2109
|
string0 = this.getAttribute(name);
|
|
2110
2110
|
string1 = value1 + "";
|
|
2111
|
-
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 =
|
|
2111
|
+
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 = interpolate2(string00 = string0, value1));
|
|
2112
2112
|
};
|
|
2113
2113
|
}
|
|
2114
|
-
function attrFunctionNS2(fullname,
|
|
2114
|
+
function attrFunctionNS2(fullname, interpolate2, value) {
|
|
2115
2115
|
var string00, string10, interpolate0;
|
|
2116
2116
|
return function() {
|
|
2117
2117
|
var string0, value1 = value(this), string1;
|
|
2118
2118
|
if (value1 == null) return void this.removeAttributeNS(fullname.space, fullname.local);
|
|
2119
2119
|
string0 = this.getAttributeNS(fullname.space, fullname.local);
|
|
2120
2120
|
string1 = value1 + "";
|
|
2121
|
-
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 =
|
|
2121
|
+
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 = interpolate2(string00 = string0, value1));
|
|
2122
2122
|
};
|
|
2123
2123
|
}
|
|
2124
2124
|
function attr_default2(name, value) {
|
|
@@ -2328,11 +2328,11 @@ function selection_default2() {
|
|
|
2328
2328
|
}
|
|
2329
2329
|
|
|
2330
2330
|
// node_modules/d3-transition/src/transition/style.js
|
|
2331
|
-
function styleNull(name,
|
|
2331
|
+
function styleNull(name, interpolate2) {
|
|
2332
2332
|
var string00, string10, interpolate0;
|
|
2333
2333
|
return function() {
|
|
2334
2334
|
var string0 = styleValue(this, name), string1 = (this.style.removeProperty(name), styleValue(this, name));
|
|
2335
|
-
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : interpolate0 =
|
|
2335
|
+
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : interpolate0 = interpolate2(string00 = string0, string10 = string1);
|
|
2336
2336
|
};
|
|
2337
2337
|
}
|
|
2338
2338
|
function styleRemove2(name) {
|
|
@@ -2340,19 +2340,19 @@ function styleRemove2(name) {
|
|
|
2340
2340
|
this.style.removeProperty(name);
|
|
2341
2341
|
};
|
|
2342
2342
|
}
|
|
2343
|
-
function styleConstant2(name,
|
|
2343
|
+
function styleConstant2(name, interpolate2, value1) {
|
|
2344
2344
|
var string00, string1 = value1 + "", interpolate0;
|
|
2345
2345
|
return function() {
|
|
2346
2346
|
var string0 = styleValue(this, name);
|
|
2347
|
-
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 =
|
|
2347
|
+
return string0 === string1 ? null : string0 === string00 ? interpolate0 : interpolate0 = interpolate2(string00 = string0, value1);
|
|
2348
2348
|
};
|
|
2349
2349
|
}
|
|
2350
|
-
function styleFunction2(name,
|
|
2350
|
+
function styleFunction2(name, interpolate2, value) {
|
|
2351
2351
|
var string00, string10, interpolate0;
|
|
2352
2352
|
return function() {
|
|
2353
2353
|
var string0 = styleValue(this, name), value1 = value(this), string1 = value1 + "";
|
|
2354
2354
|
if (value1 == null) string1 = value1 = (this.style.removeProperty(name), styleValue(this, name));
|
|
2355
|
-
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 =
|
|
2355
|
+
return string0 === string1 ? null : string0 === string00 && string1 === string10 ? interpolate0 : (string10 = string1, interpolate0 = interpolate2(string00 = string0, value1));
|
|
2356
2356
|
};
|
|
2357
2357
|
}
|
|
2358
2358
|
function styleMaybeRemove(id2, name) {
|
|
@@ -2454,9 +2454,9 @@ function transition_default() {
|
|
|
2454
2454
|
// node_modules/d3-transition/src/transition/end.js
|
|
2455
2455
|
function end_default() {
|
|
2456
2456
|
var on0, on1, that = this, id2 = that._id, size = that.size();
|
|
2457
|
-
return new Promise(function(
|
|
2457
|
+
return new Promise(function(resolve2, reject) {
|
|
2458
2458
|
var cancel = { value: reject }, end = { value: function() {
|
|
2459
|
-
if (--size === 0)
|
|
2459
|
+
if (--size === 0) resolve2();
|
|
2460
2460
|
} };
|
|
2461
2461
|
that.each(function() {
|
|
2462
2462
|
var schedule = set2(this, id2), on = schedule.on;
|
|
@@ -2468,7 +2468,7 @@ function end_default() {
|
|
|
2468
2468
|
}
|
|
2469
2469
|
schedule.on = on1;
|
|
2470
2470
|
});
|
|
2471
|
-
if (size === 0)
|
|
2471
|
+
if (size === 0) resolve2();
|
|
2472
2472
|
});
|
|
2473
2473
|
}
|
|
2474
2474
|
|
|
@@ -2670,7 +2670,7 @@ function defaultConstrain(transform2, extent, translateExtent) {
|
|
|
2670
2670
|
);
|
|
2671
2671
|
}
|
|
2672
2672
|
function zoom_default2() {
|
|
2673
|
-
var filter2 = defaultFilter2, extent = defaultExtent, constrain = defaultConstrain, wheelDelta2 = defaultWheelDelta, touchable = defaultTouchable2, scaleExtent = [0, Infinity], translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], duration = 250,
|
|
2673
|
+
var filter2 = defaultFilter2, extent = defaultExtent, constrain = defaultConstrain, wheelDelta2 = defaultWheelDelta, touchable = defaultTouchable2, scaleExtent = [0, Infinity], translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], duration = 250, interpolate2 = zoom_default, listeners2 = dispatch_default("start", "zoom", "end"), touchstarting, touchfirst, touchending, touchDelay = 500, wheelDelay = 150, clickDistance2 = 0, tapDistance = 10;
|
|
2674
2674
|
function zoom(selection2) {
|
|
2675
2675
|
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)");
|
|
2676
2676
|
}
|
|
@@ -2731,7 +2731,7 @@ function zoom_default2() {
|
|
|
2731
2731
|
}).on("interrupt.zoom end.zoom", function() {
|
|
2732
2732
|
gesture(this, arguments).event(event).end();
|
|
2733
2733
|
}).tween("zoom", function() {
|
|
2734
|
-
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 =
|
|
2734
|
+
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));
|
|
2735
2735
|
return function(t) {
|
|
2736
2736
|
if (t === 1) t = b;
|
|
2737
2737
|
else {
|
|
@@ -2936,7 +2936,7 @@ function zoom_default2() {
|
|
|
2936
2936
|
return arguments.length ? (duration = +_, zoom) : duration;
|
|
2937
2937
|
};
|
|
2938
2938
|
zoom.interpolate = function(_) {
|
|
2939
|
-
return arguments.length ? (
|
|
2939
|
+
return arguments.length ? (interpolate2 = _, zoom) : interpolate2;
|
|
2940
2940
|
};
|
|
2941
2941
|
zoom.on = function() {
|
|
2942
2942
|
var value = listeners2.on.apply(listeners2, arguments);
|
|
@@ -3429,13 +3429,13 @@ function areSetsEqual(a, b) {
|
|
|
3429
3429
|
return true;
|
|
3430
3430
|
}
|
|
3431
3431
|
function withResolvers() {
|
|
3432
|
-
let
|
|
3432
|
+
let resolve2;
|
|
3433
3433
|
let reject;
|
|
3434
3434
|
const promise = new Promise((res, rej) => {
|
|
3435
|
-
|
|
3435
|
+
resolve2 = res;
|
|
3436
3436
|
reject = rej;
|
|
3437
3437
|
});
|
|
3438
|
-
return { promise, resolve, reject };
|
|
3438
|
+
return { promise, resolve: resolve2, reject };
|
|
3439
3439
|
}
|
|
3440
3440
|
function mergeAriaLabelConfig(partial) {
|
|
3441
3441
|
return { ...defaultAriaLabelConfig, ...partial || {} };
|
|
@@ -5107,8 +5107,8 @@ function XYPanZoom({ domNode, minZoom, maxZoom, translateExtent, viewport, onPan
|
|
|
5107
5107
|
d3ZoomInstance.wheelDelta(wheelDelta);
|
|
5108
5108
|
async function setTransform(transform2, options) {
|
|
5109
5109
|
if (d3Selection) {
|
|
5110
|
-
return new Promise((
|
|
5111
|
-
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).transform(getD3Transition(d3Selection, options?.duration, options?.ease, () =>
|
|
5110
|
+
return new Promise((resolve2) => {
|
|
5111
|
+
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).transform(getD3Transition(d3Selection, options?.duration, options?.ease, () => resolve2(true)), transform2);
|
|
5112
5112
|
});
|
|
5113
5113
|
}
|
|
5114
5114
|
return false;
|
|
@@ -5210,16 +5210,16 @@ function XYPanZoom({ domNode, minZoom, maxZoom, translateExtent, viewport, onPan
|
|
|
5210
5210
|
}
|
|
5211
5211
|
async function scaleTo(zoom, options) {
|
|
5212
5212
|
if (d3Selection) {
|
|
5213
|
-
return new Promise((
|
|
5214
|
-
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).scaleTo(getD3Transition(d3Selection, options?.duration, options?.ease, () =>
|
|
5213
|
+
return new Promise((resolve2) => {
|
|
5214
|
+
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).scaleTo(getD3Transition(d3Selection, options?.duration, options?.ease, () => resolve2(true)), zoom);
|
|
5215
5215
|
});
|
|
5216
5216
|
}
|
|
5217
5217
|
return false;
|
|
5218
5218
|
}
|
|
5219
5219
|
async function scaleBy(factor, options) {
|
|
5220
5220
|
if (d3Selection) {
|
|
5221
|
-
return new Promise((
|
|
5222
|
-
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).scaleBy(getD3Transition(d3Selection, options?.duration, options?.ease, () =>
|
|
5221
|
+
return new Promise((resolve2) => {
|
|
5222
|
+
d3ZoomInstance?.interpolate(options?.interpolate === "linear" ? value_default : zoom_default).scaleBy(getD3Transition(d3Selection, options?.duration, options?.ease, () => resolve2(true)), factor);
|
|
5223
5223
|
});
|
|
5224
5224
|
}
|
|
5225
5225
|
return false;
|
|
@@ -9160,11 +9160,11 @@ function NoteNodeInner(props) {
|
|
|
9160
9160
|
const config = data.config ?? {};
|
|
9161
9161
|
const api = useFlowEditorOptional();
|
|
9162
9162
|
const hasConfigText = typeof config.text === "string" && config.text.length > 0;
|
|
9163
|
-
const
|
|
9163
|
+
const text3 = hasConfigText ? String(config.text) : typeof data.body === "string" ? data.body : typeof data.text === "string" ? data.text : "";
|
|
9164
9164
|
const title = typeof config.title === "string" && config.title || (!hasConfigText && typeof data.body === "string" && typeof data.label === "string" ? data.label : "");
|
|
9165
9165
|
const skin = NOTE_COLORS[coerceColor(config.color)];
|
|
9166
9166
|
const [editing, setEditing] = useState(false);
|
|
9167
|
-
const [draft, setDraft] = useState(
|
|
9167
|
+
const [draft, setDraft] = useState(text3);
|
|
9168
9168
|
const areaRef = useRef(null);
|
|
9169
9169
|
useEffect(() => {
|
|
9170
9170
|
if (editing && areaRef.current) {
|
|
@@ -9175,7 +9175,7 @@ function NoteNodeInner(props) {
|
|
|
9175
9175
|
}, [editing]);
|
|
9176
9176
|
const commit = () => {
|
|
9177
9177
|
setEditing(false);
|
|
9178
|
-
if (!api || draft ===
|
|
9178
|
+
if (!api || draft === text3) return;
|
|
9179
9179
|
const current = api.nodes.find((n) => n.id === props.id);
|
|
9180
9180
|
if (!current) return;
|
|
9181
9181
|
api.updateNode({
|
|
@@ -9189,7 +9189,7 @@ function NoteNodeInner(props) {
|
|
|
9189
9189
|
className: ["ff-note", props.selected ? "ff-note--selected" : ""].filter(Boolean).join(" "),
|
|
9190
9190
|
style: { background: skin.bg, borderColor: skin.border },
|
|
9191
9191
|
onDoubleClick: api ? () => {
|
|
9192
|
-
setDraft(
|
|
9192
|
+
setDraft(text3);
|
|
9193
9193
|
setEditing(true);
|
|
9194
9194
|
} : void 0,
|
|
9195
9195
|
children: [
|
|
@@ -9208,7 +9208,7 @@ function NoteNodeInner(props) {
|
|
|
9208
9208
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) commit();
|
|
9209
9209
|
}
|
|
9210
9210
|
}
|
|
9211
|
-
) : /* @__PURE__ */ jsx("div", { className: "ff-note__body", children:
|
|
9211
|
+
) : /* @__PURE__ */ jsx("div", { className: "ff-note__body", children: text3 || /* @__PURE__ */ jsx("span", { className: "ff-note__placeholder", children: api ? "Double-click to write a note\u2026" : "Note" }) })
|
|
9212
9212
|
]
|
|
9213
9213
|
}
|
|
9214
9214
|
);
|
|
@@ -9396,6 +9396,249 @@ var RunIdentity = class _RunIdentity {
|
|
|
9396
9396
|
}
|
|
9397
9397
|
};
|
|
9398
9398
|
|
|
9399
|
+
// src/runtime/terminal-transcript.ts
|
|
9400
|
+
var ESC = String.fromCharCode(27);
|
|
9401
|
+
var CSI = String.fromCharCode(155);
|
|
9402
|
+
var BEL = String.fromCharCode(7);
|
|
9403
|
+
var ANSI = new RegExp(
|
|
9404
|
+
`[${ESC}${CSI}](?:\\[[0-?]*[ -/]*[@-~]|\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)|[@-Z\\\\-_])`,
|
|
9405
|
+
"g"
|
|
9406
|
+
);
|
|
9407
|
+
var MAX_PENDING_ESCAPE = 1024;
|
|
9408
|
+
var MAX_BUFFER = 1e6;
|
|
9409
|
+
var TerminalTranscript = class {
|
|
9410
|
+
constructor() {
|
|
9411
|
+
/** Stripped, complete text not yet consumed by a wait. */
|
|
9412
|
+
this.text = "";
|
|
9413
|
+
/** Raw bytes held back because they may be the start of an escape sequence. */
|
|
9414
|
+
this.tail = "";
|
|
9415
|
+
this.waiters = /* @__PURE__ */ new Set();
|
|
9416
|
+
}
|
|
9417
|
+
/** Feed raw terminal output in. Safe to call with any chunking. */
|
|
9418
|
+
append(chunk) {
|
|
9419
|
+
this.tail += chunk;
|
|
9420
|
+
const safe = this.safeLength();
|
|
9421
|
+
if (safe > 0) {
|
|
9422
|
+
this.text += this.tail.slice(0, safe).replace(ANSI, "");
|
|
9423
|
+
this.tail = this.tail.slice(safe);
|
|
9424
|
+
}
|
|
9425
|
+
if (this.text.length > MAX_BUFFER) {
|
|
9426
|
+
this.text = this.text.slice(this.text.length - MAX_BUFFER);
|
|
9427
|
+
}
|
|
9428
|
+
for (const wake2 of [...this.waiters]) wake2();
|
|
9429
|
+
}
|
|
9430
|
+
/**
|
|
9431
|
+
* How much of `tail` can be stripped now.
|
|
9432
|
+
*
|
|
9433
|
+
* Everything, unless the final `ESC` has not yet been terminated — in which
|
|
9434
|
+
* case processing stops there and resumes when the rest arrives.
|
|
9435
|
+
*/
|
|
9436
|
+
safeLength() {
|
|
9437
|
+
const lastEsc = Math.max(this.tail.lastIndexOf(ESC), this.tail.lastIndexOf(CSI));
|
|
9438
|
+
if (lastEsc === -1) return this.tail.length;
|
|
9439
|
+
ANSI.lastIndex = lastEsc;
|
|
9440
|
+
const match = ANSI.exec(this.tail);
|
|
9441
|
+
ANSI.lastIndex = 0;
|
|
9442
|
+
if (match && match.index === lastEsc) return this.tail.length;
|
|
9443
|
+
if (this.tail.length - lastEsc > MAX_PENDING_ESCAPE) return this.tail.length;
|
|
9444
|
+
return lastEsc;
|
|
9445
|
+
}
|
|
9446
|
+
/** Unconsumed output, escape sequences removed. */
|
|
9447
|
+
peek() {
|
|
9448
|
+
return this.text;
|
|
9449
|
+
}
|
|
9450
|
+
/** Drop everything currently buffered — used before typing a new command. */
|
|
9451
|
+
clear() {
|
|
9452
|
+
this.text = "";
|
|
9453
|
+
this.tail = "";
|
|
9454
|
+
}
|
|
9455
|
+
/**
|
|
9456
|
+
* Wait until `pattern` matches the unconsumed text.
|
|
9457
|
+
*
|
|
9458
|
+
* Checks what has ALREADY arrived before subscribing, because the common case
|
|
9459
|
+
* is that the process answered while the previous node was still finishing.
|
|
9460
|
+
*
|
|
9461
|
+
* `exited` is raced deliberately. Without it, a shell that dies reports as
|
|
9462
|
+
* "timed out waiting for X" — sending whoever reads it to lengthen a timeout
|
|
9463
|
+
* for a process that is not running. Naming the exit is the difference
|
|
9464
|
+
* between a diagnosis and a wrong lead.
|
|
9465
|
+
*/
|
|
9466
|
+
waitFor(pattern, options) {
|
|
9467
|
+
return new Promise((resolve2) => {
|
|
9468
|
+
let settled = false;
|
|
9469
|
+
let timer2;
|
|
9470
|
+
const finish = (result) => {
|
|
9471
|
+
if (settled) return;
|
|
9472
|
+
settled = true;
|
|
9473
|
+
this.waiters.delete(check);
|
|
9474
|
+
if (timer2) clearTimeout(timer2);
|
|
9475
|
+
resolve2(result);
|
|
9476
|
+
};
|
|
9477
|
+
const check = () => {
|
|
9478
|
+
pattern.lastIndex = 0;
|
|
9479
|
+
const match = pattern.exec(this.text);
|
|
9480
|
+
if (!match) return;
|
|
9481
|
+
const through = match.index + (match[0].length || 1);
|
|
9482
|
+
const text3 = this.text.slice(0, through);
|
|
9483
|
+
this.text = this.text.slice(through);
|
|
9484
|
+
finish({ status: "matched", text: text3, match });
|
|
9485
|
+
};
|
|
9486
|
+
this.waiters.add(check);
|
|
9487
|
+
check();
|
|
9488
|
+
if (settled) return;
|
|
9489
|
+
if (options.timeoutMs > 0) {
|
|
9490
|
+
timer2 = setTimeout(() => {
|
|
9491
|
+
const text3 = this.text;
|
|
9492
|
+
this.text = "";
|
|
9493
|
+
finish({ status: "timeout", text: text3 });
|
|
9494
|
+
}, options.timeoutMs);
|
|
9495
|
+
timer2.unref?.();
|
|
9496
|
+
}
|
|
9497
|
+
options.exited?.then(
|
|
9498
|
+
(exit) => {
|
|
9499
|
+
if (settled) return;
|
|
9500
|
+
const text3 = this.text;
|
|
9501
|
+
this.text = "";
|
|
9502
|
+
finish({ status: "exited", text: text3, exitCode: exit.exitCode, signal: exit.signal });
|
|
9503
|
+
},
|
|
9504
|
+
() => {
|
|
9505
|
+
}
|
|
9506
|
+
);
|
|
9507
|
+
});
|
|
9508
|
+
}
|
|
9509
|
+
};
|
|
9510
|
+
|
|
9511
|
+
// src/runtime/terminal-sessions.ts
|
|
9512
|
+
var TerminalSessions = class {
|
|
9513
|
+
constructor(graph) {
|
|
9514
|
+
this.graph = graph;
|
|
9515
|
+
this.open = /* @__PURE__ */ new Map();
|
|
9516
|
+
this.laneOf = /* @__PURE__ */ new Map();
|
|
9517
|
+
this.transcripts = /* @__PURE__ */ new Map();
|
|
9518
|
+
this.listening = /* @__PURE__ */ new Map();
|
|
9519
|
+
}
|
|
9520
|
+
/**
|
|
9521
|
+
* The terminal lane a node belongs to, or null.
|
|
9522
|
+
*
|
|
9523
|
+
* Walks up `parentId` rather than checking only the immediate parent, and
|
|
9524
|
+
* caches per node id — a graph is walked once per run, and the answer cannot
|
|
9525
|
+
* change while it runs.
|
|
9526
|
+
*/
|
|
9527
|
+
laneFor(nodeId, isTerminalLane) {
|
|
9528
|
+
const cached = this.laneOf.get(nodeId);
|
|
9529
|
+
if (cached !== void 0) return cached;
|
|
9530
|
+
const byId = new Map(this.graph.nodes.map((n) => [n.id, n]));
|
|
9531
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9532
|
+
let current = byId.get(nodeId);
|
|
9533
|
+
let answer = null;
|
|
9534
|
+
while (current) {
|
|
9535
|
+
if (seen.has(current.id)) break;
|
|
9536
|
+
seen.add(current.id);
|
|
9537
|
+
if (current.id !== nodeId && isTerminalLane(current)) {
|
|
9538
|
+
answer = current.id;
|
|
9539
|
+
break;
|
|
9540
|
+
}
|
|
9541
|
+
const parentId = current.parentId;
|
|
9542
|
+
current = parentId ? byId.get(parentId) : void 0;
|
|
9543
|
+
}
|
|
9544
|
+
this.laneOf.set(nodeId, answer);
|
|
9545
|
+
return answer;
|
|
9546
|
+
}
|
|
9547
|
+
/**
|
|
9548
|
+
* The session for a lane, opening it on first use.
|
|
9549
|
+
*
|
|
9550
|
+
* The PROMISE is cached, not the resolved session. Two nodes cannot run
|
|
9551
|
+
* concurrently in the current engine, but a caller that awaits a
|
|
9552
|
+
* half-finished open would otherwise start a second process — and two shells
|
|
9553
|
+
* where a graph says one is the failure this class exists to prevent, arriving
|
|
9554
|
+
* only under concurrency and therefore only sometimes.
|
|
9555
|
+
*/
|
|
9556
|
+
session(laneId, spec) {
|
|
9557
|
+
const existing = this.open.get(laneId);
|
|
9558
|
+
if (existing) return existing;
|
|
9559
|
+
const host = getTerminalHost();
|
|
9560
|
+
if (!host) {
|
|
9561
|
+
const failed = Promise.reject(
|
|
9562
|
+
new Error(
|
|
9563
|
+
"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."
|
|
9564
|
+
)
|
|
9565
|
+
);
|
|
9566
|
+
this.open.set(laneId, failed);
|
|
9567
|
+
failed.catch(() => {
|
|
9568
|
+
});
|
|
9569
|
+
return failed;
|
|
9570
|
+
}
|
|
9571
|
+
const opening = Promise.resolve(host.open(spec)).then((session) => {
|
|
9572
|
+
const transcript = this.transcriptFor(laneId);
|
|
9573
|
+
this.listening.set(laneId, session.onData((chunk) => transcript.append(chunk)));
|
|
9574
|
+
return session;
|
|
9575
|
+
});
|
|
9576
|
+
this.open.set(laneId, opening);
|
|
9577
|
+
opening.catch(() => {
|
|
9578
|
+
});
|
|
9579
|
+
return opening;
|
|
9580
|
+
}
|
|
9581
|
+
/**
|
|
9582
|
+
* The accumulated output for a lane.
|
|
9583
|
+
*
|
|
9584
|
+
* Created on demand and kept for the run, so it exists before the session
|
|
9585
|
+
* resolves and survives every node that reads it. Consuming is the reader's
|
|
9586
|
+
* job — see `TerminalTranscript.waitFor`.
|
|
9587
|
+
*/
|
|
9588
|
+
transcriptFor(laneId) {
|
|
9589
|
+
const existing = this.transcripts.get(laneId);
|
|
9590
|
+
if (existing) return existing;
|
|
9591
|
+
const created = new TerminalTranscript();
|
|
9592
|
+
this.transcripts.set(laneId, created);
|
|
9593
|
+
return created;
|
|
9594
|
+
}
|
|
9595
|
+
/** True once a lane has a session — used to avoid opening one during teardown. */
|
|
9596
|
+
isOpen(laneId) {
|
|
9597
|
+
return this.open.has(laneId);
|
|
9598
|
+
}
|
|
9599
|
+
/**
|
|
9600
|
+
* Close every session this run opened.
|
|
9601
|
+
*
|
|
9602
|
+
* Every close is attempted even if one throws: a host that fails to close one
|
|
9603
|
+
* PTY must not strand the others, which would leave processes alive after the
|
|
9604
|
+
* run reported that it had finished. Errors are returned rather than thrown,
|
|
9605
|
+
* because teardown runs in a `finally` and throwing there would replace the
|
|
9606
|
+
* run's real error with a cleanup error.
|
|
9607
|
+
*/
|
|
9608
|
+
async closeAll() {
|
|
9609
|
+
const errors = [];
|
|
9610
|
+
for (const [laneId, pending] of this.open) {
|
|
9611
|
+
try {
|
|
9612
|
+
this.listening.get(laneId)?.();
|
|
9613
|
+
this.listening.delete(laneId);
|
|
9614
|
+
const session = await pending;
|
|
9615
|
+
await session.close();
|
|
9616
|
+
} catch (e) {
|
|
9617
|
+
errors.push(e instanceof Error ? e : new Error(`${laneId}: ${String(e)}`));
|
|
9618
|
+
}
|
|
9619
|
+
}
|
|
9620
|
+
this.open.clear();
|
|
9621
|
+
this.listening.clear();
|
|
9622
|
+
this.transcripts.clear();
|
|
9623
|
+
return errors;
|
|
9624
|
+
}
|
|
9625
|
+
};
|
|
9626
|
+
function specForLane(lane) {
|
|
9627
|
+
const config = lane.data?.config ?? {};
|
|
9628
|
+
const text3 = (key) => {
|
|
9629
|
+
const value = config[key];
|
|
9630
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
9631
|
+
};
|
|
9632
|
+
const env = config.env;
|
|
9633
|
+
return {
|
|
9634
|
+
command: text3("command"),
|
|
9635
|
+
cwd: text3("cwd"),
|
|
9636
|
+
env: env && typeof env === "object" && !Array.isArray(env) ? Object.fromEntries(
|
|
9637
|
+
Object.entries(env).map(([k, v]) => [k, String(v)])
|
|
9638
|
+
) : void 0
|
|
9639
|
+
};
|
|
9640
|
+
}
|
|
9641
|
+
|
|
9399
9642
|
// src/runtime/workflow-props.ts
|
|
9400
9643
|
function typeOf(value) {
|
|
9401
9644
|
if (value === null) return "null";
|
|
@@ -9474,6 +9717,25 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
9474
9717
|
const props = propsCheck.props;
|
|
9475
9718
|
const declaresProps = (graph.inputs?.length ?? 0) > 0;
|
|
9476
9719
|
const incomingByNode = indexIncoming(graph.edges);
|
|
9720
|
+
const sessions = new TerminalSessions(graph);
|
|
9721
|
+
const isTerminalLane = (candidate) => getNodeKind(candidate.type ?? "")?.name === "@particle-academy/terminal_lane";
|
|
9722
|
+
const terminalAccessorFor = (node) => {
|
|
9723
|
+
const laneId = sessions.laneFor(node.id, isTerminalLane);
|
|
9724
|
+
if (laneId === null) return void 0;
|
|
9725
|
+
const lane = graph.nodes.find((n) => n.id === laneId);
|
|
9726
|
+
if (!lane) return void 0;
|
|
9727
|
+
return {
|
|
9728
|
+
session: () => sessions.session(laneId, specForLane(lane)),
|
|
9729
|
+
// Awaits the session first on purpose. A transcript handed out before
|
|
9730
|
+
// anything is feeding it would look perfectly healthy and match nothing,
|
|
9731
|
+
// and "waited and nothing came" is the hardest failure here to tell apart
|
|
9732
|
+
// from a process that is simply slow.
|
|
9733
|
+
transcript: async () => {
|
|
9734
|
+
await sessions.session(laneId, specForLane(lane));
|
|
9735
|
+
return sessions.transcriptFor(laneId);
|
|
9736
|
+
}
|
|
9737
|
+
};
|
|
9738
|
+
};
|
|
9477
9739
|
const timer2 = timeoutMs ? setTimeout(() => errors.push(`Run timed out after ${timeoutMs}ms`), timeoutMs) : null;
|
|
9478
9740
|
onEvent({ type: "run-start" });
|
|
9479
9741
|
try {
|
|
@@ -9539,7 +9801,8 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
9539
9801
|
emit: onEvent,
|
|
9540
9802
|
executors,
|
|
9541
9803
|
depth,
|
|
9542
|
-
run
|
|
9804
|
+
run,
|
|
9805
|
+
terminal: terminalAccessorFor(node)
|
|
9543
9806
|
})
|
|
9544
9807
|
);
|
|
9545
9808
|
outputs[node.id] = result;
|
|
@@ -9561,6 +9824,9 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
9561
9824
|
}
|
|
9562
9825
|
} finally {
|
|
9563
9826
|
if (timer2) clearTimeout(timer2);
|
|
9827
|
+
for (const error of await sessions.closeAll()) {
|
|
9828
|
+
onEvent({ type: "log", level: "warn", message: `terminal close failed: ${error.message}` });
|
|
9829
|
+
}
|
|
9564
9830
|
}
|
|
9565
9831
|
const ok = errors.length === 0;
|
|
9566
9832
|
onEvent({ type: "run-end", ok });
|
|
@@ -9639,7 +9905,7 @@ function pickExecutor(executors, node) {
|
|
|
9639
9905
|
for (const id2 of executorLookupIds(node)) {
|
|
9640
9906
|
if (executors[id2]) return executors[id2];
|
|
9641
9907
|
}
|
|
9642
|
-
return
|
|
9908
|
+
return getNodeKind(node.type ?? "")?.executor;
|
|
9643
9909
|
}
|
|
9644
9910
|
function activatedPorts(node, result) {
|
|
9645
9911
|
if (result && typeof result === "object") {
|
|
@@ -9749,6 +10015,376 @@ var subflowExecutor = async (ctx) => {
|
|
|
9749
10015
|
return { __port: "out", value: result.outputs };
|
|
9750
10016
|
};
|
|
9751
10017
|
|
|
10018
|
+
// src/expressions/expr.ts
|
|
10019
|
+
function wholeExpression(trimmed) {
|
|
10020
|
+
if (trimmed.length < 4) return null;
|
|
10021
|
+
if (!trimmed.startsWith("{{") || !trimmed.endsWith("}}")) return null;
|
|
10022
|
+
return trimmed.slice(2, -2);
|
|
10023
|
+
}
|
|
10024
|
+
function interpolate(template, resolve2) {
|
|
10025
|
+
let out = "";
|
|
10026
|
+
let i = 0;
|
|
10027
|
+
for (; ; ) {
|
|
10028
|
+
const open = template.indexOf("{{", i);
|
|
10029
|
+
if (open === -1) return out + template.slice(i);
|
|
10030
|
+
const close = template.indexOf("}}", open + 2);
|
|
10031
|
+
if (close === -1) return out + template.slice(i);
|
|
10032
|
+
out += template.slice(i, open) + resolve2(template.slice(open + 2, close));
|
|
10033
|
+
i = close + 2;
|
|
10034
|
+
}
|
|
10035
|
+
}
|
|
10036
|
+
var FALSY_STRINGS = /* @__PURE__ */ new Set(["", "0", "false", "no", "off", "null"]);
|
|
10037
|
+
var UnresolvedPathError = class extends Error {
|
|
10038
|
+
constructor(path) {
|
|
10039
|
+
super(
|
|
10040
|
+
`Expression path "${path}" did not resolve. Under the "throw" policy an unresolvable path is an error rather than an empty string.`
|
|
10041
|
+
);
|
|
10042
|
+
this.path = path;
|
|
10043
|
+
this.name = "UnresolvedPathError";
|
|
10044
|
+
}
|
|
10045
|
+
};
|
|
10046
|
+
function tryResolvePath(path, context) {
|
|
10047
|
+
const unresolved = { resolved: false, value: null };
|
|
10048
|
+
const trimmed = path.trim();
|
|
10049
|
+
if (trimmed === "") return unresolved;
|
|
10050
|
+
const segments = trimmed.split(".");
|
|
10051
|
+
let cursor;
|
|
10052
|
+
const head = segments[0];
|
|
10053
|
+
if (head === "$json" || head === "$input") {
|
|
10054
|
+
cursor = context !== null && typeof context === "object" && "in" in context ? context.in : context;
|
|
10055
|
+
segments.shift();
|
|
10056
|
+
} else {
|
|
10057
|
+
cursor = context;
|
|
10058
|
+
}
|
|
10059
|
+
for (const segment of segments) {
|
|
10060
|
+
if (cursor === null || cursor === void 0) return unresolved;
|
|
10061
|
+
if (typeof cursor !== "object") return unresolved;
|
|
10062
|
+
const next = cursor[segment];
|
|
10063
|
+
if (next === void 0) return unresolved;
|
|
10064
|
+
cursor = next;
|
|
10065
|
+
}
|
|
10066
|
+
return { resolved: true, value: cursor === void 0 ? null : cursor };
|
|
10067
|
+
}
|
|
10068
|
+
function resolvePath(path, context) {
|
|
10069
|
+
return tryResolvePath(path, context).value;
|
|
10070
|
+
}
|
|
10071
|
+
function evaluateExpression(template, context, options = {}) {
|
|
10072
|
+
if (typeof template !== "string") return template;
|
|
10073
|
+
const policy = options.onUnresolved ?? "empty";
|
|
10074
|
+
const whole = wholeExpression(template.trim());
|
|
10075
|
+
if (whole !== null) {
|
|
10076
|
+
const r = tryResolvePath(whole, context);
|
|
10077
|
+
if (r.resolved) return r.value;
|
|
10078
|
+
if (policy === "throw") throw new UnresolvedPathError(whole);
|
|
10079
|
+
return policy === "keep" ? template : null;
|
|
10080
|
+
}
|
|
10081
|
+
return interpolate(template, (path) => {
|
|
10082
|
+
const r = tryResolvePath(path, context);
|
|
10083
|
+
if (r.resolved) return stringify(r.value);
|
|
10084
|
+
if (policy === "throw") throw new UnresolvedPathError(path);
|
|
10085
|
+
return policy === "keep" ? `{{${path}}}` : "";
|
|
10086
|
+
});
|
|
10087
|
+
}
|
|
10088
|
+
function truthy(value) {
|
|
10089
|
+
if (typeof value === "boolean") return value;
|
|
10090
|
+
if (value === null || value === void 0) return false;
|
|
10091
|
+
if (typeof value === "string") return !FALSY_STRINGS.has(value.trim().toLowerCase());
|
|
10092
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
10093
|
+
if (typeof value === "number") return value !== 0;
|
|
10094
|
+
return Boolean(value);
|
|
10095
|
+
}
|
|
10096
|
+
function text(value) {
|
|
10097
|
+
return stringify(value);
|
|
10098
|
+
}
|
|
10099
|
+
function stringify(value) {
|
|
10100
|
+
if (typeof value === "string") return value;
|
|
10101
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
10102
|
+
if (value === null || value === void 0) return "";
|
|
10103
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
10104
|
+
try {
|
|
10105
|
+
return JSON.stringify(value) ?? "";
|
|
10106
|
+
} catch {
|
|
10107
|
+
return "";
|
|
10108
|
+
}
|
|
10109
|
+
}
|
|
10110
|
+
function evaluateConfig(config, context, options = {}) {
|
|
10111
|
+
const out = {};
|
|
10112
|
+
for (const [key, value] of Object.entries(config)) {
|
|
10113
|
+
out[key] = Array.isArray(value) ? value.map((v) => evaluateExpression(v, context, options)) : value !== null && typeof value === "object" ? evaluateConfig(value, context, options) : evaluateExpression(value, context, options);
|
|
10114
|
+
}
|
|
10115
|
+
return out;
|
|
10116
|
+
}
|
|
10117
|
+
|
|
10118
|
+
// src/registry/logic.ts
|
|
10119
|
+
function configOf(node) {
|
|
10120
|
+
return node?.data?.config ?? {};
|
|
10121
|
+
}
|
|
10122
|
+
function contextOf(inputs) {
|
|
10123
|
+
return inputs;
|
|
10124
|
+
}
|
|
10125
|
+
function resolve(value, inputs) {
|
|
10126
|
+
if (typeof value !== "string") return value;
|
|
10127
|
+
return evaluateExpression(value, contextOf(inputs));
|
|
10128
|
+
}
|
|
10129
|
+
function conditionHolds(row, inputs) {
|
|
10130
|
+
const left = resolve(row.left, inputs);
|
|
10131
|
+
const right = resolve(row.right, inputs);
|
|
10132
|
+
const operator = typeof row.operator === "string" ? row.operator : "eq";
|
|
10133
|
+
const asText = (v) => text(v);
|
|
10134
|
+
const asNumber = (v) => {
|
|
10135
|
+
const n = typeof v === "number" ? v : Number(asText(v));
|
|
10136
|
+
return Number.isFinite(n) ? n : NaN;
|
|
10137
|
+
};
|
|
10138
|
+
const numeric = (fn) => {
|
|
10139
|
+
const a = asNumber(left);
|
|
10140
|
+
const b = asNumber(right);
|
|
10141
|
+
return Number.isFinite(a) && Number.isFinite(b) ? fn(a, b) : false;
|
|
10142
|
+
};
|
|
10143
|
+
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;
|
|
10144
|
+
switch (operator) {
|
|
10145
|
+
// Compared as TEXT, deliberately. `right` is a text field in the schema, so
|
|
10146
|
+
// `status == "2"` must match the number 2 an upstream node produced —
|
|
10147
|
+
// strict equality there would fail on a difference the author cannot see.
|
|
10148
|
+
case "eq":
|
|
10149
|
+
return asText(left) === asText(right);
|
|
10150
|
+
case "neq":
|
|
10151
|
+
return asText(left) !== asText(right);
|
|
10152
|
+
case "contains":
|
|
10153
|
+
return asText(left).includes(asText(right));
|
|
10154
|
+
case "not_contains":
|
|
10155
|
+
return !asText(left).includes(asText(right));
|
|
10156
|
+
case "gt":
|
|
10157
|
+
return numeric((a, b) => a > b);
|
|
10158
|
+
case "gte":
|
|
10159
|
+
return numeric((a, b) => a >= b);
|
|
10160
|
+
case "lt":
|
|
10161
|
+
return numeric((a, b) => a < b);
|
|
10162
|
+
case "lte":
|
|
10163
|
+
return numeric((a, b) => a <= b);
|
|
10164
|
+
case "truthy":
|
|
10165
|
+
return truthy(left);
|
|
10166
|
+
case "falsy":
|
|
10167
|
+
return !truthy(left);
|
|
10168
|
+
case "empty":
|
|
10169
|
+
return isEmpty(left);
|
|
10170
|
+
case "not_empty":
|
|
10171
|
+
return !isEmpty(left);
|
|
10172
|
+
default:
|
|
10173
|
+
return false;
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10176
|
+
var branchExecutor = (ctx) => {
|
|
10177
|
+
const config = configOf(ctx.node);
|
|
10178
|
+
const inputs = ctx.inputs;
|
|
10179
|
+
let taken;
|
|
10180
|
+
const raw = config.condition;
|
|
10181
|
+
if (typeof raw === "string" && raw.trim() !== "") {
|
|
10182
|
+
taken = truthy(resolve(raw, inputs));
|
|
10183
|
+
} else {
|
|
10184
|
+
const rows = Array.isArray(config.conditions) ? config.conditions : [];
|
|
10185
|
+
if (rows.length === 0) {
|
|
10186
|
+
taken = false;
|
|
10187
|
+
} else {
|
|
10188
|
+
const results = rows.map((row) => conditionHolds(row, inputs));
|
|
10189
|
+
taken = config.match === "any" ? results.some(Boolean) : results.every(Boolean);
|
|
10190
|
+
}
|
|
10191
|
+
}
|
|
10192
|
+
return { __port: taken ? "true" : "false", value: ctx.inputs.in ?? ctx.inputs };
|
|
10193
|
+
};
|
|
10194
|
+
var transformExecutor = (ctx) => {
|
|
10195
|
+
const config = configOf(ctx.node);
|
|
10196
|
+
const inputs = ctx.inputs;
|
|
10197
|
+
const passthrough = ctx.inputs.in ?? ctx.inputs;
|
|
10198
|
+
if (config.mode === "expression") {
|
|
10199
|
+
const expression = config.expression;
|
|
10200
|
+
if (typeof expression !== "string" || expression.trim() === "") return passthrough;
|
|
10201
|
+
return resolve(expression, inputs);
|
|
10202
|
+
}
|
|
10203
|
+
const rows = Array.isArray(config.fields) ? config.fields : [];
|
|
10204
|
+
if (rows.length === 0) return passthrough;
|
|
10205
|
+
const out = {};
|
|
10206
|
+
let wrote = false;
|
|
10207
|
+
for (const row of rows) {
|
|
10208
|
+
const key = typeof row.key === "string" ? row.key.trim() : "";
|
|
10209
|
+
if (key === "") continue;
|
|
10210
|
+
out[key] = resolve(row.value, inputs);
|
|
10211
|
+
wrote = true;
|
|
10212
|
+
}
|
|
10213
|
+
return wrote ? out : passthrough;
|
|
10214
|
+
};
|
|
10215
|
+
var mergeExecutor = (ctx) => {
|
|
10216
|
+
const entries = Object.entries(ctx.inputs);
|
|
10217
|
+
if (configOf(ctx.node).mode === "concat") {
|
|
10218
|
+
const out = [];
|
|
10219
|
+
for (const [, value] of entries) {
|
|
10220
|
+
if (value === null || value === void 0) continue;
|
|
10221
|
+
if (Array.isArray(value)) out.push(...value);
|
|
10222
|
+
else out.push(value);
|
|
10223
|
+
}
|
|
10224
|
+
return out;
|
|
10225
|
+
}
|
|
10226
|
+
const merged = {};
|
|
10227
|
+
for (const [port, value] of entries) {
|
|
10228
|
+
if (value === null || value === void 0) continue;
|
|
10229
|
+
if (typeof value === "object" && !Array.isArray(value)) Object.assign(merged, value);
|
|
10230
|
+
else merged[port] = value;
|
|
10231
|
+
}
|
|
10232
|
+
return merged;
|
|
10233
|
+
};
|
|
10234
|
+
var forEachExecutor = (ctx) => {
|
|
10235
|
+
const source = resolve(configOf(ctx.node).source, ctx.inputs);
|
|
10236
|
+
let items;
|
|
10237
|
+
if (Array.isArray(source)) items = source;
|
|
10238
|
+
else if (source === null || source === void 0) items = [];
|
|
10239
|
+
else if (typeof source === "object") items = Object.values(source);
|
|
10240
|
+
else items = [source];
|
|
10241
|
+
return { items, count: items.length };
|
|
10242
|
+
};
|
|
10243
|
+
|
|
10244
|
+
// src/registry/terminal-nodes.ts
|
|
10245
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
10246
|
+
var ENTER = "\r";
|
|
10247
|
+
function configOf2(node) {
|
|
10248
|
+
return node?.data?.config ?? {};
|
|
10249
|
+
}
|
|
10250
|
+
function text2(config, key) {
|
|
10251
|
+
const value = config[key];
|
|
10252
|
+
return typeof value === "string" ? value : "";
|
|
10253
|
+
}
|
|
10254
|
+
function millis(config, key, fallback) {
|
|
10255
|
+
const value = Number(config[key]);
|
|
10256
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
10257
|
+
}
|
|
10258
|
+
function exitMarker(nonce) {
|
|
10259
|
+
const token = `__fancy_flow_exit_${nonce}__`;
|
|
10260
|
+
return {
|
|
10261
|
+
token,
|
|
10262
|
+
// The expanded status is required to be digits. The shell ECHOES the typed
|
|
10263
|
+
// command back first, and that echo contains the marker with `$?`
|
|
10264
|
+
// unexpanded — so requiring digits is what stops the node matching its own
|
|
10265
|
+
// command line and reporting success before anything has run.
|
|
10266
|
+
pattern: new RegExp(`${token}:(\\d+)`)
|
|
10267
|
+
};
|
|
10268
|
+
}
|
|
10269
|
+
var counter = 0;
|
|
10270
|
+
function newNonce() {
|
|
10271
|
+
counter += 1;
|
|
10272
|
+
return `${Date.now().toString(36)}${counter.toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
10273
|
+
}
|
|
10274
|
+
function withoutMarker(output, token) {
|
|
10275
|
+
return output.split("\n").filter((line) => !line.includes(token)).join("\n").trim();
|
|
10276
|
+
}
|
|
10277
|
+
function fail(ctx, message) {
|
|
10278
|
+
ctx.abort(message);
|
|
10279
|
+
throw new Error(message);
|
|
10280
|
+
}
|
|
10281
|
+
function requireTerminal(ctx) {
|
|
10282
|
+
if (!ctx.terminal) {
|
|
10283
|
+
fail(
|
|
10284
|
+
ctx,
|
|
10285
|
+
`"${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.`
|
|
10286
|
+
);
|
|
10287
|
+
}
|
|
10288
|
+
return ctx.terminal;
|
|
10289
|
+
}
|
|
10290
|
+
function compilePattern(ctx, raw, config) {
|
|
10291
|
+
const flags = typeof config.flags === "string" ? config.flags.replace(/[gy]/g, "") : "";
|
|
10292
|
+
try {
|
|
10293
|
+
return config.mode === "regex" ? new RegExp(raw, flags) : new RegExp(raw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags);
|
|
10294
|
+
} catch (e) {
|
|
10295
|
+
return fail(ctx, `terminal_await has an invalid regex: ${e instanceof Error ? e.message : String(e)}`);
|
|
10296
|
+
}
|
|
10297
|
+
}
|
|
10298
|
+
var terminalRunExecutor = async (ctx) => {
|
|
10299
|
+
const config = configOf2(ctx.node);
|
|
10300
|
+
const command = text2(config, "command").trim();
|
|
10301
|
+
if (!command) fail(ctx, "terminal_run has no command configured");
|
|
10302
|
+
const terminal = requireTerminal(ctx);
|
|
10303
|
+
const session = await terminal.session();
|
|
10304
|
+
const transcript = await terminal.transcript();
|
|
10305
|
+
const { token, pattern } = exitMarker(newNonce());
|
|
10306
|
+
const timeoutMs = millis(config, "timeoutMs", DEFAULT_TIMEOUT_MS);
|
|
10307
|
+
transcript.clear();
|
|
10308
|
+
await session.write(`${command}; printf '${token}:%s\\n' "$?"${ENTER}`);
|
|
10309
|
+
const result = await transcript.waitFor(pattern, { timeoutMs, exited: session.exited });
|
|
10310
|
+
if (result.status === "exited") {
|
|
10311
|
+
fail(
|
|
10312
|
+
ctx,
|
|
10313
|
+
`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.`
|
|
10314
|
+
);
|
|
10315
|
+
}
|
|
10316
|
+
if (result.status === "timeout") {
|
|
10317
|
+
fail(
|
|
10318
|
+
ctx,
|
|
10319
|
+
`"${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.`
|
|
10320
|
+
);
|
|
10321
|
+
}
|
|
10322
|
+
const exitCode = Number(result.match[1]);
|
|
10323
|
+
const output = withoutMarker(result.text, token);
|
|
10324
|
+
const failOnNonZero = config.failOnNonZero !== false;
|
|
10325
|
+
if (failOnNonZero && exitCode !== 0) {
|
|
10326
|
+
ctx.abort(`"${command}" exited ${exitCode}.
|
|
10327
|
+
${output}`);
|
|
10328
|
+
}
|
|
10329
|
+
return { output, exitCode, command };
|
|
10330
|
+
};
|
|
10331
|
+
var terminalSendExecutor = async (ctx) => {
|
|
10332
|
+
const config = configOf2(ctx.node);
|
|
10333
|
+
const terminal = requireTerminal(ctx);
|
|
10334
|
+
const session = await terminal.session();
|
|
10335
|
+
const body = text2(config, "text");
|
|
10336
|
+
const submit = config.submit !== false;
|
|
10337
|
+
if (config.clearFirst === true) {
|
|
10338
|
+
(await terminal.transcript()).clear();
|
|
10339
|
+
}
|
|
10340
|
+
await session.write(submit ? `${body}${ENTER}` : body);
|
|
10341
|
+
return { sent: body, submitted: submit };
|
|
10342
|
+
};
|
|
10343
|
+
var terminalAwaitExecutor = async (ctx) => {
|
|
10344
|
+
const config = configOf2(ctx.node);
|
|
10345
|
+
const terminal = requireTerminal(ctx);
|
|
10346
|
+
const session = await terminal.session();
|
|
10347
|
+
const transcript = await terminal.transcript();
|
|
10348
|
+
const raw = text2(config, "pattern").trim();
|
|
10349
|
+
if (!raw) fail(ctx, "terminal_await has no pattern configured \u2014 there is nothing for it to wait for");
|
|
10350
|
+
const pattern = compilePattern(ctx, raw, config);
|
|
10351
|
+
const timeoutMs = millis(config, "timeoutMs", DEFAULT_TIMEOUT_MS);
|
|
10352
|
+
const result = await transcript.waitFor(pattern, {
|
|
10353
|
+
timeoutMs,
|
|
10354
|
+
exited: session.exited
|
|
10355
|
+
});
|
|
10356
|
+
if (result.status === "exited") {
|
|
10357
|
+
fail(
|
|
10358
|
+
ctx,
|
|
10359
|
+
`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.`
|
|
10360
|
+
);
|
|
10361
|
+
}
|
|
10362
|
+
if (result.status === "timeout") {
|
|
10363
|
+
if (config.onTimeout === "continue") {
|
|
10364
|
+
ctx.emit({
|
|
10365
|
+
type: "log",
|
|
10366
|
+
nodeId: ctx.node.id,
|
|
10367
|
+
level: "warn",
|
|
10368
|
+
message: `terminal_await: ${JSON.stringify(raw)} did not appear within ${timeoutMs}ms; continuing as configured.`
|
|
10369
|
+
});
|
|
10370
|
+
return { matched: false, output: result.text, groups: [] };
|
|
10371
|
+
}
|
|
10372
|
+
fail(
|
|
10373
|
+
ctx,
|
|
10374
|
+
`${JSON.stringify(raw)} did not appear within ${timeoutMs}ms. Last output:
|
|
10375
|
+
${result.text.slice(-2e3)}`
|
|
10376
|
+
);
|
|
10377
|
+
}
|
|
10378
|
+
return {
|
|
10379
|
+
matched: true,
|
|
10380
|
+
output: result.text,
|
|
10381
|
+
matchedText: result.match[0],
|
|
10382
|
+
// Capture groups are the reason to use regex mode at all — a prompt that
|
|
10383
|
+
// reports a session id or a file path is only useful if the value comes out.
|
|
10384
|
+
groups: result.match.slice(1)
|
|
10385
|
+
};
|
|
10386
|
+
};
|
|
10387
|
+
|
|
9752
10388
|
// src/registry/builtin.ts
|
|
9753
10389
|
var HUMAN_FIELD_OUTPUT_TYPE = {
|
|
9754
10390
|
text: "string",
|
|
@@ -10038,6 +10674,9 @@ var KINDS = [
|
|
|
10038
10674
|
// returns its input on the chosen port
|
|
10039
10675
|
emits: "input",
|
|
10040
10676
|
aliases: ["branch", "@fancy/branch"],
|
|
10677
|
+
// Pure logic: no I/O to choose, so a default is safe. A host
|
|
10678
|
+
// executor still wins -- pickExecutor consults the registry first.
|
|
10679
|
+
executor: branchExecutor,
|
|
10041
10680
|
category: "logic",
|
|
10042
10681
|
label: "Branch",
|
|
10043
10682
|
description: "Multi-way branch on a condition or value.",
|
|
@@ -10135,6 +10774,9 @@ var KINDS = [
|
|
|
10135
10774
|
{ path: "count", type: "number" }
|
|
10136
10775
|
],
|
|
10137
10776
|
aliases: ["for_each", "@fancy/for_each"],
|
|
10777
|
+
// Pure logic: no I/O to choose, so a default is safe. A host
|
|
10778
|
+
// executor still wins -- pickExecutor consults the registry first.
|
|
10779
|
+
executor: forEachExecutor,
|
|
10138
10780
|
category: "logic",
|
|
10139
10781
|
label: "For Each",
|
|
10140
10782
|
description: "Iterate over a list, emitting each item on `item`.",
|
|
@@ -10155,6 +10797,9 @@ var KINDS = [
|
|
|
10155
10797
|
// reference.
|
|
10156
10798
|
emits: (config) => (config?.mode ?? "merge") === "concat" ? null : "inputs-merged",
|
|
10157
10799
|
aliases: ["merge", "@fancy/merge"],
|
|
10800
|
+
// Pure logic: no I/O to choose, so a default is safe. A host
|
|
10801
|
+
// executor still wins -- pickExecutor consults the registry first.
|
|
10802
|
+
executor: mergeExecutor,
|
|
10158
10803
|
category: "logic",
|
|
10159
10804
|
label: "Merge",
|
|
10160
10805
|
description: "Combine multiple inputs into one object or array.",
|
|
@@ -10200,6 +10845,9 @@ var KINDS = [
|
|
|
10200
10845
|
// else the shape that expression names. So the RELATION is config-dependent.
|
|
10201
10846
|
emits: (config) => (config?.expression ?? "") === "" ? "input" : "expression:expression",
|
|
10202
10847
|
aliases: ["transform", "@fancy/transform"],
|
|
10848
|
+
// Pure logic: no I/O to choose, so a default is safe. A host
|
|
10849
|
+
// executor still wins -- pickExecutor consults the registry first.
|
|
10850
|
+
executor: transformExecutor,
|
|
10203
10851
|
category: "logic",
|
|
10204
10852
|
label: "Transform",
|
|
10205
10853
|
description: "Reshape data with an expression.",
|
|
@@ -10658,6 +11306,179 @@ var KINDS = [
|
|
|
10658
11306
|
resizable: { minWidth: 160, minHeight: 72 },
|
|
10659
11307
|
component: LaneNode
|
|
10660
11308
|
},
|
|
11309
|
+
{
|
|
11310
|
+
// Terminal lane — a lane that OWNS a terminal for the length of the run.
|
|
11311
|
+
//
|
|
11312
|
+
// Visually a swimlane, and `layout` for the same reason the plain lane is:
|
|
11313
|
+
// a lane is not a step, so it never executes and never appears in topo
|
|
11314
|
+
// order. What is new is that it DECLARES a resource. The runtime opens its
|
|
11315
|
+
// terminal lazily, when the first terminal node inside it runs, and closes
|
|
11316
|
+
// it in the run's `finally` — so a graph that never reaches a terminal node
|
|
11317
|
+
// never spawns a process, and one that does gets exactly one session no
|
|
11318
|
+
// matter how many nodes use it.
|
|
11319
|
+
//
|
|
11320
|
+
// Membership is `parentId`, which already persists into the WorkflowSchema.
|
|
11321
|
+
// That is what lets a headless runtime resolve the same grouping the canvas
|
|
11322
|
+
// shows, without a second association to keep in sync.
|
|
11323
|
+
name: "@particle-academy/terminal_lane",
|
|
11324
|
+
aliases: ["terminal_lane", "@fancy/terminal_lane"],
|
|
11325
|
+
category: "layout",
|
|
11326
|
+
label: "Terminal lane",
|
|
11327
|
+
description: "A lane that owns one terminal. Opens at the first terminal node, closes when the run ends.",
|
|
11328
|
+
icon: "\u25A3",
|
|
11329
|
+
inputs: [],
|
|
11330
|
+
outputs: [],
|
|
11331
|
+
configSchema: [
|
|
11332
|
+
{ type: "text", key: "title", label: "Title", default: "Terminal" },
|
|
11333
|
+
{
|
|
11334
|
+
type: "text",
|
|
11335
|
+
key: "command",
|
|
11336
|
+
label: "Command",
|
|
11337
|
+
placeholder: "Leave empty for the default shell"
|
|
11338
|
+
},
|
|
11339
|
+
{ type: "text", key: "cwd", label: "Working directory", placeholder: "Host default" },
|
|
11340
|
+
{ type: "keyvalue", key: "env", label: "Environment" },
|
|
11341
|
+
{
|
|
11342
|
+
type: "select",
|
|
11343
|
+
key: "orientation",
|
|
11344
|
+
label: "Orientation",
|
|
11345
|
+
default: "horizontal",
|
|
11346
|
+
options: [
|
|
11347
|
+
{ value: "horizontal", label: "Row" },
|
|
11348
|
+
{ value: "vertical", label: "Column" }
|
|
11349
|
+
]
|
|
11350
|
+
}
|
|
11351
|
+
],
|
|
11352
|
+
defaultConfig: { title: "Terminal", orientation: "horizontal" },
|
|
11353
|
+
resizable: { minWidth: 220, minHeight: 120 },
|
|
11354
|
+
component: LaneNode
|
|
11355
|
+
},
|
|
11356
|
+
{
|
|
11357
|
+
// Terminal nodes. All three live inside a terminal lane and talk to the
|
|
11358
|
+
// session that lane owns; outside one they abort by name rather than
|
|
11359
|
+
// opening a shell of their own.
|
|
11360
|
+
name: "@particle-academy/terminal_run",
|
|
11361
|
+
aliases: ["terminal_run", "@fancy/terminal_run"],
|
|
11362
|
+
category: "io",
|
|
11363
|
+
label: "Run in terminal",
|
|
11364
|
+
description: "Run a shell command in the lane's terminal and wait for its exit code.",
|
|
11365
|
+
icon: "$",
|
|
11366
|
+
inputs: [{ id: "in" }],
|
|
11367
|
+
outputs: [{ id: "out", label: "output" }],
|
|
11368
|
+
executor: terminalRunExecutor,
|
|
11369
|
+
configSchema: [
|
|
11370
|
+
{
|
|
11371
|
+
type: "textarea",
|
|
11372
|
+
key: "command",
|
|
11373
|
+
label: "Command",
|
|
11374
|
+
rows: 3,
|
|
11375
|
+
required: true,
|
|
11376
|
+
placeholder: "npm test",
|
|
11377
|
+
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."
|
|
11378
|
+
},
|
|
11379
|
+
{
|
|
11380
|
+
type: "number",
|
|
11381
|
+
key: "timeoutMs",
|
|
11382
|
+
label: "Timeout (ms)",
|
|
11383
|
+
default: 12e4,
|
|
11384
|
+
description: "How long to wait for the command to finish before failing the run."
|
|
11385
|
+
},
|
|
11386
|
+
{
|
|
11387
|
+
type: "switch",
|
|
11388
|
+
key: "failOnNonZero",
|
|
11389
|
+
label: "Fail the run on a non-zero exit",
|
|
11390
|
+
default: true,
|
|
11391
|
+
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."
|
|
11392
|
+
}
|
|
11393
|
+
],
|
|
11394
|
+
defaultConfig: { command: "", timeoutMs: 12e4, failOnNonZero: true }
|
|
11395
|
+
},
|
|
11396
|
+
{
|
|
11397
|
+
name: "@particle-academy/terminal_send",
|
|
11398
|
+
aliases: ["terminal_send", "@fancy/terminal_send"],
|
|
11399
|
+
category: "io",
|
|
11400
|
+
label: "Send to terminal",
|
|
11401
|
+
description: "Type text at whatever is running in the lane's terminal, without waiting.",
|
|
11402
|
+
icon: "\u2328",
|
|
11403
|
+
inputs: [{ id: "in" }],
|
|
11404
|
+
outputs: [{ id: "out", label: "sent" }],
|
|
11405
|
+
executor: terminalSendExecutor,
|
|
11406
|
+
configSchema: [
|
|
11407
|
+
{
|
|
11408
|
+
type: "textarea",
|
|
11409
|
+
key: "text",
|
|
11410
|
+
label: "Text",
|
|
11411
|
+
rows: 4,
|
|
11412
|
+
placeholder: "Summarise the failing test and propose a fix.",
|
|
11413
|
+
description: "What to type. This is how a graph prompts an agent TUI such as Claude Code or Codex."
|
|
11414
|
+
},
|
|
11415
|
+
{
|
|
11416
|
+
type: "switch",
|
|
11417
|
+
key: "submit",
|
|
11418
|
+
label: "Press Enter",
|
|
11419
|
+
default: true,
|
|
11420
|
+
description: "Off leaves the text on the input line \u2014 useful for building one up across several nodes."
|
|
11421
|
+
},
|
|
11422
|
+
{
|
|
11423
|
+
type: "switch",
|
|
11424
|
+
key: "clearFirst",
|
|
11425
|
+
label: "Forget earlier output first",
|
|
11426
|
+
default: false,
|
|
11427
|
+
description: "Discards anything the terminal said before this send, so a following Await cannot match a prompt left over from the previous exchange."
|
|
11428
|
+
}
|
|
11429
|
+
],
|
|
11430
|
+
defaultConfig: { text: "", submit: true, clearFirst: false }
|
|
11431
|
+
},
|
|
11432
|
+
{
|
|
11433
|
+
name: "@particle-academy/terminal_await",
|
|
11434
|
+
aliases: ["terminal_await", "@fancy/terminal_await"],
|
|
11435
|
+
category: "io",
|
|
11436
|
+
label: "Await terminal output",
|
|
11437
|
+
description: "Wait until the lane's terminal prints something that matches.",
|
|
11438
|
+
icon: "\u23F1",
|
|
11439
|
+
inputs: [{ id: "in" }],
|
|
11440
|
+
outputs: [{ id: "out", label: "output" }],
|
|
11441
|
+
executor: terminalAwaitExecutor,
|
|
11442
|
+
configSchema: [
|
|
11443
|
+
{
|
|
11444
|
+
type: "text",
|
|
11445
|
+
key: "pattern",
|
|
11446
|
+
label: "Wait for",
|
|
11447
|
+
required: true,
|
|
11448
|
+
placeholder: "esc to interrupt",
|
|
11449
|
+
description: "Matched against the output with colour codes already stripped, so match what you SEE."
|
|
11450
|
+
},
|
|
11451
|
+
{
|
|
11452
|
+
type: "select",
|
|
11453
|
+
key: "mode",
|
|
11454
|
+
label: "Match as",
|
|
11455
|
+
default: "text",
|
|
11456
|
+
options: [
|
|
11457
|
+
{ value: "text", label: "Plain text" },
|
|
11458
|
+
{ value: "regex", label: "Regular expression" }
|
|
11459
|
+
],
|
|
11460
|
+
description: "Regex mode also returns capture groups, which is how a value gets out of a prompt."
|
|
11461
|
+
},
|
|
11462
|
+
{
|
|
11463
|
+
type: "number",
|
|
11464
|
+
key: "timeoutMs",
|
|
11465
|
+
label: "Timeout (ms)",
|
|
11466
|
+
default: 12e4
|
|
11467
|
+
},
|
|
11468
|
+
{
|
|
11469
|
+
type: "select",
|
|
11470
|
+
key: "onTimeout",
|
|
11471
|
+
label: "If it never appears",
|
|
11472
|
+
default: "fail",
|
|
11473
|
+
options: [
|
|
11474
|
+
{ value: "fail", label: "Fail the run" },
|
|
11475
|
+
{ value: "continue", label: "Continue with matched: false" }
|
|
11476
|
+
],
|
|
11477
|
+
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."
|
|
11478
|
+
}
|
|
11479
|
+
],
|
|
11480
|
+
defaultConfig: { pattern: "", mode: "text", timeoutMs: 12e4, onTimeout: "fail" }
|
|
11481
|
+
},
|
|
10661
11482
|
{
|
|
10662
11483
|
// Note — a sticky-note annotation. Portless + visual-only: the runtime skips
|
|
10663
11484
|
// the `annotation` category, so a note's text NEVER reaches a runner — it
|
|
@@ -10905,6 +11726,6 @@ function LaneNodeInner(props) {
|
|
|
10905
11726
|
}
|
|
10906
11727
|
var LaneNode = memo(LaneNodeInner);
|
|
10907
11728
|
|
|
10908
|
-
export { BUILTIN_KINDS, Background, BackgroundVariant, Controls, DEFAULT_MAX_DEPTH, FlowEditorProvider, Handle, LaneNode, MiniMap, NodeResizer, NodeToolbar, NoteNode, Position, ReactFlowProvider, RunIdentity, ViewportPortal, addEdge2 as addEdge, applyEdgeChanges, applyNodeChanges, categoryAccent, clearNodeKindOverrides, declaredRoutes, defaultConfigFor, ensureBuiltinKinds, escapeSegment, getNodeKind, humanInputFields, index, kindIds, listNodeKinds, llmRouterExecutor, nodeConfig, onNodeKindsChanged, overrideNodeKind, reconnectEdge2 as reconnectEdge, registerBuiltinKinds, registerNodeKind, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePortSpec, runFlow, subflowExecutor, subflowMode, subflowPorts, useFlowEditor, useFlowEditorOptional, useReactFlow, validateConfig };
|
|
10909
|
-
//# sourceMappingURL=chunk-
|
|
10910
|
-
//# sourceMappingURL=chunk-
|
|
11729
|
+
export { BUILTIN_KINDS, Background, BackgroundVariant, Controls, DEFAULT_MAX_DEPTH, FlowEditorProvider, Handle, LaneNode, MiniMap, NodeResizer, NodeToolbar, NoteNode, Position, ReactFlowProvider, RunIdentity, UnresolvedPathError, ViewportPortal, addEdge2 as addEdge, applyEdgeChanges, applyNodeChanges, categoryAccent, clearNodeKindOverrides, declaredRoutes, defaultConfigFor, ensureBuiltinKinds, escapeSegment, evaluateConfig, evaluateExpression, getNodeKind, humanInputFields, index, kindIds, listNodeKinds, llmRouterExecutor, nodeConfig, onNodeKindsChanged, overrideNodeKind, reconnectEdge2 as reconnectEdge, registerBuiltinKinds, registerNodeKind, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePath, resolvePortSpec, runFlow, subflowExecutor, subflowMode, subflowPorts, text, truthy, tryResolvePath, useFlowEditor, useFlowEditorOptional, useReactFlow, validateConfig };
|
|
11730
|
+
//# sourceMappingURL=chunk-HQGOFGAL.js.map
|
|
11731
|
+
//# sourceMappingURL=chunk-HQGOFGAL.js.map
|