griptape-nodes 0.61.0__py3-none-any.whl → 0.62.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.
Files changed (26) hide show
  1. griptape_nodes/common/macro_parser/core.py +4 -4
  2. griptape_nodes/common/macro_parser/exceptions.py +3 -3
  3. griptape_nodes/common/macro_parser/resolution.py +2 -2
  4. griptape_nodes/common/project_templates/default_project_template.py +5 -10
  5. griptape_nodes/common/project_templates/directory.py +5 -5
  6. griptape_nodes/common/project_templates/loader.py +8 -7
  7. griptape_nodes/common/project_templates/project.py +1 -1
  8. griptape_nodes/common/project_templates/situation.py +5 -17
  9. griptape_nodes/common/project_templates/validation.py +3 -3
  10. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +2 -2
  11. griptape_nodes/drivers/storage/local_storage_driver.py +2 -2
  12. griptape_nodes/node_library/workflow_registry.py +1 -1
  13. griptape_nodes/retained_mode/events/project_events.py +208 -93
  14. griptape_nodes/retained_mode/managers/event_manager.py +24 -9
  15. griptape_nodes/retained_mode/managers/library_manager.py +12 -21
  16. griptape_nodes/retained_mode/managers/os_manager.py +54 -6
  17. griptape_nodes/retained_mode/managers/project_manager.py +709 -259
  18. griptape_nodes/retained_mode/managers/static_files_manager.py +1 -5
  19. griptape_nodes/retained_mode/managers/sync_manager.py +4 -1
  20. griptape_nodes/retained_mode/managers/workflow_manager.py +2 -10
  21. griptape_nodes/traits/button.py +2 -1
  22. {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.1.dist-info}/METADATA +1 -1
  23. {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.1.dist-info}/RECORD +25 -26
  24. {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.1.dist-info}/WHEEL +1 -1
  25. griptape_nodes/common/project_templates/defaults/project_template.yml +0 -89
  26. {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.1.dist-info}/entry_points.txt +0 -0
@@ -1,12 +1,11 @@
1
1
  """Events for project template management."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from dataclasses import dataclass
4
6
  from enum import StrEnum
5
- from pathlib import Path
6
- from typing import Any
7
+ from typing import TYPE_CHECKING, Any
7
8
 
8
- from griptape_nodes.common.macro_parser import MacroMatchFailure, MacroParseFailure, VariableInfo
9
- from griptape_nodes.common.project_templates import ProjectTemplate, ProjectValidationInfo
10
9
  from griptape_nodes.retained_mode.events.base_events import (
11
10
  RequestPayload,
12
11
  ResultPayloadFailure,
@@ -15,6 +14,13 @@ from griptape_nodes.retained_mode.events.base_events import (
15
14
  )
16
15
  from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
17
16
 
17
+ if TYPE_CHECKING:
18
+ from pathlib import Path
19
+
20
+ from griptape_nodes.common.macro_parser import MacroMatchFailure, ParsedMacro, VariableInfo
21
+ from griptape_nodes.common.project_templates import ProjectTemplate, ProjectValidationInfo, SituationTemplate
22
+ from griptape_nodes.retained_mode.managers.project_manager import ProjectID, ProjectInfo
23
+
18
24
  # Type alias for macro variable dictionaries (used by ParsedMacro)
19
25
  MacroVariables = dict[str, str | int]
20
26
 
@@ -49,12 +55,12 @@ class LoadProjectTemplateResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuc
49
55
  """Project template loaded successfully.
50
56
 
51
57
  Args:
52
- project_path: Path to the loaded project.yml
58
+ project_id: The identifier for the loaded project
53
59
  template: The merged ProjectTemplate (system defaults + user customizations)
54
60
  validation: Validation info with status and any problems encountered
55
61
  """
56
62
 
57
- project_path: Path
63
+ project_id: ProjectID
58
64
  template: ProjectTemplate
59
65
  validation: ProjectValidationInfo
60
66
 
@@ -65,28 +71,26 @@ class LoadProjectTemplateResultFailure(WorkflowNotAlteredMixin, ResultPayloadFai
65
71
  """Project template loading failed.
66
72
 
67
73
  Args:
68
- project_path: Path to the project.yml that failed to load
69
74
  validation: Validation info with error details
70
75
  """
71
76
 
72
- project_path: Path
73
77
  validation: ProjectValidationInfo
74
78
 
75
79
 
76
80
  @dataclass
77
81
  @PayloadRegistry.register
78
82
  class GetProjectTemplateRequest(RequestPayload):
79
- """Get cached project template for a workspace path.
83
+ """Get cached project template for a project ID.
80
84
 
81
85
  Use when: Querying current project configuration, checking validation status.
82
86
 
83
87
  Args:
84
- project_path: Path to the project.yml file
88
+ project_id: Identifier of the project
85
89
 
86
90
  Results: GetProjectTemplateResultSuccess | GetProjectTemplateResultFailure
87
91
  """
88
92
 
89
- project_path: Path
93
+ project_id: ProjectID
90
94
 
91
95
 
92
96
  @dataclass
@@ -109,40 +113,80 @@ class GetProjectTemplateResultFailure(WorkflowNotAlteredMixin, ResultPayloadFail
109
113
  """Project template retrieval failed (not loaded yet)."""
110
114
 
111
115
 
116
+ @dataclass
117
+ class ProjectTemplateInfo:
118
+ """Information about a loaded or failed project template."""
119
+
120
+ project_id: ProjectID
121
+ validation: ProjectValidationInfo
122
+
123
+
124
+ @dataclass
125
+ @PayloadRegistry.register
126
+ class ListProjectTemplatesRequest(RequestPayload):
127
+ """List all project templates that have been loaded or attempted to load.
128
+
129
+ Use when: Displaying available projects, checking which projects are loaded.
130
+
131
+ Args:
132
+ include_system_builtins: Whether to include system builtin templates like SYSTEM_DEFAULTS_KEY
133
+
134
+ Results: ListProjectTemplatesResultSuccess
135
+ """
136
+
137
+ include_system_builtins: bool = False
138
+
139
+
140
+ @dataclass
141
+ @PayloadRegistry.register
142
+ class ListProjectTemplatesResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
143
+ """List of all project templates retrieved.
144
+
145
+ Args:
146
+ successfully_loaded: List of templates that loaded successfully
147
+ failed_to_load: List of templates that failed to load with validation errors
148
+ """
149
+
150
+ successfully_loaded: list[ProjectTemplateInfo]
151
+ failed_to_load: list[ProjectTemplateInfo]
152
+
153
+
112
154
  @dataclass
113
155
  @PayloadRegistry.register
114
- class GetMacroForSituationRequest(RequestPayload):
115
- """Get the macro schema for a specific situation.
156
+ class GetSituationRequest(RequestPayload):
157
+ """Get the full situation template for a specific situation.
158
+
159
+ Returns the complete SituationTemplate including macro and policy.
116
160
 
117
- Use when: Need to know what variables a situation requires, or get schema for custom resolution.
161
+ Use when: Need situation macro and/or policy for file operations.
162
+ Uses the current project for context.
118
163
 
119
164
  Args:
120
- project_path: Path to the project.yml to use
121
165
  situation_name: Name of the situation template (e.g., "save_node_output")
122
166
 
123
- Results: GetMacroForSituationResultSuccess | GetMacroForSituationResultFailure
167
+ Results: GetSituationResultSuccess | GetSituationResultFailure
124
168
  """
125
169
 
126
- project_path: Path
127
170
  situation_name: str
128
171
 
129
172
 
130
173
  @dataclass
131
174
  @PayloadRegistry.register
132
- class GetMacroForSituationResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
133
- """Situation macro retrieved successfully.
175
+ class GetSituationResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
176
+ """Situation template retrieved successfully.
134
177
 
135
178
  Args:
136
- macro_schema: The macro template string (e.g., "{inputs}/{file_name}.{file_ext}")
179
+ situation: The complete situation template including macro and policy.
180
+ Access via situation.macro, situation.policy.create_dirs, etc.
137
181
  """
138
182
 
139
- macro_schema: str
183
+ situation: SituationTemplate
140
184
 
141
185
 
142
186
  @dataclass
143
187
  @PayloadRegistry.register
144
- class GetMacroForSituationResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
145
- """Situation macro retrieval failed (situation not found or template not loaded)."""
188
+ class GetSituationResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
189
+ """Situation template retrieval failed (situation not found or template not loaded)."""
146
190
 
147
191
 
148
192
  @dataclass
@@ -152,16 +196,17 @@ class GetPathForMacroRequest(RequestPayload):
152
196
 
153
197
  Use when: Resolving paths, saving files. Works with any macro string, not tied to situations.
154
198
 
199
+ Uses the current project for context. Caller must parse the macro string
200
+ into a ParsedMacro before creating this request.
201
+
155
202
  Args:
156
- project_path: Path to the project.yml (used for directory resolution)
157
- macro_schema: The macro template string to resolve (e.g., "{inputs}/{file_name}.{file_ext}")
203
+ parsed_macro: The parsed macro to resolve
158
204
  variables: Variable values for macro substitution (e.g., {"file_name": "output", "file_ext": "png"})
159
205
 
160
206
  Results: GetPathForMacroResultSuccess | GetPathForMacroResultFailure
161
207
  """
162
208
 
163
- project_path: Path
164
- macro_schema: str
209
+ parsed_macro: ParsedMacro
165
210
  variables: MacroVariables
166
211
 
167
212
 
@@ -171,10 +216,12 @@ class GetPathForMacroResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess
171
216
  """Path resolved successfully from macro.
172
217
 
173
218
  Args:
174
- resolved_path: The final Path after macro substitution
219
+ resolved_path: The relative project path after macro substitution (e.g., "outputs/file.png")
220
+ absolute_path: The absolute filesystem path (e.g., "/workspace/outputs/file.png")
175
221
  """
176
222
 
177
223
  resolved_path: Path
224
+ absolute_path: Path
178
225
 
179
226
 
180
227
  @dataclass
@@ -186,29 +233,27 @@ class GetPathForMacroResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure
186
233
  failure_reason: Specific reason for failure
187
234
  missing_variables: List of required variable names that were not provided (for MISSING_REQUIRED_VARIABLES)
188
235
  conflicting_variables: List of variables that conflict with directory names (for DIRECTORY_OVERRIDE_ATTEMPTED)
189
- error_details: Additional error details from ParsedMacro (for MACRO_RESOLUTION_ERROR)
190
236
  """
191
237
 
192
238
  failure_reason: PathResolutionFailureReason
193
- missing_variables: list[str] | None = None
194
- conflicting_variables: list[str] | None = None
195
- error_details: str | None = None
239
+ missing_variables: set[str] | None = None
240
+ conflicting_variables: set[str] | None = None
196
241
 
197
242
 
198
243
  @dataclass
199
244
  @PayloadRegistry.register
200
245
  class SetCurrentProjectRequest(RequestPayload):
201
- """Set which project.yml user has currently selected.
246
+ """Set which project user has currently selected.
202
247
 
203
248
  Use when: User switches between projects, opens a new workspace.
204
249
 
205
250
  Args:
206
- project_path: Path to the project.yml to set as current (None to clear)
251
+ project_id: Identifier of the project to set as current (None to clear)
207
252
 
208
253
  Results: SetCurrentProjectResultSuccess
209
254
  """
210
255
 
211
- project_path: Path | None
256
+ project_id: ProjectID | None
212
257
 
213
258
 
214
259
  @dataclass
@@ -224,7 +269,7 @@ class GetCurrentProjectRequest(RequestPayload):
224
269
 
225
270
  Use when: Need to know which project user is working with.
226
271
 
227
- Results: GetCurrentProjectResultSuccess
272
+ Results: GetCurrentProjectResultSuccess | GetCurrentProjectResultFailure
228
273
  """
229
274
 
230
275
 
@@ -234,10 +279,16 @@ class GetCurrentProjectResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSucce
234
279
  """Current project retrieved.
235
280
 
236
281
  Args:
237
- project_path: The currently selected project path ("No Project" if None)
282
+ project_info: Complete information about the current project
238
283
  """
239
284
 
240
- project_path: Path | None
285
+ project_info: ProjectInfo
286
+
287
+
288
+ @dataclass
289
+ @PayloadRegistry.register
290
+ class GetCurrentProjectResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
291
+ """No current project is set."""
241
292
 
242
293
 
243
294
  @dataclass
@@ -261,13 +312,7 @@ class SaveProjectTemplateRequest(RequestPayload):
261
312
  @dataclass
262
313
  @PayloadRegistry.register
263
314
  class SaveProjectTemplateResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
264
- """Project template saved successfully.
265
-
266
- Args:
267
- project_path: Path where project.yml was saved
268
- """
269
-
270
- project_path: Path
315
+ """Project template saved successfully."""
271
316
 
272
317
 
273
318
  @dataclass
@@ -281,122 +326,192 @@ class SaveProjectTemplateResultFailure(WorkflowNotAlteredMixin, ResultPayloadFai
281
326
  - Disk full
282
327
  """
283
328
 
284
- project_path: Path
285
-
286
329
 
287
330
  @dataclass
288
331
  @PayloadRegistry.register
289
- class MatchPathAgainstMacroRequest(RequestPayload):
290
- """Check if a path matches a macro schema and extract variables.
332
+ class AttemptMatchPathAgainstMacroRequest(RequestPayload):
333
+ """Attempt to match a path against a macro schema and extract variables.
291
334
 
292
335
  Use when: Validating paths, extracting info from file paths,
293
336
  identifying which schema produced a file.
294
337
 
338
+ Uses the current project for context. Caller must parse the macro string
339
+ into a ParsedMacro before creating this request.
340
+
341
+ Pattern non-matches are returned as success with match_failure populated.
342
+ Only true system errors (missing SecretsManager, etc.) return failure.
343
+
295
344
  Args:
296
- project_path: Path to project.yml (for directory resolution)
297
- macro_schema: Macro template string
298
- file_path: Path to test
345
+ parsed_macro: Parsed macro template to match against
346
+ file_path: Path string to test
299
347
  known_variables: Variables we already know
300
348
 
301
- Results: MatchPathAgainstMacroResultSuccess | MatchPathAgainstMacroResultFailure
349
+ Results: AttemptMatchPathAgainstMacroResultSuccess | AttemptMatchPathAgainstMacroResultFailure
302
350
  """
303
351
 
304
- project_path: Path
305
- macro_schema: str
352
+ parsed_macro: ParsedMacro
306
353
  file_path: str
307
354
  known_variables: MacroVariables
308
355
 
309
356
 
310
357
  @dataclass
311
358
  @PayloadRegistry.register
312
- class MatchPathAgainstMacroResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
313
- """Path matched the macro schema."""
359
+ class AttemptMatchPathAgainstMacroResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
360
+ """Attempt completed (match succeeded or pattern didn't match).
314
361
 
315
- extracted_variables: MacroVariables
362
+ Check match_failure to determine outcome:
363
+ - match_failure is None: Pattern matched, extracted_variables contains results
364
+ - match_failure is not None: Pattern didn't match (normal case, not an error)
365
+ """
366
+
367
+ extracted_variables: MacroVariables | None
368
+ match_failure: MacroMatchFailure | None
316
369
 
317
370
 
318
371
  @dataclass
319
372
  @PayloadRegistry.register
320
- class MatchPathAgainstMacroResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
321
- """Path did not match the macro schema."""
322
-
323
- match_failure: MacroMatchFailure
373
+ class AttemptMatchPathAgainstMacroResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
374
+ """System error occurred (missing SecretsManager, invalid configuration, etc.)."""
324
375
 
325
376
 
326
377
  @dataclass
327
378
  @PayloadRegistry.register
328
- class GetVariablesForMacroRequest(RequestPayload):
329
- """Get list of all variables in a macro schema.
379
+ class GetStateForMacroRequest(RequestPayload):
380
+ """Analyze a macro and return comprehensive state information.
330
381
 
331
- Use when: Building UI forms, showing what variables a schema needs,
332
- validating before resolution.
382
+ Use when: Building UI forms, real-time validation, checking if resolution
383
+ would succeed before actually resolving.
384
+
385
+ Uses the current project for context. Caller must parse the macro string
386
+ into a ParsedMacro before creating this request.
333
387
 
334
388
  Args:
335
- macro_schema: Macro template string to inspect
389
+ parsed_macro: The parsed macro to analyze
390
+ variables: Currently provided variable values
336
391
 
337
- Results: GetVariablesForMacroResultSuccess | GetVariablesForMacroResultFailure
392
+ Results: GetStateForMacroResultSuccess | GetStateForMacroResultFailure
338
393
  """
339
394
 
340
- macro_schema: str
395
+ parsed_macro: ParsedMacro
396
+ variables: MacroVariables
341
397
 
342
398
 
343
399
  @dataclass
344
400
  @PayloadRegistry.register
345
- class GetVariablesForMacroResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
346
- """Variables found in the macro schema."""
401
+ class GetStateForMacroResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
402
+ """Macro state analysis completed successfully.
403
+
404
+ Args:
405
+ all_variables: All variables found in the macro
406
+ satisfied_variables: Variables that have values (from user, directories, or builtins)
407
+ missing_required_variables: Required variables that are missing values
408
+ conflicting_variables: Variables that conflict (e.g., user overriding builtin with different value)
409
+ can_resolve: Whether the macro can be fully resolved (no missing required vars, no conflicts)
410
+ """
347
411
 
348
- variables: list[VariableInfo]
412
+ all_variables: set[VariableInfo]
413
+ satisfied_variables: set[str]
414
+ missing_required_variables: set[str]
415
+ conflicting_variables: set[str]
416
+ can_resolve: bool
349
417
 
350
418
 
351
419
  @dataclass
352
420
  @PayloadRegistry.register
353
- class GetVariablesForMacroResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
354
- """Failed to parse macro schema."""
421
+ class GetStateForMacroResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
422
+ """Macro state analysis failed.
355
423
 
356
- parse_failure: MacroParseFailure
424
+ Failure occurs when:
425
+ - No current project is set
426
+ - Current project template is not loaded
427
+ - A builtin variable cannot be resolved (RuntimeError or NotImplementedError)
428
+ """
357
429
 
358
430
 
359
431
  @dataclass
360
432
  @PayloadRegistry.register
361
- class ValidateMacroSyntaxRequest(RequestPayload):
362
- """Validate a macro schema string for syntax errors.
433
+ class AttemptMapAbsolutePathToProjectRequest(RequestPayload):
434
+ """Find out if an absolute path exists anywhere within a Project directory.
435
+
436
+ Use when: User selects or types an absolute path via FilePicker and you need to know:
437
+ 1. Is this path inside any project directory?
438
+ 2. If yes, what's the macro form (e.g., {outputs}/file.png)?
363
439
 
364
- Use when: User editing schemas in UI, before saving templates,
365
- real-time validation.
440
+ This enables automatic conversion of absolute paths to portable macro form for workflow portability.
441
+
442
+ Uses longest prefix matching to find the most specific directory match.
443
+ Returns Success with mapped_path if inside project, or Success with None if outside.
444
+ Returns Failure if operation cannot be performed (no project loaded, secrets unavailable).
366
445
 
367
446
  Args:
368
- macro_schema: Schema string to validate
447
+ absolute_path: The absolute filesystem path to check
448
+
449
+ Results: AttemptMapAbsolutePathToProjectResultSuccess | AttemptMapAbsolutePathToProjectResultFailure
369
450
 
370
- Results: ValidateMacroSyntaxResultSuccess | ValidateMacroSyntaxResultFailure
451
+ Examples:
452
+ Path inside project directory:
453
+ Request: absolute_path = /Users/james/project/outputs/renders/image.png
454
+ Result: mapped_path = "{outputs}/renders/image.png"
455
+
456
+ Path outside project:
457
+ Request: absolute_path = /Users/james/Downloads/image.png
458
+ Result: mapped_path = None
459
+
460
+ Path at directory root:
461
+ Request: absolute_path = /Users/james/project/outputs
462
+ Result: mapped_path = "{outputs}"
371
463
  """
372
464
 
373
- macro_schema: str
465
+ absolute_path: Path
374
466
 
375
467
 
376
468
  @dataclass
377
469
  @PayloadRegistry.register
378
- class ValidateMacroSyntaxResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
379
- """Syntax is valid."""
470
+ class AttemptMapAbsolutePathToProjectResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
471
+ """Path check completed successfully.
472
+
473
+ Success means the check was performed (not necessarily that a match was found).
474
+ - mapped_path is NOT None: Path is inside a project directory (macro form returned)
475
+ - mapped_path is None: Path is outside all project directories (valid answer)
476
+
477
+ Args:
478
+ mapped_path: The macro form if path is inside a project directory (e.g., "{outputs}/file.png"),
479
+ or None if path is outside all project directories
480
+
481
+ Examples:
482
+ Path inside project:
483
+ mapped_path = "{outputs}/renders/image.png"
484
+ result_details = "Successfully mapped absolute path to '{outputs}/renders/image.png'"
380
485
 
381
- variables: list[VariableInfo]
382
- warnings: list[str]
486
+ Path outside project:
487
+ mapped_path = None
488
+ result_details = "Attempted to map absolute path '/Users/james/Downloads/image.png'. Path is outside all project directories"
489
+ """
490
+
491
+ mapped_path: str | None
383
492
 
384
493
 
385
494
  @dataclass
386
495
  @PayloadRegistry.register
387
- class ValidateMacroSyntaxResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
388
- """Syntax is invalid."""
496
+ class AttemptMapAbsolutePathToProjectResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
497
+ """Path mapping attempt failed.
389
498
 
390
- parse_failure: MacroParseFailure
391
- partial_variables: list[VariableInfo]
499
+ Returned when the operation cannot be performed (no current project, secrets manager unavailable).
500
+ This is distinct from "path is outside project" which returns Success with None values.
501
+
502
+ Examples:
503
+ No current project:
504
+ result_details = "Attempted to map absolute path. Failed because no current project is set"
505
+
506
+ Secrets manager unavailable:
507
+ result_details = "Attempted to map absolute path. Failed because SecretsManager not available"
508
+ """
392
509
 
393
510
 
394
511
  @dataclass
395
512
  @PayloadRegistry.register
396
513
  class GetAllSituationsForProjectRequest(RequestPayload):
397
- """Get all situation names and schemas from a project template."""
398
-
399
- project_path: Path
514
+ """Get all situation names and schemas from current project template."""
400
515
 
401
516
 
402
517
  @dataclass
@@ -98,6 +98,19 @@ class EventManager:
98
98
  self._event_loop = None
99
99
  self._loop_thread_id = None
100
100
 
101
+ def _is_cross_thread_call(self) -> bool:
102
+ """Check if the current call is from a different thread than the event loop.
103
+
104
+ Returns:
105
+ True if we're on a different thread and need thread-safe operations
106
+ """
107
+ current_thread_id = threading.get_ident()
108
+ return (
109
+ self._loop_thread_id is not None
110
+ and current_thread_id != self._loop_thread_id
111
+ and self._event_loop is not None
112
+ )
113
+
101
114
  def put_event(self, event: Any) -> None:
102
115
  """Put event into async queue from sync context (non-blocking).
103
116
 
@@ -109,15 +122,9 @@ class EventManager:
109
122
  if self._event_queue is None:
110
123
  return
111
124
 
112
- # Check if we need thread-safe operation
113
- current_thread_id = threading.get_ident()
114
-
115
- if (
116
- self._loop_thread_id is not None
117
- and current_thread_id != self._loop_thread_id
118
- and self._event_loop is not None
119
- ):
125
+ if self._is_cross_thread_call() and self._event_loop is not None:
120
126
  # We're in a different thread from the event loop, use thread-safe method
127
+ # _is_cross_thread_call() guarantees _event_loop is not None
121
128
  self._event_loop.call_soon_threadsafe(self._event_queue.put_nowait, event)
122
129
  else:
123
130
  # We're on the same thread as the event loop or no loop thread tracked, use direct method
@@ -126,13 +133,21 @@ class EventManager:
126
133
  async def aput_event(self, event: Any) -> None:
127
134
  """Put event into async queue from async context.
128
135
 
136
+ Automatically detects if we're in a different thread and uses thread-safe operations.
137
+
129
138
  Args:
130
139
  event: The event to publish to the queue
131
140
  """
132
141
  if self._event_queue is None:
133
142
  return
134
143
 
135
- await self._event_queue.put(event)
144
+ if self._is_cross_thread_call() and self._event_loop is not None:
145
+ # We're in a different thread from the event loop, use thread-safe method
146
+ # _is_cross_thread_call() guarantees _event_loop is not None
147
+ self._event_loop.call_soon_threadsafe(self._event_queue.put_nowait, event)
148
+ else:
149
+ # We're on the same thread as the event loop or no loop thread tracked, use async method
150
+ await self._event_queue.put(event)
136
151
 
137
152
  def assign_manager_to_request_type(
138
153
  self,
@@ -1639,6 +1639,13 @@ class LibraryManager:
1639
1639
  user_libraries_section = "app_events.on_app_initialization_complete.libraries_to_register"
1640
1640
  libraries_to_register: list[str] = config_mgr.get_config_value(user_libraries_section)
1641
1641
 
1642
+ # Filter out empty or whitespace-only entries
1643
+ original_count = len(libraries_to_register) if libraries_to_register else 0
1644
+ libraries_to_register = [path for path in (libraries_to_register or []) if path and path.strip()]
1645
+ filtered_count = original_count - len(libraries_to_register)
1646
+ if filtered_count > 0:
1647
+ logger.warning("Filtered out %d empty library path entries from configuration", filtered_count)
1648
+
1642
1649
  if not libraries_to_register:
1643
1650
  logger.info("No libraries to register from config")
1644
1651
  return
@@ -1986,24 +1993,6 @@ class LibraryManager:
1986
1993
  ret_val.append(file_path)
1987
1994
  return ret_val
1988
1995
 
1989
- def _load_libraries_from_config_category(self, config_category: str, *, load_as_default_library: bool) -> None:
1990
- config_mgr = GriptapeNodes.ConfigManager()
1991
- libraries_to_register_category: list[str] = config_mgr.get_config_value(config_category)
1992
-
1993
- if libraries_to_register_category is not None:
1994
- for library_to_register in libraries_to_register_category:
1995
- if library_to_register:
1996
- if library_to_register.endswith(".json"):
1997
- library_load_request = RegisterLibraryFromFileRequest(
1998
- file_path=library_to_register,
1999
- load_as_default_library=load_as_default_library,
2000
- )
2001
- else:
2002
- library_load_request = RegisterLibraryFromRequirementSpecifierRequest(
2003
- requirement_specifier=library_to_register
2004
- )
2005
- GriptapeNodes.handle_request(library_load_request)
2006
-
2007
1996
  def _remove_missing_libraries_from_config(self, config_category: str) -> None:
2008
1997
  # Now remove all libraries that were missing from the user's config.
2009
1998
  config_mgr = GriptapeNodes.ConfigManager()
@@ -2076,8 +2065,10 @@ class LibraryManager:
2076
2065
  # Add from config
2077
2066
  config_libraries = config_mgr.get_config_value(user_libraries_section, default=[])
2078
2067
  for library_path_str in config_libraries:
2079
- library_path = Path(library_path_str)
2080
- if library_path.exists():
2081
- process_path(library_path)
2068
+ # Filter out falsy values that will resolve to current directory
2069
+ if library_path_str:
2070
+ library_path = Path(library_path_str)
2071
+ if library_path.exists():
2072
+ process_path(library_path)
2082
2073
 
2083
2074
  return list(discovered_libraries)