@field/cascade 0.11.0 → 0.13.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/AGENTS.md +8 -5
- package/ARCHITECTURE.md +5 -4
- package/CHANGELOG.md +245 -0
- package/README.md +1 -1
- package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
- package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
- package/dist/assets/{CodeEditor-4O7FQ__R.js → CodeEditor-C-xYG-Wn.js} +120 -120
- package/dist/assets/CodePanel-CtXd50m6.js +2 -0
- package/dist/assets/CookInfoPanel-B3NMhKJF.css +1 -0
- package/dist/assets/CookInfoPanel-DG4_TRsX.js +2 -0
- package/dist/assets/CustomNodeDialog-CHfZUs-r.js +1 -0
- package/dist/assets/DefinitionNode-CaRTXQ_b.js +1 -0
- package/dist/assets/DefinitionPanel-C3CxZY9r.js +1 -0
- package/dist/assets/DefinitionPanel-VNLnAiQD.css +1 -0
- package/dist/assets/OpenGraphDialog-BLdr6k_q.js +1 -0
- package/dist/assets/ProjectSettings-ZnvmIAir.js +2 -0
- package/dist/assets/{SettingsDialog-Bz8JxISH.js → SettingsDialog-CBX3Wa87.js} +1 -1
- package/dist/assets/TimelinePanel-CDeR0arY.css +1 -0
- package/dist/assets/TimelinePanel-CWgnFH1e.js +1 -0
- package/dist/assets/{VersionHistoryDialog-D5Z0nJ5-.js → VersionHistoryDialog-BOSoW1R-.js} +1 -1
- package/dist/assets/assert-BO2_erPD.js +1 -0
- package/dist/assets/assert-hvykGCnP.js +1 -0
- package/dist/assets/builder-BmBKcDix.js +1 -0
- package/dist/assets/camera-DFF7YA3F.js +1 -0
- package/dist/assets/checks-DAvyO9Gf.js +1 -0
- package/dist/assets/contactSheet-N1NcaWxw.js +2 -0
- package/dist/assets/{core-Yqi0Ni61.js → core-DB-MkGjj.js} +3 -4
- package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
- package/dist/assets/{cssMode-CxUzc4vp.js → cssMode-DPVckSOc.js} +1 -1
- package/dist/assets/definitions-B227e7f_.js +1 -0
- package/dist/assets/definitions-D0WOUJd7.js +1 -0
- package/dist/assets/{editor.api-BNFb25CX.js → editor.api-Dkutg_Z1.js} +1 -1
- package/dist/assets/expressions-X-Z-GrgW.js +5 -0
- package/dist/assets/file-DAL6IfjV.js +1 -0
- package/dist/assets/filters-D3k1LRMa.js +1 -0
- package/dist/assets/{freemarker2-0ZDnWxeM.js → freemarker2-CLiLKk3B.js} +1 -1
- package/dist/assets/generators-BhDxRAFC.js +1 -0
- package/dist/assets/geo-BsMImJIJ.js +1 -0
- package/dist/assets/geo-C4YngDob.js +2 -0
- package/dist/assets/{handlebars-Cy18MoaD.js → handlebars-CcD3aMeL.js} +1 -1
- package/dist/assets/{html-B-2Dx7nq.js → html-DvZWGfqb.js} +1 -1
- package/dist/assets/{htmlMode-DUAc9B_d.js → htmlMode-DUD3VaoN.js} +1 -1
- package/dist/assets/image-0SfH-snh.js +1 -0
- package/dist/assets/image-Ck-RdGU4.js +2 -0
- package/dist/assets/index-BWnrmIB9.js +137 -0
- package/dist/assets/index-DIaxEONI.css +1 -0
- package/dist/assets/io-BGmt2g0D.js +1 -0
- package/dist/assets/{javascript-DeQsDqP5.js → javascript-Cap2ahgh.js} +1 -1
- package/dist/assets/{jsonMode-BiFwJVSk.js → jsonMode-B0uLjp9C.js} +1 -1
- package/dist/assets/{liquid-BLoHH68a.js → liquid-CnxwhfLn.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-CNFgm1Kv.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
- package/dist/assets/math-GeULS-sk.js +1 -0
- package/dist/assets/{mdx-CFXQVNfs.js → mdx-BV3jbrVa.js} +1 -1
- package/dist/assets/pop-KMF_qloi.js +1 -0
- package/dist/assets/preload-helper-Czpn1I53.js +1 -0
- package/dist/assets/{python-Cf7S7p2E.js → python-CGyFk4XK.js} +1 -1
- package/dist/assets/{razor-CMHBcG-g.js → razor-CQfzX3-N.js} +1 -1
- package/dist/assets/remap-5SjXQU-q.js +1 -0
- package/dist/assets/renderMarkdown-ork0Oir7.js +67 -0
- package/dist/assets/studioImages-CAyH-0Pl.js +1 -0
- package/dist/assets/text-ZpX7qNkx.js +2 -0
- package/dist/assets/time-P_OiBhcf.js +1 -0
- package/dist/assets/{tsMode-BEih0GDj.js → tsMode-BOeXzO9H.js} +1 -1
- package/dist/assets/{typescript-COEKYD_a.js → typescript-J1SnE_Tu.js} +1 -1
- package/dist/assets/{workers-BNhAhMNn.js → workers-CYwwTiVF.js} +1 -1
- package/dist/assets/{xml-CPiMpnPl.js → xml-DnPRNzK9.js} +1 -1
- package/dist/assets/{yaml-Bgp1ITLR.js → yaml-BmmYBNa9.js} +1 -1
- package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
- package/dist/cli/{Graph-ZJJTRQTM.js → Graph-OB6EWSGL.js} +12 -12
- package/dist/cli/assert-GROGQNCQ.js +25 -0
- package/dist/cli/checks-3WB44XBL.js +189 -0
- package/dist/cli/{chunk-3JTJHF7V.js → chunk-2L34ERS2.js} +4 -4
- package/dist/cli/chunk-53LGFPWX.js +138 -0
- package/dist/cli/chunk-5ORUQ6ES.js +267 -0
- package/dist/cli/{chunk-YAT5Y25U.js → chunk-7QE2ZNSN.js} +3 -3
- package/dist/cli/{chunk-SGJAET2L.js → chunk-BPODRH52.js} +8 -4
- package/dist/cli/{chunk-XWDSKG7N.js → chunk-BZ63XQ3I.js} +2 -2
- package/dist/cli/{chunk-FCDXJ6EK.js → chunk-CTPLRSWF.js} +2 -13
- package/dist/cli/{chunk-WIH4TEIS.js → chunk-EZRWJ4G2.js} +1 -1
- package/dist/cli/{chunk-BIJO5OFS.js → chunk-FWMSGKEH.js} +1 -1
- package/dist/cli/{chunk-POMMNKZZ.js → chunk-G23A2BZZ.js} +38 -5
- package/dist/cli/{chunk-35D343DC.js → chunk-GRIN2DDP.js} +19 -1
- package/dist/cli/{chunk-WKURDVYP.js → chunk-JEW5MPGI.js} +28 -331
- package/dist/cli/chunk-JVOCK5W4.js +83 -0
- package/dist/cli/chunk-KBGUO27Q.js +15 -0
- package/dist/cli/{chunk-GFS6W3C5.js → chunk-L6B6FUQP.js} +54 -32
- package/dist/cli/chunk-LTXWBXOM.js +438 -0
- package/dist/cli/chunk-MI5JLQYA.js +220 -0
- package/dist/cli/{chunk-A4OAMOZH.js → chunk-NJFMDXHR.js} +13 -3
- package/dist/cli/{chunk-DUIUFVU2.js → chunk-OBZAP3GG.js} +49 -23
- package/dist/cli/chunk-QAGATJGE.js +336 -0
- package/dist/cli/{chunk-EUIAC3U3.js → chunk-QKL7CCRS.js} +966 -132
- package/dist/cli/{chunk-RLCAJBVR.js → chunk-TJW67QSI.js} +8 -7
- package/dist/cli/{chunk-RQZ4SX5R.js → chunk-W5PJ5RMP.js} +156 -84
- package/dist/cli/chunk-W7OUAT7J.js +122 -0
- package/dist/cli/chunk-WGKPJZDY.js +364 -0
- package/dist/cli/{chunk-D6BUOHUG.js → chunk-X4WKAOQP.js} +4 -4
- package/dist/cli/{chunk-VVGSLVSF.js → chunk-X566FWBV.js} +6 -1
- package/dist/cli/chunk-YH637PDL.js +317 -0
- package/dist/cli/chunk-ZSCUZE6I.js +112 -0
- package/dist/cli/{cliCommands-UQSFGEHF.js → cliCommands-XVFRD7CF.js} +23 -17
- package/dist/cli/{commands-Q23M26QM.js → commands-M2XLTPTZ.js} +129 -47
- package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
- package/dist/cli/{definitions-HF2FTHYU.js → definitions-BPOPW52L.js} +7 -7
- package/dist/cli/{doctorCommand-B7BPQMN6.js → doctorCommand-RAEI7GVY.js} +36 -6
- package/dist/cli/{edit-JHCPTQFT.js → edit-S5KEQ5RA.js} +3 -3
- package/dist/cli/editorCommands-5BBG4S6V.js +41 -0
- package/dist/cli/{encode-HAUZTYPT.js → encode-ICPEPMH5.js} +2 -2
- package/dist/cli/file-3HTQHPYQ.js +41 -0
- package/dist/cli/filters-6ODFWYK4.js +379 -0
- package/dist/cli/generators-YU7GB4MD.js +210 -0
- package/dist/cli/{geo-4RNK3QVN.js → geo-6ZPGKZRU.js} +7 -7
- package/dist/cli/{graphCommands-6BMAKGQE.js → graphCommands-MULQ7VD2.js} +4 -4
- package/dist/cli/{graphFile-VPSQ6OF7.js → graphFile-CITF36ZM.js} +25 -19
- package/dist/cli/{headlessCanvas-5XLDZT7M.js → headlessCanvas-ENRHC7GS.js} +3 -2
- package/dist/cli/{helpCommand-3VRE3KVO.js → helpCommand-X2QDANM3.js} +9 -7
- package/dist/cli/image-TXELZZK5.js +55 -0
- package/dist/cli/index.js +14 -14
- package/dist/cli/{math-ZRMLKU2D.js → math-RSYRQ3C6.js} +5 -5
- package/dist/cli/{mcp-ZLEEMYGF.js → mcp-YRIGRLYQ.js} +4 -4
- package/dist/cli/{parity-KINARCP2.js → parity-3N3HTKWX.js} +42 -36
- package/dist/cli/{pop-USDFXB7H.js → pop-B7SIIYKN.js} +5 -5
- package/dist/cli/project-SJF5ZF3F.js +12 -0
- package/dist/cli/{projectDefinition-DMTZN2NE.js → projectDefinition-F3D7UIHJ.js} +8 -8
- package/dist/cli/{projectRuntime-Z5PJOH47.js → projectRuntime-NY4XMDFH.js} +19 -12
- package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
- package/dist/cli/{render-YURAPPUI.js → render-Y23W6MY6.js} +29 -22
- package/dist/cli/{runCommand-PI764LEG.js → runCommand-XOG7AVC5.js} +3 -3
- package/dist/cli/runner-DNJO5WO2.js +43 -0
- package/dist/cli/runtime/image.ts +27 -2
- package/dist/cli/runtime/io.ts +78 -0
- package/dist/cli/share-B7HTCOJX.js +166 -0
- package/dist/cli/stageCache-TY2BFZJR.js +15 -0
- package/dist/cli/{stageRunner-KUNIMQKJ.js → stageRunner-KY2EJP7X.js} +2 -2
- package/dist/cli/text-MB3EKR4H.js +126 -0
- package/dist/cli/{verbs-RCE3K4VQ.js → verbs-PI2JH5FI.js} +2 -2
- package/dist/cli/webBuild-2K4COCGY.js +44 -0
- package/dist/contracts/capabilities.d.ts +39 -1
- package/dist/contracts/color.d.ts +8 -0
- package/dist/contracts/color.js +23 -0
- package/dist/contracts/definition.d.ts +10 -0
- package/dist/contracts/schema.d.ts +1 -1
- package/dist/contracts/schema.js +1 -0
- package/dist/contracts/validate.js +11 -1
- package/dist/contracts/values.d.ts +24 -0
- package/dist/image.d.ts +15 -0
- package/dist/image.js +36 -2
- package/dist/index.html +8 -2
- package/dist/io.d.ts +29 -0
- package/dist/io.js +40 -0
- package/dist/player/app.js +11 -7
- package/dist/player/editor.js +3 -2
- package/dist/player/editorEngine.js +3 -2
- package/dist/player/embed.js +2 -0
- package/dist/player/types.d.ts +6 -0
- package/dist/runtime/builtins/assert/checks.d.ts +18 -0
- package/dist/runtime/builtins/assert/checks.js +207 -0
- package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
- package/dist/runtime/builtins/assert/definitions.js +54 -0
- package/dist/runtime/builtins/assert/index.d.ts +152 -0
- package/dist/runtime/builtins/assert/index.js +36 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +3 -165
- package/dist/runtime/builtins/image/contactSheet.js +0 -49
- package/dist/runtime/builtins/image/definitions.d.ts +950 -0
- package/dist/runtime/builtins/image/definitions.js +343 -0
- package/dist/runtime/builtins/image/file.d.ts +8 -0
- package/dist/runtime/builtins/image/file.js +37 -0
- package/dist/runtime/builtins/image/filters.d.ts +8 -0
- package/dist/runtime/builtins/image/filters.js +394 -0
- package/dist/runtime/builtins/image/generators.d.ts +7 -0
- package/dist/runtime/builtins/image/generators.js +144 -0
- package/dist/runtime/builtins/image/index.d.ts +947 -4
- package/dist/runtime/builtins/image/index.js +41 -6
- package/dist/runtime/builtins/image/io.d.ts +100 -0
- package/dist/runtime/builtins/image/io.js +142 -0
- package/dist/runtime/builtins/image/planar.d.ts +55 -0
- package/dist/runtime/builtins/image/planar.js +274 -0
- package/dist/runtime/builtins/image/simplex.d.ts +22 -0
- package/dist/runtime/builtins/image/simplex.js +92 -0
- package/dist/runtime/builtins/image/text.d.ts +4 -0
- package/dist/runtime/builtins/image/text.js +141 -0
- package/dist/runtime/builtins/index.d.ts +3 -0
- package/dist/runtime/builtins/index.js +7 -0
- package/dist/runtime/definition/architecture.js +89 -0
- package/dist/runtime/gpu.d.ts +25 -0
- package/dist/runtime/gpu.js +40 -0
- package/dist/runtime/runtime.d.ts +2 -0
- package/dist/runtime/runtime.js +268 -44
- package/dist/runtime/types.d.ts +30 -0
- package/doc/CLI_REFERENCE.md +21 -3
- package/doc/EDITOR_EXPORT.md +26 -0
- package/doc/MCP.md +2 -1
- package/doc/NODE_REFERENCE.md +286 -14
- package/doc/PROJECT_AUTHORING.md +158 -4
- package/doc/WEB_PLAYER.md +9 -1
- package/package.json +4 -1
- package/dist/assets/CodePanel-BU-autmq.js +0 -2
- package/dist/assets/CustomNodeDialog-BRpDfOV5.js +0 -1
- package/dist/assets/OpenGraphDialog-BwbRlC0X.js +0 -1
- package/dist/assets/ProjectSettings-DLOXLxcA.js +0 -2
- package/dist/assets/definitions-GreEMXLV.js +0 -1
- package/dist/assets/geo-u0n_jOsD.js +0 -1
- package/dist/assets/image-cAa-Nu9O.js +0 -2062
- package/dist/assets/index-5ZtDAuy2.js +0 -220
- package/dist/assets/index-7vae6YFC.css +0 -1
- package/dist/assets/math-DioIQOPJ.js +0 -1
- package/dist/assets/pop-DC_TQG9t.js +0 -1
- package/dist/cli/chunk-36P5K3SD.js +0 -631
- package/dist/cli/chunk-4UDFYF5G.js +0 -132
- package/dist/cli/chunk-53NMBJAO.js +0 -35
- package/dist/cli/chunk-MIIEFUGG.js +0 -623
- package/dist/cli/chunk-QBBKRNGY.js +0 -90
- package/dist/cli/chunk-WPUDGEFY.js +0 -20
- package/dist/cli/editorCommands-UIF5YLUZ.js +0 -33
- package/dist/cli/project-K3QIPGBB.js +0 -12
- package/dist/cli/registerStandardNodes-S2VLMF6J.js +0 -3081
- package/dist/cli/runner-RA6UKEPD.js +0 -37
- package/dist/cli/webBuild-GT4PZT5D.js +0 -37
- package/dist/runtime/builtins/headless.d.ts +0 -19
- package/dist/runtime/builtins/headless.js +0 -25
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// server/src/pythonWorker.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import readline from "readline";
|
|
6
|
+
var REQUEST_TIMEOUT_MS = 12e4;
|
|
7
|
+
var CANCEL_GRACE_MS = 3e3;
|
|
8
|
+
var StageCancelledError = class extends Error {
|
|
9
|
+
constructor(stage) {
|
|
10
|
+
super(`stage "${stage}" was cancelled`);
|
|
11
|
+
this.cancelled = true;
|
|
12
|
+
this.name = "StageCancelledError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var liveWorkers = /* @__PURE__ */ new Set();
|
|
16
|
+
function pythonWorkerStatuses() {
|
|
17
|
+
return [...liveWorkers].map((worker) => worker.status);
|
|
18
|
+
}
|
|
19
|
+
function shutdownAllPythonWorkers() {
|
|
20
|
+
for (const worker of [...liveWorkers]) worker.shutdown();
|
|
21
|
+
}
|
|
22
|
+
var PythonWorker = class {
|
|
23
|
+
constructor(project, entrypoint, python = "python3", env = process.env, options = {}) {
|
|
24
|
+
this.proc = null;
|
|
25
|
+
this.nextId = 1;
|
|
26
|
+
this.queue = [];
|
|
27
|
+
/** The request the worker is running, or one it was told to abandon and has
|
|
28
|
+
* not yet answered. Nothing else is written until it answers or exits. */
|
|
29
|
+
this.inFlight = null;
|
|
30
|
+
/** Set once the in-flight request has been cancelled: its answer is
|
|
31
|
+
* discarded whatever it says. */
|
|
32
|
+
this.inFlightCancelled = false;
|
|
33
|
+
this.killTimer = null;
|
|
34
|
+
/** The code hash the running process started with. */
|
|
35
|
+
this.spawnedCodeHash = null;
|
|
36
|
+
/** Set by a restart, cleared by the answer that follows it. */
|
|
37
|
+
this.restartPending = false;
|
|
38
|
+
/** How many times the worker restarted because its code changed. */
|
|
39
|
+
this.restarts = 0;
|
|
40
|
+
this.project = project;
|
|
41
|
+
this.entrypoint = entrypoint;
|
|
42
|
+
this.python = python;
|
|
43
|
+
this.env = env;
|
|
44
|
+
this.cancelGraceMs = options.cancelGraceMs ?? CANCEL_GRACE_MS;
|
|
45
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
46
|
+
this.sources = options.sources ?? null;
|
|
47
|
+
this.onStderr = options.onStderr ?? ((text) => console.error("[python-worker]", text));
|
|
48
|
+
liveWorkers.add(this);
|
|
49
|
+
}
|
|
50
|
+
get status() {
|
|
51
|
+
const pid = this.pid;
|
|
52
|
+
return {
|
|
53
|
+
entrypoint: this.entrypoint,
|
|
54
|
+
pid,
|
|
55
|
+
state: pid === null ? "stopped" : this.inFlight ? "busy" : "idle",
|
|
56
|
+
queued: this.queue.length,
|
|
57
|
+
codeHash: this.spawnedCodeHash,
|
|
58
|
+
restarts: this.restarts
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Requests sent or waiting: what a pool balances on. */
|
|
62
|
+
get pending() {
|
|
63
|
+
return (this.inFlight ? 1 : 0) + this.queue.length;
|
|
64
|
+
}
|
|
65
|
+
/** The code hash the running worker started with; null before the first
|
|
66
|
+
* request or when sources are not watched. */
|
|
67
|
+
get codeHash() {
|
|
68
|
+
return this.spawnedCodeHash;
|
|
69
|
+
}
|
|
70
|
+
/** The worker's process id, or null when none is running. For tests and
|
|
71
|
+
* diagnostics: a cancelled stage must not leave an orphan behind. */
|
|
72
|
+
get pid() {
|
|
73
|
+
return this.proc?.pid ?? null;
|
|
74
|
+
}
|
|
75
|
+
ensureStarted() {
|
|
76
|
+
if (this.proc && this.proc.exitCode === null && this.proc.signalCode === null) return this.proc;
|
|
77
|
+
this.spawnedCodeHash = this.sources?.codeHash() ?? null;
|
|
78
|
+
const scriptPath = this.project.resolve(this.entrypoint);
|
|
79
|
+
const proc = spawn(this.python, [scriptPath], { cwd: this.project.root, env: this.spawnEnv() });
|
|
80
|
+
readline.createInterface({ input: proc.stdout }).on("line", (line) => this.handleLine(proc, line));
|
|
81
|
+
proc.stderr.on("data", (chunk) => this.onStderr(chunk.toString().trimEnd()));
|
|
82
|
+
proc.stdin.on("error", () => {
|
|
83
|
+
});
|
|
84
|
+
proc.on("error", (error) => {
|
|
85
|
+
if (this.proc !== proc) return;
|
|
86
|
+
this.proc = null;
|
|
87
|
+
const current = this.inFlight;
|
|
88
|
+
this.inFlight = null;
|
|
89
|
+
this.inFlightCancelled = false;
|
|
90
|
+
if (current) this.settle(current, new Error(`python worker could not start (${this.python}): ${error.message}`));
|
|
91
|
+
else this.onStderr(`python worker could not start (${this.python}): ${error.message}`);
|
|
92
|
+
});
|
|
93
|
+
proc.on("exit", (code, signal) => {
|
|
94
|
+
if (this.proc !== proc) return;
|
|
95
|
+
this.proc = null;
|
|
96
|
+
this.clearKillTimer();
|
|
97
|
+
const current = this.inFlight;
|
|
98
|
+
const cancelled = this.inFlightCancelled;
|
|
99
|
+
this.inFlight = null;
|
|
100
|
+
this.inFlightCancelled = false;
|
|
101
|
+
if (current && !cancelled) {
|
|
102
|
+
console.warn(`[python-worker] exited (${signal ?? `code ${code}`}) \u2014 will respawn on next call`);
|
|
103
|
+
this.settle(current, new Error(`python worker exited (${signal ?? `code ${code}`}) before responding`));
|
|
104
|
+
}
|
|
105
|
+
this.pump();
|
|
106
|
+
});
|
|
107
|
+
this.proc = proc;
|
|
108
|
+
return proc;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The environment for a new process. With sources watched, Python's bytecode
|
|
112
|
+
* cache is moved to a folder named by the code hash.
|
|
113
|
+
*
|
|
114
|
+
* Without that a restart is not enough: Python validates a `.pyc` by the
|
|
115
|
+
* source's size and its mtime in whole seconds, so an edit that keeps the
|
|
116
|
+
* length (`VALUE = 1` to `VALUE = 2`) within the same second is served from
|
|
117
|
+
* the old bytecode by the fresh process. Found by the respawn test.
|
|
118
|
+
*/
|
|
119
|
+
spawnEnv() {
|
|
120
|
+
if (!this.spawnedCodeHash) return this.env;
|
|
121
|
+
const prefix = path.join(os.tmpdir(), "cascade-pycache", this.spawnedCodeHash);
|
|
122
|
+
return { ...this.env, PYTHONPYCACHEPREFIX: prefix };
|
|
123
|
+
}
|
|
124
|
+
handleLine(proc, line) {
|
|
125
|
+
if (this.proc !== proc) return;
|
|
126
|
+
let msg;
|
|
127
|
+
try {
|
|
128
|
+
msg = JSON.parse(line);
|
|
129
|
+
} catch {
|
|
130
|
+
console.error("[python-worker] non-JSON line from worker:", line);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const current = this.inFlight;
|
|
134
|
+
if (!current || msg.id !== current.id) return;
|
|
135
|
+
const cancelled = this.inFlightCancelled;
|
|
136
|
+
this.inFlight = null;
|
|
137
|
+
this.inFlightCancelled = false;
|
|
138
|
+
this.clearKillTimer();
|
|
139
|
+
if (!cancelled) {
|
|
140
|
+
const restarted = this.restartPending;
|
|
141
|
+
this.restartPending = false;
|
|
142
|
+
if (msg.ok) {
|
|
143
|
+
this.settle(current, null, {
|
|
144
|
+
result: msg.result,
|
|
145
|
+
codeHash: this.spawnedCodeHash,
|
|
146
|
+
restarted,
|
|
147
|
+
...typeof msg.cache === "string" ? { projectCache: msg.cache } : {}
|
|
148
|
+
});
|
|
149
|
+
} else this.settle(current, new Error(msg.error || "python worker reported an error"));
|
|
150
|
+
}
|
|
151
|
+
this.pump();
|
|
152
|
+
}
|
|
153
|
+
settle(request, error, answer) {
|
|
154
|
+
if (request.timer) clearTimeout(request.timer);
|
|
155
|
+
request.timer = null;
|
|
156
|
+
if (error) request.reject(error);
|
|
157
|
+
else request.resolve(answer);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Stop a running worker whose code changed since it started. Only ever
|
|
161
|
+
* called with nothing in flight, so no request is lost: the exit handler
|
|
162
|
+
* finds no current request, and the next `ensureStarted` spawns afresh.
|
|
163
|
+
*/
|
|
164
|
+
restartIfCodeChanged() {
|
|
165
|
+
if (!this.sources || !this.proc) return;
|
|
166
|
+
const now = this.sources.codeHash();
|
|
167
|
+
if (now === this.spawnedCodeHash) return;
|
|
168
|
+
console.warn(`[python-worker] stage code changed (${this.spawnedCodeHash} -> ${now}), restarting`);
|
|
169
|
+
const stale = this.proc;
|
|
170
|
+
this.proc = null;
|
|
171
|
+
stale.kill();
|
|
172
|
+
this.restarts += 1;
|
|
173
|
+
this.restartPending = true;
|
|
174
|
+
}
|
|
175
|
+
clearKillTimer() {
|
|
176
|
+
if (this.killTimer) clearTimeout(this.killTimer);
|
|
177
|
+
this.killTimer = null;
|
|
178
|
+
}
|
|
179
|
+
/** Send the next queued request if the worker is free. */
|
|
180
|
+
pump() {
|
|
181
|
+
if (this.inFlight) return;
|
|
182
|
+
const next = this.queue.shift();
|
|
183
|
+
if (!next) return;
|
|
184
|
+
this.restartIfCodeChanged();
|
|
185
|
+
const proc = this.ensureStarted();
|
|
186
|
+
this.inFlight = next;
|
|
187
|
+
this.inFlightCancelled = false;
|
|
188
|
+
proc.stdin.write(next.line);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Start the process now rather than on the first call, so its interpreter
|
|
192
|
+
* start and imports (4.7 s for cloud-plots) overlap other work. A render
|
|
193
|
+
* worker warms while the probe frames run. A no-op when one is running.
|
|
194
|
+
*/
|
|
195
|
+
warm() {
|
|
196
|
+
if (this.inFlight || this.queue.length) return;
|
|
197
|
+
this.restartIfCodeChanged();
|
|
198
|
+
this.ensureStarted();
|
|
199
|
+
}
|
|
200
|
+
/** Run a stage and return its result. */
|
|
201
|
+
async call(stage, args, options = {}) {
|
|
202
|
+
return (await this.callWithMeta(stage, args, options)).result;
|
|
203
|
+
}
|
|
204
|
+
/** Run a stage and return its result with the code hash that produced it. */
|
|
205
|
+
callWithMeta(stage, args, options = {}) {
|
|
206
|
+
const { signal, cascade } = options;
|
|
207
|
+
if (signal?.aborted) return Promise.reject(new StageCancelledError(stage));
|
|
208
|
+
const id = this.nextId++;
|
|
209
|
+
return new Promise((resolve, reject) => {
|
|
210
|
+
const onAbort = () => this.cancel(id);
|
|
211
|
+
const request = {
|
|
212
|
+
id,
|
|
213
|
+
stage,
|
|
214
|
+
line: JSON.stringify(cascade ? { id, stage, args, cascade } : { id, stage, args }) + "\n",
|
|
215
|
+
resolve: (r) => {
|
|
216
|
+
signal?.removeEventListener("abort", onAbort);
|
|
217
|
+
resolve(r);
|
|
218
|
+
},
|
|
219
|
+
reject: (e) => {
|
|
220
|
+
signal?.removeEventListener("abort", onAbort);
|
|
221
|
+
reject(e);
|
|
222
|
+
},
|
|
223
|
+
timer: null
|
|
224
|
+
};
|
|
225
|
+
request.timer = setTimeout(() => {
|
|
226
|
+
this.cancel(id, new Error(`python worker timed out after ${this.requestTimeoutMs}ms on stage "${stage}"`));
|
|
227
|
+
}, this.requestTimeoutMs);
|
|
228
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
229
|
+
this.queue.push(request);
|
|
230
|
+
this.pump();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Withdraw a request. A queued one is dropped without ever reaching Python;
|
|
235
|
+
* the one in flight is interrupted, and its answer — whenever it comes — is
|
|
236
|
+
* discarded, so a superseded result is never delivered.
|
|
237
|
+
*/
|
|
238
|
+
cancel(id, reason) {
|
|
239
|
+
const queued = this.queue.findIndex((request) => request.id === id);
|
|
240
|
+
if (queued >= 0) {
|
|
241
|
+
const [request] = this.queue.splice(queued, 1);
|
|
242
|
+
this.settle(request, reason ?? new StageCancelledError(request.stage));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const current = this.inFlight;
|
|
246
|
+
if (!current || current.id !== id || this.inFlightCancelled) return;
|
|
247
|
+
this.inFlightCancelled = true;
|
|
248
|
+
this.settle(current, reason ?? new StageCancelledError(current.stage));
|
|
249
|
+
const proc = this.proc;
|
|
250
|
+
if (!proc) return;
|
|
251
|
+
proc.kill("SIGINT");
|
|
252
|
+
this.clearKillTimer();
|
|
253
|
+
this.killTimer = setTimeout(() => {
|
|
254
|
+
this.killTimer = null;
|
|
255
|
+
if (this.proc === proc && this.inFlight === current) proc.kill("SIGKILL");
|
|
256
|
+
}, this.cancelGraceMs);
|
|
257
|
+
}
|
|
258
|
+
shutdown() {
|
|
259
|
+
liveWorkers.delete(this);
|
|
260
|
+
this.clearKillTimer();
|
|
261
|
+
for (const request of this.queue.splice(0)) this.settle(request, new Error("python worker shut down"));
|
|
262
|
+
const current = this.inFlight;
|
|
263
|
+
this.inFlight = null;
|
|
264
|
+
this.inFlightCancelled = false;
|
|
265
|
+
if (current) this.settle(current, new Error("python worker shut down"));
|
|
266
|
+
const proc = this.proc;
|
|
267
|
+
this.proc = null;
|
|
268
|
+
proc?.kill();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
function defaultStageWorkerCount(cores = os.availableParallelism?.() ?? os.cpus().length, memoryBytes = os.totalmem()) {
|
|
272
|
+
const byCores = Math.floor(cores / 4);
|
|
273
|
+
const byMemory = Math.floor(memoryBytes / (8 * 1024 ** 3));
|
|
274
|
+
return Math.max(2, Math.min(4, byCores, byMemory));
|
|
275
|
+
}
|
|
276
|
+
var PythonWorkerPool = class {
|
|
277
|
+
constructor(size, create) {
|
|
278
|
+
this.size = size;
|
|
279
|
+
this.create = create;
|
|
280
|
+
this.workers = [];
|
|
281
|
+
}
|
|
282
|
+
/** The workers started so far. */
|
|
283
|
+
get members() {
|
|
284
|
+
return this.workers;
|
|
285
|
+
}
|
|
286
|
+
/** Start the first worker now (see `PythonWorker.warm`). */
|
|
287
|
+
warm() {
|
|
288
|
+
if (!this.workers.length) this.workers.push(this.create());
|
|
289
|
+
this.workers[0].warm();
|
|
290
|
+
}
|
|
291
|
+
callWithMeta(stage, args, options = {}) {
|
|
292
|
+
return this.pick().callWithMeta(stage, args, options);
|
|
293
|
+
}
|
|
294
|
+
pick() {
|
|
295
|
+
const idle = this.workers.find((worker) => worker.pending === 0);
|
|
296
|
+
if (idle) return idle;
|
|
297
|
+
if (this.workers.length < Math.max(1, this.size)) {
|
|
298
|
+
const worker = this.create();
|
|
299
|
+
this.workers.push(worker);
|
|
300
|
+
return worker;
|
|
301
|
+
}
|
|
302
|
+
return this.workers.reduce((best, worker) => worker.pending < best.pending ? worker : best);
|
|
303
|
+
}
|
|
304
|
+
shutdown() {
|
|
305
|
+
for (const worker of this.workers.splice(0)) worker.shutdown();
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export {
|
|
310
|
+
StageCancelledError,
|
|
311
|
+
pythonWorkerStatuses,
|
|
312
|
+
shutdownAllPythonWorkers,
|
|
313
|
+
PythonWorker,
|
|
314
|
+
defaultStageWorkerCount,
|
|
315
|
+
PythonWorkerPool
|
|
316
|
+
};
|
|
317
|
+
//# sourceMappingURL=chunk-YH637PDL.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/nodes/definition/studioImages.ts
|
|
2
|
+
var STUDIO_IMAGE_ROOT = ".cascade-studio/";
|
|
3
|
+
var PER_NAME = 3;
|
|
4
|
+
var BUDGET_BYTES = 512 * 1024 * 1024;
|
|
5
|
+
var kept = /* @__PURE__ */ new Map();
|
|
6
|
+
var bytes = 0;
|
|
7
|
+
var sequence = 0;
|
|
8
|
+
function clean(path) {
|
|
9
|
+
return path.replace(/^\.\//, "");
|
|
10
|
+
}
|
|
11
|
+
function drop(path) {
|
|
12
|
+
const entry = kept.get(path);
|
|
13
|
+
if (!entry) return;
|
|
14
|
+
kept.delete(path);
|
|
15
|
+
bytes -= entry.pixels.data.byteLength;
|
|
16
|
+
}
|
|
17
|
+
function keepStudioImage(pixels, name = "image.png") {
|
|
18
|
+
const safe = name.replace(/[^A-Za-z0-9._-]/g, "_") || "image.png";
|
|
19
|
+
const path = `${STUDIO_IMAGE_ROOT}${++sequence}-${safe}`;
|
|
20
|
+
kept.set(path, { pixels, name: safe });
|
|
21
|
+
bytes += pixels.data.byteLength;
|
|
22
|
+
const siblings = [...kept].filter(([, entry]) => entry.name === safe).map(([key]) => key);
|
|
23
|
+
for (const old of siblings.slice(0, Math.max(0, siblings.length - PER_NAME))) drop(old);
|
|
24
|
+
for (const old of kept.keys()) {
|
|
25
|
+
if (bytes <= BUDGET_BYTES || old === path) break;
|
|
26
|
+
drop(old);
|
|
27
|
+
}
|
|
28
|
+
return path;
|
|
29
|
+
}
|
|
30
|
+
function isStudioImage(path) {
|
|
31
|
+
return kept.has(clean(path));
|
|
32
|
+
}
|
|
33
|
+
function studioImagePixels(path) {
|
|
34
|
+
return kept.get(clean(path))?.pixels;
|
|
35
|
+
}
|
|
36
|
+
function draw(pixels) {
|
|
37
|
+
const globals = globalThis;
|
|
38
|
+
if (!globals.ImageData) throw new Error("Studio cannot draw a kept image: no ImageData here");
|
|
39
|
+
let canvas;
|
|
40
|
+
if (globals.document) {
|
|
41
|
+
canvas = globals.document.createElement("canvas");
|
|
42
|
+
canvas.width = pixels.width;
|
|
43
|
+
canvas.height = pixels.height;
|
|
44
|
+
} else if (globals.OffscreenCanvas) {
|
|
45
|
+
canvas = new globals.OffscreenCanvas(pixels.width, pixels.height);
|
|
46
|
+
} else {
|
|
47
|
+
throw new Error("Studio cannot draw a kept image: no canvas here");
|
|
48
|
+
}
|
|
49
|
+
canvas.getContext("2d").putImageData(new globals.ImageData(pixels.data, pixels.width, pixels.height), 0, 0);
|
|
50
|
+
return canvas;
|
|
51
|
+
}
|
|
52
|
+
async function studioImageBytes(path) {
|
|
53
|
+
const entry = kept.get(clean(path));
|
|
54
|
+
if (!entry) return void 0;
|
|
55
|
+
const canvas = draw(entry.pixels);
|
|
56
|
+
const blob = canvas.convertToBlob ? await canvas.convertToBlob({ type: "image/png" }) : await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
57
|
+
if (!blob) throw new Error(`Studio could not encode ${path}`);
|
|
58
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
59
|
+
}
|
|
60
|
+
function studioImageUrl(path) {
|
|
61
|
+
const entry = kept.get(clean(path));
|
|
62
|
+
if (!entry) return void 0;
|
|
63
|
+
entry.url ??= draw(entry.pixels).toDataURL("image/png");
|
|
64
|
+
return entry.url;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/nodes/definition/browserCapabilities.ts
|
|
68
|
+
function assetUrl(path) {
|
|
69
|
+
const clean2 = path.replace(/^\.\//, "");
|
|
70
|
+
return `/api/media/${clean2.split("/").map(encodeURIComponent).join("/")}?raw=1`;
|
|
71
|
+
}
|
|
72
|
+
var browserAssetCapability = {
|
|
73
|
+
async read(ref, { signal }) {
|
|
74
|
+
const kept2 = await studioImageBytes(ref.path);
|
|
75
|
+
if (kept2) return kept2;
|
|
76
|
+
const response = await fetch(assetUrl(ref.path), { signal });
|
|
77
|
+
if (!response.ok) throw new Error(`Could not read asset ${ref.path} (${response.status})`);
|
|
78
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
79
|
+
},
|
|
80
|
+
async write(data, metadata, { signal }) {
|
|
81
|
+
const name = metadata.suggestedName?.trim() || "asset.bin";
|
|
82
|
+
const form = new FormData();
|
|
83
|
+
form.append("file", new Blob([new Uint8Array(data)], { type: metadata.mediaType }), name);
|
|
84
|
+
const response = await fetch("/api/assets", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
body: form,
|
|
87
|
+
signal
|
|
88
|
+
});
|
|
89
|
+
if (!response.ok) throw new Error(`Could not write asset ${name} (${response.status})`);
|
|
90
|
+
const result = await response.json();
|
|
91
|
+
if (!result.path) throw new Error(`Asset upload returned no path for ${name}`);
|
|
92
|
+
return { path: result.path, ...metadata.mediaType ? { mediaType: metadata.mediaType } : {} };
|
|
93
|
+
},
|
|
94
|
+
imagePixels(ref) {
|
|
95
|
+
return studioImagePixels(ref.path);
|
|
96
|
+
},
|
|
97
|
+
async resolveUrl(ref) {
|
|
98
|
+
return { value: isStudioImage(ref.path) ? studioImageUrl(ref.path) : assetUrl(ref.path), release: () => {
|
|
99
|
+
} };
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var studioImageAssetCapability = {
|
|
103
|
+
...browserAssetCapability,
|
|
104
|
+
async keepImage(pixels, metadata) {
|
|
105
|
+
return { path: keepStudioImage(pixels, metadata.suggestedName), mediaType: "image/png" };
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
browserAssetCapability
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=chunk-ZSCUZE6I.js.map
|
|
@@ -2,26 +2,28 @@ import {
|
|
|
2
2
|
formatCompactJSON
|
|
3
3
|
} from "./chunk-OWWBYLHC.js";
|
|
4
4
|
import {
|
|
5
|
-
PythonWorker,
|
|
6
|
-
StageCancelledError,
|
|
7
|
-
callStage,
|
|
8
5
|
compileEmbedded,
|
|
9
6
|
compileProjectModule,
|
|
10
|
-
compileProjectPanel
|
|
11
|
-
|
|
12
|
-
pythonWorkerStatuses,
|
|
13
|
-
shutdownAllPythonWorkers,
|
|
14
|
-
stageSources
|
|
15
|
-
} from "./chunk-MIIEFUGG.js";
|
|
7
|
+
compileProjectPanel
|
|
8
|
+
} from "./chunk-W7OUAT7J.js";
|
|
16
9
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
PythonWorker,
|
|
11
|
+
StageCancelledError,
|
|
12
|
+
pythonWorkerStatuses,
|
|
13
|
+
shutdownAllPythonWorkers
|
|
14
|
+
} from "./chunk-YH637PDL.js";
|
|
20
15
|
import {
|
|
21
16
|
readStageConfig,
|
|
22
17
|
runProjectStage
|
|
23
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-X566FWBV.js";
|
|
24
19
|
import "./chunk-WD62JIP6.js";
|
|
20
|
+
import {
|
|
21
|
+
callStage,
|
|
22
|
+
formatStageTrace,
|
|
23
|
+
stageSources,
|
|
24
|
+
writeFileAtomic,
|
|
25
|
+
writeFileAtomicSync
|
|
26
|
+
} from "./chunk-5ORUQ6ES.js";
|
|
25
27
|
import {
|
|
26
28
|
CredentialStore,
|
|
27
29
|
PathSafetyError,
|
|
@@ -32,10 +34,10 @@ import {
|
|
|
32
34
|
resolveWithinRoot,
|
|
33
35
|
secretValue,
|
|
34
36
|
writeProjectManifest
|
|
35
|
-
} from "./chunk-
|
|
36
|
-
import "./chunk-
|
|
37
|
+
} from "./chunk-W5PJ5RMP.js";
|
|
38
|
+
import "./chunk-NJFMDXHR.js";
|
|
37
39
|
import "./chunk-J7VK7MNF.js";
|
|
38
|
-
import "./chunk-
|
|
40
|
+
import "./chunk-FWMSGKEH.js";
|
|
39
41
|
|
|
40
42
|
// server/src/cliCommands.ts
|
|
41
43
|
import fs13 from "node:fs";
|
|
@@ -2306,6 +2308,10 @@ Read \`node_modules/cascade/AGENTS.md\` for the framework itself. What follows i
|
|
|
2306
2308
|
Do not start another Cascade process on an occupied port. Remote access requires
|
|
2307
2309
|
both the explicit bind address and the exact trusted browser hostname.
|
|
2308
2310
|
|
|
2311
|
+
## Choosing a variant
|
|
2312
|
+
|
|
2313
|
+
\`npx --no-install cascade ladder index.cascade <node> --name <name> --seeds 1,2,3\` cooks the variants side by side; \`cascade pick <ladder id> <n>\` locks the approved one. To let Marcus pick on his phone in the Cairn web chat, add \`--cairn <Substrate vault>/<space>/_attachments/ladders/<ladder id> --json\`, post \`data.cairnChoice\` (a \`cairn:choice\` fence, at most 8 pictures) in the reply as is, and when "Picked: <label> (pick:<id>:<n>)" comes back run \`cascade pick <id> <n>\` and reply with that locked image. See \`node_modules/cascade/doc/CLI_REFERENCE.md\`, "Picking a ladder variant in Cairn".
|
|
2314
|
+
|
|
2309
2315
|
## The \`.cascade\` document format
|
|
2310
2316
|
|
|
2311
2317
|
Hand-editing is supported. Preserve metadata, source fields, IDs, and valid connection endpoints, then run the static checks. This example uses the node below; save it at \`nodes/Multiply/index.ts\` before running the graph.
|
|
@@ -3461,4 +3467,4 @@ export {
|
|
|
3461
3467
|
resolveProjectTarget,
|
|
3462
3468
|
runStudioCli
|
|
3463
3469
|
};
|
|
3464
|
-
//# sourceMappingURL=cliCommands-
|
|
3470
|
+
//# sourceMappingURL=cliCommands-XVFRD7CF.js.map
|