kailash 0.6.6__py3-none-any.whl → 0.8.0__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 (82) hide show
  1. kailash/__init__.py +35 -5
  2. kailash/access_control.py +64 -46
  3. kailash/adapters/__init__.py +5 -0
  4. kailash/adapters/mcp_platform_adapter.py +273 -0
  5. kailash/api/workflow_api.py +34 -3
  6. kailash/channels/__init__.py +21 -0
  7. kailash/channels/api_channel.py +409 -0
  8. kailash/channels/base.py +271 -0
  9. kailash/channels/cli_channel.py +661 -0
  10. kailash/channels/event_router.py +496 -0
  11. kailash/channels/mcp_channel.py +648 -0
  12. kailash/channels/session.py +423 -0
  13. kailash/mcp_server/discovery.py +57 -18
  14. kailash/middleware/communication/api_gateway.py +23 -3
  15. kailash/middleware/communication/realtime.py +83 -0
  16. kailash/middleware/core/agent_ui.py +1 -1
  17. kailash/middleware/gateway/storage_backends.py +393 -0
  18. kailash/middleware/mcp/enhanced_server.py +22 -16
  19. kailash/nexus/__init__.py +21 -0
  20. kailash/nexus/cli/__init__.py +5 -0
  21. kailash/nexus/cli/__main__.py +6 -0
  22. kailash/nexus/cli/main.py +176 -0
  23. kailash/nexus/factory.py +413 -0
  24. kailash/nexus/gateway.py +545 -0
  25. kailash/nodes/__init__.py +8 -5
  26. kailash/nodes/ai/iterative_llm_agent.py +988 -17
  27. kailash/nodes/ai/llm_agent.py +29 -9
  28. kailash/nodes/api/__init__.py +2 -2
  29. kailash/nodes/api/monitoring.py +1 -1
  30. kailash/nodes/base.py +29 -5
  31. kailash/nodes/base_async.py +54 -14
  32. kailash/nodes/code/async_python.py +1 -1
  33. kailash/nodes/code/python.py +50 -6
  34. kailash/nodes/data/async_sql.py +90 -0
  35. kailash/nodes/data/bulk_operations.py +939 -0
  36. kailash/nodes/data/query_builder.py +373 -0
  37. kailash/nodes/data/query_cache.py +512 -0
  38. kailash/nodes/monitoring/__init__.py +10 -0
  39. kailash/nodes/monitoring/deadlock_detector.py +964 -0
  40. kailash/nodes/monitoring/performance_anomaly.py +1078 -0
  41. kailash/nodes/monitoring/race_condition_detector.py +1151 -0
  42. kailash/nodes/monitoring/transaction_metrics.py +790 -0
  43. kailash/nodes/monitoring/transaction_monitor.py +931 -0
  44. kailash/nodes/security/behavior_analysis.py +414 -0
  45. kailash/nodes/system/__init__.py +17 -0
  46. kailash/nodes/system/command_parser.py +820 -0
  47. kailash/nodes/transaction/__init__.py +48 -0
  48. kailash/nodes/transaction/distributed_transaction_manager.py +983 -0
  49. kailash/nodes/transaction/saga_coordinator.py +652 -0
  50. kailash/nodes/transaction/saga_state_storage.py +411 -0
  51. kailash/nodes/transaction/saga_step.py +467 -0
  52. kailash/nodes/transaction/transaction_context.py +756 -0
  53. kailash/nodes/transaction/two_phase_commit.py +978 -0
  54. kailash/nodes/transform/processors.py +17 -1
  55. kailash/nodes/validation/__init__.py +21 -0
  56. kailash/nodes/validation/test_executor.py +532 -0
  57. kailash/nodes/validation/validation_nodes.py +447 -0
  58. kailash/resources/factory.py +1 -1
  59. kailash/runtime/access_controlled.py +9 -7
  60. kailash/runtime/async_local.py +84 -21
  61. kailash/runtime/local.py +21 -2
  62. kailash/runtime/parameter_injector.py +187 -31
  63. kailash/runtime/runner.py +6 -4
  64. kailash/runtime/testing.py +1 -1
  65. kailash/security.py +22 -3
  66. kailash/servers/__init__.py +32 -0
  67. kailash/servers/durable_workflow_server.py +430 -0
  68. kailash/servers/enterprise_workflow_server.py +522 -0
  69. kailash/servers/gateway.py +183 -0
  70. kailash/servers/workflow_server.py +293 -0
  71. kailash/utils/data_validation.py +192 -0
  72. kailash/workflow/builder.py +382 -15
  73. kailash/workflow/cyclic_runner.py +102 -10
  74. kailash/workflow/validation.py +144 -8
  75. kailash/workflow/visualization.py +99 -27
  76. {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/METADATA +3 -2
  77. {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/RECORD +81 -40
  78. kailash/workflow/builder_improvements.py +0 -207
  79. {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/WHEEL +0 -0
  80. {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/entry_points.txt +0 -0
  81. {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/licenses/LICENSE +0 -0
  82. {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,661 @@
1
+ """CLI Channel implementation for interactive command-line interface."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import sys
6
+ from dataclasses import dataclass, field
7
+ from typing import Any, Callable, Dict, List, Optional, TextIO
8
+
9
+ from ..nodes.system.command_parser import (
10
+ CommandParserNode,
11
+ CommandRouterNode,
12
+ InteractiveShellNode,
13
+ ParsedCommand,
14
+ )
15
+ from ..runtime.local import LocalRuntime
16
+ from ..workflow.builder import WorkflowBuilder
17
+ from .base import (
18
+ Channel,
19
+ ChannelConfig,
20
+ ChannelEvent,
21
+ ChannelResponse,
22
+ ChannelStatus,
23
+ ChannelType,
24
+ )
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ @dataclass
30
+ class CLISession:
31
+ """Represents a CLI session."""
32
+
33
+ session_id: str
34
+ user_id: Optional[str] = None
35
+ shell_state: Dict[str, Any] = field(default_factory=dict)
36
+ command_history: List[str] = field(default_factory=list)
37
+ active: bool = True
38
+ last_command_time: Optional[float] = None
39
+
40
+
41
+ class CLIChannel(Channel):
42
+ """Command-line interface channel implementation.
43
+
44
+ This channel provides an interactive CLI interface for executing workflows
45
+ and managing the Kailash system through command-line commands.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ config: ChannelConfig,
51
+ input_stream: Optional[TextIO] = None,
52
+ output_stream: Optional[TextIO] = None,
53
+ ):
54
+ """Initialize CLI channel.
55
+
56
+ Args:
57
+ config: Channel configuration
58
+ input_stream: Input stream (defaults to sys.stdin)
59
+ output_stream: Output stream (defaults to sys.stdout)
60
+ """
61
+ super().__init__(config)
62
+
63
+ self.input_stream = input_stream or sys.stdin
64
+ self.output_stream = output_stream or sys.stdout
65
+
66
+ # CLI-specific components
67
+ self.command_parser = CommandParserNode()
68
+ self.shell_node = InteractiveShellNode()
69
+ self.router_node = CommandRouterNode()
70
+
71
+ # Session management
72
+ self._sessions: Dict[str, CLISession] = {}
73
+ self._current_session: Optional[CLISession] = None
74
+
75
+ # Command definitions and routing
76
+ self._command_definitions = self._setup_default_commands()
77
+ self._routing_config = self._setup_default_routing()
78
+
79
+ # Runtime for executing workflows
80
+ self.runtime = LocalRuntime()
81
+
82
+ # CLI state
83
+ self._running = False
84
+ self._main_task: Optional[asyncio.Task] = None
85
+
86
+ logger.info(f"Initialized CLI channel {self.name}")
87
+
88
+ def _setup_default_commands(self) -> Dict[str, Any]:
89
+ """Set up default command definitions."""
90
+ return {
91
+ "run": {
92
+ "type": "workflow",
93
+ "description": "Execute a workflow",
94
+ "arguments": {
95
+ "workflow": {
96
+ "flags": ["workflow", "--workflow", "-w"],
97
+ "type": str,
98
+ "required": True,
99
+ "help": "Name of the workflow to execute",
100
+ },
101
+ "input": {
102
+ "flags": ["--input", "-i"],
103
+ "type": str,
104
+ "required": False,
105
+ "help": "JSON string of input parameters",
106
+ },
107
+ "file": {
108
+ "flags": ["--file", "-f"],
109
+ "type": str,
110
+ "required": False,
111
+ "help": "File path containing input parameters",
112
+ },
113
+ },
114
+ },
115
+ "list": {
116
+ "type": "system",
117
+ "description": "List available items",
118
+ "subcommands": {
119
+ "workflows": {
120
+ "help": "List available workflows",
121
+ "arguments": {
122
+ "verbose": {
123
+ "flags": ["--verbose", "-v"],
124
+ "action": "store_true",
125
+ "help": "Show detailed information",
126
+ }
127
+ },
128
+ },
129
+ "sessions": {"help": "List active sessions", "arguments": {}},
130
+ },
131
+ },
132
+ "status": {
133
+ "type": "system",
134
+ "description": "Show system status",
135
+ "arguments": {
136
+ "verbose": {
137
+ "flags": ["--verbose", "-v"],
138
+ "action": "store_true",
139
+ "help": "Show detailed status",
140
+ }
141
+ },
142
+ },
143
+ "config": {
144
+ "type": "admin",
145
+ "description": "Manage configuration",
146
+ "subcommands": {
147
+ "show": {"help": "Show current configuration", "arguments": {}},
148
+ "set": {
149
+ "help": "Set configuration value",
150
+ "arguments": {
151
+ "key": {
152
+ "flags": ["key"],
153
+ "type": str,
154
+ "required": True,
155
+ "help": "Configuration key",
156
+ },
157
+ "value": {
158
+ "flags": ["value"],
159
+ "type": str,
160
+ "required": True,
161
+ "help": "Configuration value",
162
+ },
163
+ },
164
+ },
165
+ },
166
+ },
167
+ }
168
+
169
+ def _setup_default_routing(self) -> Dict[str, Any]:
170
+ """Set up default command routing configuration."""
171
+ return {
172
+ "run": {
173
+ "type": "workflow_executor",
174
+ "handler": "execute_workflow",
175
+ "description": "Execute workflow command",
176
+ },
177
+ "list:workflows": {
178
+ "type": "handler",
179
+ "handler": "list_workflows",
180
+ "description": "List available workflows",
181
+ },
182
+ "list:sessions": {
183
+ "type": "handler",
184
+ "handler": "list_sessions",
185
+ "description": "List active sessions",
186
+ },
187
+ "status": {
188
+ "type": "handler",
189
+ "handler": "show_status",
190
+ "description": "Show system status",
191
+ },
192
+ "config:show": {
193
+ "type": "handler",
194
+ "handler": "show_config",
195
+ "description": "Show configuration",
196
+ },
197
+ "config:set": {
198
+ "type": "handler",
199
+ "handler": "set_config",
200
+ "description": "Set configuration",
201
+ },
202
+ "help": {
203
+ "type": "handler",
204
+ "handler": "show_help",
205
+ "description": "Show help information",
206
+ },
207
+ "exit": {
208
+ "type": "handler",
209
+ "handler": "exit_cli",
210
+ "description": "Exit CLI",
211
+ },
212
+ "quit": {
213
+ "type": "handler",
214
+ "handler": "exit_cli",
215
+ "description": "Exit CLI",
216
+ },
217
+ "type:workflow": {
218
+ "type": "handler",
219
+ "handler": "handle_unknown_workflow",
220
+ "description": "Handle unknown workflow commands",
221
+ },
222
+ }
223
+
224
+ async def start(self) -> None:
225
+ """Start the CLI channel."""
226
+ if self.status == ChannelStatus.RUNNING:
227
+ logger.warning(f"CLI channel {self.name} is already running")
228
+ return
229
+
230
+ try:
231
+ self.status = ChannelStatus.STARTING
232
+ self._setup_event_queue()
233
+
234
+ # Create default session
235
+ default_session = CLISession(session_id="default", user_id="cli_user")
236
+ self._sessions["default"] = default_session
237
+ self._current_session = default_session
238
+
239
+ self._running = True
240
+
241
+ # Start main CLI loop
242
+ self._main_task = asyncio.create_task(self._cli_loop())
243
+
244
+ self.status = ChannelStatus.RUNNING
245
+
246
+ # Emit startup event
247
+ await self.emit_event(
248
+ ChannelEvent(
249
+ event_id=f"cli_startup_{asyncio.get_event_loop().time()}",
250
+ channel_name=self.name,
251
+ channel_type=self.channel_type,
252
+ event_type="channel_started",
253
+ payload={"session_count": len(self._sessions)},
254
+ )
255
+ )
256
+
257
+ logger.info(f"CLI channel {self.name} started")
258
+
259
+ except Exception as e:
260
+ self.status = ChannelStatus.ERROR
261
+ logger.error(f"Failed to start CLI channel {self.name}: {e}")
262
+ raise
263
+
264
+ async def stop(self) -> None:
265
+ """Stop the CLI channel."""
266
+ if self.status == ChannelStatus.STOPPED:
267
+ return
268
+
269
+ try:
270
+ self.status = ChannelStatus.STOPPING
271
+ self._running = False
272
+
273
+ # Emit shutdown event
274
+ await self.emit_event(
275
+ ChannelEvent(
276
+ event_id=f"cli_shutdown_{asyncio.get_event_loop().time()}",
277
+ channel_name=self.name,
278
+ channel_type=self.channel_type,
279
+ event_type="channel_stopping",
280
+ payload={},
281
+ )
282
+ )
283
+
284
+ # Cancel main task
285
+ if self._main_task and not self._main_task.done():
286
+ self._main_task.cancel()
287
+ try:
288
+ await self._main_task
289
+ except asyncio.CancelledError:
290
+ pass
291
+
292
+ await self._cleanup()
293
+ self.status = ChannelStatus.STOPPED
294
+
295
+ logger.info(f"CLI channel {self.name} stopped")
296
+
297
+ except Exception as e:
298
+ self.status = ChannelStatus.ERROR
299
+ logger.error(f"Error stopping CLI channel {self.name}: {e}")
300
+ raise
301
+
302
+ async def handle_request(self, request: Dict[str, Any]) -> ChannelResponse:
303
+ """Handle a CLI request.
304
+
305
+ Args:
306
+ request: Request data containing command information
307
+
308
+ Returns:
309
+ ChannelResponse with command execution results
310
+ """
311
+ try:
312
+ command_input = request.get("command", "")
313
+ session_id = request.get("session_id", "default")
314
+
315
+ # Get or create session
316
+ session = self._sessions.get(session_id)
317
+ if not session:
318
+ session = CLISession(session_id=session_id)
319
+ self._sessions[session_id] = session
320
+
321
+ # Process command through parsing pipeline
322
+ result = await self._process_command(command_input, session)
323
+
324
+ return ChannelResponse(
325
+ success=True,
326
+ data=result,
327
+ metadata={"channel": self.name, "session_id": session_id},
328
+ )
329
+
330
+ except Exception as e:
331
+ logger.error(f"Error handling CLI request: {e}")
332
+ return ChannelResponse(
333
+ success=False, error=str(e), metadata={"channel": self.name}
334
+ )
335
+
336
+ async def _cli_loop(self) -> None:
337
+ """Main CLI interaction loop."""
338
+ self._write_output("Kailash CLI started. Type 'help' for available commands.\n")
339
+
340
+ while self._running:
341
+ try:
342
+ # Generate prompt
343
+ prompt = await self._generate_prompt()
344
+ self._write_output(prompt)
345
+
346
+ # Read command (this would be blocking in real implementation)
347
+ # For now, we'll simulate with a small delay
348
+ await asyncio.sleep(0.1)
349
+
350
+ # In a real implementation, this would read from input_stream
351
+ # For testing/simulation purposes, we'll break here
352
+ if not self.config.extra_config.get("interactive_mode", False):
353
+ break
354
+
355
+ except asyncio.CancelledError:
356
+ break
357
+ except Exception as e:
358
+ logger.error(f"Error in CLI loop: {e}")
359
+ self._write_output(f"Error: {e}\n")
360
+
361
+ async def _generate_prompt(self) -> str:
362
+ """Generate CLI prompt."""
363
+ if not self._current_session:
364
+ return "kailash> "
365
+
366
+ # Use shell node to generate prompt
367
+ shell_result = self.shell_node.execute(
368
+ session_id=self._current_session.session_id,
369
+ command_input="", # Empty for prompt generation
370
+ session_state=self._current_session.shell_state,
371
+ prompt_template=self.config.extra_config.get(
372
+ "prompt_template", "kailash> "
373
+ ),
374
+ )
375
+
376
+ return shell_result.get("prompt", "kailash> ")
377
+
378
+ async def _process_command(
379
+ self, command_input: str, session: CLISession
380
+ ) -> Dict[str, Any]:
381
+ """Process a command through the parsing and routing pipeline.
382
+
383
+ Args:
384
+ command_input: Raw command input
385
+ session: CLI session
386
+
387
+ Returns:
388
+ Command processing results
389
+ """
390
+ try:
391
+ # Update session
392
+ session.command_history.append(command_input)
393
+ session.last_command_time = asyncio.get_event_loop().time()
394
+
395
+ # Parse command
396
+ parse_result = self.command_parser.execute(
397
+ command_input=command_input,
398
+ command_definitions=self._command_definitions,
399
+ allow_unknown_commands=True,
400
+ default_command_type="workflow",
401
+ )
402
+
403
+ if not parse_result["success"]:
404
+ return {
405
+ "type": "error",
406
+ "message": parse_result.get("error", "Failed to parse command"),
407
+ "command": command_input,
408
+ }
409
+
410
+ # Process through shell node
411
+ shell_result = self.shell_node.execute(
412
+ session_id=session.session_id,
413
+ command_input=command_input,
414
+ session_state=session.shell_state,
415
+ )
416
+
417
+ # Check if shell handled the command
418
+ if shell_result["shell_result"]["type"] != "passthrough":
419
+ return {
420
+ "type": "shell_command",
421
+ "result": shell_result["shell_result"],
422
+ "session_state": shell_result["session_state"],
423
+ }
424
+
425
+ # Route command
426
+ router_result = self.router_node.execute(
427
+ parsed_command=parse_result["parsed_command"],
428
+ routing_config=self._routing_config,
429
+ default_handler="help",
430
+ )
431
+
432
+ if not router_result["success"]:
433
+ return {
434
+ "type": "error",
435
+ "message": router_result.get("error", "Failed to route command"),
436
+ "command": command_input,
437
+ }
438
+
439
+ # Execute routed command
440
+ execution_result = await self._execute_routed_command(
441
+ router_result["routing_target"],
442
+ router_result["execution_params"],
443
+ session,
444
+ )
445
+
446
+ return {
447
+ "type": "command_execution",
448
+ "parse_result": parse_result,
449
+ "routing_result": router_result,
450
+ "execution_result": execution_result,
451
+ "session_id": session.session_id,
452
+ }
453
+
454
+ except Exception as e:
455
+ logger.error(f"Error processing command: {e}")
456
+ return {"type": "error", "message": str(e), "command": command_input}
457
+
458
+ async def _execute_routed_command(
459
+ self,
460
+ routing_target: Dict[str, Any],
461
+ execution_params: Dict[str, Any],
462
+ session: CLISession,
463
+ ) -> Dict[str, Any]:
464
+ """Execute a routed command.
465
+
466
+ Args:
467
+ routing_target: Routing target information
468
+ execution_params: Execution parameters
469
+ session: CLI session
470
+
471
+ Returns:
472
+ Execution results
473
+ """
474
+ handler_name = routing_target.get("handler")
475
+ target_type = routing_target.get("type")
476
+
477
+ try:
478
+ if target_type == "workflow_executor":
479
+ return await self._execute_workflow_command(execution_params)
480
+ elif target_type == "handler":
481
+ return await self._execute_handler_command(
482
+ handler_name, execution_params, session
483
+ )
484
+ else:
485
+ return {
486
+ "success": False,
487
+ "error": f"Unknown target type: {target_type}",
488
+ }
489
+
490
+ except Exception as e:
491
+ logger.error(f"Error executing routed command: {e}")
492
+ return {"success": False, "error": str(e)}
493
+
494
+ async def _execute_workflow_command(
495
+ self, execution_params: Dict[str, Any]
496
+ ) -> Dict[str, Any]:
497
+ """Execute a workflow command.
498
+
499
+ Args:
500
+ execution_params: Execution parameters
501
+
502
+ Returns:
503
+ Workflow execution results
504
+ """
505
+ # This would integrate with the workflow execution system
506
+ # For now, return a placeholder
507
+ return {
508
+ "success": True,
509
+ "message": "Workflow execution not yet implemented in CLI channel",
510
+ "params": execution_params,
511
+ }
512
+
513
+ async def _execute_handler_command(
514
+ self, handler_name: str, execution_params: Dict[str, Any], session: CLISession
515
+ ) -> Dict[str, Any]:
516
+ """Execute a handler command.
517
+
518
+ Args:
519
+ handler_name: Name of the handler
520
+ execution_params: Execution parameters
521
+ session: CLI session
522
+
523
+ Returns:
524
+ Handler execution results
525
+ """
526
+ if handler_name == "show_help":
527
+ return await self._handle_help(execution_params)
528
+ elif handler_name == "show_status":
529
+ return await self._handle_status(execution_params)
530
+ elif handler_name == "list_workflows":
531
+ return await self._handle_list_workflows(execution_params)
532
+ elif handler_name == "list_sessions":
533
+ return await self._handle_list_sessions(execution_params)
534
+ elif handler_name == "exit_cli":
535
+ return await self._handle_exit(execution_params, session)
536
+ else:
537
+ return {"success": False, "error": f"Unknown handler: {handler_name}"}
538
+
539
+ async def _handle_help(self, params: Dict[str, Any]) -> Dict[str, Any]:
540
+ """Handle help command."""
541
+ topic = params.get("command_arguments", {}).get("topic")
542
+
543
+ if topic and topic in self._command_definitions:
544
+ cmd_def = self._command_definitions[topic]
545
+ help_text = f"{topic}: {cmd_def.get('description', 'No description')}\n"
546
+
547
+ # Add argument information
548
+ arguments = cmd_def.get("arguments", {})
549
+ if arguments:
550
+ help_text += "Arguments:\n"
551
+ for arg_name, arg_config in arguments.items():
552
+ flags = ", ".join(arg_config.get("flags", [f"--{arg_name}"]))
553
+ help_desc = arg_config.get("help", "No description")
554
+ help_text += f" {flags}: {help_desc}\n"
555
+
556
+ # Add subcommands
557
+ subcommands = cmd_def.get("subcommands", {})
558
+ if subcommands:
559
+ help_text += "Subcommands:\n"
560
+ for sub_name, sub_config in subcommands.items():
561
+ help_desc = sub_config.get("help", "No description")
562
+ help_text += f" {sub_name}: {help_desc}\n"
563
+ else:
564
+ # General help
565
+ help_text = "Available commands:\n"
566
+ for cmd_name, cmd_def in self._command_definitions.items():
567
+ description = cmd_def.get("description", "No description")
568
+ help_text += f" {cmd_name}: {description}\n"
569
+
570
+ help_text += "\nType 'help <command>' for detailed information about a specific command.\n"
571
+
572
+ return {"success": True, "message": help_text.strip()}
573
+
574
+ async def _handle_status(self, params: Dict[str, Any]) -> Dict[str, Any]:
575
+ """Handle status command."""
576
+ verbose = params.get("command_arguments", {}).get("verbose", False)
577
+
578
+ status_info = {
579
+ "channel": self.name,
580
+ "status": self.status.value,
581
+ "sessions": len(self._sessions),
582
+ "active_sessions": len([s for s in self._sessions.values() if s.active]),
583
+ }
584
+
585
+ if verbose:
586
+ status_info.update(await self.get_status())
587
+
588
+ return {"success": True, "data": status_info}
589
+
590
+ async def _handle_list_workflows(self, params: Dict[str, Any]) -> Dict[str, Any]:
591
+ """Handle list workflows command."""
592
+ # This would integrate with workflow registry
593
+ return {
594
+ "success": True,
595
+ "message": "Workflow listing not yet implemented",
596
+ "workflows": [],
597
+ }
598
+
599
+ async def _handle_list_sessions(self, params: Dict[str, Any]) -> Dict[str, Any]:
600
+ """Handle list sessions command."""
601
+ sessions_info = []
602
+ for session_id, session in self._sessions.items():
603
+ sessions_info.append(
604
+ {
605
+ "session_id": session_id,
606
+ "user_id": session.user_id,
607
+ "active": session.active,
608
+ "command_count": len(session.command_history),
609
+ "last_command_time": session.last_command_time,
610
+ }
611
+ )
612
+
613
+ return {"success": True, "data": {"sessions": sessions_info}}
614
+
615
+ async def _handle_exit(
616
+ self, params: Dict[str, Any], session: CLISession
617
+ ) -> Dict[str, Any]:
618
+ """Handle exit command."""
619
+ force = params.get("command_arguments", {}).get("force", False)
620
+
621
+ if force or len(self._sessions) == 1:
622
+ # Stop the entire CLI
623
+ asyncio.create_task(self.stop())
624
+ return {"success": True, "message": "Exiting CLI..."}
625
+ else:
626
+ # Just deactivate current session
627
+ session.active = False
628
+ return {
629
+ "success": True,
630
+ "message": f"Session {session.session_id} deactivated",
631
+ }
632
+
633
+ def _write_output(self, text: str) -> None:
634
+ """Write text to output stream."""
635
+ if self.output_stream:
636
+ self.output_stream.write(text)
637
+ self.output_stream.flush()
638
+
639
+ async def health_check(self) -> Dict[str, Any]:
640
+ """Perform comprehensive health check."""
641
+ base_health = await super().health_check()
642
+
643
+ # Add CLI-specific health checks
644
+ cli_checks = {
645
+ "sessions_active": len([s for s in self._sessions.values() if s.active])
646
+ > 0,
647
+ "command_parser_ready": self.command_parser is not None,
648
+ "routing_configured": len(self._routing_config) > 0,
649
+ "runtime_available": self.runtime is not None,
650
+ }
651
+
652
+ all_healthy = base_health["healthy"] and all(cli_checks.values())
653
+
654
+ return {
655
+ **base_health,
656
+ "healthy": all_healthy,
657
+ "checks": {**base_health["checks"], **cli_checks},
658
+ "sessions": len(self._sessions),
659
+ "active_sessions": len([s for s in self._sessions.values() if s.active]),
660
+ "commands_available": len(self._command_definitions),
661
+ }