griptape-nodes 0.64.11__py3-none-any.whl → 0.65.1__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.
- griptape_nodes/app/app.py +25 -5
- griptape_nodes/cli/commands/init.py +65 -54
- griptape_nodes/cli/commands/libraries.py +92 -85
- griptape_nodes/cli/commands/self.py +121 -0
- griptape_nodes/common/node_executor.py +2142 -101
- griptape_nodes/exe_types/base_iterative_nodes.py +1004 -0
- griptape_nodes/exe_types/connections.py +114 -19
- griptape_nodes/exe_types/core_types.py +225 -7
- griptape_nodes/exe_types/flow.py +3 -3
- griptape_nodes/exe_types/node_types.py +681 -225
- griptape_nodes/exe_types/param_components/README.md +414 -0
- griptape_nodes/exe_types/param_components/api_key_provider_parameter.py +200 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +2 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +79 -5
- griptape_nodes/exe_types/param_types/parameter_button.py +443 -0
- griptape_nodes/machines/control_flow.py +84 -38
- griptape_nodes/machines/dag_builder.py +148 -70
- griptape_nodes/machines/parallel_resolution.py +61 -35
- griptape_nodes/machines/sequential_resolution.py +11 -113
- griptape_nodes/retained_mode/events/app_events.py +1 -0
- griptape_nodes/retained_mode/events/base_events.py +16 -13
- griptape_nodes/retained_mode/events/connection_events.py +3 -0
- griptape_nodes/retained_mode/events/execution_events.py +35 -0
- griptape_nodes/retained_mode/events/flow_events.py +15 -2
- griptape_nodes/retained_mode/events/library_events.py +347 -0
- griptape_nodes/retained_mode/events/node_events.py +48 -0
- griptape_nodes/retained_mode/events/os_events.py +86 -3
- griptape_nodes/retained_mode/events/project_events.py +15 -1
- griptape_nodes/retained_mode/events/workflow_events.py +48 -1
- griptape_nodes/retained_mode/griptape_nodes.py +6 -2
- griptape_nodes/retained_mode/managers/config_manager.py +10 -8
- griptape_nodes/retained_mode/managers/event_manager.py +168 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/old_xdg_location_warning_problem.py +43 -0
- griptape_nodes/retained_mode/managers/flow_manager.py +664 -123
- griptape_nodes/retained_mode/managers/library_manager.py +1142 -138
- griptape_nodes/retained_mode/managers/model_manager.py +2 -3
- griptape_nodes/retained_mode/managers/node_manager.py +148 -25
- griptape_nodes/retained_mode/managers/object_manager.py +3 -1
- griptape_nodes/retained_mode/managers/operation_manager.py +3 -1
- griptape_nodes/retained_mode/managers/os_manager.py +1158 -122
- griptape_nodes/retained_mode/managers/secrets_manager.py +2 -3
- griptape_nodes/retained_mode/managers/settings.py +21 -1
- griptape_nodes/retained_mode/managers/sync_manager.py +2 -3
- griptape_nodes/retained_mode/managers/workflow_manager.py +358 -104
- griptape_nodes/retained_mode/retained_mode.py +3 -3
- griptape_nodes/traits/button.py +44 -2
- griptape_nodes/traits/file_system_picker.py +2 -2
- griptape_nodes/utils/file_utils.py +101 -0
- griptape_nodes/utils/git_utils.py +1236 -0
- griptape_nodes/utils/library_utils.py +122 -0
- {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/METADATA +2 -1
- {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/RECORD +55 -47
- {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/entry_points.txt +0 -0
|
@@ -188,7 +188,9 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
188
188
|
|
|
189
189
|
try:
|
|
190
190
|
result_event = event_mgr.handle_request(request=request)
|
|
191
|
-
|
|
191
|
+
# Only queue result event if not suppressed
|
|
192
|
+
if not event_mgr.should_suppress_event(result_event):
|
|
193
|
+
event_mgr.put_event(GriptapeNodeEvent(wrapped_event=result_event))
|
|
192
194
|
except Exception as e:
|
|
193
195
|
logger.exception(
|
|
194
196
|
"Unhandled exception while processing request of type %s. "
|
|
@@ -214,7 +216,9 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
214
216
|
|
|
215
217
|
try:
|
|
216
218
|
result_event = await event_mgr.ahandle_request(request=request)
|
|
217
|
-
|
|
219
|
+
# Only queue result event if not suppressed
|
|
220
|
+
if not event_mgr.should_suppress_event(result_event):
|
|
221
|
+
await event_mgr.aput_event(GriptapeNodeEvent(wrapped_event=result_event))
|
|
218
222
|
except Exception as e:
|
|
219
223
|
logger.exception(
|
|
220
224
|
"Unhandled exception while processing async request of type %s. "
|
|
@@ -33,7 +33,7 @@ from griptape_nodes.retained_mode.events.config_events import (
|
|
|
33
33
|
SetConfigValueResultSuccess,
|
|
34
34
|
)
|
|
35
35
|
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
36
|
-
from griptape_nodes.retained_mode.managers.settings import Settings
|
|
36
|
+
from griptape_nodes.retained_mode.managers.settings import WORKFLOWS_TO_REGISTER_KEY, Settings
|
|
37
37
|
from griptape_nodes.utils.dict_utils import get_dot_value, merge_dicts, set_dot_value
|
|
38
38
|
|
|
39
39
|
logger = logging.getLogger("griptape_nodes")
|
|
@@ -210,7 +210,7 @@ class ConfigManager:
|
|
|
210
210
|
An exception is made for `workflows_to_register` since resetting it gives the appearance of the user losing their workflows.
|
|
211
211
|
"""
|
|
212
212
|
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/1241 need a better way to annotate fields to ignore.
|
|
213
|
-
workflows_to_register = self.get_config_value(
|
|
213
|
+
workflows_to_register = self.get_config_value(WORKFLOWS_TO_REGISTER_KEY)
|
|
214
214
|
USER_CONFIG_PATH.write_text(
|
|
215
215
|
json.dumps(
|
|
216
216
|
{
|
|
@@ -226,7 +226,7 @@ class ConfigManager:
|
|
|
226
226
|
self.load_configs()
|
|
227
227
|
|
|
228
228
|
def save_user_workflow_json(self, workflow_file_name: str) -> None:
|
|
229
|
-
config_loc =
|
|
229
|
+
config_loc = WORKFLOWS_TO_REGISTER_KEY
|
|
230
230
|
existing_workflows = self.get_config_value(config_loc)
|
|
231
231
|
if not existing_workflows:
|
|
232
232
|
existing_workflows = []
|
|
@@ -234,14 +234,14 @@ class ConfigManager:
|
|
|
234
234
|
self.set_config_value(config_loc, existing_workflows)
|
|
235
235
|
|
|
236
236
|
def delete_user_workflow(self, workflow_file_name: str) -> None:
|
|
237
|
-
default_workflows = self.get_config_value(
|
|
237
|
+
default_workflows = self.get_config_value(WORKFLOWS_TO_REGISTER_KEY)
|
|
238
238
|
if default_workflows:
|
|
239
239
|
default_workflows = [
|
|
240
240
|
saved_workflow
|
|
241
241
|
for saved_workflow in default_workflows
|
|
242
242
|
if (saved_workflow.lower() != workflow_file_name.lower())
|
|
243
243
|
]
|
|
244
|
-
self.set_config_value(
|
|
244
|
+
self.set_config_value(WORKFLOWS_TO_REGISTER_KEY, default_workflows)
|
|
245
245
|
|
|
246
246
|
def get_full_path(self, relative_path: str) -> Path:
|
|
247
247
|
"""Get a full path by combining the base path with a relative path.
|
|
@@ -288,7 +288,7 @@ class ConfigManager:
|
|
|
288
288
|
|
|
289
289
|
if value is None:
|
|
290
290
|
msg = f"Config key '{key}' not found in config file."
|
|
291
|
-
logger.
|
|
291
|
+
logger.debug(msg)
|
|
292
292
|
return None
|
|
293
293
|
|
|
294
294
|
if should_load_env_var_if_detected and isinstance(value, str) and value.startswith("$"):
|
|
@@ -528,7 +528,9 @@ class ConfigManager:
|
|
|
528
528
|
level: The log level to set (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
|
|
529
529
|
"""
|
|
530
530
|
try:
|
|
531
|
-
|
|
532
|
-
|
|
531
|
+
level_upper = level.upper()
|
|
532
|
+
log_level = getattr(logging, level_upper)
|
|
533
|
+
logger.setLevel(log_level)
|
|
534
|
+
except (ValueError, AttributeError):
|
|
533
535
|
logger.error("Invalid log level %s. Defaulting to INFO.", level)
|
|
534
536
|
logger.setLevel(logging.INFO)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import inspect
|
|
5
|
+
import logging
|
|
5
6
|
import threading
|
|
6
7
|
from collections import defaultdict
|
|
7
8
|
from dataclasses import fields
|
|
@@ -13,14 +14,18 @@ from typing_extensions import TypedDict, TypeVar
|
|
|
13
14
|
from griptape_nodes.exe_types.node_types import BaseNode
|
|
14
15
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
15
16
|
AppPayload,
|
|
17
|
+
BaseEvent,
|
|
16
18
|
EventResultFailure,
|
|
17
19
|
EventResultSuccess,
|
|
20
|
+
ProgressEvent,
|
|
18
21
|
RequestPayload,
|
|
22
|
+
ResultDetails,
|
|
19
23
|
ResultPayload,
|
|
20
24
|
)
|
|
21
25
|
from griptape_nodes.utils.async_utils import call_function
|
|
22
26
|
|
|
23
27
|
if TYPE_CHECKING:
|
|
28
|
+
import types
|
|
24
29
|
from collections.abc import Awaitable, Callable
|
|
25
30
|
|
|
26
31
|
|
|
@@ -51,6 +56,8 @@ class EventManager:
|
|
|
51
56
|
self._loop_thread_id: int | None = None
|
|
52
57
|
# Keep a reference to the event loop for thread-safe operations
|
|
53
58
|
self._event_loop: asyncio.AbstractEventLoop | None = None
|
|
59
|
+
# Per-event reference counting for event suppression
|
|
60
|
+
self._event_suppression_counts: dict[type, int] = {}
|
|
54
61
|
|
|
55
62
|
@property
|
|
56
63
|
def event_queue(self) -> asyncio.Queue:
|
|
@@ -59,6 +66,15 @@ class EventManager:
|
|
|
59
66
|
raise ValueError(msg)
|
|
60
67
|
return self._event_queue
|
|
61
68
|
|
|
69
|
+
def should_suppress_event(self, event: BaseEvent | ProgressEvent) -> bool:
|
|
70
|
+
"""Check if events should be suppressed from being sent to websockets."""
|
|
71
|
+
event_type = type(event)
|
|
72
|
+
return self._event_suppression_counts.get(event_type, 0) > 0
|
|
73
|
+
|
|
74
|
+
def clear_event_suppression(self) -> None:
|
|
75
|
+
"""Clear all event suppression counts."""
|
|
76
|
+
self._event_suppression_counts.clear()
|
|
77
|
+
|
|
62
78
|
def initialize_queue(self, queue: asyncio.Queue | None = None) -> None:
|
|
63
79
|
"""Set the event queue for this manager.
|
|
64
80
|
|
|
@@ -162,6 +178,28 @@ class EventManager:
|
|
|
162
178
|
if request_type in self._request_type_to_manager:
|
|
163
179
|
del self._request_type_to_manager[request_type]
|
|
164
180
|
|
|
181
|
+
def _override_result_log_level(self, result: ResultPayload, level: int) -> None:
|
|
182
|
+
"""Override the log level on all result details.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
result: The result payload to modify
|
|
186
|
+
level: The new log level to set
|
|
187
|
+
"""
|
|
188
|
+
if isinstance(result.result_details, ResultDetails):
|
|
189
|
+
for detail in result.result_details.result_details:
|
|
190
|
+
detail.level = level
|
|
191
|
+
|
|
192
|
+
def _log_result_details(self, result: ResultPayload) -> None:
|
|
193
|
+
"""Log the result details at their specified levels.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
result: The result payload containing details to log
|
|
197
|
+
"""
|
|
198
|
+
if isinstance(result.result_details, ResultDetails):
|
|
199
|
+
logger = logging.getLogger("griptape_nodes")
|
|
200
|
+
for detail in result.result_details.result_details:
|
|
201
|
+
logger.log(detail.level, detail.message)
|
|
202
|
+
|
|
165
203
|
def _handle_request_core(
|
|
166
204
|
self,
|
|
167
205
|
request: RP,
|
|
@@ -182,6 +220,13 @@ class EventManager:
|
|
|
182
220
|
if workflow_mgr.should_squelch_workflow_altered():
|
|
183
221
|
callback_result.altered_workflow_state = False
|
|
184
222
|
|
|
223
|
+
# Override failure log level if requested
|
|
224
|
+
if callback_result.failed() and request.failure_log_level is not None:
|
|
225
|
+
self._override_result_log_level(callback_result, request.failure_log_level)
|
|
226
|
+
|
|
227
|
+
# Log result details (after potential level override)
|
|
228
|
+
self._log_result_details(callback_result)
|
|
229
|
+
|
|
185
230
|
retained_mode_str = None
|
|
186
231
|
# If request_id exists, that means it's a direct request from the GUI (not internal), and should be echoed by retained mode.
|
|
187
232
|
if depth_manager.is_top_level() and context.get("request_id") is not None:
|
|
@@ -332,3 +377,126 @@ class EventManager:
|
|
|
332
377
|
# Only flush if there are actually tracked parameters
|
|
333
378
|
if node._tracked_parameters:
|
|
334
379
|
node.emit_parameter_changes()
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class EventSuppressionContext:
|
|
383
|
+
"""Context manager to suppress events from being sent to websockets.
|
|
384
|
+
|
|
385
|
+
Use this to prevent internal operations (like deserialization/deletion of iteration flows)
|
|
386
|
+
from sending events to the GUI while still allowing the operations to complete normally.
|
|
387
|
+
|
|
388
|
+
Uses per-event reference counting to track nested suppression contexts.
|
|
389
|
+
Each event type maintains its own reference count, and is only unsuppressed
|
|
390
|
+
when its count reaches zero.
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
events_to_suppress: set[type]
|
|
394
|
+
|
|
395
|
+
def __init__(self, manager: EventManager, events_to_suppress: set[type]):
|
|
396
|
+
self.manager = manager
|
|
397
|
+
self.events_to_suppress = events_to_suppress
|
|
398
|
+
|
|
399
|
+
def __enter__(self) -> None:
|
|
400
|
+
for event_type in self.events_to_suppress:
|
|
401
|
+
current_count = self.manager._event_suppression_counts.get(event_type, 0)
|
|
402
|
+
self.manager._event_suppression_counts[event_type] = current_count + 1
|
|
403
|
+
|
|
404
|
+
def __exit__(
|
|
405
|
+
self,
|
|
406
|
+
exc_type: type[BaseException] | None,
|
|
407
|
+
exc_value: BaseException | None,
|
|
408
|
+
exc_traceback: types.TracebackType | None,
|
|
409
|
+
) -> None:
|
|
410
|
+
for event_type in self.events_to_suppress:
|
|
411
|
+
current_count = self.manager._event_suppression_counts.get(event_type, 0)
|
|
412
|
+
if current_count <= 1:
|
|
413
|
+
self.manager._event_suppression_counts.pop(event_type, None)
|
|
414
|
+
else:
|
|
415
|
+
self.manager._event_suppression_counts[event_type] = current_count - 1
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class EventTranslationContext:
|
|
419
|
+
"""Context manager to translate node names in events from packaged to original names.
|
|
420
|
+
|
|
421
|
+
Use this to make loop execution events reference the original nodes that the user placed,
|
|
422
|
+
rather than the packaged node copies. This allows the UI to highlight the correct nodes
|
|
423
|
+
during loop execution.
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
def __init__(self, manager: EventManager, node_name_mapping: dict[str, str]):
|
|
427
|
+
"""Initialize the event translation context.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
manager: The EventManager to intercept events from
|
|
431
|
+
node_name_mapping: Dict mapping packaged node names to original node names
|
|
432
|
+
"""
|
|
433
|
+
self.manager = manager
|
|
434
|
+
self.node_name_mapping = node_name_mapping
|
|
435
|
+
self.original_put_event: Any = None
|
|
436
|
+
|
|
437
|
+
def __enter__(self) -> None:
|
|
438
|
+
"""Enter the context and start translating events."""
|
|
439
|
+
self.original_put_event = self.manager.put_event
|
|
440
|
+
self.manager.put_event = self._translate_and_put # type: ignore[method-assign]
|
|
441
|
+
|
|
442
|
+
def __exit__(
|
|
443
|
+
self,
|
|
444
|
+
exc_type: type[BaseException] | None,
|
|
445
|
+
exc_value: BaseException | None,
|
|
446
|
+
exc_traceback: types.TracebackType | None,
|
|
447
|
+
) -> None:
|
|
448
|
+
"""Exit the context and restore original event sending."""
|
|
449
|
+
self.manager.put_event = self.original_put_event # type: ignore[method-assign]
|
|
450
|
+
|
|
451
|
+
def _translate_and_put(self, event: Any) -> None:
|
|
452
|
+
"""Translate node names in events and put them in the queue.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
event: The event to potentially translate and send
|
|
456
|
+
"""
|
|
457
|
+
# Check if event has node_name attribute and needs translation
|
|
458
|
+
if hasattr(event, "node_name"):
|
|
459
|
+
node_name = event.node_name
|
|
460
|
+
if node_name in self.node_name_mapping:
|
|
461
|
+
# Create a copy of the event with the translated node name
|
|
462
|
+
translated_event = self._copy_event_with_translated_name(event)
|
|
463
|
+
self.original_put_event(translated_event)
|
|
464
|
+
return
|
|
465
|
+
|
|
466
|
+
# No translation needed, send as-is
|
|
467
|
+
self.original_put_event(event)
|
|
468
|
+
|
|
469
|
+
def _copy_event_with_translated_name(self, event: Any) -> Any:
|
|
470
|
+
"""Create a copy of an event with the node name translated to the original name.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
event: The event to copy and translate
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
A new event instance with the translated node name
|
|
477
|
+
"""
|
|
478
|
+
# Get the original node name from the mapping
|
|
479
|
+
node_name = event.node_name
|
|
480
|
+
original_node_name = self.node_name_mapping[node_name]
|
|
481
|
+
|
|
482
|
+
# Get the event class
|
|
483
|
+
event_class = type(event)
|
|
484
|
+
|
|
485
|
+
# Create a dict of all event attributes
|
|
486
|
+
if hasattr(event, "model_dump"):
|
|
487
|
+
event_dict = event.model_dump()
|
|
488
|
+
elif hasattr(event, "__dict__"):
|
|
489
|
+
event_dict = event.__dict__.copy()
|
|
490
|
+
else:
|
|
491
|
+
# Can't copy this event, return as-is
|
|
492
|
+
return event
|
|
493
|
+
|
|
494
|
+
# Replace the node name with the original name
|
|
495
|
+
event_dict["node_name"] = original_node_name
|
|
496
|
+
|
|
497
|
+
# Create a new event instance with the translated name
|
|
498
|
+
try:
|
|
499
|
+
return event_class(**event_dict)
|
|
500
|
+
except Exception:
|
|
501
|
+
# If we can't create a new instance, return the original
|
|
502
|
+
return event
|
|
@@ -22,6 +22,7 @@ from .modified_parameters_set_removed_problem import ModifiedParametersSetRemove
|
|
|
22
22
|
from .node_class_not_base_node_problem import NodeClassNotBaseNodeProblem
|
|
23
23
|
from .node_class_not_found_problem import NodeClassNotFoundProblem
|
|
24
24
|
from .node_module_import_problem import NodeModuleImportProblem
|
|
25
|
+
from .old_xdg_location_warning_problem import OldXdgLocationWarningProblem
|
|
25
26
|
from .sandbox_directory_missing_problem import SandboxDirectoryMissingProblem
|
|
26
27
|
from .ui_options_field_modified_incompatible_problem import UiOptionsFieldModifiedIncompatibleProblem
|
|
27
28
|
from .ui_options_field_modified_warning_problem import UiOptionsFieldModifiedWarningProblem
|
|
@@ -51,6 +52,7 @@ __all__ = [
|
|
|
51
52
|
"NodeClassNotBaseNodeProblem",
|
|
52
53
|
"NodeClassNotFoundProblem",
|
|
53
54
|
"NodeModuleImportProblem",
|
|
55
|
+
"OldXdgLocationWarningProblem",
|
|
54
56
|
"SandboxDirectoryMissingProblem",
|
|
55
57
|
"UiOptionsFieldModifiedIncompatibleProblem",
|
|
56
58
|
"UiOptionsFieldModifiedWarningProblem",
|
griptape_nodes/retained_mode/managers/fitness_problems/libraries/old_xdg_location_warning_problem.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from xdg_base_dirs import xdg_data_home
|
|
7
|
+
|
|
8
|
+
from griptape_nodes.retained_mode.managers.fitness_problems.libraries.library_problem import LibraryProblem
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class OldXdgLocationWarningProblem(LibraryProblem):
|
|
15
|
+
"""Problem warning that a library is located in the old XDG data directory.
|
|
16
|
+
|
|
17
|
+
This is a warning-level problem (FLAWED status) - the library will still
|
|
18
|
+
load and function normally, but users should migrate to the new location.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
old_path: str
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def collate_problems_for_display(cls, instances: list[OldXdgLocationWarningProblem]) -> str:
|
|
25
|
+
"""Display old XDG location warning.
|
|
26
|
+
|
|
27
|
+
There should only be one instance per library since each LibraryInfo
|
|
28
|
+
is already associated with a specific library path.
|
|
29
|
+
"""
|
|
30
|
+
if len(instances) > 1:
|
|
31
|
+
logger.error(
|
|
32
|
+
"OldXdgLocationWarningProblem: Expected 1 instance but got %s. Each LibraryInfo should only have one OldXdgLocationWarningProblem.",
|
|
33
|
+
len(instances),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
old_libraries_path = xdg_data_home() / "griptape_nodes" / "libraries"
|
|
37
|
+
return (
|
|
38
|
+
f"WARNING: Starting with version 0.65.0, libraries are now managed in your workspace directory. "
|
|
39
|
+
f"This library is located in {old_libraries_path} and will not receive updates because it is not tracked "
|
|
40
|
+
f"by the library manager. "
|
|
41
|
+
f"To migrate: run 'gtn init'. "
|
|
42
|
+
f"The library will continue to function normally until migrated."
|
|
43
|
+
)
|