diffusers-workflow 0.4.0a3__py3-none-any.whl
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.
- diffusers_workflow-0.4.0a3.dist-info/METADATA +310 -0
- diffusers_workflow-0.4.0a3.dist-info/RECORD +171 -0
- diffusers_workflow-0.4.0a3.dist-info/WHEEL +5 -0
- diffusers_workflow-0.4.0a3.dist-info/entry_points.txt +6 -0
- diffusers_workflow-0.4.0a3.dist-info/licenses/LICENSE +201 -0
- diffusers_workflow-0.4.0a3.dist-info/top_level.txt +1 -0
- dw/__init__.py +353 -0
- dw/arguments.py +906 -0
- dw/cache_blocks.json +16 -0
- dw/cache_blocks.py +145 -0
- dw/community_pipelines/pipeline_flux_rf_inversion.py +1184 -0
- dw/events.py +78 -0
- dw/hub_cache.py +289 -0
- dw/introspection.py +458 -0
- dw/log_setup.py +45 -0
- dw/pipeline_processors/chain.py +750 -0
- dw/pipeline_processors/config_objects.py +235 -0
- dw/pipeline_processors/pipeline.py +1687 -0
- dw/pipeline_processors/remote.py +18 -0
- dw/previous_results.py +259 -0
- dw/prompt_weighting.py +378 -0
- dw/repl.py +298 -0
- dw/repl_commands.py +808 -0
- dw/repl_worker.py +129 -0
- dw/result.py +850 -0
- dw/run.py +92 -0
- dw/schema.py +24 -0
- dw/security.py +379 -0
- dw/serve.py +70 -0
- dw/server/__init__.py +2 -0
- dw/server/app.py +588 -0
- dw/server/jobs.py +547 -0
- dw/server/ui/assets/abap-08VXUWAP.js +1 -0
- dw/server/ui/assets/apex-BWPQTe0t.js +1 -0
- dw/server/ui/assets/azcli-Bc_sGQ0U.js +1 -0
- dw/server/ui/assets/bat-i0X4ZdIN.js +1 -0
- dw/server/ui/assets/bicep-B5-_aFwp.js +2 -0
- dw/server/ui/assets/cameligo-DMUM7wLl.js +1 -0
- dw/server/ui/assets/clojure-Cm7r79vr.js +1 -0
- dw/server/ui/assets/codicon-Brq4_Ui5.ttf +0 -0
- dw/server/ui/assets/coffee-Ba7i2nA0.js +1 -0
- dw/server/ui/assets/cpp-C7h46wYY.js +1 -0
- dw/server/ui/assets/csharp-BKxtCVv1.js +1 -0
- dw/server/ui/assets/csp-bTuwJoIa.js +1 -0
- dw/server/ui/assets/css-DIMkf-bt.js +3 -0
- dw/server/ui/assets/css.worker-B3ciXF_0.js +93 -0
- dw/server/ui/assets/cssMode-CEh6hWi2.js +1 -0
- dw/server/ui/assets/cypher-CVaqCwHa.js +1 -0
- dw/server/ui/assets/dart-onAF5SnQ.js +1 -0
- dw/server/ui/assets/dockerfile-DZFCIeNp.js +1 -0
- dw/server/ui/assets/ecl-D05T4iGw.js +1 -0
- dw/server/ui/assets/editor-jjEx9u7D.css +1 -0
- dw/server/ui/assets/editor.api-CExg3_mM.js +847 -0
- dw/server/ui/assets/editor.worker-q-txB4vs.js +30 -0
- dw/server/ui/assets/elixir-6RTg0lbw.js +1 -0
- dw/server/ui/assets/flow9-C5_-GSwl.js +1 -0
- dw/server/ui/assets/freemarker2-DH6orYh2.js +3 -0
- dw/server/ui/assets/fsharp-C8Ef5oNN.js +1 -0
- dw/server/ui/assets/go-C-y9NEjX.js +1 -0
- dw/server/ui/assets/graphql-fmXr3nnJ.js +1 -0
- dw/server/ui/assets/handlebars-CbrMVW4Q.js +1 -0
- dw/server/ui/assets/hcl-CpzslTdj.js +1 -0
- dw/server/ui/assets/html-YDNPZw2M.js +1 -0
- dw/server/ui/assets/html.worker-C93Ht9o9.js +506 -0
- dw/server/ui/assets/htmlMode-B_zSGWO2.js +1 -0
- dw/server/ui/assets/index-B7-VcYS-.css +1 -0
- dw/server/ui/assets/index-D_EiPU3b.js +13 -0
- dw/server/ui/assets/ini-sBoK_t0W.js +1 -0
- dw/server/ui/assets/java-BEtHBSE6.js +1 -0
- dw/server/ui/assets/javascript-dYuBvioq.js +1 -0
- dw/server/ui/assets/json.worker-B2V3pomh.js +62 -0
- dw/server/ui/assets/jsonMode-CUqLM39V.js +7 -0
- dw/server/ui/assets/julia-Bri6UV-V.js +1 -0
- dw/server/ui/assets/kotlin-BOotOW0E.js +1 -0
- dw/server/ui/assets/less-B9JPFI3C.js +2 -0
- dw/server/ui/assets/lexon-CfSJPG6W.js +1 -0
- dw/server/ui/assets/liquid-D6vxBzMv.js +1 -0
- dw/server/ui/assets/lspLanguageFeatures-1WJ2palX.js +4 -0
- dw/server/ui/assets/lua-CsQS60Ue.js +1 -0
- dw/server/ui/assets/m3-D-oSqn_W.js +1 -0
- dw/server/ui/assets/markdown-Cimd5fb3.js +1 -0
- dw/server/ui/assets/mdx-SHQb6vmD.js +1 -0
- dw/server/ui/assets/mips-CIPQ_RoX.js +1 -0
- dw/server/ui/assets/monaco--ixms01u.css +1 -0
- dw/server/ui/assets/monaco-CP-s5rcP.js +56 -0
- dw/server/ui/assets/msdax-DauUninz.js +1 -0
- dw/server/ui/assets/mysql-SOo6toE5.js +1 -0
- dw/server/ui/assets/objective-c-FvmIjYaQ.js +1 -0
- dw/server/ui/assets/pascal-DrH0SRf2.js +1 -0
- dw/server/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
- dw/server/ui/assets/perl-oz_6vUea.js +1 -0
- dw/server/ui/assets/pgsql-DTj74zXo.js +1 -0
- dw/server/ui/assets/php-nr791fC2.js +1 -0
- dw/server/ui/assets/pla-CopQ2nXW.js +1 -0
- dw/server/ui/assets/postiats-43DmfD33.js +1 -0
- dw/server/ui/assets/powerquery-D3hlyOfw.js +1 -0
- dw/server/ui/assets/powershell-DmHpPYUd.js +1 -0
- dw/server/ui/assets/protobuf-C531GsRP.js +2 -0
- dw/server/ui/assets/pug-Z5eAx3Zn.js +1 -0
- dw/server/ui/assets/python-x0_EGHq9.js +1 -0
- dw/server/ui/assets/qsharp-DkqhCAOL.js +1 -0
- dw/server/ui/assets/r-BwWrilGY.js +1 -0
- dw/server/ui/assets/razor-BZC4LQDP.js +1 -0
- dw/server/ui/assets/redis-ClamHrr6.js +1 -0
- dw/server/ui/assets/redshift-DT7zqm-g.js +1 -0
- dw/server/ui/assets/restructuredtext-BYgofb2h.js +1 -0
- dw/server/ui/assets/ruby-DezsRK8O.js +1 -0
- dw/server/ui/assets/rust-DdL9SqIa.js +1 -0
- dw/server/ui/assets/sb-CcwsVR0C.js +1 -0
- dw/server/ui/assets/scala-DHpiXF5c.js +1 -0
- dw/server/ui/assets/scheme-BeGwcela.js +1 -0
- dw/server/ui/assets/scss-gp-XZpBa.js +3 -0
- dw/server/ui/assets/shell-CC2rA5mh.js +1 -0
- dw/server/ui/assets/solidity-BEEn4gHE.js +1 -0
- dw/server/ui/assets/sophia-CRfGWb83.js +1 -0
- dw/server/ui/assets/sparql-D_Lu-MrJ.js +1 -0
- dw/server/ui/assets/sql-NEE52Syq.js +1 -0
- dw/server/ui/assets/st-DbInun42.js +1 -0
- dw/server/ui/assets/swift-Bxkupp3x.js +1 -0
- dw/server/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
- dw/server/ui/assets/tcl-DISqw1ZD.js +1 -0
- dw/server/ui/assets/ts.worker-D7T1-Ig5.js +67738 -0
- dw/server/ui/assets/tsMode-BTfA6SbD.js +11 -0
- dw/server/ui/assets/twig-De2hgUGE.js +1 -0
- dw/server/ui/assets/typescript-CWA4MsNk.js +1 -0
- dw/server/ui/assets/typespec-B8J7ngcE.js +1 -0
- dw/server/ui/assets/vb-DV3o63ZY.js +1 -0
- dw/server/ui/assets/wgsl-DpFanUEy.js +298 -0
- dw/server/ui/assets/workers-CWU0uvj5.js +1 -0
- dw/server/ui/assets/xml-KmfTm3rg.js +1 -0
- dw/server/ui/assets/yaml-nFO_dDS6.js +1 -0
- dw/server/ui/index.html +17 -0
- dw/settings.py +77 -0
- dw/step.py +132 -0
- dw/tasks/audio_utils.py +266 -0
- dw/tasks/background_remover.py +43 -0
- dw/tasks/borders.py +113 -0
- dw/tasks/concat_videos.py +80 -0
- dw/tasks/depth_estimator.py +54 -0
- dw/tasks/diffusion_upscale.py +109 -0
- dw/tasks/format_messages.py +24 -0
- dw/tasks/gather.py +139 -0
- dw/tasks/image_to_text.py +43 -0
- dw/tasks/image_utils.py +661 -0
- dw/tasks/interpolate_frames.py +227 -0
- dw/tasks/model_cache.py +39 -0
- dw/tasks/pair_audio.py +58 -0
- dw/tasks/qr_code.py +19 -0
- dw/tasks/restore_faces.py +175 -0
- dw/tasks/rife_model.py +192 -0
- dw/tasks/segment.py +121 -0
- dw/tasks/task.py +474 -0
- dw/tasks/tensor_image.py +57 -0
- dw/tasks/text_generation.py +168 -0
- dw/tasks/text_sections.py +80 -0
- dw/tasks/upscale.py +203 -0
- dw/tasks/video_utils.py +154 -0
- dw/tasks/zoe_depth.py +71 -0
- dw/teacache.py +376 -0
- dw/teacache_models.json +99 -0
- dw/test.py +29 -0
- dw/type_helpers.py +68 -0
- dw/validate.py +43 -0
- dw/variables.py +153 -0
- dw/worker.py +517 -0
- dw/workflow.py +553 -0
- dw/workflow_schema.json +1157 -0
- dw/workflows/augment_prompt.json +65 -0
- dw/workflows/describe_image.json +58 -0
- dw/workflows/h3_context_ir.json +57 -0
- dw/workflows/test.json +31 -0
dw/repl_commands.py
ADDED
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command handlers for REPL.
|
|
3
|
+
|
|
4
|
+
Contains all the command implementations organized by category:
|
|
5
|
+
- Config commands
|
|
6
|
+
- Argument commands
|
|
7
|
+
- Memory commands
|
|
8
|
+
- Workflow commands
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import contextlib
|
|
12
|
+
import os
|
|
13
|
+
import shlex
|
|
14
|
+
import logging
|
|
15
|
+
from .security import (
|
|
16
|
+
validate_path,
|
|
17
|
+
validate_workflow_path,
|
|
18
|
+
validate_output_path,
|
|
19
|
+
validate_variable_name,
|
|
20
|
+
validate_string_input,
|
|
21
|
+
SecurityError,
|
|
22
|
+
InvalidInputError,
|
|
23
|
+
MAX_VARIABLE_VALUE_LENGTH,
|
|
24
|
+
MAX_FILE_PATH_LENGTH,
|
|
25
|
+
)
|
|
26
|
+
from .workflow import workflow_from_file
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger("dw")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ConfigCommands:
|
|
32
|
+
"""Handles configuration commands."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, repl):
|
|
35
|
+
"""Initialize with reference to REPL instance."""
|
|
36
|
+
self.repl = repl
|
|
37
|
+
|
|
38
|
+
def do_config(self, arg: str):
|
|
39
|
+
"""Configure global settings. Usage: config ? | show | set <name>=<value>"""
|
|
40
|
+
if not arg or arg == "?":
|
|
41
|
+
print("\nConfig commands:")
|
|
42
|
+
print(" config show - Show all configuration settings")
|
|
43
|
+
print(" config set <name>=<value> - Set a configuration value")
|
|
44
|
+
print("\nAvailable settings:")
|
|
45
|
+
print(" output_dir - Directory for output files (default: ./outputs)")
|
|
46
|
+
print(
|
|
47
|
+
" log_level - Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL"
|
|
48
|
+
)
|
|
49
|
+
print(" workflow_dir - Where 'workflow load <name>' and 'workflow list'")
|
|
50
|
+
print(" look for workflows (default: ./examples)")
|
|
51
|
+
print()
|
|
52
|
+
print("These apply to this REPL session. Standing settings (device,")
|
|
53
|
+
print("log file, TF32) live in ~/.diffusers_helper/settings.json")
|
|
54
|
+
print()
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
parts = arg.split(None, 1)
|
|
58
|
+
subcommand = parts[0]
|
|
59
|
+
subarg = parts[1] if len(parts) > 1 else ""
|
|
60
|
+
|
|
61
|
+
if subcommand == "show":
|
|
62
|
+
self._config_show(subarg)
|
|
63
|
+
elif subcommand == "set":
|
|
64
|
+
self._config_set(subarg)
|
|
65
|
+
else:
|
|
66
|
+
print(f"Unknown config subcommand: {subcommand}")
|
|
67
|
+
print("Use 'config ?' for help")
|
|
68
|
+
|
|
69
|
+
def _config_show(self, arg: str):
|
|
70
|
+
"""Show configuration settings"""
|
|
71
|
+
print("\nCurrent configuration:")
|
|
72
|
+
for name, value in self.repl.globals.items():
|
|
73
|
+
print(f" {name}={value}")
|
|
74
|
+
print()
|
|
75
|
+
|
|
76
|
+
def _config_set(self, arg: str):
|
|
77
|
+
"""Set a configuration value"""
|
|
78
|
+
if not arg:
|
|
79
|
+
# If no argument, show all config (backward compatibility with 'set')
|
|
80
|
+
self._config_show(arg)
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
name, value = arg.split("=", 1)
|
|
85
|
+
name = name.strip()
|
|
86
|
+
value = value.strip()
|
|
87
|
+
|
|
88
|
+
# Special handling for output_dir
|
|
89
|
+
if name == "output_dir":
|
|
90
|
+
try:
|
|
91
|
+
value = validate_output_path(value, None)
|
|
92
|
+
# Check if directory exists
|
|
93
|
+
if not os.path.exists(value):
|
|
94
|
+
print(f"Warning: Directory '{value}' does not exist")
|
|
95
|
+
except SecurityError as e:
|
|
96
|
+
print(f"Error: Invalid output directory: {e}")
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
# Special handling for log_level
|
|
100
|
+
elif name == "log_level":
|
|
101
|
+
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
102
|
+
value = value.upper()
|
|
103
|
+
if value not in valid_levels:
|
|
104
|
+
print(f"Error: Log level must be one of: {', '.join(valid_levels)}")
|
|
105
|
+
return
|
|
106
|
+
# Update the log level for dw's own logger
|
|
107
|
+
logging.getLogger("dw").setLevel(value)
|
|
108
|
+
print(f"Log level set to {value}")
|
|
109
|
+
|
|
110
|
+
elif name == "workflow_dir":
|
|
111
|
+
try:
|
|
112
|
+
value = validate_path(value, allow_create=False)
|
|
113
|
+
if not os.path.exists(value):
|
|
114
|
+
print(f"Warning: Directory '{value}' does not exist")
|
|
115
|
+
return
|
|
116
|
+
except SecurityError as e:
|
|
117
|
+
print(f"Error: Invalid workflow directory: {e}")
|
|
118
|
+
return
|
|
119
|
+
else:
|
|
120
|
+
print(f"Warning: Unknown setting '{name}'")
|
|
121
|
+
|
|
122
|
+
self.repl.globals[name] = value
|
|
123
|
+
print(f"Set {name}={value}")
|
|
124
|
+
|
|
125
|
+
except ValueError:
|
|
126
|
+
print("Error: Invalid format. Use: config set name=value")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def display_value(value, limit=90):
|
|
130
|
+
"""A value shortened for terminal display - a paragraph-long prompt
|
|
131
|
+
default would otherwise bury every other line of arg show."""
|
|
132
|
+
text = str(value)
|
|
133
|
+
text = " ".join(text.split())
|
|
134
|
+
if len(text) > limit:
|
|
135
|
+
return text[: limit - 3] + "..."
|
|
136
|
+
return text
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ArgCommands:
|
|
140
|
+
"""Handles workflow argument management commands."""
|
|
141
|
+
|
|
142
|
+
def __init__(self, repl):
|
|
143
|
+
"""Initialize with reference to REPL instance."""
|
|
144
|
+
self.repl = repl
|
|
145
|
+
|
|
146
|
+
def do_arg(self, arg: str):
|
|
147
|
+
"""Manage workflow arguments. Usage: arg ? | show | set <name>=<value> | clear"""
|
|
148
|
+
if arg == "?":
|
|
149
|
+
print("\nArg commands:")
|
|
150
|
+
print(
|
|
151
|
+
" arg show - Show workflow variables and current values"
|
|
152
|
+
)
|
|
153
|
+
print(" arg set <name>=<value> - Set an argument (quotes optional):")
|
|
154
|
+
print(' arg set prompt="a cat in a hat"')
|
|
155
|
+
print(" arg clear [<name>] - Clear one argument, or all of them")
|
|
156
|
+
print()
|
|
157
|
+
print("Arguments override the workflow's variables on the next run and")
|
|
158
|
+
print("reset when a different workflow is loaded. Shortcuts: 'set n=v'")
|
|
159
|
+
print("is 'arg set', and 'workflow run n=v ...' sets and runs in one line.")
|
|
160
|
+
print()
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
if not arg:
|
|
164
|
+
# If no argument, show args (backward compatibility)
|
|
165
|
+
self._arg_show(arg)
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
parts = arg.split(None, 1)
|
|
169
|
+
subcommand = parts[0]
|
|
170
|
+
subarg = parts[1] if len(parts) > 1 else ""
|
|
171
|
+
|
|
172
|
+
if subcommand == "show":
|
|
173
|
+
self._arg_show(subarg)
|
|
174
|
+
elif subcommand == "set":
|
|
175
|
+
self._arg_set(subarg)
|
|
176
|
+
elif subcommand == "clear":
|
|
177
|
+
self._arg_clear(subarg)
|
|
178
|
+
else:
|
|
179
|
+
# Try to parse as set command for backward compatibility
|
|
180
|
+
self._arg_set(arg)
|
|
181
|
+
|
|
182
|
+
def _arg_show(self, arg: str):
|
|
183
|
+
"""Show workflow arguments"""
|
|
184
|
+
if not self.repl.current_workflow:
|
|
185
|
+
print("Error: No workflow loaded. Use 'workflow load' command first")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
print("\nAvailable variables in workflow and their default values:")
|
|
189
|
+
workflow_vars = self.repl.current_workflow.variables
|
|
190
|
+
if not workflow_vars:
|
|
191
|
+
print(" No variables defined in workflow")
|
|
192
|
+
else:
|
|
193
|
+
for var_name, var_def in workflow_vars.items():
|
|
194
|
+
print(f" {var_name}: {display_value(var_def)}")
|
|
195
|
+
|
|
196
|
+
print("\nCurrent argument values:")
|
|
197
|
+
if not self.repl.workflow_args:
|
|
198
|
+
print(" No arguments set")
|
|
199
|
+
else:
|
|
200
|
+
for name, value in self.repl.workflow_args.items():
|
|
201
|
+
print(f" {name}={display_value(value)}")
|
|
202
|
+
print()
|
|
203
|
+
|
|
204
|
+
def _arg_set(self, arg: str):
|
|
205
|
+
"""Set a workflow argument.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
True when the argument was set, False otherwise - callers that
|
|
209
|
+
set several in a row (workflow run n=v ...) stop at the first
|
|
210
|
+
failure instead of running with half the overrides.
|
|
211
|
+
"""
|
|
212
|
+
if not self.repl.current_workflow:
|
|
213
|
+
print("Error: No workflow loaded. Use 'workflow load' command first")
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
if not arg:
|
|
217
|
+
print("Error: Please specify argument name and value")
|
|
218
|
+
print("Usage: arg set <name>=<value>")
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
name, value = arg.split("=", 1)
|
|
223
|
+
name = name.strip()
|
|
224
|
+
value = value.strip()
|
|
225
|
+
|
|
226
|
+
# There is no shell here to strip quotes, so a value quoted the
|
|
227
|
+
# way every example shows it would otherwise keep literal quotes
|
|
228
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in "'\"":
|
|
229
|
+
value = value[1:-1]
|
|
230
|
+
|
|
231
|
+
# Validate variable name
|
|
232
|
+
try:
|
|
233
|
+
name = validate_variable_name(name)
|
|
234
|
+
value = validate_string_input(
|
|
235
|
+
value, max_length=MAX_VARIABLE_VALUE_LENGTH, allow_empty=True
|
|
236
|
+
)
|
|
237
|
+
except InvalidInputError as e:
|
|
238
|
+
print(f"Error: Invalid input: {e}")
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
# Verify this is a valid variable name for the workflow
|
|
242
|
+
if name not in self.repl.current_workflow.variables:
|
|
243
|
+
print(f"Error: '{name}' is not defined in workflow variables")
|
|
244
|
+
available = ", ".join(self.repl.current_workflow.variables)
|
|
245
|
+
print(f"Available variables: {available or '(none)'}")
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
self.repl.workflow_args[name] = value
|
|
249
|
+
print(f"Set argument {name}={value}")
|
|
250
|
+
return True
|
|
251
|
+
|
|
252
|
+
except ValueError:
|
|
253
|
+
print("Error: Invalid format. Use: arg set name=value")
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
def _arg_clear(self, arg: str):
|
|
257
|
+
"""Clear one workflow argument, or all of them"""
|
|
258
|
+
name = arg.strip()
|
|
259
|
+
if name:
|
|
260
|
+
if name in self.repl.workflow_args:
|
|
261
|
+
del self.repl.workflow_args[name]
|
|
262
|
+
print(f"Cleared argument {name}")
|
|
263
|
+
else:
|
|
264
|
+
print(f"Argument '{name}' is not set")
|
|
265
|
+
return
|
|
266
|
+
self.repl.workflow_args = {}
|
|
267
|
+
print("All workflow arguments cleared")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class MemoryCommands:
|
|
271
|
+
"""Handles GPU memory management commands."""
|
|
272
|
+
|
|
273
|
+
def __init__(self, repl):
|
|
274
|
+
"""Initialize with reference to REPL instance."""
|
|
275
|
+
self.repl = repl
|
|
276
|
+
|
|
277
|
+
def do_memory(self, arg: str):
|
|
278
|
+
"""Manage GPU memory. Usage: memory ? | show | clear"""
|
|
279
|
+
if not arg or arg == "?":
|
|
280
|
+
print("\nMemory commands:")
|
|
281
|
+
print(" memory show - Show the worker's GPU memory usage")
|
|
282
|
+
print(" memory clear - Release cached models and empty the device cache")
|
|
283
|
+
print()
|
|
284
|
+
print("The worker process starts on the first 'workflow run', so there")
|
|
285
|
+
print("is nothing to show before that. 'workflow restart' frees")
|
|
286
|
+
print("everything by stopping the worker process itself.")
|
|
287
|
+
print()
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
parts = arg.split(None, 1)
|
|
291
|
+
subcommand = parts[0]
|
|
292
|
+
subarg = parts[1] if len(parts) > 1 else ""
|
|
293
|
+
|
|
294
|
+
if subcommand == "show":
|
|
295
|
+
self._memory_show(subarg)
|
|
296
|
+
elif subcommand == "clear":
|
|
297
|
+
self._memory_clear(subarg)
|
|
298
|
+
else:
|
|
299
|
+
print(f"Unknown memory subcommand: {subcommand}")
|
|
300
|
+
print("Use 'memory ?' for help")
|
|
301
|
+
|
|
302
|
+
def _memory_show(self, arg: str):
|
|
303
|
+
"""Show current GPU memory usage"""
|
|
304
|
+
if not self.repl.worker_manager.worker_active:
|
|
305
|
+
print("No worker process running")
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
self.repl.worker_manager.send_command({"type": "memory_status"})
|
|
310
|
+
result = self.repl.worker_manager.get_result(timeout=5)
|
|
311
|
+
|
|
312
|
+
if result["type"] == "memory_status":
|
|
313
|
+
self.repl._print_memory_info(result.get("info", {}))
|
|
314
|
+
else:
|
|
315
|
+
print(f"Unexpected response: {result}")
|
|
316
|
+
except Exception as e:
|
|
317
|
+
print(f"Error getting memory status: {e}")
|
|
318
|
+
|
|
319
|
+
def _memory_clear(self, arg: str):
|
|
320
|
+
"""Clear GPU memory and cached models"""
|
|
321
|
+
if not self.repl.worker_manager.worker_active:
|
|
322
|
+
print("No worker process running")
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
print("Clearing GPU memory...")
|
|
327
|
+
self.repl.worker_manager.send_command({"type": "clear_memory"})
|
|
328
|
+
|
|
329
|
+
# Wait for response
|
|
330
|
+
result = self.repl.worker_manager.get_result(timeout=30)
|
|
331
|
+
if result["type"] == "memory_cleared":
|
|
332
|
+
self.repl._print_memory_info(result.get("info", {}))
|
|
333
|
+
print("GPU memory cleared successfully")
|
|
334
|
+
else:
|
|
335
|
+
print(f"Unexpected response: {result}")
|
|
336
|
+
except Exception as e:
|
|
337
|
+
print(f"Error clearing memory: {e}")
|
|
338
|
+
self.repl.worker_manager.shutdown_worker()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class WorkflowCommands:
|
|
342
|
+
"""Handles workflow management commands."""
|
|
343
|
+
|
|
344
|
+
def __init__(self, repl):
|
|
345
|
+
"""Initialize with reference to REPL instance."""
|
|
346
|
+
self.repl = repl
|
|
347
|
+
|
|
348
|
+
def do_workflow(self, arg: str):
|
|
349
|
+
"""Manage workflows. Usage: workflow ? | list | load <file> | reload | status | run | restart"""
|
|
350
|
+
if not arg or arg == "?":
|
|
351
|
+
print("\nWorkflow commands:")
|
|
352
|
+
print(
|
|
353
|
+
" workflow list - List workflows in the workflow directory"
|
|
354
|
+
)
|
|
355
|
+
print(
|
|
356
|
+
" workflow load <name> - Load by name from workflow_dir, or by path"
|
|
357
|
+
)
|
|
358
|
+
print(
|
|
359
|
+
" workflow reload - Re-read the current workflow file (keeps args)"
|
|
360
|
+
)
|
|
361
|
+
print(
|
|
362
|
+
" workflow status - Show workflow, arguments and worker state"
|
|
363
|
+
)
|
|
364
|
+
print(" workflow run - Run the loaded workflow")
|
|
365
|
+
print(" workflow run <n>=<v> .. - Set arguments, then run, in one line")
|
|
366
|
+
print(" workflow run ask <arg> - Prompt for one argument value, then run")
|
|
367
|
+
print(
|
|
368
|
+
" workflow restart - Stop the worker process (frees all models)"
|
|
369
|
+
)
|
|
370
|
+
print()
|
|
371
|
+
print("Runs execute in a persistent worker, so models stay loaded between")
|
|
372
|
+
print("runs. After editing the workflow file, just run again - pipelines")
|
|
373
|
+
print("whose definition did not change stay cached. Ctrl+C during a run")
|
|
374
|
+
print("cancels it (models stay cached); a second Ctrl+C stops the worker.")
|
|
375
|
+
print()
|
|
376
|
+
return
|
|
377
|
+
|
|
378
|
+
parts = arg.split(None, 1)
|
|
379
|
+
subcommand = parts[0]
|
|
380
|
+
subarg = parts[1] if len(parts) > 1 else ""
|
|
381
|
+
|
|
382
|
+
if subcommand == "list":
|
|
383
|
+
self._workflow_list(subarg)
|
|
384
|
+
elif subcommand == "load":
|
|
385
|
+
self._workflow_load(subarg)
|
|
386
|
+
elif subcommand == "reload":
|
|
387
|
+
self._workflow_reload(subarg)
|
|
388
|
+
elif subcommand == "status":
|
|
389
|
+
self._workflow_status(subarg)
|
|
390
|
+
elif subcommand == "run":
|
|
391
|
+
self._workflow_run(subarg)
|
|
392
|
+
elif subcommand == "restart":
|
|
393
|
+
self._workflow_restart(subarg)
|
|
394
|
+
else:
|
|
395
|
+
print(f"Unknown workflow subcommand: {subcommand}")
|
|
396
|
+
print("Use 'workflow ?' for help")
|
|
397
|
+
|
|
398
|
+
def workflow_names(self):
|
|
399
|
+
"""Workflow names under workflow_dir, as load accepts them.
|
|
400
|
+
|
|
401
|
+
Names are paths relative to workflow_dir with the .json dropped
|
|
402
|
+
('flux/FluxDev'), sorted for stable listing and completion.
|
|
403
|
+
"""
|
|
404
|
+
workflow_dir = self.repl.globals["workflow_dir"]
|
|
405
|
+
names = []
|
|
406
|
+
if not os.path.isdir(workflow_dir):
|
|
407
|
+
return names
|
|
408
|
+
for root, _dirs, files in os.walk(workflow_dir):
|
|
409
|
+
for file_name in files:
|
|
410
|
+
if file_name.endswith(".json"):
|
|
411
|
+
relative = os.path.relpath(
|
|
412
|
+
os.path.join(root, file_name), workflow_dir
|
|
413
|
+
)
|
|
414
|
+
names.append(relative[: -len(".json")])
|
|
415
|
+
return sorted(names)
|
|
416
|
+
|
|
417
|
+
def _workflow_list(self, arg: str):
|
|
418
|
+
"""List workflows available in the workflow directory"""
|
|
419
|
+
workflow_dir = self.repl.globals["workflow_dir"]
|
|
420
|
+
names = self.workflow_names()
|
|
421
|
+
if not names:
|
|
422
|
+
print(f"No workflows found in {workflow_dir}")
|
|
423
|
+
print(
|
|
424
|
+
"Point workflow_dir somewhere else with: config set workflow_dir=<path>"
|
|
425
|
+
)
|
|
426
|
+
return
|
|
427
|
+
print(f"\nWorkflows in {workflow_dir}:")
|
|
428
|
+
for name in names:
|
|
429
|
+
print(f" {name}")
|
|
430
|
+
print("\nLoad one with: workflow load <name>")
|
|
431
|
+
print()
|
|
432
|
+
|
|
433
|
+
def _workflow_load(self, arg: str):
|
|
434
|
+
"""Load a workflow from a JSON file"""
|
|
435
|
+
if not arg:
|
|
436
|
+
print("Error: Please specify a workflow file path or name")
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
file_path = validate_string_input(
|
|
441
|
+
arg.strip(), max_length=MAX_FILE_PATH_LENGTH
|
|
442
|
+
)
|
|
443
|
+
except InvalidInputError as e:
|
|
444
|
+
print(f"Error: Invalid file path: {e}")
|
|
445
|
+
return
|
|
446
|
+
|
|
447
|
+
# If this isn't an absolute path or relative path starting with ./ or ../
|
|
448
|
+
if not os.path.isabs(file_path) and not file_path.startswith(("./", "../")):
|
|
449
|
+
# Treat as a workflow name in the default directory
|
|
450
|
+
# Add .json extension if not present
|
|
451
|
+
if not file_path.endswith(".json"):
|
|
452
|
+
file_path = f"{file_path}.json"
|
|
453
|
+
try:
|
|
454
|
+
file_path = validate_path(
|
|
455
|
+
os.path.join(self.repl.globals["workflow_dir"], file_path),
|
|
456
|
+
self.repl.globals["workflow_dir"],
|
|
457
|
+
allow_create=False,
|
|
458
|
+
)
|
|
459
|
+
except SecurityError as e:
|
|
460
|
+
print(f"Error: Invalid workflow path: {e}")
|
|
461
|
+
return
|
|
462
|
+
else:
|
|
463
|
+
try:
|
|
464
|
+
file_path = validate_workflow_path(
|
|
465
|
+
file_path, self.repl.globals["workflow_dir"]
|
|
466
|
+
)
|
|
467
|
+
except SecurityError as e:
|
|
468
|
+
print(f"Error: Invalid workflow path: {e}")
|
|
469
|
+
return
|
|
470
|
+
|
|
471
|
+
if not os.path.exists(file_path):
|
|
472
|
+
print(f"Error: File {file_path} does not exist")
|
|
473
|
+
return
|
|
474
|
+
|
|
475
|
+
try:
|
|
476
|
+
output_dir = self.repl.globals["output_dir"]
|
|
477
|
+
if not os.path.exists(output_dir):
|
|
478
|
+
print(f"Warning: Output directory {output_dir} does not exist")
|
|
479
|
+
|
|
480
|
+
workflow = workflow_from_file(file_path, output_dir)
|
|
481
|
+
|
|
482
|
+
# Try to validate the workflow immediately
|
|
483
|
+
try:
|
|
484
|
+
workflow.validate()
|
|
485
|
+
|
|
486
|
+
# The worker handles workflow switches itself now - it frees
|
|
487
|
+
# the old workflow's models before loading the new one - so
|
|
488
|
+
# loading a different file no longer restarts the process
|
|
489
|
+
self.repl.current_workflow = workflow
|
|
490
|
+
# Clear any existing arguments when loading new workflow
|
|
491
|
+
self.repl.workflow_args = {}
|
|
492
|
+
print(f"Loaded workflow: {workflow.name}")
|
|
493
|
+
print("Workflow validated successfully")
|
|
494
|
+
|
|
495
|
+
except Exception as e:
|
|
496
|
+
print(f"Warning: Workflow validation failed: {str(e)}")
|
|
497
|
+
|
|
498
|
+
except Exception as e:
|
|
499
|
+
print(f"Error loading workflow: {str(e)}")
|
|
500
|
+
self.repl.current_workflow = None
|
|
501
|
+
|
|
502
|
+
def _workflow_reload(self, arg: str):
|
|
503
|
+
"""Reload the current workflow from its file"""
|
|
504
|
+
if not self.repl.current_workflow:
|
|
505
|
+
print("Error: No workflow loaded. Use 'workflow load' command first")
|
|
506
|
+
return
|
|
507
|
+
|
|
508
|
+
try:
|
|
509
|
+
file_path = self.repl.current_workflow.file_spec
|
|
510
|
+
print(f"Reloading workflow from: {file_path}")
|
|
511
|
+
|
|
512
|
+
# Load and validate the workflow
|
|
513
|
+
workflow = workflow_from_file(file_path, self.repl.globals["output_dir"])
|
|
514
|
+
workflow.validate()
|
|
515
|
+
|
|
516
|
+
# Replace current workflow
|
|
517
|
+
self.repl.current_workflow = workflow
|
|
518
|
+
print(f"Reloaded workflow: {workflow.name}")
|
|
519
|
+
print("Workflow validated successfully")
|
|
520
|
+
|
|
521
|
+
except Exception as e:
|
|
522
|
+
print(f"Error reloading workflow: {str(e)}")
|
|
523
|
+
|
|
524
|
+
def _workflow_status(self, arg: str):
|
|
525
|
+
"""Show current workflow status"""
|
|
526
|
+
if self.repl.current_workflow is None:
|
|
527
|
+
print("No workflow currently loaded")
|
|
528
|
+
print(
|
|
529
|
+
"Use 'workflow list' to see what is available, then 'workflow load <name>'"
|
|
530
|
+
)
|
|
531
|
+
return
|
|
532
|
+
|
|
533
|
+
workflow = self.repl.current_workflow
|
|
534
|
+
print(f"\nCurrent workflow: {workflow.name}")
|
|
535
|
+
print(f" File: {workflow.file_spec}")
|
|
536
|
+
print(f" Output directory: {self.repl.globals['output_dir']}")
|
|
537
|
+
|
|
538
|
+
variables = workflow.variables
|
|
539
|
+
if variables:
|
|
540
|
+
print(" Variables:")
|
|
541
|
+
for name, default in variables.items():
|
|
542
|
+
if name in self.repl.workflow_args:
|
|
543
|
+
print(
|
|
544
|
+
f" {name} = {display_value(self.repl.workflow_args[name])}"
|
|
545
|
+
f" (default: {display_value(default, limit=40)})"
|
|
546
|
+
)
|
|
547
|
+
else:
|
|
548
|
+
print(f" {name} = {display_value(default)}")
|
|
549
|
+
else:
|
|
550
|
+
print(" Variables: none defined")
|
|
551
|
+
|
|
552
|
+
manager = self.repl.worker_manager
|
|
553
|
+
worker_alive = (
|
|
554
|
+
manager.worker_active
|
|
555
|
+
and manager.worker_process is not None
|
|
556
|
+
and manager.worker_process.is_alive()
|
|
557
|
+
)
|
|
558
|
+
if worker_alive:
|
|
559
|
+
print(" Worker: running (models stay cached between runs)")
|
|
560
|
+
else:
|
|
561
|
+
print(" Worker: not running (starts on the next 'workflow run')")
|
|
562
|
+
print()
|
|
563
|
+
|
|
564
|
+
@contextlib.contextmanager
|
|
565
|
+
def _history_suppressed(self):
|
|
566
|
+
"""Readline auto-history off for one input, so values are not saved"""
|
|
567
|
+
readline = None
|
|
568
|
+
try:
|
|
569
|
+
import readline
|
|
570
|
+
except ImportError:
|
|
571
|
+
pass
|
|
572
|
+
|
|
573
|
+
suppress = readline is not None and hasattr(readline, "set_auto_history")
|
|
574
|
+
if suppress:
|
|
575
|
+
readline.set_auto_history(False)
|
|
576
|
+
try:
|
|
577
|
+
yield
|
|
578
|
+
finally:
|
|
579
|
+
if suppress:
|
|
580
|
+
readline.set_auto_history(True)
|
|
581
|
+
|
|
582
|
+
def _prompt_for_argument(self, arg_name):
|
|
583
|
+
"""Ask the user for one workflow argument's value.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
True when the value was set, False on invalid input or cancel
|
|
587
|
+
"""
|
|
588
|
+
# Validate that the argument exists in the workflow's variables
|
|
589
|
+
if arg_name not in self.repl.current_workflow.variables:
|
|
590
|
+
print(f"Error: '{arg_name}' is not defined in workflow variables")
|
|
591
|
+
print(
|
|
592
|
+
f"Available variables: {', '.join(self.repl.current_workflow.variables.keys())}"
|
|
593
|
+
)
|
|
594
|
+
return False
|
|
595
|
+
|
|
596
|
+
try:
|
|
597
|
+
with self._history_suppressed():
|
|
598
|
+
user_value = input(f"Enter value for '{arg_name}': ").strip()
|
|
599
|
+
except (EOFError, KeyboardInterrupt):
|
|
600
|
+
print("\nCancelled")
|
|
601
|
+
return False
|
|
602
|
+
|
|
603
|
+
try:
|
|
604
|
+
validated_name = validate_variable_name(arg_name)
|
|
605
|
+
validated_value = validate_string_input(
|
|
606
|
+
user_value,
|
|
607
|
+
max_length=MAX_VARIABLE_VALUE_LENGTH,
|
|
608
|
+
allow_empty=True,
|
|
609
|
+
)
|
|
610
|
+
except InvalidInputError as e:
|
|
611
|
+
print(f"Error: Invalid input: {e}")
|
|
612
|
+
return False
|
|
613
|
+
|
|
614
|
+
self.repl.workflow_args[validated_name] = validated_value
|
|
615
|
+
print(f"Set argument {validated_name}={validated_value}")
|
|
616
|
+
print()
|
|
617
|
+
return True
|
|
618
|
+
|
|
619
|
+
def _workflow_run(self, arg: str):
|
|
620
|
+
"""Run the currently loaded workflow with set arguments"""
|
|
621
|
+
if not self.repl.current_workflow:
|
|
622
|
+
print("Error: No workflow loaded. Use 'workflow load' command first")
|
|
623
|
+
return
|
|
624
|
+
|
|
625
|
+
# Handle "run ask <arg_name>" subcommand. "ask" with nothing after it
|
|
626
|
+
# (any amount of whitespace) is caught here, so the name is never empty
|
|
627
|
+
if arg.strip() == "ask":
|
|
628
|
+
print("Error: Please specify an argument name")
|
|
629
|
+
print("Usage: workflow run ask <arg_name>")
|
|
630
|
+
return
|
|
631
|
+
|
|
632
|
+
if arg.startswith("ask "):
|
|
633
|
+
if not self._prompt_for_argument(arg[4:].strip()):
|
|
634
|
+
return
|
|
635
|
+
elif arg.strip():
|
|
636
|
+
# Inline overrides: workflow run steps=30 prompt="a cat". Same
|
|
637
|
+
# semantics as arg set for each pair, then run.
|
|
638
|
+
try:
|
|
639
|
+
assignments = shlex.split(arg)
|
|
640
|
+
except ValueError as e:
|
|
641
|
+
print(f"Error: Could not parse arguments: {e}")
|
|
642
|
+
return
|
|
643
|
+
for assignment in assignments:
|
|
644
|
+
if "=" not in assignment:
|
|
645
|
+
print(f"Error: Expected <name>=<value>, got '{assignment}'")
|
|
646
|
+
print("Usage: workflow run [<name>=<value> ...] | ask <arg>")
|
|
647
|
+
return
|
|
648
|
+
for assignment in assignments:
|
|
649
|
+
if not self.repl.arg_commands._arg_set(assignment):
|
|
650
|
+
return
|
|
651
|
+
|
|
652
|
+
try:
|
|
653
|
+
# Validate inputs
|
|
654
|
+
output_dir = validate_output_path(self.repl.globals["output_dir"], None)
|
|
655
|
+
workflow_spec = validate_workflow_path(self.repl.current_workflow.file_spec)
|
|
656
|
+
|
|
657
|
+
# Ensure worker is running
|
|
658
|
+
self.repl.worker_manager.ensure_worker(self.repl.globals["log_level"])
|
|
659
|
+
|
|
660
|
+
print(f"Running workflow: {self.repl.current_workflow.name}")
|
|
661
|
+
if self.repl.workflow_args:
|
|
662
|
+
print(f"Using arguments: {self.repl.workflow_args}")
|
|
663
|
+
|
|
664
|
+
# Send execute command to worker
|
|
665
|
+
self.repl.worker_manager.send_command(
|
|
666
|
+
{
|
|
667
|
+
"type": "execute",
|
|
668
|
+
"workflow_path": workflow_spec,
|
|
669
|
+
"arguments": self.repl.workflow_args,
|
|
670
|
+
"output_dir": output_dir,
|
|
671
|
+
"log_level": self.repl.globals["log_level"],
|
|
672
|
+
}
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
# Process results from worker. There is no fixed timeout - a video
|
|
676
|
+
# generation or cold model download takes however long it takes,
|
|
677
|
+
# progress events stream in while it works, and get_result raises
|
|
678
|
+
# if the worker dies. Ctrl+C asks the worker to cancel the run; a
|
|
679
|
+
# second Ctrl+C gives up on it and shuts the worker down.
|
|
680
|
+
cancelling = False
|
|
681
|
+
inline_progress = False
|
|
682
|
+
|
|
683
|
+
def end_inline():
|
|
684
|
+
nonlocal inline_progress
|
|
685
|
+
if inline_progress:
|
|
686
|
+
print()
|
|
687
|
+
inline_progress = False
|
|
688
|
+
|
|
689
|
+
while True:
|
|
690
|
+
try:
|
|
691
|
+
result = self.repl.worker_manager.get_result()
|
|
692
|
+
except KeyboardInterrupt:
|
|
693
|
+
end_inline()
|
|
694
|
+
if not cancelling:
|
|
695
|
+
cancelling = True
|
|
696
|
+
print("Cancelling... (Ctrl+C again to force-stop the worker)")
|
|
697
|
+
try:
|
|
698
|
+
self.repl.worker_manager.cancel()
|
|
699
|
+
except Exception as e:
|
|
700
|
+
print(f"Could not send cancel: {e}")
|
|
701
|
+
self.repl.worker_manager.shutdown_worker()
|
|
702
|
+
break
|
|
703
|
+
continue
|
|
704
|
+
print("Force-stopping worker...")
|
|
705
|
+
self.repl.worker_manager.shutdown_worker()
|
|
706
|
+
break
|
|
707
|
+
except Exception as e:
|
|
708
|
+
end_inline()
|
|
709
|
+
print("\n" + "=" * 80)
|
|
710
|
+
print(f"ERROR receiving results: {e}")
|
|
711
|
+
print("=" * 80)
|
|
712
|
+
print("Worker communication failed. Shutting down worker.\n")
|
|
713
|
+
self.repl.worker_manager.shutdown_worker()
|
|
714
|
+
break
|
|
715
|
+
|
|
716
|
+
result_type = result.get("type")
|
|
717
|
+
|
|
718
|
+
if result_type == "progress":
|
|
719
|
+
event = result.get("event")
|
|
720
|
+
if event == "step_start":
|
|
721
|
+
end_inline()
|
|
722
|
+
print(
|
|
723
|
+
f"[{result['index'] + 1}/{result['total_steps']}] "
|
|
724
|
+
f"{result['step']}"
|
|
725
|
+
)
|
|
726
|
+
elif event == "iteration_start":
|
|
727
|
+
if result["total_iterations"] > 1:
|
|
728
|
+
end_inline()
|
|
729
|
+
print(
|
|
730
|
+
f" iteration {result['iteration']}/"
|
|
731
|
+
f"{result['total_iterations']}"
|
|
732
|
+
)
|
|
733
|
+
elif event == "pipeline_step":
|
|
734
|
+
total = result.get("total_steps") or "?"
|
|
735
|
+
print(
|
|
736
|
+
f"\r denoise step {result['step']}/{total}",
|
|
737
|
+
end="",
|
|
738
|
+
flush=True,
|
|
739
|
+
)
|
|
740
|
+
inline_progress = True
|
|
741
|
+
elif event == "step_end":
|
|
742
|
+
end_inline()
|
|
743
|
+
for saved_file in result.get("files", []):
|
|
744
|
+
print(f" saved {saved_file}")
|
|
745
|
+
# workflow_start / workflow_end stay quiet - the step
|
|
746
|
+
# lines carry the story
|
|
747
|
+
elif result_type == "output":
|
|
748
|
+
end_inline()
|
|
749
|
+
print(result["message"])
|
|
750
|
+
elif result_type == "workflow_loaded":
|
|
751
|
+
end_inline()
|
|
752
|
+
print(f"Workflow loaded: {result['workflow_name']}")
|
|
753
|
+
elif result_type == "memory_info":
|
|
754
|
+
end_inline()
|
|
755
|
+
self.repl._print_memory_info(result["info"])
|
|
756
|
+
elif result_type == "success":
|
|
757
|
+
end_inline()
|
|
758
|
+
print(result["message"])
|
|
759
|
+
break
|
|
760
|
+
elif result_type == "cancelled":
|
|
761
|
+
end_inline()
|
|
762
|
+
print(result.get("message", "Workflow run cancelled"))
|
|
763
|
+
break
|
|
764
|
+
elif result_type == "error":
|
|
765
|
+
end_inline()
|
|
766
|
+
print("\n" + "=" * 80)
|
|
767
|
+
print(f"ERROR: {result['message']}")
|
|
768
|
+
if "traceback" in result:
|
|
769
|
+
print("\nTraceback:")
|
|
770
|
+
print(result["traceback"])
|
|
771
|
+
print("=" * 80)
|
|
772
|
+
print("Worker process encountered an error and stopped.\n")
|
|
773
|
+
break
|
|
774
|
+
elif result_type == "worker_crashed":
|
|
775
|
+
end_inline()
|
|
776
|
+
print("\n" + "=" * 80)
|
|
777
|
+
print(f"WORKER CRASHED: {result['message']}")
|
|
778
|
+
if "traceback" in result:
|
|
779
|
+
print("\nTraceback:")
|
|
780
|
+
print(result["traceback"])
|
|
781
|
+
print("=" * 80)
|
|
782
|
+
print(
|
|
783
|
+
"Worker process has terminated. Use 'workflow restart' to start a new worker.\n"
|
|
784
|
+
)
|
|
785
|
+
# Already crashed - no shutdown handshake to attempt
|
|
786
|
+
self.repl.worker_manager.mark_crashed()
|
|
787
|
+
break
|
|
788
|
+
else:
|
|
789
|
+
end_inline()
|
|
790
|
+
print(f"Unknown result type: {result_type}")
|
|
791
|
+
|
|
792
|
+
except SecurityError as e:
|
|
793
|
+
print("\n" + "=" * 80)
|
|
794
|
+
print(f"SECURITY ERROR: {e}")
|
|
795
|
+
print("=" * 80 + "\n")
|
|
796
|
+
except Exception as e:
|
|
797
|
+
print("\n" + "=" * 80)
|
|
798
|
+
print(f"ERROR running workflow: {str(e)}")
|
|
799
|
+
print("=" * 80)
|
|
800
|
+
print("Shutting down worker.\n")
|
|
801
|
+
self.repl.worker_manager.shutdown_worker()
|
|
802
|
+
|
|
803
|
+
def _workflow_restart(self, arg: str):
|
|
804
|
+
"""Restart the worker process"""
|
|
805
|
+
print("Restarting worker process...")
|
|
806
|
+
self.repl.worker_manager.shutdown_worker()
|
|
807
|
+
print("Worker shutdown complete")
|
|
808
|
+
print("Worker will restart on next run")
|