Compare commits
13 Commits
e8cd3e5e87
...
f796292fb5
| Author | SHA1 | Date | |
|---|---|---|---|
| f796292fb5 | |||
| d0009bb23a | |||
| 5cc8f76bf8 | |||
| 92da9727b6 | |||
| 9b17667aca | |||
| ea5bb4eedf | |||
| de6d2b0df6 | |||
| 24f385e612 | |||
| a519a9ba00 | |||
| c102392320 | |||
| a0276e0894 | |||
| 30f2ec6689 | |||
| 1eb9d2923f |
42
Readme.md
42
Readme.md
@@ -35,24 +35,26 @@ The **MMA (Multi-Model Agent)** system decomposes epics into tracks, tracks into
|
||||
|
||||
## Module Map
|
||||
|
||||
| File | Lines | Role |
|
||||
|---|---|---|
|
||||
| `gui_2.py` | ~3080 | Primary ImGui interface — App class, frame-sync, HITL dialogs |
|
||||
| `ai_client.py` | ~1800 | Multi-provider LLM abstraction (Gemini, Anthropic, DeepSeek, Gemini CLI) |
|
||||
| `mcp_client.py` | ~870 | 26 MCP tools with filesystem sandboxing and tool dispatch |
|
||||
| `api_hooks.py` | ~330 | HookServer — REST API for external automation on `:8999` |
|
||||
| `api_hook_client.py` | ~245 | Python client for the Hook API (used by tests and external tooling) |
|
||||
| `multi_agent_conductor.py` | ~250 | ConductorEngine — Tier 2 orchestration loop with DAG execution |
|
||||
| `conductor_tech_lead.py` | ~100 | Tier 2 ticket generation from track briefs |
|
||||
| `dag_engine.py` | ~100 | TrackDAG (dependency graph) + ExecutionEngine (tick-based state machine) |
|
||||
| `models.py` | ~100 | Ticket, Track, WorkerContext dataclasses |
|
||||
| `events.py` | ~89 | EventEmitter, AsyncEventQueue, UserRequestEvent |
|
||||
| `project_manager.py` | ~300 | TOML config persistence, discussion management, track state |
|
||||
| `session_logger.py` | ~200 | JSON-L + markdown audit trails (comms, tools, CLI, hooks) |
|
||||
| `shell_runner.py` | ~100 | PowerShell execution with timeout, env config, QA callback |
|
||||
| `file_cache.py` | ~150 | ASTParser (tree-sitter) — skeleton and curated views |
|
||||
| `summarize.py` | ~120 | Heuristic file summaries (imports, classes, functions) |
|
||||
| `outline_tool.py` | ~80 | Hierarchical code outline via stdlib `ast` |
|
||||
Core implementation resides in the `src/` directory.
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `src/gui_2.py` | Primary ImGui interface — App class, frame-sync, HITL dialogs |
|
||||
| `src/ai_client.py` | Multi-provider LLM abstraction (Gemini, Anthropic, DeepSeek, Gemini CLI) |
|
||||
| `src/mcp_client.py` | 26 MCP tools with filesystem sandboxing and tool dispatch |
|
||||
| `src/api_hooks.py` | HookServer — REST API for external automation on `:8999` |
|
||||
| `src/api_hook_client.py` | Python client for the Hook API (used by tests and external tooling) |
|
||||
| `src/multi_agent_conductor.py` | ConductorEngine — Tier 2 orchestration loop with DAG execution |
|
||||
| `src/conductor_tech_lead.py` | Tier 2 ticket generation from track briefs |
|
||||
| `src/dag_engine.py` | TrackDAG (dependency graph) + ExecutionEngine (tick-based state machine) |
|
||||
| `src/models.py` | Ticket, Track, WorkerContext dataclasses |
|
||||
| `src/events.py` | EventEmitter, AsyncEventQueue, UserRequestEvent |
|
||||
| `src/project_manager.py` | TOML config persistence, discussion management, track state |
|
||||
| `src/session_logger.py` | JSON-L + markdown audit trails (comms, tools, CLI, hooks) |
|
||||
| `src/shell_runner.py` | PowerShell execution with timeout, env config, QA callback |
|
||||
| `src/file_cache.py` | ASTParser (tree-sitter) — skeleton and curated views |
|
||||
| `src/summarize.py` | Heuristic file summaries (imports, classes, functions) |
|
||||
| `src/outline_tool.py` | Hierarchical code outline via stdlib `ast` |
|
||||
|
||||
---
|
||||
|
||||
@@ -89,8 +91,8 @@ api_key = "YOUR_KEY"
|
||||
### Running
|
||||
|
||||
```powershell
|
||||
uv run gui_2.py # Normal mode
|
||||
uv run gui_2.py --enable-test-hooks # With Hook API on :8999
|
||||
uv run sloppy.py # Normal mode
|
||||
uv run sloppy.py --enable-test-hooks # With Hook API on :8999
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
@@ -44,7 +44,7 @@ For deep implementation details when planning or implementing tracks, consult `d
|
||||
- **Integrated Workspace:** A consolidated Hub-based layout (Context, AI Settings, Discussion, Operations) designed for expert multi-monitor workflows.
|
||||
- **Session Analysis:** Ability to load and visualize historical session logs with a dedicated tinted "Prior Session" viewing mode.
|
||||
- **Structured Log Taxonomy:** Automated session-based log organization into `logs/sessions/`, `logs/agents/`, and `logs/errors/`. Includes a dedicated GUI panel for monitoring and manual whitelisting. Features an intelligent heuristic-based pruner that automatically cleans up insignificant logs older than 24 hours while preserving valuable sessions.
|
||||
- **Clean Project Root:** Enforces a "Cruft-Free Root" policy by redirecting all temporary test data, configurations, and AI-generated artifacts to `tests/artifacts/`.
|
||||
- **Clean Project Root:** Enforces a "Cruft-Free Root" policy by organizing core implementation into a `src/` directory and redirecting all temporary test data, configurations, and AI-generated artifacts to `tests/artifacts/`.
|
||||
- **Performance Diagnostics:** Built-in telemetry for FPS, Frame Time, and CPU usage, with a dedicated Diagnostics Panel and AI API hooks for performance analysis.
|
||||
- **Automated UX Verification:** A robust IPC mechanism via API hooks and a modular simulation suite allows for human-like simulation walkthroughs and automated regression testing of the full GUI lifecycle across multiple specialized scenarios.
|
||||
- **Headless Backend Service:** Optional headless mode allowing the core AI and tool execution logic to run as a decoupled REST API service (FastAPI), optimized for Docker and server-side environments (e.g., Unraid).
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
- **psutil:** For system and process monitoring (CPU/Memory telemetry).
|
||||
- **uv:** An extremely fast Python package and project manager.
|
||||
- **pytest:** For unit and integration testing, leveraging custom fixtures for live GUI verification.
|
||||
- **Taxonomy & Artifacts:** Enforces a clean root by redirecting session logs to `logs/sessions/`, sub-agent logs to `logs/agents/`, and error logs to `logs/errors/`. Temporary test data and test logs are siloed in `tests/artifacts/` and `tests/logs/`.
|
||||
- **Taxonomy & Artifacts:** Enforces a clean root by organizing core implementation into a `src/` directory, and redirecting session logs to `logs/sessions/`, sub-agent logs to `logs/agents/`, and error logs to `logs/errors/`. Temporary test data and test logs are siloed in `tests/artifacts/` and `tests/logs/`.
|
||||
- **ApiHookClient:** A dedicated IPC client for automated GUI interaction and state inspection.
|
||||
- **mma-exec / mma.ps1:** Python-based execution engine and PowerShell wrapper for managing the 4-Tier MMA hierarchy and automated documentation mapping.
|
||||
- **dag_engine.py:** A native Python utility implementing `TrackDAG` and `ExecutionEngine` for dependency resolution, cycle detection, transitive blocking propagation, and programmable task execution loops.
|
||||
|
||||
@@ -8,10 +8,10 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
|
||||
*The following tracks MUST be executed in this exact order to safely resolve tech debt before feature development.*
|
||||
|
||||
1. [ ] **Track: Codebase Migration to `src` & Cleanup**
|
||||
1. [x] **Track: Codebase Migration to `src` & Cleanup**
|
||||
*Link: [./tracks/codebase_migration_20260302/](./tracks/codebase_migration_20260302/)*
|
||||
|
||||
2. [ ] **Track: GUI Decoupling & Controller Architecture**
|
||||
2. [~] **Track: GUI Decoupling & Controller Architecture**
|
||||
*Link: [./tracks/gui_decoupling_controller_20260302/](./tracks/gui_decoupling_controller_20260302/)*
|
||||
|
||||
3. [ ] **Track: Hook API UI State Verification**
|
||||
|
||||
@@ -1,54 +1,22 @@
|
||||
# Implementation Plan: Codebase Migration to `src` & Cleanup (codebase_migration_20260302)
|
||||
|
||||
## Phase 1: Unused File Identification & Removal
|
||||
- [ ] Task: Initialize MMA Environment `activate_skill mma-orchestrator`
|
||||
- [ ] Task: Audit Codebase for Dead Files
|
||||
- [ ] WHERE: Project root
|
||||
- [ ] WHAT: Run `py_find_usages` or grep on suspected unused files to verify they are not referenced by `gui_2.py`, `tests/`, `simulation/`, or core config files.
|
||||
- [ ] HOW: Gather a list of unused files.
|
||||
- [ ] SAFETY: Do not delete files referenced in `.toml` files or Github action workflows.
|
||||
- [ ] Task: Delete Unused Files
|
||||
- [ ] WHERE: Project root
|
||||
- [ ] WHAT: Use `run_powershell` with `Remove-Item` to delete the identified unused files.
|
||||
- [ ] HOW: Explicitly list and delete them.
|
||||
- [ ] SAFETY: Stage deletions to Git carefully.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Unused File Identification & Removal' (Protocol in workflow.md)
|
||||
- [x] Task: Initialize MMA Environment `activate_skill mma-orchestrator`
|
||||
- [x] Task: Audit Codebase for Dead Files (1eb9d29)
|
||||
- [x] Task: Delete Unused Files (1eb9d29)
|
||||
- [-] Task: Conductor - User Manual Verification 'Phase 1: Unused File Identification & Removal' (SKIPPED)
|
||||
|
||||
## Phase 2: Directory Restructuring & Migration
|
||||
- [ ] Task: Create `src/` Directory
|
||||
- [ ] WHERE: Project root
|
||||
- [ ] WHAT: Create the `src/` directory. Add an empty `__init__.py` to make it a package.
|
||||
- [ ] HOW: `New-Item -ItemType Directory src; New-Item src/__init__.py`.
|
||||
- [ ] SAFETY: None.
|
||||
- [ ] Task: Move Application Files to `src/`
|
||||
- [ ] WHERE: Project root
|
||||
- [ ] WHAT: Move core `.py` files (`gui_2.py`, `ai_client.py`, `mcp_client.py`, `shell_runner.py`, `project_manager.py`, `events.py`, etc.) into `src/`.
|
||||
- [ ] HOW: Use `git mv` via `run_powershell` or standard `Move-Item`.
|
||||
- [ ] SAFETY: Preserve git history of these files.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Directory Restructuring & Migration' (Protocol in workflow.md)
|
||||
- [x] Task: Create `src/` Directory
|
||||
- [x] Task: Move Application Files to `src/`
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 2: Directory Restructuring & Migration' (Checkpoint: 24f385e)
|
||||
|
||||
## Phase 3: Entry Point & Import Resolution
|
||||
- [ ] Task: Create `sloppy.py` Entry Point
|
||||
- [ ] WHERE: Project root (`sloppy.py`)
|
||||
- [ ] WHAT: Create the script to act as the primary launch point. It should import `App` from `src.gui_2` and pass CLI args.
|
||||
- [ ] HOW: Write a standard Python script wrapper.
|
||||
- [ ] SAFETY: Ensure it correctly propagates `sys.argv`.
|
||||
- [ ] Task: Resolve Absolute and Relative Imports
|
||||
- [ ] WHERE: `src/*.py`, `tests/*.py`, `simulation/*.py`
|
||||
- [ ] WHAT: Update import statements. E.g., `import gui_2` becomes `from src import gui_2` or adjust `sys.path.append` in tests.
|
||||
- [ ] HOW: Surgical string replacements. Ensure `pytest` can still find fixtures and test modules.
|
||||
- [ ] SAFETY: Run `uv run pytest` to aggressively check for `ModuleNotFoundError`s.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 3: Entry Point & Import Resolution' (Protocol in workflow.md)
|
||||
- [x] Task: Create `sloppy.py` Entry Point (c102392)
|
||||
- [x] Task: Resolve Absolute and Relative Imports (c102392)
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 3: Entry Point & Import Resolution' (Checkpoint: 24f385e)
|
||||
|
||||
## Phase 4: Final Validation & Documentation
|
||||
- [ ] Task: Full Test Suite Validation
|
||||
- [ ] WHERE: Project root
|
||||
- [ ] WHAT: Run `uv run pytest`. Fix any remaining path resolution issues for logs, artifacts, and configs.
|
||||
- [ ] HOW: Verify 100% pass rate.
|
||||
- [ ] SAFETY: Artifacts must still be written to `tests/artifacts/`.
|
||||
- [ ] Task: Update Core Documentation
|
||||
- [ ] WHERE: `Readme.md`, `docs/`, `conductor/tech-stack.md`
|
||||
- [ ] WHAT: Document `sloppy.py` as the new entry point. Document the `src/` directory layout.
|
||||
- [ ] HOW: Surgical text replacement.
|
||||
- [ ] SAFETY: Accurate representation of new structure.
|
||||
- [x] Task: Full Test Suite Validation (ea5bb4e)
|
||||
- [x] Task: Update Core Documentation (ea5bb4e)
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Final Validation & Documentation' (Protocol in workflow.md)
|
||||
@@ -1,17 +1,9 @@
|
||||
# Implementation Plan: GUI Decoupling & Controller Architecture (gui_decoupling_controller_20260302)
|
||||
|
||||
## Phase 1: Controller Skeleton & State Migration
|
||||
- [ ] Task: Initialize MMA Environment `activate_skill mma-orchestrator`
|
||||
- [ ] Task: Create `app_controller.py` Skeleton
|
||||
- [ ] WHERE: `app_controller.py` (New file)
|
||||
- [ ] WHAT: Create the `AppController` class. Initialize basic state structures (logs, metrics, flags).
|
||||
- [ ] HOW: Standard class definition.
|
||||
- [ ] SAFETY: Do not break existing GUI yet.
|
||||
- [ ] Task: Migrate Data State from GUI
|
||||
- [ ] WHERE: `gui_2.py:__init__` and `app_controller.py`
|
||||
- [ ] WHAT: Move variables like `_comms_log`, `_tool_log`, `mma_streams`, `active_tickets` to the controller.
|
||||
- [ ] HOW: Update GUI to reference `self.controller.mma_streams` instead of `self.mma_streams`.
|
||||
- [ ] SAFETY: Search and replace carefully; use `py_check_syntax`.
|
||||
- [x] Task: Initialize MMA Environment `activate_skill mma-orchestrator` [d0009bb]
|
||||
- [x] Task: Create `app_controller.py` Skeleton [d0009bb]
|
||||
- [x] Task: Migrate Data State from GUI [d0009bb]
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: State Migration' (Protocol in workflow.md)
|
||||
|
||||
## Phase 2: Logic & Background Thread Migration
|
||||
|
||||
@@ -8,6 +8,28 @@
|
||||
|
||||
Manual Slop solves a single tension: **AI reasoning is high-latency and non-deterministic; GUI interaction must be low-latency and responsive.** The engine enforces strict decoupling between three thread domains so that multi-second LLM calls never block the render loop, and every AI-generated payload passes through a human-auditable gate before execution.
|
||||
|
||||
## Project Structure
|
||||
|
||||
The codebase is organized into a `src/` layout to separate implementation from configuration and artifacts.
|
||||
|
||||
```
|
||||
manual_slop/
|
||||
├── conductor/ # Conductor tracks, specs, and plans
|
||||
├── docs/ # Deep-dive architectural documentation
|
||||
├── logs/ # Session logs, agent traces, and errors
|
||||
├── scripts/ # Build, migration, and IPC bridge scripts
|
||||
├── src/ # Core Python implementation
|
||||
│ ├── ai_client.py # LLM provider abstraction
|
||||
│ ├── gui_2.py # Main ImGui application
|
||||
│ ├── mcp_client.py # MCP tool implementation
|
||||
│ └── ... # Other core modules
|
||||
├── tests/ # Pytest suite and simulation fixtures
|
||||
├── simulation/ # Workflow and agent simulation logic
|
||||
├── sloppy.py # Primary application entry point
|
||||
├── config.toml # Global application settings
|
||||
└── manual_slop.toml # Project-specific configuration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Thread Domains
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
role = "tier3-worker"
|
||||
prompt = """FIX DeepSeek implementation in ai_client.py.
|
||||
|
||||
CONTEXT:
|
||||
Several tests in @tests/test_deepseek_provider.py are failing (returning '(No text returned by the model)') because the current implementation of '_send_deepseek' in @ai_client.py forces 'stream=True' and expects SSE format, but the test mocks provide standard JSON responses.
|
||||
|
||||
TASK:
|
||||
1. Modify '_send_deepseek' in @ai_client.py to handle the response correctly whether it is a stream or a standard JSON response.
|
||||
- You should probably determine this based on the 'stream' value in the payload (which is currently hardcoded to True, but the implementation should be flexible).
|
||||
- If 'stream' is True, use the iter_lines() logic to aggregate chunks.
|
||||
- If 'stream' is False, use resp.json() to get the content.
|
||||
2. Fix the 'NameError: name 'data' is not defined' and ensure 'usage' is correctly extracted.
|
||||
3. Ensure 'full_content', 'full_reasoning' (thinking tags), and 'tool_calls' are correctly captured and added to the conversation history in both modes.
|
||||
4. Ensure all tests in @tests/test_deepseek_provider.py pass.
|
||||
|
||||
OUTPUT: Provide the raw Python code for the modified '_send_deepseek' function."""
|
||||
docs = ["ai_client.py", "tests/test_deepseek_provider.py"]
|
||||
File diff suppressed because it is too large
Load Diff
35
gemini.py
35
gemini.py
@@ -1,35 +0,0 @@
|
||||
# gemini.py
|
||||
from __future__ import annotations
|
||||
import tomllib
|
||||
from typing import Any
|
||||
from google import genai
|
||||
|
||||
_client: genai.Client | None = None
|
||||
_chat: Any = None
|
||||
|
||||
def _load_key() -> str:
|
||||
with open("credentials.toml", "rb") as f:
|
||||
return tomllib.load(f)["gemini"]["api_key"]
|
||||
|
||||
def _ensure_client() -> None:
|
||||
global _client
|
||||
if _client is None:
|
||||
_client = genai.Client(api_key=_load_key())
|
||||
|
||||
def _ensure_chat() -> None:
|
||||
global _chat
|
||||
if _chat is None:
|
||||
_ensure_client()
|
||||
_chat = _client.chats.create(model="gemini-2.0-flash")
|
||||
|
||||
def send(md_content: str, user_message: str) -> str:
|
||||
global _chat
|
||||
_ensure_chat()
|
||||
full_message = f"<context>\n{md_content}\n</context>\n\n{user_message}"
|
||||
response = _chat.send_message(full_message)
|
||||
return response.text
|
||||
|
||||
def reset_session() -> None:
|
||||
global _client, _chat
|
||||
_client = None
|
||||
_chat = None
|
||||
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
uv run python scripts/tool_call.py get_file_summary
|
||||
BIN
gui_2_mypy.txt
BIN
gui_2_mypy.txt
Binary file not shown.
Binary file not shown.
@@ -8,5 +8,5 @@ active = "main"
|
||||
|
||||
[discussions.main]
|
||||
git_commit = ""
|
||||
last_updated = "2026-03-04T09:44:10"
|
||||
last_updated = "2026-03-04T10:09:06"
|
||||
history = []
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
role = "tier3-worker"
|
||||
prompt = """Implement strict type hints for ALL functions and methods in @gui_2.py.
|
||||
1. Use specific types (e.g., dict[str, Any], list[str], Union[str, Path], etc.) for arguments and returns.
|
||||
2. Maintain the 'AI-Optimized' style: 1-space indentation, NO blank lines within function bodies, and maximum 1 blank line between definitions.
|
||||
3. Since this file is very large, you MUST use surgical tools (discovered_tool_py_update_definition, discovered_tool_py_set_signature, discovered_tool_py_set_var_declaration) to apply changes. Do NOT try to overwrite the entire file at once.
|
||||
4. Do NOT change any logic.
|
||||
5. Use discovered_tool_py_check_syntax after each major change to verify syntax.
|
||||
6. Ensure 'from typing import Any, dict, list, Union, Optional, Callable' etc. are present.
|
||||
7. Focus on completing the task efficiently without hitting timeouts."""
|
||||
docs = ["gui_2.py", "conductor/workflow.md"]
|
||||
@@ -1,21 +0,0 @@
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def test_type_hints() -> None:
|
||||
files = ["project_manager.py", "session_logger.py"]
|
||||
all_missing = []
|
||||
for f in files:
|
||||
print(f"Scanning {f}...")
|
||||
result = subprocess.run(["uv", "run", "python", "scripts/type_hint_scanner.py", f], capture_output=True, text=True)
|
||||
if result.stdout.strip():
|
||||
print(f"Missing hints in {f}:\n{result.stdout}")
|
||||
all_missing.append(f)
|
||||
if all_missing:
|
||||
print(f"FAILURE: Missing type hints in: {', '.join(all_missing)}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("SUCCESS: All functions have type hints.")
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_type_hints()
|
||||
@@ -1,3 +0,0 @@
|
||||
role = "tier3-worker"
|
||||
prompt = "Read @ai_client.py and describe the current placeholder implementation of _send_deepseek. Just a one-sentence summary."
|
||||
docs = ["ai_client.py"]
|
||||
@@ -1,31 +0,0 @@
|
||||
Files with untyped items: 25
|
||||
|
||||
File NoRet Params Vars Total
|
||||
-------------------------------------------------------------------------------------
|
||||
./debug_ast.py 1 2 4 7
|
||||
./tests/visual_mma_verification.py 0 0 4 4
|
||||
./debug_ast_2.py 0 0 3 3
|
||||
./scripts/cli_tool_bridge.py 1 0 1 2
|
||||
./scripts/mcp_server.py 0 0 2 2
|
||||
./tests/test_gui_diagnostics.py 0 0 2 2
|
||||
./tests/test_gui_updates.py 0 0 2 2
|
||||
./tests/test_layout_reorganization.py 0 0 2 2
|
||||
./scripts/check_hints.py 0 0 1 1
|
||||
./scripts/check_hints_v2.py 0 0 1 1
|
||||
./scripts/claude_tool_bridge.py 0 0 1 1
|
||||
./scripts/type_hint_scanner.py 1 0 0 1
|
||||
./tests/mock_alias_tool.py 0 0 1 1
|
||||
./tests/test_gemini_cli_adapter_parity.py 0 0 1 1
|
||||
./tests/test_gui2_parity.py 0 0 1 1
|
||||
./tests/test_gui2_performance.py 0 0 1 1
|
||||
./tests/test_gui_performance_requirements.py 0 1 0 1
|
||||
./tests/test_gui_stress_performance.py 0 1 0 1
|
||||
./tests/test_hooks.py 0 1 0 1
|
||||
./tests/test_live_workflow.py 0 1 0 1
|
||||
./tests/test_track_state_persistence.py 0 1 0 1
|
||||
./tests/verify_mma_gui_robust.py 0 0 1 1
|
||||
./tests/visual_diag.py 0 0 1 1
|
||||
./tests/visual_orchestration_verification.py 0 1 0 1
|
||||
./tests/visual_sim_mma_v2.py 0 1 0 1
|
||||
-------------------------------------------------------------------------------------
|
||||
TOTAL 41
|
||||
@@ -3,10 +3,13 @@ import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
# Add project root to sys.path so we can import api_hook_client
|
||||
# Add project root and src/ to sys.path so we can import api_hook_client
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if project_root not in sys.path:
|
||||
sys.path.append(project_root)
|
||||
src_path = os.path.join(project_root, "src")
|
||||
if src_path not in sys.path:
|
||||
sys.path.append(src_path)
|
||||
|
||||
try:
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
@@ -3,11 +3,14 @@ import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
# Add project root to sys.path so we can import api_hook_client
|
||||
# Add project root and src/ to sys.path so we can import api_hook_client
|
||||
# This helps in cases where the script is run from different directories
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if project_root not in sys.path:
|
||||
sys.path.append(project_root)
|
||||
src_path = os.path.join(project_root, "src")
|
||||
if src_path not in sys.path:
|
||||
sys.path.append(src_path)
|
||||
|
||||
try:
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
@@ -13,8 +13,10 @@ import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add project root to sys.path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
# Add project root and src/ to sys.path
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
sys.path.insert(0, project_root)
|
||||
sys.path.insert(0, os.path.join(project_root, "src"))
|
||||
|
||||
import mcp_client
|
||||
import shell_runner
|
||||
|
||||
@@ -7,8 +7,10 @@ import io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||
|
||||
# Add project root to sys.path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
# Add project root and src/ to sys.path
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
sys.path.append(project_root)
|
||||
sys.path.append(os.path.join(project_root, "src"))
|
||||
|
||||
try:
|
||||
import mcp_client
|
||||
|
||||
@@ -2,8 +2,10 @@ import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add project root to sys.path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
# Add project root and src/ to sys.path
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
sys.path.append(project_root)
|
||||
sys.path.append(os.path.join(project_root, "src"))
|
||||
|
||||
try:
|
||||
import mcp_client
|
||||
|
||||
20
scripts/update_paths.py
Normal file
20
scripts/update_paths.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
import glob
|
||||
|
||||
pattern = 'sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))'
|
||||
replacement = pattern + '\nsys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))'
|
||||
|
||||
# Files to update
|
||||
files = glob.glob("tests/*.py") + glob.glob("simulation/*.py") + glob.glob("scripts/*.py")
|
||||
|
||||
for file_path in files:
|
||||
if not os.path.isfile(file_path):
|
||||
continue
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
if pattern in content and replacement not in content:
|
||||
print(f"Updating {file_path}")
|
||||
new_content = content.replace(pattern, replacement)
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
@@ -4,6 +4,7 @@ import time
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
from simulation.user_agent import UserSimAgent
|
||||
|
||||
@@ -5,8 +5,10 @@ from typing import Any, Optional
|
||||
from api_hook_client import ApiHookClient
|
||||
from simulation.workflow_sim import WorkflowSimulator
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
# Ensure project root and src/ are in path
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
sys.path.append(project_root)
|
||||
sys.path.append(os.path.join(project_root, "src"))
|
||||
|
||||
class BaseSimulation:
|
||||
def __init__(self, client: ApiHookClient = None) -> None:
|
||||
|
||||
12
sloppy.py
Normal file
12
sloppy.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add src to sys.path so we can import from it easily
|
||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||
src_path = os.path.join(project_root, "src")
|
||||
sys.path.insert(0, src_path)
|
||||
|
||||
from gui_2 import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
276
src/app_controller.py
Normal file
276
src/app_controller.py
Normal file
@@ -0,0 +1,276 @@
|
||||
import asyncio
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
from typing import Any, List, Dict, Optional, Tuple
|
||||
from pathlib import Path
|
||||
|
||||
from src import events
|
||||
from src import session_logger
|
||||
from src import project_manager
|
||||
from src.performance_monitor import PerformanceMonitor
|
||||
from src.models import Track, Ticket, load_config, parse_history_entries, DISC_ROLES, AGENT_TOOL_NAMES
|
||||
|
||||
class AppController:
|
||||
"""
|
||||
The headless controller for the Manual Slop application.
|
||||
Owns the application state and manages background services.
|
||||
"""
|
||||
def __init__(self):
|
||||
# Initialize locks first to avoid initialization order issues
|
||||
self._send_thread_lock: threading.Lock = threading.Lock()
|
||||
self._disc_entries_lock: threading.Lock = threading.Lock()
|
||||
self._pending_comms_lock: threading.Lock = threading.Lock()
|
||||
self._pending_tool_calls_lock: threading.Lock = threading.Lock()
|
||||
self._pending_history_adds_lock: threading.Lock = threading.Lock()
|
||||
self._pending_gui_tasks_lock: threading.Lock = threading.Lock()
|
||||
self._pending_dialog_lock: threading.Lock = threading.Lock()
|
||||
self._api_event_queue_lock: threading.Lock = threading.Lock()
|
||||
|
||||
self.config: Dict[str, Any] = {}
|
||||
self.project: Dict[str, Any] = {}
|
||||
self.active_project_path: str = ""
|
||||
self.project_paths: List[str] = []
|
||||
self.active_discussion: str = "main"
|
||||
self.disc_entries: List[Dict[str, Any]] = []
|
||||
self.disc_roles: List[str] = []
|
||||
self.files: List[str] = []
|
||||
self.screenshots: List[str] = []
|
||||
|
||||
self.event_queue: events.AsyncEventQueue = events.AsyncEventQueue()
|
||||
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
self._loop_thread: Optional[threading.Thread] = None
|
||||
|
||||
self.tracks: List[Dict[str, Any]] = []
|
||||
self.active_track: Optional[Track] = None
|
||||
self.active_tickets: List[Dict[str, Any]] = []
|
||||
self.mma_streams: Dict[str, str] = {}
|
||||
self.mma_status: str = "idle"
|
||||
|
||||
self._tool_log: List[Dict[str, Any]] = []
|
||||
self._comms_log: List[Dict[str, Any]] = []
|
||||
|
||||
self.session_usage: Dict[str, Any] = {
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"cache_read_input_tokens": 0,
|
||||
"cache_creation_input_tokens": 0,
|
||||
"last_latency": 0.0
|
||||
}
|
||||
|
||||
self.mma_tier_usage: Dict[str, Dict[str, Any]] = {
|
||||
"Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"},
|
||||
"Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview"},
|
||||
"Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
|
||||
"Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
|
||||
}
|
||||
|
||||
self.perf_monitor: PerformanceMonitor = PerformanceMonitor()
|
||||
self._pending_gui_tasks: List[Dict[str, Any]] = []
|
||||
|
||||
# AI settings state
|
||||
self._current_provider: str = "gemini"
|
||||
self._current_model: str = "gemini-2.5-flash-lite"
|
||||
self.temperature: float = 0.0
|
||||
self.max_tokens: int = 8192
|
||||
self.history_trunc_limit: int = 8000
|
||||
|
||||
# UI-related state moved to controller
|
||||
self.ui_ai_input: str = ""
|
||||
self.ui_disc_new_name_input: str = ""
|
||||
self.ui_disc_new_role_input: str = ""
|
||||
self.ui_epic_input: str = ""
|
||||
self.ui_new_track_name: str = ""
|
||||
self.ui_new_track_desc: str = ""
|
||||
self.ui_new_track_type: str = "feature"
|
||||
self.ui_conductor_setup_summary: str = ""
|
||||
self.ui_last_script_text: str = ""
|
||||
self.ui_last_script_output: str = ""
|
||||
self.ui_new_ticket_id: str = ""
|
||||
self.ui_new_ticket_desc: str = ""
|
||||
self.ui_new_ticket_target: str = ""
|
||||
self.ui_new_ticket_deps: str = ""
|
||||
|
||||
self.ui_output_dir: str = ""
|
||||
self.ui_files_base_dir: str = ""
|
||||
self.ui_shots_base_dir: str = ""
|
||||
self.ui_project_git_dir: str = ""
|
||||
self.ui_project_main_context: str = ""
|
||||
self.ui_project_system_prompt: str = ""
|
||||
self.ui_gemini_cli_path: str = "gemini"
|
||||
self.ui_word_wrap: bool = True
|
||||
self.ui_summary_only: bool = False
|
||||
self.ui_auto_add_history: bool = False
|
||||
self.ui_global_system_prompt: str = ""
|
||||
self.ui_agent_tools: Dict[str, bool] = {}
|
||||
|
||||
self.available_models: List[str] = []
|
||||
self.proposed_tracks: List[Dict[str, Any]] = []
|
||||
self._show_track_proposal_modal: bool = False
|
||||
self.ai_status: str = 'idle'
|
||||
self.ai_response: str = ''
|
||||
self.last_md: str = ''
|
||||
self.last_md_path: Optional[Path] = None
|
||||
self.last_file_items: List[Any] = []
|
||||
self.send_thread: Optional[threading.Thread] = None
|
||||
self.models_thread: Optional[threading.Thread] = None
|
||||
self.show_windows: Dict[str, bool] = {}
|
||||
self.show_script_output: bool = False
|
||||
self.show_text_viewer: bool = False
|
||||
self.text_viewer_title: str = ''
|
||||
self.text_viewer_content: str = ''
|
||||
self._pending_comms: List[Dict[str, Any]] = []
|
||||
self._pending_tool_calls: List[Dict[str, Any]] = []
|
||||
self._pending_history_adds: List[Dict[str, Any]] = []
|
||||
self.perf_history: Dict[str, List[float]] = {'frame_time': [0.0]*100, 'fps': [0.0]*100, 'cpu': [0.0]*100, 'input_lag': [0.0]*100}
|
||||
self._perf_last_update: float = 0.0
|
||||
self._autosave_interval: float = 60.0
|
||||
self._last_autosave: float = time.time()
|
||||
|
||||
# More state moved from App
|
||||
self._ask_dialog_open: bool = False
|
||||
self._ask_request_id: Optional[str] = None
|
||||
self._ask_tool_data: Optional[Dict[str, Any]] = None
|
||||
self.mma_step_mode: bool = False
|
||||
self.active_tier: Optional[str] = None
|
||||
self.ui_focus_agent: Optional[str] = None
|
||||
self._pending_mma_approval: Optional[Dict[str, Any]] = None
|
||||
self._mma_approval_open: bool = False
|
||||
self._mma_approval_edit_mode: bool = False
|
||||
self._mma_approval_payload: str = ""
|
||||
self._pending_mma_spawn: Optional[Dict[str, Any]] = None
|
||||
self._mma_spawn_open: bool = False
|
||||
self._mma_spawn_edit_mode: bool = False
|
||||
self._mma_spawn_prompt: str = ''
|
||||
self._mma_spawn_context: str = ''
|
||||
|
||||
self._trigger_blink: bool = False
|
||||
self._is_blinking: bool = False
|
||||
self._blink_start_time: float = 0.0
|
||||
self._trigger_script_blink: bool = False
|
||||
self._is_script_blinking: bool = False
|
||||
self._script_blink_start_time: float = 0.0
|
||||
self._scroll_disc_to_bottom: bool = False
|
||||
self._scroll_comms_to_bottom: bool = False
|
||||
self._scroll_tool_calls_to_bottom: bool = False
|
||||
self._gemini_cache_text: str = ""
|
||||
self._last_stable_md: str = ''
|
||||
self._token_stats: Dict[str, Any] = {}
|
||||
self._token_stats_dirty: bool = False
|
||||
self.ui_disc_truncate_pairs: int = 2
|
||||
self.ui_auto_scroll_comms: bool = True
|
||||
self.ui_auto_scroll_tool_calls: bool = True
|
||||
self._show_add_ticket_form: bool = False
|
||||
self._track_discussion_active: bool = False
|
||||
self._tier_stream_last_len: Dict[str, int] = {}
|
||||
self.is_viewing_prior_session: bool = False
|
||||
self.prior_session_entries: List[Dict[str, Any]] = []
|
||||
self.test_hooks_enabled: bool = ("--enable-test-hooks" in sys.argv) or (os.environ.get("SLOP_TEST_HOOKS") == "1")
|
||||
self.ui_manual_approve: bool = False
|
||||
|
||||
def init_state(self):
|
||||
"""Initializes the application state from configurations."""
|
||||
self.config = load_config()
|
||||
ai_cfg = self.config.get("ai", {})
|
||||
self._current_provider = ai_cfg.get("provider", "gemini")
|
||||
self._current_model = ai_cfg.get("model", "gemini-2.5-flash-lite")
|
||||
self.temperature = ai_cfg.get("temperature", 0.0)
|
||||
self.max_tokens = ai_cfg.get("max_tokens", 8192)
|
||||
self.history_trunc_limit = ai_cfg.get("history_trunc_limit", 8000)
|
||||
|
||||
projects_cfg = self.config.get("projects", {})
|
||||
self.project_paths = list(projects_cfg.get("paths", []))
|
||||
self.active_project_path = projects_cfg.get("active", "")
|
||||
|
||||
self._load_active_project()
|
||||
|
||||
self.files = list(self.project.get("files", {}).get("paths", []))
|
||||
self.screenshots = list(self.project.get("screenshots", {}).get("paths", []))
|
||||
|
||||
disc_sec = self.project.get("discussion", {})
|
||||
self.disc_roles = list(disc_sec.get("roles", list(DISC_ROLES)))
|
||||
self.active_discussion = disc_sec.get("active", "main")
|
||||
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
|
||||
# UI state
|
||||
self.ui_output_dir = self.project.get("output", {}).get("output_dir", "./md_gen")
|
||||
self.ui_files_base_dir = self.project.get("files", {}).get("base_dir", ".")
|
||||
self.ui_shots_base_dir = self.project.get("screenshots", {}).get("base_dir", ".")
|
||||
proj_meta = self.project.get("project", {})
|
||||
self.ui_project_git_dir = proj_meta.get("git_dir", "")
|
||||
self.ui_project_main_context = proj_meta.get("main_context", "")
|
||||
self.ui_project_system_prompt = proj_meta.get("system_prompt", "")
|
||||
self.ui_gemini_cli_path = self.project.get("gemini_cli", {}).get("binary_path", "gemini")
|
||||
self.ui_word_wrap = proj_meta.get("word_wrap", True)
|
||||
self.ui_summary_only = proj_meta.get("summary_only", False)
|
||||
self.ui_auto_add_history = disc_sec.get("auto_add", False)
|
||||
self.ui_global_system_prompt = self.config.get("ai", {}).get("system_prompt", "")
|
||||
|
||||
_default_windows = {
|
||||
"Context Hub": True,
|
||||
"Files & Media": True,
|
||||
"AI Settings": True,
|
||||
"MMA Dashboard": True,
|
||||
"Tier 1: Strategy": True,
|
||||
"Tier 2: Tech Lead": True,
|
||||
"Tier 3: Workers": True,
|
||||
"Tier 4: QA": True,
|
||||
"Discussion Hub": True,
|
||||
"Operations Hub": True,
|
||||
"Theme": True,
|
||||
"Log Management": False,
|
||||
"Diagnostics": False,
|
||||
}
|
||||
saved = self.config.get("gui", {}).get("show_windows", {})
|
||||
self.show_windows = {k: saved.get(k, v) for k, v in _default_windows.items()}
|
||||
|
||||
agent_tools_cfg = self.project.get("agent", {}).get("tools", {})
|
||||
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in AGENT_TOOL_NAMES}
|
||||
|
||||
label = self.project.get("project", {}).get("name", "")
|
||||
session_logger.open_session(label=label)
|
||||
|
||||
def _load_active_project(self) -> None:
|
||||
"""Loads the active project configuration, with fallbacks."""
|
||||
if self.active_project_path and Path(self.active_project_path).exists():
|
||||
try:
|
||||
self.project = project_manager.load_project(self.active_project_path)
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"Failed to load project {self.active_project_path}: {e}")
|
||||
for pp in self.project_paths:
|
||||
if Path(pp).exists():
|
||||
try:
|
||||
self.project = project_manager.load_project(pp)
|
||||
self.active_project_path = pp
|
||||
return
|
||||
except Exception:
|
||||
continue
|
||||
self.project = project_manager.migrate_from_legacy_config(self.config)
|
||||
name = self.project.get("project", {}).get("name", "project")
|
||||
fallback_path = f"{name}.toml"
|
||||
project_manager.save_project(self.project, fallback_path)
|
||||
self.active_project_path = fallback_path
|
||||
if fallback_path not in self.project_paths:
|
||||
self.project_paths.append(fallback_path)
|
||||
|
||||
def start_services(self):
|
||||
"""Starts background threads and async event loop."""
|
||||
self._loop = asyncio.new_event_loop()
|
||||
self._loop_thread = threading.Thread(target=self._run_event_loop, daemon=True)
|
||||
self._loop_thread.start()
|
||||
|
||||
def _run_event_loop(self):
|
||||
"""Internal loop runner."""
|
||||
asyncio.set_event_loop(self._loop)
|
||||
self._loop.run_forever()
|
||||
|
||||
def stop_services(self):
|
||||
"""Stops background threads and async event loop."""
|
||||
if self._loop:
|
||||
self._loop.call_soon_threadsafe(self._loop.stop)
|
||||
if self._loop_thread:
|
||||
self._loop_thread.join(timeout=2.0)
|
||||
@@ -32,7 +32,8 @@ from log_registry import LogRegistry
|
||||
from log_pruner import LogPruner
|
||||
import conductor_tech_lead
|
||||
import multi_agent_conductor
|
||||
from models import Track, Ticket
|
||||
from models import Track, Ticket, DISC_ROLES, AGENT_TOOL_NAMES, CONFIG_PATH, load_config, parse_history_entries
|
||||
from app_controller import AppController
|
||||
from file_cache import ASTParser
|
||||
|
||||
from fastapi import FastAPI, Depends, HTTPException
|
||||
@@ -40,14 +41,9 @@ from fastapi.security.api_key import APIKeyHeader
|
||||
from pydantic import BaseModel
|
||||
from imgui_bundle import imgui, hello_imgui, immapp
|
||||
|
||||
CONFIG_PATH: Path = Path("config.toml")
|
||||
PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek"]
|
||||
COMMS_CLAMP_CHARS: int = 300
|
||||
|
||||
def load_config() -> dict[str, Any]:
|
||||
with open(CONFIG_PATH, "rb") as f:
|
||||
return tomllib.load(f)
|
||||
|
||||
def save_config(config: dict[str, Any]) -> None:
|
||||
with open(CONFIG_PATH, "wb") as f:
|
||||
tomli_w.dump(config, f)
|
||||
@@ -78,17 +74,6 @@ DIR_COLORS: dict[str, imgui.ImVec4] = {"OUT": C_OUT, "IN": C_IN}
|
||||
KIND_COLORS: dict[str, imgui.ImVec4] = {"request": C_REQ, "response": C_RES, "tool_call": C_TC, "tool_result": C_TR, "tool_result_send": C_TRS}
|
||||
HEAVY_KEYS: set[str] = {"message", "text", "script", "output", "content"}
|
||||
|
||||
DISC_ROLES: list[str] = ["User", "AI", "Vendor API", "System"]
|
||||
AGENT_TOOL_NAMES: list[str] = [
|
||||
"run_powershell", "read_file", "list_directory", "search_files", "get_file_summary",
|
||||
"web_search", "fetch_url", "py_get_skeleton", "py_get_code_outline", "get_file_slice",
|
||||
"py_get_definition", "py_get_signature", "py_get_class_summary", "py_get_var_declaration",
|
||||
"get_git_diff", "py_find_usages", "py_get_imports", "py_check_syntax", "py_get_hierarchy",
|
||||
"py_get_docstring", "get_tree", "get_ui_performance",
|
||||
# Mutating tools — disabled by default
|
||||
"set_file_slice", "py_update_definition", "py_set_signature", "py_set_var_declaration",
|
||||
]
|
||||
|
||||
def truncate_entries(entries: list[dict[str, Any]], max_pairs: int) -> list[dict[str, Any]]:
|
||||
if max_pairs <= 0:
|
||||
return []
|
||||
@@ -102,14 +87,6 @@ def truncate_entries(entries: list[dict[str, Any]], max_pairs: int) -> list[dict
|
||||
return entries[i:]
|
||||
return entries
|
||||
|
||||
def _parse_history_entries(history: list[str], roles: list[str] | None = None) -> list[dict[str, Any]]:
|
||||
known = roles if roles is not None else DISC_ROLES
|
||||
entries = []
|
||||
for raw in history:
|
||||
entry = project_manager.str_to_entry(raw, known)
|
||||
entries.append(entry)
|
||||
return entries
|
||||
|
||||
class ConfirmDialog:
|
||||
def __init__(self, script: str, base_dir: str) -> None:
|
||||
self._uid = str(uuid.uuid4())
|
||||
@@ -172,170 +149,41 @@ class App:
|
||||
"""The main ImGui interface orchestrator for Manual Slop."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# Initialize locks first to avoid initialization order issues
|
||||
self._send_thread_lock: threading.Lock = threading.Lock()
|
||||
self._disc_entries_lock: threading.Lock = threading.Lock()
|
||||
self._pending_comms_lock: threading.Lock = threading.Lock()
|
||||
self._pending_tool_calls_lock: threading.Lock = threading.Lock()
|
||||
self._pending_history_adds_lock: threading.Lock = threading.Lock()
|
||||
self._pending_gui_tasks_lock: threading.Lock = threading.Lock()
|
||||
self._pending_dialog_lock: threading.Lock = threading.Lock()
|
||||
self._api_event_queue_lock: threading.Lock = threading.Lock()
|
||||
# Initialize controller and delegate state
|
||||
self.controller = AppController()
|
||||
self.controller.init_state()
|
||||
|
||||
# Aliases for controller-owned locks
|
||||
self._send_thread_lock = self.controller._send_thread_lock
|
||||
self._disc_entries_lock = self.controller._disc_entries_lock
|
||||
self._pending_comms_lock = self.controller._pending_comms_lock
|
||||
self._pending_tool_calls_lock = self.controller._pending_tool_calls_lock
|
||||
self._pending_history_adds_lock = self.controller._pending_history_adds_lock
|
||||
self._pending_gui_tasks_lock = self.controller._pending_gui_tasks_lock
|
||||
self._pending_dialog_lock = self.controller._pending_dialog_lock
|
||||
self._api_event_queue_lock = self.controller._api_event_queue_lock
|
||||
|
||||
self.config: dict[str, Any] = load_config()
|
||||
self.event_queue: events.AsyncEventQueue = events.AsyncEventQueue()
|
||||
self._loop: asyncio.AbstractEventLoop = asyncio.new_event_loop()
|
||||
self._loop_thread: threading.Thread = threading.Thread(target=self._run_event_loop, daemon=True)
|
||||
self._loop_thread.start()
|
||||
ai_cfg = self.config.get("ai", {})
|
||||
self._current_provider: str = ai_cfg.get("provider", "gemini")
|
||||
self._current_model: str = ai_cfg.get("model", "gemini-2.5-flash-lite")
|
||||
self.available_models: list[str] = []
|
||||
self.temperature: float = ai_cfg.get("temperature", 0.0)
|
||||
self.max_tokens: int = ai_cfg.get("max_tokens", 8192)
|
||||
self.history_trunc_limit: int = ai_cfg.get("history_trunc_limit", 8000)
|
||||
projects_cfg = self.config.get("projects", {})
|
||||
self.project_paths: list[str] = list(projects_cfg.get("paths", []))
|
||||
self.active_project_path: str = projects_cfg.get("active", "")
|
||||
self.project: dict[str, Any] = {}
|
||||
self.active_discussion: str = "main"
|
||||
self._load_active_project()
|
||||
self.files: list[str] = list(self.project.get("files", {}).get("paths", []))
|
||||
self.screenshots: list[str] = list(self.project.get("screenshots", {}).get("paths", []))
|
||||
disc_sec = self.project.get("discussion", {})
|
||||
self.disc_roles: list[str] = list(disc_sec.get("roles", list(DISC_ROLES)))
|
||||
self.active_discussion = disc_sec.get("active", "main")
|
||||
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries: list[dict[str, Any]] = _parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
self.ui_output_dir: str = self.project.get("output", {}).get("output_dir", "./md_gen")
|
||||
self.ui_files_base_dir: str = self.project.get("files", {}).get("base_dir", ".")
|
||||
self.ui_shots_base_dir: str = self.project.get("screenshots", {}).get("base_dir", ".")
|
||||
proj_meta = self.project.get("project", {})
|
||||
self.ui_project_git_dir: str = proj_meta.get("git_dir", "")
|
||||
self.ui_project_main_context: str = proj_meta.get("main_context", "")
|
||||
self.ui_project_system_prompt: str = proj_meta.get("system_prompt", "")
|
||||
self.ui_gemini_cli_path: str = self.project.get("gemini_cli", {}).get("binary_path", "gemini")
|
||||
self.ui_word_wrap: bool = proj_meta.get("word_wrap", True)
|
||||
self.ui_summary_only: bool = proj_meta.get("summary_only", False)
|
||||
self.ui_auto_add_history: bool = disc_sec.get("auto_add", False)
|
||||
self.ui_global_system_prompt: str = self.config.get("ai", {}).get("system_prompt", "")
|
||||
self.ui_ai_input: str = ""
|
||||
self.ui_disc_new_name_input: str = ""
|
||||
self.ui_disc_new_role_input: str = ""
|
||||
self.ui_epic_input: str = ""
|
||||
self.proposed_tracks: list[dict[str, Any]] = []
|
||||
self._show_track_proposal_modal: bool = False
|
||||
self.ui_new_track_name: str = ""
|
||||
self.ui_new_track_desc: str = ""
|
||||
self.ui_new_track_type: str = "feature"
|
||||
self.ui_conductor_setup_summary: str = ""
|
||||
self.ui_last_script_text: str = ""
|
||||
self.ui_last_script_output: str = ""
|
||||
self.ai_status: str = "idle"
|
||||
self.ai_response: str = ""
|
||||
self.last_md: str = ""
|
||||
self.last_md_path: Path | None = None
|
||||
self.last_file_items: list[Any] = []
|
||||
self.send_thread: threading.Thread | None = None
|
||||
self.models_thread: threading.Thread | None = None
|
||||
_default_windows = {
|
||||
"Context Hub": True,
|
||||
"Files & Media": True,
|
||||
"AI Settings": True,
|
||||
"MMA Dashboard": True,
|
||||
"Tier 1: Strategy": True,
|
||||
"Tier 2: Tech Lead": True,
|
||||
"Tier 3: Workers": True,
|
||||
"Tier 4: QA": True,
|
||||
"Discussion Hub": True,
|
||||
"Operations Hub": True,
|
||||
"Theme": True,
|
||||
"Log Management": False,
|
||||
"Diagnostics": False,
|
||||
}
|
||||
saved = self.config.get("gui", {}).get("show_windows", {})
|
||||
self.show_windows: dict[str, bool] = {k: saved.get(k, v) for k, v in _default_windows.items()}
|
||||
self.show_script_output: bool = False
|
||||
self.show_text_viewer: bool = False
|
||||
self.text_viewer_title: str = ""
|
||||
self.text_viewer_content: str = ""
|
||||
self._pending_dialog: ConfirmDialog | None = None
|
||||
self._pending_dialog_open: bool = False
|
||||
self._pending_actions: dict[str, ConfirmDialog] = {}
|
||||
self._pending_ask_dialog: bool = False
|
||||
self._ask_dialog_open: bool = False
|
||||
self._ask_request_id: str | None = None
|
||||
self._ask_tool_data: dict[str, Any] | None = None
|
||||
self.mma_step_mode: bool = False
|
||||
self.active_track: Track | None = None
|
||||
self.active_tickets: list[dict[str, Any]] = []
|
||||
self.active_tier: str | None = None
|
||||
self.ui_focus_agent: str | None = None
|
||||
self.mma_status: str = "idle"
|
||||
self._pending_mma_approval: dict[str, Any] | None = None
|
||||
self._mma_approval_open: bool = False
|
||||
self._mma_approval_edit_mode: bool = False
|
||||
self._mma_approval_payload: str = ""
|
||||
self._pending_mma_spawn: dict[str, Any] | None = None
|
||||
self._mma_spawn_open: bool = False
|
||||
self._mma_spawn_edit_mode: bool = False
|
||||
self._mma_spawn_prompt: str = ''
|
||||
self._mma_spawn_context: str = ''
|
||||
self.mma_tier_usage: dict[str, dict[str, Any]] = {
|
||||
"Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"},
|
||||
"Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview"},
|
||||
"Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
|
||||
"Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
|
||||
}
|
||||
self._tool_log: list[dict[str, Any]] = []
|
||||
self._comms_log: list[dict[str, Any]] = []
|
||||
self._pending_comms: list[dict[str, Any]] = []
|
||||
self._pending_tool_calls: list[dict[str, Any]] = []
|
||||
self._pending_history_adds: list[dict[str, Any]] = []
|
||||
self._trigger_blink: bool = False
|
||||
self._is_blinking: bool = False
|
||||
self._blink_start_time: float = 0.0
|
||||
self._trigger_script_blink: bool = False
|
||||
self._is_script_blinking: bool = False
|
||||
self._script_blink_start_time: float = 0.0
|
||||
self._scroll_disc_to_bottom: bool = False
|
||||
self._scroll_comms_to_bottom: bool = False
|
||||
self._scroll_tool_calls_to_bottom: bool = False
|
||||
self._pending_gui_tasks: list[dict[str, Any]] = []
|
||||
self.session_usage: dict[str, Any] = {"input_tokens": 0, "output_tokens": 0, "cache_read_input_tokens": 0, "cache_creation_input_tokens": 0, "last_latency": 0.0}
|
||||
self._gemini_cache_text: str = ""
|
||||
self._last_stable_md: str = ''
|
||||
self._token_stats: dict[str, Any] = {}
|
||||
self._token_stats_dirty: bool = False
|
||||
self.ui_disc_truncate_pairs: int = 2
|
||||
self.ui_auto_scroll_comms: bool = True
|
||||
self.ui_auto_scroll_tool_calls: bool = True
|
||||
agent_tools_cfg = self.project.get("agent", {}).get("tools", {})
|
||||
self.ui_agent_tools: dict[str, bool] = {t: agent_tools_cfg.get(t, True) for t in AGENT_TOOL_NAMES}
|
||||
self.tracks: list[dict[str, Any]] = []
|
||||
self._show_add_ticket_form: bool = False
|
||||
self.ui_new_ticket_id: str = ""
|
||||
self.ui_new_ticket_desc: str = ""
|
||||
self.ui_new_ticket_target: str = ""
|
||||
self.ui_new_ticket_deps: str = ""
|
||||
self._track_discussion_active: bool = False
|
||||
self.mma_streams: dict[str, str] = {}
|
||||
self._tier_stream_last_len: dict[str, int] = {}
|
||||
self.is_viewing_prior_session: bool = False
|
||||
self.prior_session_entries: list[dict[str, Any]] = []
|
||||
self.test_hooks_enabled: bool = ("--enable-test-hooks" in sys.argv) or (os.environ.get("SLOP_TEST_HOOKS") == "1")
|
||||
self.ui_manual_approve: bool = False
|
||||
self.perf_monitor: PerformanceMonitor = PerformanceMonitor()
|
||||
self.perf_history: dict[str, list[float]] = {"frame_time": [0.0]*100, "fps": [0.0]*100, "cpu": [0.0]*100, "input_lag": [0.0]*100}
|
||||
self._perf_last_update: float = 0.0
|
||||
self._autosave_interval: float = 60.0
|
||||
self._last_autosave: float = time.time()
|
||||
label = self.project.get("project", {}).get("name", "")
|
||||
session_logger.open_session(label=label)
|
||||
|
||||
self._prune_old_logs()
|
||||
self._init_ai_and_hooks()
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if name != 'controller' and hasattr(self, 'controller') and hasattr(self.controller, name):
|
||||
return getattr(self.controller, name)
|
||||
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
if name == 'controller':
|
||||
super().__setattr__(name, value)
|
||||
elif hasattr(self, 'controller') and hasattr(self.controller, name):
|
||||
setattr(self.controller, name, value)
|
||||
else:
|
||||
super().__setattr__(name, value)
|
||||
|
||||
def _prune_old_logs(self) -> None:
|
||||
"""Asynchronously prunes old insignificant logs on startup."""
|
||||
|
||||
@@ -638,33 +486,10 @@ class App:
|
||||
|
||||
def _cb_disc_create(self) -> None:
|
||||
nm = self.ui_disc_new_name_input.strip()
|
||||
if nm:
|
||||
if nm:
|
||||
self._create_discussion(nm)
|
||||
self.ui_disc_new_name_input = ""
|
||||
|
||||
def _load_active_project(self) -> None:
|
||||
if self.active_project_path and Path(self.active_project_path).exists():
|
||||
try:
|
||||
self.project = project_manager.load_project(self.active_project_path)
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"Failed to load project {self.active_project_path}: {e}")
|
||||
for pp in self.project_paths:
|
||||
if Path(pp).exists():
|
||||
try:
|
||||
self.project = project_manager.load_project(pp)
|
||||
self.active_project_path = pp
|
||||
return
|
||||
except Exception:
|
||||
continue
|
||||
self.project = project_manager.migrate_from_legacy_config(self.config)
|
||||
name = self.project.get("project", {}).get("name", "project")
|
||||
fallback_path = f"{name}.toml"
|
||||
project_manager.save_project(self.project, fallback_path)
|
||||
self.active_project_path = fallback_path
|
||||
if fallback_path not in self.project_paths:
|
||||
self.project_paths.append(fallback_path)
|
||||
|
||||
def _switch_project(self, path: str) -> None:
|
||||
if not Path(path).exists():
|
||||
self.ai_status = f"project file not found: {path}"
|
||||
@@ -690,7 +515,7 @@ class App:
|
||||
self.active_discussion = disc_sec.get("active", "main")
|
||||
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = _parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
self.disc_entries = parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
proj = self.project
|
||||
self.ui_output_dir = proj.get("output", {}).get("output_dir", "./md_gen")
|
||||
self.ui_files_base_dir = proj.get("files", {}).get("base_dir", ".")
|
||||
@@ -734,7 +559,7 @@ class App:
|
||||
track_history = project_manager.load_track_history(self.active_track.id, self.ui_files_base_dir)
|
||||
if track_history:
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = _parse_history_entries(track_history, self.disc_roles)
|
||||
self.disc_entries = parse_history_entries(track_history, self.disc_roles)
|
||||
|
||||
def _cb_load_track(self, track_id: str) -> None:
|
||||
state = project_manager.load_track_state(track_id, self.ui_files_base_dir)
|
||||
@@ -759,7 +584,7 @@ class App:
|
||||
history = project_manager.load_track_history(track_id, self.ui_files_base_dir)
|
||||
with self._disc_entries_lock:
|
||||
if history:
|
||||
self.disc_entries = _parse_history_entries(history, self.disc_roles)
|
||||
self.disc_entries = parse_history_entries(history, self.disc_roles)
|
||||
else:
|
||||
self.disc_entries = []
|
||||
self._recalculate_session_usage()
|
||||
@@ -803,7 +628,7 @@ class App:
|
||||
self._discussion_names_dirty = True
|
||||
disc_data = discussions[name]
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = _parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
self.disc_entries = parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
self.ai_status = f"discussion: {name}"
|
||||
|
||||
def _flush_disc_entries_to_project(self) -> None:
|
||||
@@ -2568,7 +2393,7 @@ class App:
|
||||
self._flush_disc_entries_to_project()
|
||||
history_strings = project_manager.load_track_history(self.active_track.id, self.ui_files_base_dir)
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = _parse_history_entries(history_strings, self.disc_roles)
|
||||
self.disc_entries = parse_history_entries(history_strings, self.disc_roles)
|
||||
self.ai_status = f"track discussion: {self.active_track.id}"
|
||||
else:
|
||||
self._flush_disc_entries_to_project()
|
||||
@@ -3617,6 +3442,22 @@ class App:
|
||||
|
||||
def run(self) -> None:
|
||||
"""Initializes the ImGui runner and starts the main application loop."""
|
||||
self.controller.start_services()
|
||||
self._loop = self.controller._loop
|
||||
self._loop_thread = self.controller._loop_thread
|
||||
if self._loop:
|
||||
self._loop.call_soon_threadsafe(lambda: self._loop.create_task(self._process_event_queue()))
|
||||
|
||||
async def queue_fallback() -> None:
|
||||
while True:
|
||||
try:
|
||||
self._process_pending_gui_tasks()
|
||||
self._process_pending_history_adds()
|
||||
except: pass
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
self._loop.call_soon_threadsafe(lambda: self._loop.create_task(queue_fallback()))
|
||||
|
||||
if "--headless" in sys.argv:
|
||||
print("Headless mode active")
|
||||
self._fetch_models(self.current_provider)
|
||||
@@ -1,6 +1,33 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import tomllib
|
||||
from src import project_manager
|
||||
|
||||
CONFIG_PATH: Path = Path('config.toml')
|
||||
DISC_ROLES: list[str] = ['User', 'AI', 'Vendor API', 'System']
|
||||
AGENT_TOOL_NAMES: list[str] = [
|
||||
"run_powershell", "read_file", "list_directory", "search_files", "get_file_summary",
|
||||
"web_search", "fetch_url", "py_get_skeleton", "py_get_code_outline", "get_file_slice",
|
||||
"py_get_definition", "py_get_signature", "py_get_class_summary", "py_get_var_declaration",
|
||||
"get_git_diff", "py_find_usages", "py_get_imports", "py_check_syntax", "py_get_hierarchy",
|
||||
"py_get_docstring", "get_tree", "get_ui_performance",
|
||||
# Mutating tools — disabled by default
|
||||
"set_file_slice", "py_update_definition", "py_set_signature", "py_set_var_declaration",
|
||||
]
|
||||
|
||||
def load_config() -> dict[str, Any]:
|
||||
with open(CONFIG_PATH, "rb") as f:
|
||||
return tomllib.load(f)
|
||||
|
||||
def parse_history_entries(history: list[str], roles: list[str] | None = None) -> list[dict[str, Any]]:
|
||||
known = roles if roles is not None else DISC_ROLES
|
||||
entries = []
|
||||
for raw in history:
|
||||
entry = project_manager.str_to_entry(raw, known)
|
||||
entries.append(entry)
|
||||
return entries
|
||||
|
||||
@dataclass
|
||||
class Ticket:
|
||||
17
task.toml
17
task.toml
@@ -1,17 +0,0 @@
|
||||
role = "tier3-worker"
|
||||
prompt = """TASK: Implement streaming support for the DeepSeek provider in ai_client.py and add failing tests.
|
||||
|
||||
INSTRUCTIONS:
|
||||
1. In @tests/test_deepseek_provider.py:
|
||||
- Add a test function 'test_deepseek_streaming' that mocks a streaming API response using 'requests.post(..., stream=True)'.
|
||||
- Use 'mock_response.iter_lines()' to simulate chunks of data.
|
||||
- Assert that 'ai_client.send()' correctly aggregates these chunks into a single string.
|
||||
|
||||
2. In @ai_client.py:
|
||||
- Modify the '_send_deepseek' function to use 'requests.post(..., stream=True)'.
|
||||
- Implement a loop to iterate over the response lines using 'iter_lines()'.
|
||||
- Aggregate the content from each chunk.
|
||||
- Ensure the aggregated content is added to the history and returned by the function.
|
||||
|
||||
OUTPUT: Provide the raw Python code for the modified sections or the full files. No pleasantries."""
|
||||
docs = ["conductor/workflow.md", "ai_client.py", "tests/test_deepseek_provider.py", "mcp_client.py"]
|
||||
@@ -12,8 +12,9 @@ from pathlib import Path
|
||||
from typing import Generator, Any
|
||||
from unittest.mock import patch
|
||||
|
||||
# Ensure project root is in path for imports
|
||||
# Ensure project root and src/ are in path for imports
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
# Import the App class after patching if necessary, but here we just need the type hint
|
||||
from gui_2 import App
|
||||
@@ -161,10 +162,10 @@ def app_instance() -> Generator[App, None, None]:
|
||||
@pytest.fixture(scope="session")
|
||||
def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]:
|
||||
"""
|
||||
Session-scoped fixture that starts gui_2.py with --enable-test-hooks.
|
||||
Session-scoped fixture that starts sloppy.py with --enable-test-hooks.
|
||||
Includes high-signal environment telemetry and workspace isolation.
|
||||
"""
|
||||
gui_script = os.path.abspath("gui_2.py")
|
||||
gui_script = os.path.abspath("sloppy.py")
|
||||
diag = VerificationLogger("live_gui_startup", "live_gui_diag")
|
||||
diag.log_state("GUI Script", "N/A", "gui_2.py")
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from unittest.mock import patch, MagicMock
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
import ai_client
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import ai_client
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from ai_client import set_agent_tools, _build_anthropic_tools
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
|
||||
# Ensure project root is in path for imports
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from unittest.mock import patch
|
||||
|
||||
# Ensure project root is in path for imports
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import os
|
||||
|
||||
# Add project root to sys.path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
# Import after path fix
|
||||
from scripts.cli_tool_bridge import main
|
||||
|
||||
@@ -7,6 +7,7 @@ import os
|
||||
|
||||
# Add project root to sys.path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
# Import after path fix
|
||||
from scripts.cli_tool_bridge import main
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Any
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import sys
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
import ai_client
|
||||
import project_manager
|
||||
|
||||
@@ -6,6 +6,7 @@ import os
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
from simulation.sim_context import ContextSimulation
|
||||
|
||||
@@ -8,6 +8,7 @@ import os
|
||||
|
||||
# Ensure the project root is in sys.path to resolve imports correctly
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from gemini_cli_adapter import GeminiCliAdapter
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
# Import the necessary functions from ai_client, including the reset helper
|
||||
from ai_client import get_gemini_cache_stats, reset_session
|
||||
|
||||
@@ -8,6 +8,7 @@ import sys
|
||||
|
||||
# Ensure project root is in path for imports
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
# Define a temporary file path for callback testing
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Any
|
||||
|
||||
# Ensure project root is in path for imports
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
|
||||
def test_diagnostics_panel_initialization(app_instance: Any) -> None:
|
||||
|
||||
@@ -5,6 +5,7 @@ from gui_2 import App
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
def test_gui_updates_on_event(app_instance: App) -> None:
|
||||
app_instance.last_md = "mock_md"
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Any
|
||||
|
||||
# Ensure project root is in path for imports
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from gui_2 import App
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from pathlib import Path
|
||||
|
||||
# Ensure project root is in path for imports
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
# Import necessary modules from the project
|
||||
import aggregate
|
||||
|
||||
@@ -4,6 +4,7 @@ from unittest.mock import patch
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from gui_2 import App
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from unittest.mock import patch
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
import mcp_client
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import time
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from performance_monitor import PerformanceMonitor
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from simulation.sim_ai_settings import AISettingsSimulation
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from simulation.sim_base import BaseSimulation
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from simulation.sim_context import ContextSimulation
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from simulation.sim_execution import ExecutionSimulation
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from simulation.sim_tools import ToolsSimulation
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from types import SimpleNamespace
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
import ai_client
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import os
|
||||
|
||||
# Ensure project root is in path for imports
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from simulation.user_agent import UserSimAgent
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
import json
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from api_hook_client import ApiHookClient
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from simulation.workflow_sim import WorkflowSimulator
|
||||
|
||||
|
||||
Reference in New Issue
Block a user