daveloop 1.5.0__py3-none-any.whl → 1.5.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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: daveloop
3
- Version: 1.5.0
3
+ Version: 1.5.1
4
4
  Summary: Self-healing debug agent powered by Claude Code CLI
5
5
  Home-page: https://github.com/davebruzil/DaveLoop
6
6
  Author: Dave Bruzil
@@ -19,6 +19,17 @@ Classifier: Topic :: Software Development :: Debuggers
19
19
  Classifier: Topic :: Software Development :: Quality Assurance
20
20
  Requires-Python: >=3.7
21
21
  Description-Content-Type: text/markdown
22
+ Provides-Extra: turbo
23
+ Requires-Dist: rich>=13.0; extra == "turbo"
24
+ Dynamic: author
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: keywords
30
+ Dynamic: provides-extra
31
+ Dynamic: requires-python
32
+ Dynamic: summary
22
33
 
23
34
  # DaveLoop
24
35
 
@@ -0,0 +1,7 @@
1
+ daveloop.py,sha256=SrIlZMZpRgYw8dPEequNoWbZ2hMyip0wLMNFVzVskcQ,85885
2
+ daveloop_swebench.py,sha256=iD9AU3XRiMQpt7TknFNlvnmPCNp64V-JaTfqTFgsGBM,15996
3
+ daveloop-1.5.1.dist-info/METADATA,sha256=c6DPGhLDRu5ODmf7iu2bSyMsqCQDk0SdOVjUQKrxRxQ,13113
4
+ daveloop-1.5.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
5
+ daveloop-1.5.1.dist-info/entry_points.txt,sha256=QcFAZgFrDfPtIikNQb7eW9DxOpBK7T-qWrKqbGAS9Ww,86
6
+ daveloop-1.5.1.dist-info/top_level.txt,sha256=36DiYt70m4DIK8t7IhV_y6hAzUIyeb5-qDUf3-gbDdg,27
7
+ daveloop-1.5.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.46.3)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
daveloop.py CHANGED
@@ -14,6 +14,8 @@ import itertools
14
14
  import json
15
15
  from datetime import datetime
16
16
  from pathlib import Path
17
+ from collections import deque
18
+ from concurrent.futures import ThreadPoolExecutor, as_completed
17
19
 
18
20
  # Configuration
19
21
  MAX_ITERATIONS = 20
@@ -318,6 +320,386 @@ class SwarmBudget:
318
320
  }
319
321
 
320
322
 
323
+ # ============================================================================
324
+ # Turbo Dashboard (Rich-based split-pane UI)
325
+ # ============================================================================
326
+ class TurboDashboard:
327
+ """Rich-based split-pane terminal dashboard for parallel DaveLoop instances.
328
+
329
+ Shows one panel per instance with: instance ID, current task name,
330
+ and a scrolling window of the last ~20 lines of output.
331
+ """
332
+
333
+ PANEL_COLORS = ["cyan", "green", "yellow", "magenta", "blue", "red"]
334
+
335
+ def __init__(self, instance_count: int):
336
+ from rich.live import Live
337
+ from rich.layout import Layout
338
+ from rich.panel import Panel
339
+ from rich.text import Text
340
+ from rich.console import Console
341
+
342
+ self._Live = Live
343
+ self._Layout = Layout
344
+ self._Panel = Panel
345
+ self._Text = Text
346
+ self._console = Console()
347
+
348
+ self.instance_count = instance_count
349
+ self._lock = threading.Lock()
350
+
351
+ # Per-instance state
352
+ self.task_names = ["Waiting..."] * instance_count
353
+ self.statuses = ["pending"] * instance_count # pending, running, done, failed
354
+ self.output_buffers = [deque(maxlen=20) for _ in range(instance_count)]
355
+ self.iterations = [0] * instance_count
356
+ self.max_iterations = [0] * instance_count
357
+ self.start_times = [None] * instance_count
358
+
359
+ self._live = None
360
+
361
+ def _build_layout(self):
362
+ """Build the Rich Layout with panels for each instance."""
363
+ layout = self._Layout()
364
+
365
+ # Create rows of 2 panels each
366
+ rows = []
367
+ for i in range(0, self.instance_count, 2):
368
+ row_name = f"row_{i // 2}"
369
+ row = self._Layout(name=row_name)
370
+ left = self._Layout(name=f"inst_{i}")
371
+
372
+ if i + 1 < self.instance_count:
373
+ right = self._Layout(name=f"inst_{i + 1}")
374
+ row.split_row(left, right)
375
+ else:
376
+ row.split_row(left)
377
+
378
+ rows.append(row)
379
+
380
+ if rows:
381
+ layout.split_column(*rows)
382
+
383
+ return layout
384
+
385
+ def _render_panel(self, idx: int):
386
+ """Render a single instance panel."""
387
+ color = self.PANEL_COLORS[idx % len(self.PANEL_COLORS)]
388
+ status = self.statuses[idx]
389
+
390
+ # Status indicator
391
+ if status == "running":
392
+ status_icon = "[bold bright_green]\u25cf RUNNING[/]"
393
+ elif status == "done":
394
+ status_icon = "[bold bright_green]\u2713 RESOLVED[/]"
395
+ elif status == "failed":
396
+ status_icon = "[bold bright_red]\u2717 FAILED[/]"
397
+ else:
398
+ status_icon = "[dim]\u25cb PENDING[/]"
399
+
400
+ # Iteration info
401
+ iter_info = ""
402
+ if self.max_iterations[idx] > 0:
403
+ iter_info = f" | Iter {self.iterations[idx]}/{self.max_iterations[idx]}"
404
+
405
+ # Elapsed time
406
+ elapsed = ""
407
+ if self.start_times[idx]:
408
+ secs = int(time.time() - self.start_times[idx])
409
+ mins, secs = divmod(secs, 60)
410
+ elapsed = f" | {mins}m{secs:02d}s"
411
+
412
+ # Build subtitle
413
+ subtitle = f"{status_icon}{iter_info}{elapsed}"
414
+
415
+ # Build output text
416
+ lines = list(self.output_buffers[idx])
417
+ if not lines:
418
+ lines = ["[dim]Waiting for output...[/]"]
419
+
420
+ output_text = "\n".join(lines)
421
+
422
+ task_name = self.task_names[idx]
423
+ if len(task_name) > 50:
424
+ task_name = task_name[:47] + "..."
425
+
426
+ panel = self._Panel(
427
+ output_text,
428
+ title=f"[bold {color}] Instance {idx + 1} [/] {task_name}",
429
+ subtitle=subtitle,
430
+ border_style=color if status == "running" else "dim" if status == "pending" else "green" if status == "done" else "red",
431
+ padding=(0, 1),
432
+ )
433
+ return panel
434
+
435
+ def _refresh(self):
436
+ """Rebuild and update the live display."""
437
+ layout = self._build_layout()
438
+ for i in range(self.instance_count):
439
+ try:
440
+ layout[f"inst_{i}"].update(self._render_panel(i))
441
+ except KeyError:
442
+ pass
443
+ return layout
444
+
445
+ def start(self):
446
+ """Start the live dashboard."""
447
+ self._live = self._Live(
448
+ self._refresh(),
449
+ console=self._console,
450
+ refresh_per_second=4,
451
+ screen=True,
452
+ )
453
+ self._live.start()
454
+
455
+ def stop(self):
456
+ """Stop the live dashboard."""
457
+ if self._live:
458
+ self._live.stop()
459
+
460
+ def update_task(self, idx: int, task_name: str, max_iterations: int):
461
+ """Set the task name and max iterations for an instance."""
462
+ with self._lock:
463
+ self.task_names[idx] = task_name
464
+ self.max_iterations[idx] = max_iterations
465
+ self.statuses[idx] = "running"
466
+ self.start_times[idx] = time.time()
467
+ if self._live:
468
+ self._live.update(self._refresh())
469
+
470
+ def update_iteration(self, idx: int, iteration: int):
471
+ """Update the current iteration number for an instance."""
472
+ with self._lock:
473
+ self.iterations[idx] = iteration
474
+ if self._live:
475
+ self._live.update(self._refresh())
476
+
477
+ def append_output(self, idx: int, line: str):
478
+ """Append a line to an instance's output buffer."""
479
+ with self._lock:
480
+ # Strip ANSI codes for cleaner display in Rich panels
481
+ clean = _strip_ansi(line)
482
+ if clean.strip():
483
+ self.output_buffers[idx].append(clean)
484
+ if self._live:
485
+ self._live.update(self._refresh())
486
+
487
+ def mark_done(self, idx: int, outcome: str = "done"):
488
+ """Mark an instance as completed."""
489
+ with self._lock:
490
+ self.statuses[idx] = outcome
491
+ if self._live:
492
+ self._live.update(self._refresh())
493
+
494
+
495
+ def _strip_ansi(text: str) -> str:
496
+ """Remove ANSI escape sequences from a string."""
497
+ import re
498
+ return re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', text)
499
+
500
+
501
+ def run_claude_code_turbo(prompt: str, working_dir: str, instance_idx: int,
502
+ dashboard: TurboDashboard, timeout: int = DEFAULT_TIMEOUT,
503
+ swarm_mode: bool = False, swarm_budget_max: int = 5,
504
+ swarm_depth_max: int = 1) -> str:
505
+ """Execute Claude Code CLI for turbo mode, streaming output to a dashboard panel.
506
+
507
+ Similar to run_claude_code but routes output to a TurboDashboard panel
508
+ instead of printing directly to stdout.
509
+ """
510
+ claude_cmd = find_claude_cli()
511
+ if not claude_cmd:
512
+ return "[DAVELOOP:ERROR] Claude CLI not found"
513
+
514
+ cmd = [claude_cmd]
515
+ allowed = ALLOWED_TOOLS_SWARM if swarm_mode else ALLOWED_TOOLS_DEFAULT
516
+ cmd.extend(["-p", "--verbose", "--output-format", "stream-json", "--allowedTools", allowed])
517
+
518
+ try:
519
+ process = subprocess.Popen(
520
+ cmd,
521
+ stdin=subprocess.PIPE,
522
+ stdout=subprocess.PIPE,
523
+ stderr=subprocess.STDOUT,
524
+ text=True,
525
+ encoding='utf-8',
526
+ errors='replace',
527
+ cwd=working_dir,
528
+ bufsize=1
529
+ )
530
+
531
+ process.stdin.write(prompt)
532
+ process.stdin.close()
533
+
534
+ full_text = []
535
+
536
+ for line in process.stdout:
537
+ line = line.strip()
538
+ if not line:
539
+ continue
540
+
541
+ try:
542
+ data = json.loads(line)
543
+ msg_type = data.get("type", "")
544
+
545
+ if msg_type == "assistant":
546
+ content = data.get("message", {}).get("content", [])
547
+ for block in content:
548
+ if block.get("type") == "text":
549
+ text = block.get("text", "")
550
+ for text_line in text.split('\n'):
551
+ if text_line.strip():
552
+ dashboard.append_output(instance_idx, text_line)
553
+ full_text.append(text)
554
+ elif block.get("type") == "tool_use":
555
+ tool_name = block.get("name", "unknown")
556
+ tool_input = block.get("input", {})
557
+ tool_desc = _format_tool_short(tool_name, tool_input)
558
+ dashboard.append_output(instance_idx, f"\u25b6 {tool_desc}")
559
+
560
+ elif msg_type == "content_block_delta":
561
+ delta = data.get("delta", {})
562
+ if delta.get("type") == "text_delta":
563
+ text = delta.get("text", "")
564
+ full_text.append(text)
565
+ # Only push meaningful chunks to dashboard
566
+ for text_line in text.split('\n'):
567
+ if text_line.strip():
568
+ dashboard.append_output(instance_idx, text_line)
569
+
570
+ elif msg_type == "tool_use":
571
+ tool_name = data.get("name", "unknown")
572
+ tool_input = data.get("input", {})
573
+ tool_desc = _format_tool_short(tool_name, tool_input)
574
+ dashboard.append_output(instance_idx, f"\u25b6 {tool_desc}")
575
+
576
+ elif msg_type == "tool_result":
577
+ dashboard.append_output(instance_idx, "\u2514\u2500 \u2713 done")
578
+
579
+ elif msg_type == "result":
580
+ text = data.get("result", "")
581
+ if text:
582
+ full_text.append(text)
583
+
584
+ elif msg_type == "error":
585
+ error_msg = data.get("error", {}).get("message", "Unknown error")
586
+ dashboard.append_output(instance_idx, f"\u2717 ERROR: {error_msg}")
587
+
588
+ except json.JSONDecodeError:
589
+ dashboard.append_output(instance_idx, line)
590
+ full_text.append(line)
591
+
592
+ process.wait(timeout=timeout)
593
+ return '\n'.join(full_text)
594
+
595
+ except subprocess.TimeoutExpired:
596
+ return f"[DAVELOOP:TIMEOUT] Claude Code timed out after {timeout // 60} minutes"
597
+ except FileNotFoundError:
598
+ return "[DAVELOOP:ERROR] Claude Code CLI not found"
599
+ except Exception as e:
600
+ return f"[DAVELOOP:ERROR] {str(e)}"
601
+
602
+
603
+ def _format_tool_short(tool_name: str, tool_input: dict) -> str:
604
+ """Format a tool call for compact dashboard display."""
605
+ if tool_name == "Bash":
606
+ cmd = tool_input.get("command", "")
607
+ return f"Bash({cmd[:40]}{'...' if len(cmd) > 40 else ''})"
608
+ elif tool_name in ("Read", "Write", "Edit"):
609
+ fp = tool_input.get("file_path", "")
610
+ fname = fp.split("\\")[-1].split("/")[-1]
611
+ return f"{tool_name}({fname})"
612
+ elif tool_name == "Grep":
613
+ pat = tool_input.get("pattern", "")
614
+ return f"Grep({pat[:25]}{'...' if len(pat) > 25 else ''})"
615
+ elif tool_name == "Glob":
616
+ pat = tool_input.get("pattern", "")
617
+ return f"Glob({pat})"
618
+ elif tool_name == "Task":
619
+ desc = tool_input.get("description", "")
620
+ return f"Task({desc[:30]}{'...' if len(desc) > 30 else ''})"
621
+ return tool_name
622
+
623
+
624
+ def run_turbo_task(task_desc: str, task_idx: int, system_prompt: str,
625
+ working_dir: str, max_iterations: int, timeout: int,
626
+ dashboard: TurboDashboard, swarm_mode: bool = False,
627
+ swarm_budget_max: int = 5, swarm_depth_max: int = 1) -> dict:
628
+ """Run a single DaveLoop task inside the turbo dashboard.
629
+
630
+ Returns a dict with outcome, iterations, and output.
631
+ """
632
+ dashboard.update_task(task_idx, task_desc[:50], max_iterations)
633
+
634
+ context = f"""
635
+ ## Bug Report
636
+
637
+ {task_desc}
638
+
639
+ ## Instructions
640
+
641
+ Analyze this bug. Gather whatever logs/information you need to understand it.
642
+ Then fix it. Use the reasoning protocol before each action.
643
+ """
644
+ full_output = []
645
+
646
+ for iteration in range(1, max_iterations + 1):
647
+ dashboard.update_iteration(task_idx, iteration)
648
+ dashboard.append_output(task_idx, f"--- Iteration {iteration}/{max_iterations} ---")
649
+
650
+ if iteration == 1:
651
+ full_prompt = f"{system_prompt}\n\n---\n\n{context}"
652
+ else:
653
+ full_prompt = context
654
+
655
+ output = run_claude_code_turbo(
656
+ full_prompt, working_dir,
657
+ instance_idx=task_idx,
658
+ dashboard=dashboard,
659
+ timeout=timeout,
660
+ swarm_mode=swarm_mode,
661
+ swarm_budget_max=swarm_budget_max,
662
+ swarm_depth_max=swarm_depth_max,
663
+ )
664
+
665
+ full_output.append(output)
666
+
667
+ signal, should_exit = check_exit_condition(output)
668
+
669
+ if should_exit:
670
+ if signal == "RESOLVED":
671
+ dashboard.append_output(task_idx, "\u2713 BUG RESOLVED!")
672
+ dashboard.mark_done(task_idx, "done")
673
+ return {"outcome": "RESOLVED", "iterations": iteration, "output": '\n'.join(full_output)}
674
+ elif signal == "BLOCKED":
675
+ dashboard.append_output(task_idx, "\u2717 BLOCKED - needs human help")
676
+ dashboard.mark_done(task_idx, "failed")
677
+ return {"outcome": "BLOCKED", "iterations": iteration, "output": '\n'.join(full_output)}
678
+ elif signal == "CLARIFY":
679
+ dashboard.append_output(task_idx, "\u2717 NEEDS CLARIFICATION")
680
+ dashboard.mark_done(task_idx, "failed")
681
+ return {"outcome": "CLARIFY", "iterations": iteration, "output": '\n'.join(full_output)}
682
+ else:
683
+ dashboard.append_output(task_idx, f"\u2717 Error: {signal}")
684
+ dashboard.mark_done(task_idx, "failed")
685
+ return {"outcome": signal, "iterations": iteration, "output": '\n'.join(full_output)}
686
+
687
+ # Prepare next iteration context
688
+ context = f"""
689
+ ## Iteration {iteration + 1}
690
+
691
+ The bug is NOT yet resolved. You have full context from previous iterations.
692
+
693
+ Continue debugging. Analyze what happened, determine next steps, and proceed.
694
+ Use the reasoning protocol before each action.
695
+ """
696
+
697
+ # Max iterations reached
698
+ dashboard.append_output(task_idx, f"\u2717 Max iterations ({max_iterations}) reached")
699
+ dashboard.mark_done(task_idx, "failed")
700
+ return {"outcome": "MAX_ITERATIONS", "iterations": max_iterations, "output": '\n'.join(full_output)}
701
+
702
+
321
703
  # ============================================================================
322
704
  # Token Tracker
323
705
  # ============================================================================
@@ -1052,6 +1434,10 @@ def main():
1052
1434
  help="Max sub-agent depth in swarm mode (default: 1, no recursive spawning)")
1053
1435
  parser.add_argument("--show-tokens", action="store_true",
1054
1436
  help="Show verbose per-turn token usage during execution")
1437
+ parser.add_argument("--turbo", action="store_true",
1438
+ help="Run all tasks in parallel with a Rich split-pane dashboard")
1439
+ parser.add_argument("--turbo-workers", type=int, default=None,
1440
+ help="Max parallel workers in turbo mode (default: number of tasks)")
1055
1441
 
1056
1442
  args = parser.parse_args()
1057
1443
 
@@ -1106,6 +1492,9 @@ def main():
1106
1492
  print_status("Tools", ALLOWED_TOOLS_SWARM, C.WHITE)
1107
1493
  else:
1108
1494
  print_status("Tools", ALLOWED_TOOLS_DEFAULT, C.WHITE)
1495
+ if args.turbo:
1496
+ workers = args.turbo_workers or len(bug_descriptions)
1497
+ print_status("Turbo", f"ENABLED ({workers} parallel workers)", C.BRIGHT_MAGENTA)
1109
1498
  print(f"{C.BRIGHT_BLUE}└{'─' * 70}┘{C.RESET}")
1110
1499
 
1111
1500
  # Build task queue
@@ -1118,15 +1507,97 @@ def main():
1118
1507
  print(f"{C.BRIGHT_BLUE}│{C.RESET} Type while running: {C.BRIGHT_WHITE}wait{C.RESET} {C.DIM}·{C.RESET} {C.BRIGHT_WHITE}pause{C.RESET} {C.DIM}·{C.RESET} {C.BRIGHT_WHITE}add{C.RESET} {C.DIM}·{C.RESET} {C.BRIGHT_WHITE}done{C.RESET} {C.DIM}·{C.RESET} {C.BRIGHT_WHITE}stop{C.RESET} {C.BRIGHT_BLUE}│{C.RESET}")
1119
1508
  print(f"{C.BRIGHT_BLUE}└{'─' * 70}┘{C.RESET}")
1120
1509
 
1121
- # Start input monitor
1122
- input_monitor = InputMonitor()
1123
- input_monitor.start()
1124
-
1125
1510
  # Build history context for initial prompt
1126
1511
  history_context = ""
1127
1512
  if history_data["sessions"]:
1128
1513
  history_context = "\n\n" + format_history_context(history_data["sessions"])
1129
1514
 
1515
+ # ========================================================================
1516
+ # TURBO MODE: parallel execution with Rich dashboard
1517
+ # ========================================================================
1518
+ if args.turbo and len(bug_descriptions) >= 1:
1519
+ num_workers = args.turbo_workers or len(bug_descriptions)
1520
+ num_workers = min(num_workers, len(bug_descriptions))
1521
+
1522
+ print(f"\n {C.BRIGHT_MAGENTA}{C.BOLD}◆ Launching Turbo Mode with {num_workers} parallel workers...{C.RESET}\n")
1523
+ time.sleep(1) # Brief pause so user sees the message before dashboard takes over
1524
+
1525
+ dashboard = TurboDashboard(len(bug_descriptions))
1526
+ system_prompt_full = system_prompt + history_context
1527
+
1528
+ # Launch all tasks in parallel
1529
+ results = [None] * len(bug_descriptions)
1530
+
1531
+ dashboard.start()
1532
+ try:
1533
+ with ThreadPoolExecutor(max_workers=num_workers) as executor:
1534
+ futures = {}
1535
+ for idx, desc in enumerate(bug_descriptions):
1536
+ future = executor.submit(
1537
+ run_turbo_task,
1538
+ task_desc=desc,
1539
+ task_idx=idx,
1540
+ system_prompt=system_prompt_full,
1541
+ working_dir=working_dir,
1542
+ max_iterations=args.max_iterations,
1543
+ timeout=args.timeout,
1544
+ dashboard=dashboard,
1545
+ swarm_mode=args.swarm,
1546
+ swarm_budget_max=args.swarm_budget,
1547
+ swarm_depth_max=args.swarm_depth,
1548
+ )
1549
+ futures[future] = idx
1550
+
1551
+ for future in as_completed(futures):
1552
+ idx = futures[future]
1553
+ try:
1554
+ results[idx] = future.result()
1555
+ except Exception as e:
1556
+ results[idx] = {"outcome": "ERROR", "iterations": 0, "output": str(e)}
1557
+ dashboard.append_output(idx, f"\u2717 Exception: {e}")
1558
+ dashboard.mark_done(idx, "failed")
1559
+ finally:
1560
+ # Keep dashboard visible for a moment so user can see final state
1561
+ time.sleep(2)
1562
+ dashboard.stop()
1563
+
1564
+ # Print final summary
1565
+ print(f"\n{BANNER}\n")
1566
+ print(f"\n{C.BRIGHT_BLUE}{C.BOLD}◆ TURBO MODE COMPLETE{C.RESET}")
1567
+ print(f"{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}")
1568
+ for idx, desc in enumerate(bug_descriptions):
1569
+ r = results[idx]
1570
+ outcome = r["outcome"] if r else "ERROR"
1571
+ iters = r["iterations"] if r else 0
1572
+ desc_short = desc[:55]
1573
+ if outcome == "RESOLVED":
1574
+ print(f" {C.BRIGHT_GREEN}✓{C.RESET} {C.WHITE}{desc_short}{C.RESET} {C.DIM}({iters} iter){C.RESET}")
1575
+ else:
1576
+ print(f" {C.BRIGHT_RED}✗{C.RESET} {C.RED}{desc_short}{C.RESET} {C.DIM}({outcome}, {iters} iter){C.RESET}")
1577
+
1578
+ # Save to history
1579
+ session_entry = summarize_session(desc, outcome, iters)
1580
+ history_data["sessions"].append(session_entry)
1581
+
1582
+ # Save log
1583
+ save_log(idx + 1, r.get("output", "") if r else "", session_id)
1584
+
1585
+ save_history(working_dir, history_data)
1586
+
1587
+ print(f"\n {C.DIM}Session: {session_id}{C.RESET}")
1588
+ print(f" {C.DIM}Logs: {LOG_DIR}{C.RESET}\n")
1589
+
1590
+ all_resolved = all(r and r["outcome"] == "RESOLVED" for r in results)
1591
+ return 0 if all_resolved else 1
1592
+
1593
+ # ========================================================================
1594
+ # SEQUENTIAL MODE (original behavior)
1595
+ # ========================================================================
1596
+
1597
+ # Start input monitor
1598
+ input_monitor = InputMonitor()
1599
+ input_monitor.start()
1600
+
1130
1601
  # Session-wide token tracking (aggregates across all tasks)
1131
1602
  session_token_tracker = TokenTracker()
1132
1603
 
@@ -1,7 +0,0 @@
1
- daveloop.py,sha256=ge03G7KAmYwCZllCFztGlfpuwbhzo70Wdaki0loSaO0,66852
2
- daveloop_swebench.py,sha256=iD9AU3XRiMQpt7TknFNlvnmPCNp64V-JaTfqTFgsGBM,15996
3
- daveloop-1.5.0.dist-info/METADATA,sha256=FyXtTgGZmdn8jooM6q6MWEhD5Rs0Mhk65jVOj7iYIAk,12842
4
- daveloop-1.5.0.dist-info/WHEEL,sha256=hPN0AlP2dZM_3ZJZWP4WooepkmU9wzjGgCLCeFjkHLA,92
5
- daveloop-1.5.0.dist-info/entry_points.txt,sha256=QcFAZgFrDfPtIikNQb7eW9DxOpBK7T-qWrKqbGAS9Ww,86
6
- daveloop-1.5.0.dist-info/top_level.txt,sha256=36DiYt70m4DIK8t7IhV_y6hAzUIyeb5-qDUf3-gbDdg,27
7
- daveloop-1.5.0.dist-info/RECORD,,