dbagent-cli 0.9.9__tar.gz → 0.10.0__tar.gz

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 (87) hide show
  1. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/PKG-INFO +1 -1
  2. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/__init__.py +1 -1
  3. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/cli.py +49 -15
  4. dbagent_cli-0.10.0/dbagent/execution/prompt_queue.py +152 -0
  5. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent_cli.egg-info/PKG-INFO +1 -1
  6. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent_cli.egg-info/SOURCES.txt +2 -0
  7. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/pyproject.toml +1 -1
  8. dbagent_cli-0.10.0/tests/test_prompt_queue.py +80 -0
  9. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/README.md +0 -0
  10. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/agent/confidence.py +0 -0
  11. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/agent/conversation.py +0 -0
  12. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/agent/doctor.py +0 -0
  13. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/agent/error_classifier.py +0 -0
  14. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/agent/generator.py +0 -0
  15. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/agent/pipeline.py +0 -0
  16. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/agent/recommender.py +0 -0
  17. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/agent/validator.py +0 -0
  18. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/config.py +0 -0
  19. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/connectors/base.py +0 -0
  20. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/connectors/factory.py +0 -0
  21. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/connectors/mongo.py +0 -0
  22. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/connectors/relational.py +0 -0
  23. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/execution/__init__.py +0 -0
  24. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/execution/cache.py +0 -0
  25. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/execution/changeset.py +0 -0
  26. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/execution/cost.py +0 -0
  27. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/execution/recovery.py +0 -0
  28. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/execution/sandbox.py +0 -0
  29. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/execution/streaming.py +0 -0
  30. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/anthropic_provider.py +0 -0
  31. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/auto_setup.py +0 -0
  32. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/base.py +0 -0
  33. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/deepseek_provider.py +0 -0
  34. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/factory.py +0 -0
  35. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/gemini_provider.py +0 -0
  36. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/groq_provider.py +0 -0
  37. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/mistral_provider.py +0 -0
  38. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/mock_provider.py +0 -0
  39. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/ollama_provider.py +0 -0
  40. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/openai_provider.py +0 -0
  41. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/llm/openrouter_provider.py +0 -0
  42. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/schema/cache.py +0 -0
  43. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/schema/formatter.py +0 -0
  44. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/schema/graph.py +0 -0
  45. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/schema/ground_truth.py +0 -0
  46. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/schema/models.py +0 -0
  47. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/schema/retriever.py +0 -0
  48. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/schema/selector.py +0 -0
  49. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/security/__init__.py +0 -0
  50. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/security/audit.py +0 -0
  51. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/security/environment.py +0 -0
  52. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/security/masking.py +0 -0
  53. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/security/modes.py +0 -0
  54. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/security/rbac.py +0 -0
  55. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/semantic/__init__.py +0 -0
  56. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/semantic/dictionary.py +0 -0
  57. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/semantic/knowledge.py +0 -0
  58. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/semantic/templates.py +0 -0
  59. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/sql/__init__.py +0 -0
  60. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/sql/optimizer.py +0 -0
  61. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/sql/validator.py +0 -0
  62. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/ui/chat_screen.py +0 -0
  63. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/ui/console.py +0 -0
  64. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/ui/grid_window.py +0 -0
  65. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/ui/live_streamer.py +0 -0
  66. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/ui/viewer.py +0 -0
  67. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent/ui/visualizer.py +0 -0
  68. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent_cli.egg-info/dependency_links.txt +0 -0
  69. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent_cli.egg-info/entry_points.txt +0 -0
  70. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent_cli.egg-info/requires.txt +0 -0
  71. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/dbagent_cli.egg-info/top_level.txt +0 -0
  72. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/setup.cfg +0 -0
  73. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_cache.py +0 -0
  74. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_cli.py +0 -0
  75. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_connectors.py +0 -0
  76. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_direct_sql.py +0 -0
  77. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_doctor_and_viz.py +0 -0
  78. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_generator.py +0 -0
  79. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_graph.py +0 -0
  80. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_knowledge.py +0 -0
  81. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_providers.py +0 -0
  82. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_recovery.py +0 -0
  83. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_safety.py +0 -0
  84. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_sandbox.py +0 -0
  85. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_schema.py +0 -0
  86. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_security.py +0 -0
  87. {dbagent_cli-0.9.9 → dbagent_cli-0.10.0}/tests/test_semantic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.9.9
3
+ Version: 0.10.0
4
4
  Summary: Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)
5
5
  Author: Santhosh Gupta
6
6
  Requires-Python: >=3.9
@@ -2,5 +2,5 @@
2
2
  DB-Agent: Universal Database Introspection and Script Generation AI Agent (CLI).
3
3
  """
4
4
 
5
- __version__ = "0.9.9"
5
+ __version__ = "0.10.0"
6
6
  __author__ = "Santhosh Gupta"
@@ -617,7 +617,7 @@ def chat_command(
617
617
  ":help", ":tables", ":schema", ":dt", ":table", ":fk", ":path", ":join",
618
618
  ":doctor", ":mode", ":changes", ":provider", ":model", ":models",
619
619
  ":page", ":card", ":wide", ":scroll", ":window", ":grid", ":web",
620
- ":explain", ":history", ":auto", ":noauto", ":run", ":clear", ":export",
620
+ ":explain", ":history", ":queue", ":auto", ":noauto", ":run", ":clear", ":export",
621
621
  ":exit", ":quit", ":q",
622
622
  ], ignore_case=True)
623
623
  chat_style = Style.from_dict({
@@ -638,6 +638,9 @@ def chat_command(
638
638
  query_history: List[PipelineResult] = []
639
639
  last_result: Optional[PipelineResult] = None
640
640
 
641
+ from dbagent.execution.prompt_queue import PromptQueueManager
642
+ prompt_queue = PromptQueueManager(console=console)
643
+
641
644
  # Create pipeline ONCE and reuse across all queries (avoids re-init overhead)
642
645
  pipeline = QueryPipeline(
643
646
  connector=connector,
@@ -654,26 +657,37 @@ def chat_command(
654
657
  curr_model = model or getattr(llm, "default_model", "default")
655
658
  width = min(console.width or 100, 120)
656
659
  rule = "─" * (width - 1)
660
+ queue_count = prompt_queue.count()
661
+ queue_badge = f" │ <style class='bottom-toolbar.cyan'>📥 Queued: {queue_count}</style>" if queue_count > 0 else ""
657
662
  return HTML(
658
663
  f"<ansidarkgray>{rule}</ansidarkgray>\n"
659
664
  f" <style class='bottom-toolbar.green'>● Connected:</style> <b>{db_name}</b> <i>({dialect} • {len(table_names)} tables)</i> │ "
660
665
  f"<style class='bottom-toolbar.cyan'>🤖 AI:</style> <b>{curr_model}</b> │ "
661
- f"<style class='bottom-toolbar.yellow'>🛡️ {current_mode.value}</style> │ "
666
+ f"<style class='bottom-toolbar.yellow'>🛡️ {current_mode.value}</style>{queue_badge} │ "
662
667
  f"<b>:help</b> for commands · <b>:models</b> to switch"
663
668
  )
664
669
 
665
670
  while True:
666
671
  try:
667
- width = min(console.width or 100, 120)
668
- console.print(f"[dim cyan]{'─' * (width - 1)}[/dim cyan]")
669
- user_input = session.prompt(
670
- HTML("<ansicyan><b>❯</b></ansicyan> "),
671
- bottom_toolbar=_get_bottom_toolbar,
672
- rprompt=HTML(f"<ansidarkgray>db-agent v{__version__}</ansidarkgray>"),
673
- placeholder=HTML("<ansidarkgray><i>Ask in plain English or paste direct SQL...</i></ansidarkgray>"),
674
- ).strip()
675
- if not user_input:
676
- continue
672
+ if prompt_queue.has_next():
673
+ user_input = prompt_queue.pop_next()
674
+ rem = prompt_queue.count()
675
+ rem_str = f" [dim cyan]({rem} more in queue)[/dim cyan]" if rem > 0 else ""
676
+ console.print(f"\n[bold cyan]❯[/bold cyan] [bold white]{user_input}[/bold white] [bold yellow](Executing queued prompt)[/bold yellow]{rem_str}")
677
+ else:
678
+ width = min(console.width or 100, 120)
679
+ console.print(f"[dim cyan]{'─' * (width - 1)}[/dim cyan]")
680
+ user_input = session.prompt(
681
+ HTML("<ansicyan><b>❯</b></ansicyan> "),
682
+ bottom_toolbar=_get_bottom_toolbar,
683
+ rprompt=HTML(f"<ansidarkgray>db-agent v{__version__}</ansidarkgray>"),
684
+ placeholder=HTML("<ansidarkgray><i>Ask in plain English or paste direct SQL...</i></ansidarkgray>"),
685
+ ).strip()
686
+ if not user_input:
687
+ continue
688
+
689
+ # Split chained prompts if user entered multiple commands like "prompt 1 ; prompt 2"
690
+ user_input = prompt_queue.split_and_enqueue_chained(user_input)
677
691
 
678
692
  # --- REPL commands (all start with :) ---
679
693
  if user_input.startswith(":"):
@@ -703,6 +717,7 @@ def chat_command(
703
717
  cmd_table.add_row(":forget <id|topic>", "Remove a learned rule from persistent memory")
704
718
  cmd_table.add_row(":mode <name>", "Switch safety mode (SAFE, CONFIRM, ADMIN, READ_ONLY)")
705
719
  cmd_table.add_row(":history", "View session query history, execution timing & row counts")
720
+ cmd_table.add_row(":queue, :q-list", "View queued in-line prompts (:queue clear to reset)")
706
721
  cmd_table.add_row(":page <n>, :card", "Inspect wide multi-column query results")
707
722
  cmd_table.add_row(":window, :web", "Open interactive desktop or browser data grid")
708
723
  cmd_table.add_row(":explain", "Explain last generated SQL query with execution plan")
@@ -1007,6 +1022,21 @@ def chat_command(
1007
1022
  print_warning("No previous query to explain.")
1008
1023
  continue
1009
1024
 
1025
+ if cmd_name in [":queue", ":q-list"]:
1026
+ if cmd_arg.lower() in ["clear", "cls", "reset"]:
1027
+ cleared_count = prompt_queue.clear()
1028
+ print_success(f"Cleared {cleared_count} queued prompt(s).")
1029
+ else:
1030
+ queued_items = prompt_queue.list_all()
1031
+ if not queued_items:
1032
+ console.print("[dim]No queued prompts in line. Chain prompts with ';' or type while streaming.[/dim]\n")
1033
+ else:
1034
+ console.print(f"\n[bold cyan]📥 Pending Prompts in Line ({len(queued_items)}):[/bold cyan]")
1035
+ for idx, q_item in enumerate(queued_items, 1):
1036
+ console.print(f" [{idx}] [bold white]{q_item}[/bold white]")
1037
+ console.print("\n[dim]To clear queue: [bold]:queue clear[/bold][/dim]\n")
1038
+ continue
1039
+
1010
1040
  if cmd_name == ":history":
1011
1041
  render_session_history(query_history)
1012
1042
  continue
@@ -1115,7 +1145,7 @@ def chat_command(
1115
1145
  console.print("[bold cyan]Goodbye![/bold cyan]")
1116
1146
  break
1117
1147
 
1118
- # --- Natural language query via pipeline with live streaming ---
1148
+ # --- Natural language query via pipeline with live streaming & background queue listener ---
1119
1149
  pipeline.model = model
1120
1150
  pipeline.mode = current_mode
1121
1151
  pipeline.auto_execute = auto_execute
@@ -1135,8 +1165,12 @@ def chat_command(
1135
1165
  console.print(f"\n[bold cyan]● DB-Agent{model_display}[/bold cyan] [dim]{timing_str}[/dim]")
1136
1166
  streamer.feed(chunk)
1137
1167
 
1138
- with spinner:
1139
- result = pipeline.run(user_prompt=user_input, stream_callback=_live_stream_callback)
1168
+ prompt_queue.start_background_listener()
1169
+ try:
1170
+ with spinner:
1171
+ result = pipeline.run(user_prompt=user_input, stream_callback=_live_stream_callback)
1172
+ finally:
1173
+ prompt_queue.stop_background_listener()
1140
1174
 
1141
1175
  if stream_started[0]:
1142
1176
  streamer.finalize()
@@ -0,0 +1,152 @@
1
+ """
2
+ In-Line Prompt Queue Manager for DB-Agent CLI.
3
+ Allows users to queue follow-up prompts while a query is running or stream is active,
4
+ and supports multi-prompt chaining using ';' or '&&'.
5
+ """
6
+
7
+ import collections
8
+ import os
9
+ import re
10
+ import sys
11
+ import threading
12
+ import time
13
+ from typing import List, Optional, Callable
14
+ from rich.console import Console
15
+
16
+
17
+ class PromptQueueManager:
18
+ """
19
+ Thread-safe FIFO prompt queue with cross-platform background input listener.
20
+ Supports asynchronous queuing during active execution and multi-prompt chaining.
21
+ """
22
+
23
+ def __init__(self, console: Optional[Console] = None):
24
+ self.console = console or Console()
25
+ self._queue = collections.deque()
26
+ self._lock = threading.Lock()
27
+ self._listener_thread: Optional[threading.Thread] = None
28
+ self._stop_listener = threading.Event()
29
+ self._current_line: List[str] = []
30
+
31
+ def enqueue(self, prompt: str, notify: bool = True) -> None:
32
+ """Add a prompt to the back of the queue."""
33
+ p = prompt.strip()
34
+ if not p:
35
+ return
36
+ with self._lock:
37
+ self._queue.append(p)
38
+ if notify:
39
+ self.console.print(f"\n[bold yellow]📥 Queued next in line:[/bold yellow] [bold white]\"{p}\"[/bold white]")
40
+
41
+ def has_next(self) -> bool:
42
+ """Check if there are any queued prompts waiting."""
43
+ with self._lock:
44
+ return len(self._queue) > 0
45
+
46
+ def pop_next(self) -> Optional[str]:
47
+ """Pop the next prompt from the front of the queue."""
48
+ with self._lock:
49
+ if self._queue:
50
+ return self._queue.popleft()
51
+ return None
52
+
53
+ def peek_next(self) -> Optional[str]:
54
+ """Peek at the next prompt without removing it."""
55
+ with self._lock:
56
+ if self._queue:
57
+ return self._queue[0]
58
+ return None
59
+
60
+ def list_all(self) -> List[str]:
61
+ """List all pending queued prompts."""
62
+ with self._lock:
63
+ return list(self._queue)
64
+
65
+ def count(self) -> int:
66
+ """Return the number of queued prompts."""
67
+ with self._lock:
68
+ return len(self._queue)
69
+
70
+ def clear(self) -> int:
71
+ """Clear all pending prompts from the queue and return count cleared."""
72
+ with self._lock:
73
+ cleared = len(self._queue)
74
+ self._queue.clear()
75
+ return cleared
76
+
77
+ def split_and_enqueue_chained(self, raw_input: str) -> str:
78
+ """
79
+ Split prompts separated by ';' or '&&' if not a raw SQL statement or inside quotes.
80
+ Returns the first prompt to execute immediately and enqueues the rest.
81
+ """
82
+ raw_clean = raw_input.strip()
83
+ if not raw_clean:
84
+ return raw_clean
85
+
86
+ # Do not split direct SQL statements like "SELECT 1; SELECT 2;"
87
+ if raw_clean.lower().startswith(("select ", "insert ", "update ", "delete ", "create ", "alter ", "drop ", "with ")):
88
+ return raw_clean
89
+
90
+ # Do not split special REPL commands
91
+ if raw_clean.startswith(":"):
92
+ return raw_clean
93
+
94
+ # Split by ';' or '&&'
95
+ delimiter_pattern = r'\s*(?:;|&&)\s*'
96
+ parts = [p.strip() for p in re.split(delimiter_pattern, raw_clean) if p.strip()]
97
+
98
+ if len(parts) > 1:
99
+ first_prompt = parts[0]
100
+ for remaining_prompt in parts[1:]:
101
+ self.enqueue(remaining_prompt, notify=True)
102
+ return first_prompt
103
+
104
+ return raw_clean
105
+
106
+ def start_background_listener(self) -> None:
107
+ """Start listening for keyboard input in the background while a task/stream runs."""
108
+ self._stop_listener.clear()
109
+ self._current_line = []
110
+ self._listener_thread = threading.Thread(target=self._listen_loop, daemon=True)
111
+ self._listener_thread.start()
112
+
113
+ def stop_background_listener(self) -> None:
114
+ """Stop the background listener thread."""
115
+ self._stop_listener.set()
116
+ if self._listener_thread:
117
+ self._listener_thread.join(timeout=0.15)
118
+ self._listener_thread = None
119
+
120
+ def _listen_loop(self) -> None:
121
+ """Cross-platform non-blocking input listener loop."""
122
+ is_windows = os.name == "nt"
123
+ if is_windows:
124
+ try:
125
+ import msvcrt
126
+ while not self._stop_listener.is_set():
127
+ if msvcrt.kbhit():
128
+ ch = msvcrt.getwch()
129
+ if ch in ("\r", "\n"):
130
+ line = "".join(self._current_line).strip()
131
+ self._current_line = []
132
+ if line:
133
+ self.enqueue(line, notify=True)
134
+ elif ch == "\b":
135
+ if self._current_line:
136
+ self._current_line.pop()
137
+ elif ord(ch) >= 32:
138
+ self._current_line.append(ch)
139
+ time.sleep(0.02)
140
+ except Exception:
141
+ pass
142
+ else:
143
+ try:
144
+ import select
145
+ while not self._stop_listener.is_set():
146
+ r, _, _ = select.select([sys.stdin], [], [], 0.05)
147
+ if r:
148
+ line = sys.stdin.readline().strip()
149
+ if line:
150
+ self.enqueue(line, notify=True)
151
+ except Exception:
152
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.9.9
3
+ Version: 0.10.0
4
4
  Summary: Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)
5
5
  Author: Santhosh Gupta
6
6
  Requires-Python: >=3.9
@@ -19,6 +19,7 @@ dbagent/execution/__init__.py
19
19
  dbagent/execution/cache.py
20
20
  dbagent/execution/changeset.py
21
21
  dbagent/execution/cost.py
22
+ dbagent/execution/prompt_queue.py
22
23
  dbagent/execution/recovery.py
23
24
  dbagent/execution/sandbox.py
24
25
  dbagent/execution/streaming.py
@@ -74,6 +75,7 @@ tests/test_doctor_and_viz.py
74
75
  tests/test_generator.py
75
76
  tests/test_graph.py
76
77
  tests/test_knowledge.py
78
+ tests/test_prompt_queue.py
77
79
  tests/test_providers.py
78
80
  tests/test_recovery.py
79
81
  tests/test_safety.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dbagent-cli"
7
- version = "0.9.9"
7
+ version = "0.10.0"
8
8
  description = "Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,80 @@
1
+ """
2
+ Unit tests for In-Line Prompt Queue Manager.
3
+ """
4
+
5
+ import pytest
6
+ from dbagent.execution.prompt_queue import PromptQueueManager
7
+
8
+
9
+ def test_prompt_queue_fifo():
10
+ qm = PromptQueueManager()
11
+ assert qm.count() == 0
12
+ assert not qm.has_next()
13
+
14
+ qm.enqueue("first prompt", notify=False)
15
+ qm.enqueue("second prompt", notify=False)
16
+
17
+ assert qm.count() == 2
18
+ assert qm.has_next()
19
+ assert qm.peek_next() == "first prompt"
20
+ assert qm.pop_next() == "first prompt"
21
+ assert qm.count() == 1
22
+ assert qm.pop_next() == "second prompt"
23
+ assert qm.count() == 0
24
+ assert not qm.has_next()
25
+
26
+
27
+ def test_split_and_enqueue_chained():
28
+ qm = PromptQueueManager()
29
+ raw = "how ivis_user and ivis_site were linked ; give the flow diagram ; count total users"
30
+ first = qm.split_and_enqueue_chained(raw)
31
+
32
+ assert first == "how ivis_user and ivis_site were linked"
33
+ assert qm.count() == 2
34
+ assert qm.list_all() == ["give the flow diagram", "count total users"]
35
+
36
+
37
+ def test_split_and_enqueue_double_ampersand():
38
+ qm = PromptQueueManager()
39
+ raw = "show active tenants && list users in tenant 1"
40
+ first = qm.split_and_enqueue_chained(raw)
41
+
42
+ assert first == "show active tenants"
43
+ assert qm.count() == 1
44
+ assert qm.pop_next() == "list users in tenant 1"
45
+
46
+
47
+ def test_do_not_split_sql():
48
+ qm = PromptQueueManager()
49
+ sql_prompt = "SELECT * FROM users WHERE id = 1; SELECT * FROM sites;"
50
+ res = qm.split_and_enqueue_chained(sql_prompt)
51
+ assert res == sql_prompt
52
+ assert qm.count() == 0
53
+
54
+
55
+ def test_do_not_split_repl_commands():
56
+ qm = PromptQueueManager()
57
+ cmd = ":learn tenant_rule = all users belong to tenants"
58
+ res = qm.split_and_enqueue_chained(cmd)
59
+ assert res == cmd
60
+ assert qm.count() == 0
61
+
62
+
63
+ def test_prompt_queue_clear():
64
+ qm = PromptQueueManager()
65
+ qm.enqueue("item 1", notify=False)
66
+ qm.enqueue("item 2", notify=False)
67
+ assert qm.count() == 2
68
+
69
+ cleared = qm.clear()
70
+ assert cleared == 2
71
+ assert qm.count() == 0
72
+ assert not qm.has_next()
73
+
74
+
75
+ def test_background_listener_lifecycle():
76
+ qm = PromptQueueManager()
77
+ qm.start_background_listener()
78
+ assert qm._listener_thread is not None
79
+ qm.stop_background_listener()
80
+ assert qm._listener_thread is None
File without changes
File without changes