polish: make repo presentable for employer review (#162)
This commit is contained in:
committed by
GitHub
parent
1de97619e8
commit
a927241dbe
@@ -14,8 +14,8 @@
|
|||||||
# In production (docker-compose.prod.yml), this is set to http://ollama:11434 automatically.
|
# In production (docker-compose.prod.yml), this is set to http://ollama:11434 automatically.
|
||||||
# OLLAMA_URL=http://localhost:11434
|
# OLLAMA_URL=http://localhost:11434
|
||||||
|
|
||||||
# LLM model to use via Ollama (default: llama3.2)
|
# LLM model to use via Ollama (default: qwen2.5:14b)
|
||||||
# OLLAMA_MODEL=llama3.2
|
# OLLAMA_MODEL=qwen2.5:14b
|
||||||
|
|
||||||
# Enable FastAPI interactive docs at /docs and /redoc (default: false)
|
# Enable FastAPI interactive docs at /docs and /redoc (default: false)
|
||||||
# DEBUG=true
|
# DEBUG=true
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# Timmy Time — Mission Control
|
# Timmy Time — Mission Control
|
||||||
|
|
||||||
[](https://github.com/AlexanderWhitestone/Timmy-time-dashboard/actions/workflows/tests.yml)
|
[](https://github.com/AlexanderWhitestone/Timmy-time-dashboard/actions/workflows/tests.yml)
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
A local-first, sovereign AI agent system. Talk to Timmy, watch his swarm, gate
|
A local-first, sovereign AI agent system. Talk to Timmy, watch his swarm, gate
|
||||||
API access with Bitcoin Lightning — all from a browser, no cloud AI required.
|
API access with Bitcoin Lightning — all from a browser, no cloud AI required.
|
||||||
@@ -66,7 +69,7 @@ make help # see all commands
|
|||||||
| [CLAUDE.md](CLAUDE.md) | AI assistant development guide |
|
| [CLAUDE.md](CLAUDE.md) | AI assistant development guide |
|
||||||
| [AGENTS.md](AGENTS.md) | Multi-agent development standards |
|
| [AGENTS.md](AGENTS.md) | Multi-agent development standards |
|
||||||
| [.env.example](.env.example) | Configuration reference |
|
| [.env.example](.env.example) | Configuration reference |
|
||||||
| [docs/](docs/) | Architecture docs, ADRs, audits |
|
| [docs/](docs/) | Architecture, ADRs, security audit, roadmap |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -79,7 +82,7 @@ cp .env.example .env
|
|||||||
| Variable | Default | Purpose |
|
| Variable | Default | Purpose |
|
||||||
|----------|---------|---------|
|
|----------|---------|---------|
|
||||||
| `OLLAMA_URL` | `http://localhost:11434` | Ollama host |
|
| `OLLAMA_URL` | `http://localhost:11434` | Ollama host |
|
||||||
| `OLLAMA_MODEL` | `llama3.1:8b-instruct` | Model for tool calling. Use llama3.1:8b-instruct for reliable tool use; fallback to qwen2.5:14b |
|
| `OLLAMA_MODEL` | `qwen2.5:14b` | Primary model for reasoning and tool calling. Fallback: `llama3.1:8b-instruct` |
|
||||||
| `DEBUG` | `false` | Enable `/docs` and `/redoc` |
|
| `DEBUG` | `false` | Enable `/docs` and `/redoc` |
|
||||||
| `TIMMY_MODEL_BACKEND` | `ollama` | `ollama` \| `airllm` \| `auto` |
|
| `TIMMY_MODEL_BACKEND` | `ollama` | `ollama` \| `airllm` \| `auto` |
|
||||||
| `AIRLLM_MODEL_SIZE` | `70b` | `8b` \| `70b` \| `405b` |
|
| `AIRLLM_MODEL_SIZE` | `70b` | `8b` \| `70b` \| `405b` |
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
# Changelog — 2025-02-27
|
|
||||||
|
|
||||||
## Model Upgrade & Hallucination Fix
|
|
||||||
|
|
||||||
### Change 1: Model Upgrade (Primary Fix)
|
|
||||||
**Problem:** llama3.2 (3B parameters) consistently hallucinated tool output instead of waiting for real results.
|
|
||||||
|
|
||||||
**Solution:** Upgraded default model to `llama3.1:8b-instruct` which is specifically fine-tuned for reliable tool/function calling.
|
|
||||||
|
|
||||||
**Changes:**
|
|
||||||
- `src/config.py`: Changed `ollama_model` default from `llama3.2` to `llama3.1:8b-instruct`
|
|
||||||
- Added fallback logic: if primary model unavailable, auto-fallback to `qwen2.5:14b`
|
|
||||||
- `README.md`: Updated setup instructions with new model requirement
|
|
||||||
|
|
||||||
**User Action Required:**
|
|
||||||
```bash
|
|
||||||
ollama pull llama3.1:8b-instruct
|
|
||||||
```
|
|
||||||
|
|
||||||
### Change 2: Structured Output Enforcement (Foundation)
|
|
||||||
**Preparation:** Added infrastructure for two-phase tool calling with JSON schema enforcement.
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- Session context tracking in `TimmyOrchestrator`
|
|
||||||
- `_session_init()` runs on first message to load real data
|
|
||||||
|
|
||||||
### Change 3: Git Tool Working Directory Fix
|
|
||||||
**Problem:** Git tools failed with "fatal: Not a git repository" due to wrong working directory.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- Rewrote `src/tools/git_tools.py` to use subprocess with explicit `cwd=REPO_ROOT`
|
|
||||||
- Added `REPO_ROOT` module-level constant auto-detected at import time
|
|
||||||
- All git commands now run from the correct directory
|
|
||||||
|
|
||||||
### Change 4: Session Init with Git Log
|
|
||||||
**Problem:** Timmy couldn't answer "what's new?" from real data.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- `_session_init()` now reads `git log --oneline -15` from repo root on first message
|
|
||||||
- Recent commits prepended to system prompt
|
|
||||||
- Timmy now grounds self-description in actual commit history
|
|
||||||
|
|
||||||
### Change 5: Documentation Updates
|
|
||||||
- `README.md`: Updated Quickstart with new model requirement
|
|
||||||
- `README.md`: Configuration table reflects new default model
|
|
||||||
- Added notes explaining why llama3.1:8b-instruct is required
|
|
||||||
|
|
||||||
### Files Modified
|
|
||||||
- `src/config.py` — Model configuration with fallback
|
|
||||||
- `src/tools/git_tools.py` — Complete rewrite with subprocess + cwd
|
|
||||||
- `src/agents/timmy.py` — Session init with git log reading
|
|
||||||
- `README.md` — Updated setup and configuration docs
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
- All git tool tests pass with new subprocess implementation
|
|
||||||
- Git log correctly returns commits from repo root
|
|
||||||
- Session init loads context on first message
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
# Timmy Time — Implementation Summary
|
|
||||||
|
|
||||||
**Date:** 2026-02-25
|
|
||||||
**Phase:** 1, 2 Complete (MCP, Event Bus, Agents)
|
|
||||||
**Status:** ✅ Ready for Phase 3 (Cascade Router)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What Was Built
|
|
||||||
|
|
||||||
### 1. MCP (Model Context Protocol) ✅
|
|
||||||
|
|
||||||
**Location:** `src/mcp/`
|
|
||||||
|
|
||||||
| Component | Purpose | Status |
|
|
||||||
|-----------|---------|--------|
|
|
||||||
| Registry | Tool catalog with health tracking | ✅ Complete |
|
|
||||||
| Server | MCP protocol implementation | ✅ Complete |
|
|
||||||
| Schemas | JSON schema utilities | ✅ Complete |
|
|
||||||
| Bootstrap | Auto-load all tools | ✅ Complete |
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- 6 tools registered with full schemas
|
|
||||||
- Health tracking (healthy/degraded/unhealthy)
|
|
||||||
- Metrics collection (latency, error rates)
|
|
||||||
- Pattern-based discovery
|
|
||||||
- `@register_tool` decorator
|
|
||||||
|
|
||||||
**Tools Implemented:**
|
|
||||||
```python
|
|
||||||
web_search # DuckDuckGo search
|
|
||||||
read_file # File reading
|
|
||||||
write_file # File writing (with confirmation)
|
|
||||||
list_directory # Directory listing
|
|
||||||
python # Python execution
|
|
||||||
memory_search # Vector memory search
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Event Bus ✅
|
|
||||||
|
|
||||||
**Location:** `src/events/bus.py`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Async publish/subscribe
|
|
||||||
- Wildcard pattern matching (`agent.task.*`)
|
|
||||||
- Event history (last 1000 events)
|
|
||||||
- Concurrent handler execution
|
|
||||||
- System-wide singleton
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
```python
|
|
||||||
from events.bus import event_bus, Event
|
|
||||||
|
|
||||||
@event_bus.subscribe("agent.task.*")
|
|
||||||
async def handle_task(event):
|
|
||||||
print(f"Task: {event.data}")
|
|
||||||
|
|
||||||
await event_bus.publish(Event(
|
|
||||||
type="agent.task.assigned",
|
|
||||||
source="timmy",
|
|
||||||
data={"task_id": "123"}
|
|
||||||
))
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Sub-Agents ✅
|
|
||||||
|
|
||||||
**Location:** `src/agents/`
|
|
||||||
|
|
||||||
| Agent | ID | Role | Key Tools |
|
|
||||||
|-------|-----|------|-----------|
|
|
||||||
| Seer | seer | Research | web_search, read_file, memory_search |
|
|
||||||
| Forge | forge | Code | python, write_file, read_file |
|
|
||||||
| Quill | quill | Writing | write_file, read_file, memory_search |
|
|
||||||
| Echo | echo | Memory | memory_search, read_file, write_file |
|
|
||||||
| Helm | helm | Routing | memory_search |
|
|
||||||
| Timmy | timmy | Orchestrator | All tools |
|
|
||||||
|
|
||||||
**BaseAgent Features:**
|
|
||||||
- Agno Agent integration
|
|
||||||
- MCP tool registry access
|
|
||||||
- Event bus connectivity
|
|
||||||
- Structured logging
|
|
||||||
- Task execution framework
|
|
||||||
|
|
||||||
**Orchestrator Logic:**
|
|
||||||
```python
|
|
||||||
timmy = create_timmy_swarm()
|
|
||||||
|
|
||||||
# Automatic routing:
|
|
||||||
# - Simple questions → Direct response
|
|
||||||
# - "Remember..." → Echo agent
|
|
||||||
# - Complex tasks → Helm routes to specialist
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Memory System (Previously Complete) ✅
|
|
||||||
|
|
||||||
**Three-Tier Architecture:**
|
|
||||||
|
|
||||||
```
|
|
||||||
Tier 1: Hot Memory (MEMORY.md)
|
|
||||||
↓ Always loaded
|
|
||||||
|
|
||||||
Tier 2: Vault (memory/)
|
|
||||||
├── self/identity.md
|
|
||||||
├── self/user_profile.md
|
|
||||||
├── self/methodology.md
|
|
||||||
├── notes/*.md
|
|
||||||
└── aar/*.md
|
|
||||||
|
|
||||||
Tier 3: Semantic Search
|
|
||||||
└── Vector embeddings over vault
|
|
||||||
```
|
|
||||||
|
|
||||||
**Handoff Protocol:**
|
|
||||||
- `last-session-handoff.md` written at session end
|
|
||||||
- Auto-loaded at next session start
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture Diagram
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|
||||||
│ USER INTERFACE │
|
|
||||||
│ (Dashboard/CLI) │
|
|
||||||
└──────────────────────────┬──────────────────────────────────┘
|
|
||||||
│
|
|
||||||
┌──────────────────────────▼──────────────────────────────────┐
|
|
||||||
│ TIMMY ORCHESTRATOR │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
||||||
│ │ Request │ │ Router │ │ Response │ │
|
|
||||||
│ │ Analysis │→ │ (Helm) │→ │ Synthesis │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
||||||
│ │
|
|
||||||
└──────────────────────────┬──────────────────────────────────┘
|
|
||||||
│
|
|
||||||
┌──────────────────┼──────────────────┐
|
|
||||||
│ │ │
|
|
||||||
┌───────▼──────┐ ┌───────▼──────┐ ┌───────▼──────┐
|
|
||||||
│ Seer │ │ Forge │ │ Quill │
|
|
||||||
│ (Research) │ │ (Code) │ │ (Writing) │
|
|
||||||
└──────────────┘ └──────────────┘ └──────────────┘
|
|
||||||
│
|
|
||||||
┌───────▼──────┐ ┌───────▼──────┐
|
|
||||||
│ Echo │ │ Helm │
|
|
||||||
│ (Memory) │ │ (Routing) │
|
|
||||||
└──────────────┘ └──────────────┘
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|
||||||
│ MCP TOOL REGISTRY │
|
|
||||||
│ │
|
|
||||||
│ web_search read_file write_file list_directory │
|
|
||||||
│ python memory_search │
|
|
||||||
│ │
|
|
||||||
└──────────────────────────┬──────────────────────────────────┘
|
|
||||||
│
|
|
||||||
┌──────────────────────────▼──────────────────────────────────┐
|
|
||||||
│ EVENT BUS │
|
|
||||||
│ (Async pub/sub, wildcard patterns) │
|
|
||||||
└──────────────────────────┬──────────────────────────────────┘
|
|
||||||
│
|
|
||||||
┌──────────────────────────▼──────────────────────────────────┐
|
|
||||||
│ MEMORY SYSTEM │
|
|
||||||
│ │
|
|
||||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
||||||
│ │ Hot │ │ Vault │ │ Semantic │ │
|
|
||||||
│ │ MEMORY │ │ Files │ │ Search │ │
|
|
||||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
||||||
│ │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Results
|
|
||||||
|
|
||||||
```
|
|
||||||
All 973 tests pass ✅
|
|
||||||
|
|
||||||
Manual verification:
|
|
||||||
- MCP Bootstrap: ✅ 6 tools loaded
|
|
||||||
- Tool Registry: ✅ web_search, file_ops, etc.
|
|
||||||
- Event Bus: ✅ Events published/subscribed
|
|
||||||
- Agent Imports: ✅ All agents loadable
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Created
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── mcp/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── bootstrap.py # Auto-load tools
|
|
||||||
│ ├── registry.py # Tool catalog
|
|
||||||
│ ├── server.py # MCP protocol
|
|
||||||
│ └── schemas/
|
|
||||||
│ └── base.py # Schema utilities
|
|
||||||
│
|
|
||||||
├── tools/
|
|
||||||
│ ├── web_search.py # DuckDuckGo search
|
|
||||||
│ ├── file_ops.py # File operations
|
|
||||||
│ ├── code_exec.py # Python execution
|
|
||||||
│ └── memory_tool.py # Memory search
|
|
||||||
│
|
|
||||||
├── events/
|
|
||||||
│ └── bus.py # Event bus
|
|
||||||
│
|
|
||||||
└── agents/
|
|
||||||
├── __init__.py
|
|
||||||
├── base.py # Base agent class
|
|
||||||
├── timmy.py # Orchestrator
|
|
||||||
├── seer.py # Research
|
|
||||||
├── forge.py # Code
|
|
||||||
├── quill.py # Writing
|
|
||||||
├── echo.py # Memory
|
|
||||||
└── helm.py # Routing
|
|
||||||
|
|
||||||
MEMORY.md # Hot memory
|
|
||||||
memory/ # Vault structure
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage Example
|
|
||||||
|
|
||||||
```python
|
|
||||||
from agents import create_timmy_swarm
|
|
||||||
|
|
||||||
# Create fully configured Timmy
|
|
||||||
timmy = create_timmy_swarm()
|
|
||||||
|
|
||||||
# Simple chat (handles directly)
|
|
||||||
response = await timmy.orchestrate("What is your name?")
|
|
||||||
|
|
||||||
# Research (routes to Seer)
|
|
||||||
response = await timmy.orchestrate("Search for Bitcoin news")
|
|
||||||
|
|
||||||
# Code (routes to Forge)
|
|
||||||
response = await timmy.orchestrate("Write a Python script to...")
|
|
||||||
|
|
||||||
# Memory (routes to Echo)
|
|
||||||
response = await timmy.orchestrate("What did we discuss yesterday?")
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next: Phase 3 (Cascade Router)
|
|
||||||
|
|
||||||
To complete the brief, implement:
|
|
||||||
|
|
||||||
### 1. Cascade LLM Router
|
|
||||||
```yaml
|
|
||||||
# config/providers.yaml
|
|
||||||
providers:
|
|
||||||
- name: ollama-local
|
|
||||||
type: ollama
|
|
||||||
url: http://localhost:11434
|
|
||||||
priority: 1
|
|
||||||
models: [llama3.2, deepseek-r1]
|
|
||||||
|
|
||||||
- name: openai-backup
|
|
||||||
type: openai
|
|
||||||
api_key: ${OPENAI_API_KEY}
|
|
||||||
priority: 2
|
|
||||||
models: [gpt-4o-mini]
|
|
||||||
```
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- Priority-ordered fallback
|
|
||||||
- Latency/error tracking
|
|
||||||
- Cost accounting
|
|
||||||
- Health checks
|
|
||||||
|
|
||||||
### 2. Self-Upgrade Loop
|
|
||||||
- Detect failures from logs
|
|
||||||
- Propose fixes via Forge
|
|
||||||
- Present to user for approval
|
|
||||||
- Apply changes with rollback
|
|
||||||
|
|
||||||
### 3. Dashboard Integration
|
|
||||||
- Tool registry browser
|
|
||||||
- Agent activity feed
|
|
||||||
- Memory browser
|
|
||||||
- Upgrade queue
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Criteria Status
|
|
||||||
|
|
||||||
| Criteria | Status |
|
|
||||||
|----------|--------|
|
|
||||||
| Start with `python main.py` | 🟡 Need entry point |
|
|
||||||
| Dashboard at localhost | ✅ Exists |
|
|
||||||
| Timmy responds to questions | ✅ Working |
|
|
||||||
| Routes to sub-agents | ✅ Implemented |
|
|
||||||
| MCP tool discovery | ✅ Working |
|
|
||||||
| LLM failover | 🟡 Phase 3 |
|
|
||||||
| Search memory | ✅ Working |
|
|
||||||
| Self-upgrade proposals | 🟡 Phase 3 |
|
|
||||||
| Lightning payments | ✅ Mock exists |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Key Achievements
|
|
||||||
|
|
||||||
1. ✅ **MCP Protocol** — Full implementation with schemas, registry, server
|
|
||||||
2. ✅ **6 Production Tools** — All with error handling and health tracking
|
|
||||||
3. ✅ **Event Bus** — Async pub/sub for agent communication
|
|
||||||
4. ✅ **6 Agents** — Full roster with specialized roles
|
|
||||||
5. ✅ **Orchestrator** — Intelligent routing logic
|
|
||||||
6. ✅ **Memory System** — Three-tier architecture
|
|
||||||
7. ✅ **All Tests Pass** — No regressions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Ready for Phase 3
|
|
||||||
|
|
||||||
The foundation is solid. Next steps:
|
|
||||||
1. Cascade Router for LLM failover
|
|
||||||
2. Self-upgrade loop
|
|
||||||
3. Enhanced dashboard views
|
|
||||||
4. Production hardening
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
# Implementation Summary: 3 New Features
|
|
||||||
|
|
||||||
## Completed Features
|
|
||||||
|
|
||||||
### 1. Cascade Router Integration ✅
|
|
||||||
|
|
||||||
**Files Created:**
|
|
||||||
- `src/timmy/cascade_adapter.py` - Adapter between Timmy and Cascade Router
|
|
||||||
- `src/dashboard/routes/router.py` - Dashboard routes for router status
|
|
||||||
- `src/dashboard/templates/router_status.html` - Router status UI
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `src/dashboard/app.py` - Registered router routes
|
|
||||||
- `src/dashboard/templates/base.html` - Added ROUTER nav link
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
```python
|
|
||||||
from timmy.cascade_adapter import get_cascade_adapter
|
|
||||||
adapter = get_cascade_adapter()
|
|
||||||
response = await adapter.chat("Hello")
|
|
||||||
print(f"Response: {response.content}")
|
|
||||||
print(f"Provider: {response.provider_used}")
|
|
||||||
```
|
|
||||||
|
|
||||||
**Dashboard:** `/router/status`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Self-Upgrade Approval Queue *(originally implemented, since refactored)*
|
|
||||||
|
|
||||||
> **Note:** The original `src/upgrades/` module was planned for consolidation into
|
|
||||||
> `src/self_coding/` but that consolidation was never completed. The module paths
|
|
||||||
> below reflect the original implementation and may no longer be accurate.
|
|
||||||
|
|
||||||
**Original files:**
|
|
||||||
- `src/upgrades/models.py` - Database models for upgrades table
|
|
||||||
- `src/upgrades/queue.py` - Queue management logic
|
|
||||||
- `src/dashboard/routes/upgrades.py` - Dashboard routes
|
|
||||||
- `src/dashboard/templates/upgrade_queue.html` - Queue UI
|
|
||||||
|
|
||||||
**Dashboard:** `/self-modify/queue`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Real-Time Activity Feed *(originally implemented, since refactored)*
|
|
||||||
|
|
||||||
> **Note:** The original module paths below reflect the pre-refactoring structure.
|
|
||||||
> Events are now under `src/infrastructure/events/`, WebSocket manager is now under
|
|
||||||
> `src/infrastructure/ws_manager/`.
|
|
||||||
|
|
||||||
**Original files:**
|
|
||||||
- `src/events/broadcaster.py` → now `src/infrastructure/events/broadcaster.py`
|
|
||||||
- `src/ws_manager/handler.py` → now `src/infrastructure/ws_manager/handler.py`
|
|
||||||
|
|
||||||
**Architecture:**
|
|
||||||
```
|
|
||||||
Event Occurs → log_event() → SQLite
|
|
||||||
↓
|
|
||||||
event_broadcaster.broadcast_sync()
|
|
||||||
↓
|
|
||||||
ws_manager.broadcast_json()
|
|
||||||
↓
|
|
||||||
Dashboard (WebSocket)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Dashboard:** `/swarm/live` (activity feed panel)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Test Results
|
|
||||||
|
|
||||||
**Unit Tests:** 101 passed
|
|
||||||
```
|
|
||||||
tests/test_event_log.py 25 passed
|
|
||||||
tests/test_ledger.py 18 passed
|
|
||||||
tests/test_vector_store.py 11 passed
|
|
||||||
tests/test_swarm.py 29 passed
|
|
||||||
tests/test_dashboard.py 18 passed
|
|
||||||
```
|
|
||||||
|
|
||||||
**E2E Tests:** Created (3 new test files)
|
|
||||||
- `tests/functional/test_cascade_router_e2e.py`
|
|
||||||
- `tests/functional/test_upgrade_queue_e2e.py`
|
|
||||||
- `tests/functional/test_activity_feed_e2e.py`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Running E2E Tests (Non-Headless)
|
|
||||||
|
|
||||||
Watch the browser execute tests in real-time:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Start the server
|
|
||||||
cd /Users/apayne/Timmy-time-dashboard
|
|
||||||
source .venv/bin/activate
|
|
||||||
make dev
|
|
||||||
|
|
||||||
# 2. In another terminal, run E2E tests
|
|
||||||
source .venv/bin/activate
|
|
||||||
SELENIUM_UI=1 pytest tests/functional/test_cascade_router_e2e.py -v --headed
|
|
||||||
|
|
||||||
# Or run all E2E tests
|
|
||||||
SELENIUM_UI=1 pytest tests/functional/ -v --headed
|
|
||||||
```
|
|
||||||
|
|
||||||
The `--headed` flag runs Chrome in visible mode so you can watch.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Database Schema Updates
|
|
||||||
|
|
||||||
Three new tables created automatically:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Event Log (existing, now with broadcast)
|
|
||||||
CREATE TABLE event_log (...);
|
|
||||||
|
|
||||||
-- Lightning Ledger (existing)
|
|
||||||
CREATE TABLE ledger (...);
|
|
||||||
|
|
||||||
-- Vector Store (existing)
|
|
||||||
CREATE TABLE memory_entries (...);
|
|
||||||
|
|
||||||
-- NEW: Upgrade Queue
|
|
||||||
CREATE TABLE upgrades (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
status TEXT NOT NULL,
|
|
||||||
proposed_at TEXT NOT NULL,
|
|
||||||
branch_name TEXT NOT NULL,
|
|
||||||
description TEXT NOT NULL,
|
|
||||||
files_changed TEXT,
|
|
||||||
diff_preview TEXT,
|
|
||||||
test_passed INTEGER DEFAULT 0,
|
|
||||||
test_output TEXT,
|
|
||||||
error_message TEXT,
|
|
||||||
approved_by TEXT
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Navigation Updates
|
|
||||||
|
|
||||||
New nav links in dashboard header:
|
|
||||||
- **EVENTS** → `/swarm/events`
|
|
||||||
- **LEDGER** → `/lightning/ledger`
|
|
||||||
- **MEMORY** → `/memory`
|
|
||||||
- **ROUTER** → `/router/status`
|
|
||||||
- **UPGRADES** → `/self-modify/queue`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture Alignment
|
|
||||||
|
|
||||||
All 3 features follow existing patterns:
|
|
||||||
- **Singleton pattern** for services (cascade_adapter, event_broadcaster)
|
|
||||||
- **SQLite persistence** through consistent DB access pattern
|
|
||||||
- **Dashboard routes** following existing route structure
|
|
||||||
- **Jinja2 templates** extending base.html
|
|
||||||
- **Event-driven** using existing event log infrastructure
|
|
||||||
- **WebSocket** using existing ws_manager
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
| Feature | Risk | Mitigation |
|
|
||||||
|---------|------|------------|
|
|
||||||
| Cascade Router | API key exposure | Uses existing config system |
|
|
||||||
| Upgrade Queue | Unauthorized changes | Human approval required |
|
|
||||||
| Activity Feed | Data leak | Events sanitized before broadcast |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Run E2E tests with `SELENIUM_UI=1 pytest tests/functional/ -v --headed`
|
|
||||||
2. Manually test each dashboard page
|
|
||||||
3. Verify WebSocket real-time updates in `/swarm/live`
|
|
||||||
4. Test upgrade queue workflow end-to-end
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,478 +0,0 @@
|
|||||||
# Plan: Full Creative & DevOps Capabilities for Timmy
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Add five major capability domains to Timmy's agent system, turning it into a
|
|
||||||
sovereign creative studio and full-stack DevOps operator. All tools are
|
|
||||||
open-source, self-hosted, and GPU-accelerated where needed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 1: Git & DevOps Tools (Forge + Helm personas)
|
|
||||||
|
|
||||||
**Goal:** Timmy can observe local/remote repos, read code, create branches,
|
|
||||||
stage changes, commit, diff, log, and manage PRs — all through the swarm
|
|
||||||
task system with Spark event capture.
|
|
||||||
|
|
||||||
### New module: `src/tools/git_tools.py`
|
|
||||||
|
|
||||||
Tools to add (using **GitPython** — BSD-3, `pip install GitPython`):
|
|
||||||
|
|
||||||
| Tool | Function | Persona Access |
|
|
||||||
|---|---|---|
|
|
||||||
| `git_clone` | Clone a remote repo to local path | Forge, Helm |
|
|
||||||
| `git_status` | Show working tree status | Forge, Helm, Timmy |
|
|
||||||
| `git_diff` | Show staged/unstaged diffs | Forge, Helm, Timmy |
|
|
||||||
| `git_log` | Show recent commit history | Forge, Helm, Echo, Timmy |
|
|
||||||
| `git_branch` | List/create/switch branches | Forge, Helm |
|
|
||||||
| `git_add` | Stage files for commit | Forge, Helm |
|
|
||||||
| `git_commit` | Create a commit with message | Forge, Helm |
|
|
||||||
| `git_push` | Push to remote | Forge, Helm |
|
|
||||||
| `git_pull` | Pull from remote | Forge, Helm |
|
|
||||||
| `git_blame` | Show line-by-line authorship | Forge, Echo |
|
|
||||||
| `git_stash` | Stash/pop changes | Forge, Helm |
|
|
||||||
|
|
||||||
### Changes to existing files
|
|
||||||
|
|
||||||
- **`src/timmy/tools.py`** — Add `create_git_tools()` factory, wire into
|
|
||||||
`PERSONA_TOOLKITS` for Forge and Helm
|
|
||||||
- **`src/swarm/tool_executor.py`** — Enhance `_infer_tools_needed()` with
|
|
||||||
git keywords (commit, branch, push, pull, diff, clone, merge)
|
|
||||||
- **`src/config.py`** — Add `git_default_repo_dir: str = "~/repos"` setting
|
|
||||||
- **`src/spark/engine.py`** — Add `on_tool_executed()` method to capture
|
|
||||||
individual tool invocations (not just task-level events)
|
|
||||||
- **`src/swarm/personas.py`** — Add git-related keywords to Forge and Helm
|
|
||||||
preferred_keywords
|
|
||||||
|
|
||||||
### New dependency
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# pyproject.toml
|
|
||||||
dependencies = [
|
|
||||||
...,
|
|
||||||
"GitPython>=3.1.40",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dashboard
|
|
||||||
|
|
||||||
- **`/tools`** page updated to show git tools in the catalog
|
|
||||||
- Git tool usage stats visible per agent
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- `tests/test_git_tools.py` — test all git tool functions against tmp repos
|
|
||||||
- Mock GitPython's `Repo` class for unit tests
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 2: Image Generation (new "Pixel" persona)
|
|
||||||
|
|
||||||
**Goal:** Generate storyboard frames and standalone images from text prompts
|
|
||||||
using FLUX.2 Klein 4B locally.
|
|
||||||
|
|
||||||
### New persona: Pixel — Visual Architect
|
|
||||||
|
|
||||||
```python
|
|
||||||
"pixel": {
|
|
||||||
"id": "pixel",
|
|
||||||
"name": "Pixel",
|
|
||||||
"role": "Visual Architect",
|
|
||||||
"description": "Image generation, storyboard frames, and visual design.",
|
|
||||||
"capabilities": "image-generation,storyboard,design",
|
|
||||||
"rate_sats": 80,
|
|
||||||
"bid_base": 60,
|
|
||||||
"bid_jitter": 20,
|
|
||||||
"preferred_keywords": [
|
|
||||||
"image", "picture", "photo", "draw", "illustration",
|
|
||||||
"storyboard", "frame", "visual", "design", "generate",
|
|
||||||
"portrait", "landscape", "scene", "artwork",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### New module: `src/tools/image_tools.py`
|
|
||||||
|
|
||||||
Tools (using **diffusers** + **FLUX.2 Klein 4B** — Apache 2.0):
|
|
||||||
|
|
||||||
| Tool | Function |
|
|
||||||
|---|---|
|
|
||||||
| `generate_image` | Text-to-image generation (returns file path) |
|
|
||||||
| `generate_storyboard` | Generate N frames from scene descriptions |
|
|
||||||
| `image_variations` | Generate variations of an existing image |
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
generate_image(prompt, width=1024, height=1024, steps=4)
|
|
||||||
→ loads FLUX.2 Klein via diffusers FluxPipeline
|
|
||||||
→ saves to data/images/{uuid}.png
|
|
||||||
→ returns path + metadata
|
|
||||||
```
|
|
||||||
|
|
||||||
- Model loaded lazily on first use, kept in memory for subsequent calls
|
|
||||||
- Falls back to CPU generation (slower) if no GPU
|
|
||||||
- Output saved to `data/images/` with metadata JSON sidecar
|
|
||||||
|
|
||||||
### New dependency (optional extra)
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[project.optional-dependencies]
|
|
||||||
creative = [
|
|
||||||
"diffusers>=0.30.0",
|
|
||||||
"transformers>=4.40.0",
|
|
||||||
"accelerate>=0.30.0",
|
|
||||||
"torch>=2.2.0",
|
|
||||||
"safetensors>=0.4.0",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Config
|
|
||||||
|
|
||||||
```python
|
|
||||||
# config.py additions
|
|
||||||
flux_model_id: str = "black-forest-labs/FLUX.2-klein-4b"
|
|
||||||
image_output_dir: str = "data/images"
|
|
||||||
image_default_steps: int = 4
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dashboard
|
|
||||||
|
|
||||||
- `/creative/ui` — new Creative Studio page (image gallery + generation form)
|
|
||||||
- HTMX-powered: submit prompt, poll for result, display inline
|
|
||||||
- Gallery view of all generated images with metadata
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- `tests/test_image_tools.py` — mock diffusers pipeline, test prompt handling,
|
|
||||||
file output, storyboard generation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 3: Music Generation (new "Lyra" persona)
|
|
||||||
|
|
||||||
**Goal:** Generate full songs with vocals, instrumentals, and lyrics using
|
|
||||||
ACE-Step 1.5 locally.
|
|
||||||
|
|
||||||
### New persona: Lyra — Sound Weaver
|
|
||||||
|
|
||||||
```python
|
|
||||||
"lyra": {
|
|
||||||
"id": "lyra",
|
|
||||||
"name": "Lyra",
|
|
||||||
"role": "Sound Weaver",
|
|
||||||
"description": "Music and song generation with vocals, instrumentals, and lyrics.",
|
|
||||||
"capabilities": "music-generation,vocals,composition",
|
|
||||||
"rate_sats": 90,
|
|
||||||
"bid_base": 70,
|
|
||||||
"bid_jitter": 20,
|
|
||||||
"preferred_keywords": [
|
|
||||||
"music", "song", "sing", "vocal", "instrumental",
|
|
||||||
"melody", "beat", "track", "compose", "lyrics",
|
|
||||||
"audio", "sound", "album", "remix",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### New module: `src/tools/music_tools.py`
|
|
||||||
|
|
||||||
Tools (using **ACE-Step 1.5** — Apache 2.0, `pip install ace-step`):
|
|
||||||
|
|
||||||
| Tool | Function |
|
|
||||||
|---|---|
|
|
||||||
| `generate_song` | Text/lyrics → full song (vocals + instrumentals) |
|
|
||||||
| `generate_instrumental` | Text prompt → instrumental track |
|
|
||||||
| `generate_vocals` | Lyrics + style → vocal track |
|
|
||||||
| `list_genres` | Return supported genre/style tags |
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
generate_song(lyrics, genre="pop", duration=120, language="en")
|
|
||||||
→ loads ACE-Step model (lazy, cached)
|
|
||||||
→ generates audio
|
|
||||||
→ saves to data/music/{uuid}.wav
|
|
||||||
→ returns path + metadata (duration, genre, etc.)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Model loaded lazily, ~4GB VRAM minimum
|
|
||||||
- Output saved to `data/music/` with metadata sidecar
|
|
||||||
- Supports 19 languages, genre tags, tempo control
|
|
||||||
|
|
||||||
### New dependency (optional extra, extends `creative`)
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[project.optional-dependencies]
|
|
||||||
creative = [
|
|
||||||
...,
|
|
||||||
"ace-step>=1.5.0",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Config
|
|
||||||
|
|
||||||
```python
|
|
||||||
music_output_dir: str = "data/music"
|
|
||||||
ace_step_model: str = "ace-step/ACE-Step-v1.5"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dashboard
|
|
||||||
|
|
||||||
- `/creative/ui` expanded with Music tab
|
|
||||||
- Audio player widget (HTML5 `<audio>` element)
|
|
||||||
- Lyrics input form with genre/style selector
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- `tests/test_music_tools.py` — mock ACE-Step model, test generation params
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 4: Video Generation (new "Reel" persona)
|
|
||||||
|
|
||||||
**Goal:** Generate video clips from text/image prompts using Wan 2.1 locally.
|
|
||||||
|
|
||||||
### New persona: Reel — Motion Director
|
|
||||||
|
|
||||||
```python
|
|
||||||
"reel": {
|
|
||||||
"id": "reel",
|
|
||||||
"name": "Reel",
|
|
||||||
"role": "Motion Director",
|
|
||||||
"description": "Video generation from text and image prompts.",
|
|
||||||
"capabilities": "video-generation,animation,motion",
|
|
||||||
"rate_sats": 100,
|
|
||||||
"bid_base": 80,
|
|
||||||
"bid_jitter": 20,
|
|
||||||
"preferred_keywords": [
|
|
||||||
"video", "clip", "animate", "motion", "film",
|
|
||||||
"scene", "cinematic", "footage", "render", "timelapse",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### New module: `src/tools/video_tools.py`
|
|
||||||
|
|
||||||
Tools (using **Wan 2.1** via diffusers — Apache 2.0):
|
|
||||||
|
|
||||||
| Tool | Function |
|
|
||||||
|---|---|
|
|
||||||
| `generate_video_clip` | Text → short video clip (3–6 seconds) |
|
|
||||||
| `image_to_video` | Image + prompt → animated video from still |
|
|
||||||
| `list_video_styles` | Return supported style presets |
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
generate_video_clip(prompt, duration=5, resolution="480p", fps=24)
|
|
||||||
→ loads Wan 2.1 via diffusers pipeline (lazy, cached)
|
|
||||||
→ generates frames
|
|
||||||
→ encodes to MP4 via FFmpeg
|
|
||||||
→ saves to data/video/{uuid}.mp4
|
|
||||||
→ returns path + metadata
|
|
||||||
```
|
|
||||||
|
|
||||||
- Wan 2.1 1.3B model: ~16GB VRAM
|
|
||||||
- Output saved to `data/video/`
|
|
||||||
- Resolution options: 480p (16GB), 720p (24GB+)
|
|
||||||
|
|
||||||
### New dependency (extends `creative` extra)
|
|
||||||
|
|
||||||
```toml
|
|
||||||
creative = [
|
|
||||||
...,
|
|
||||||
# Wan 2.1 uses diffusers (already listed) + model weights downloaded on first use
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Config
|
|
||||||
|
|
||||||
```python
|
|
||||||
video_output_dir: str = "data/video"
|
|
||||||
wan_model_id: str = "Wan-AI/Wan2.1-T2V-1.3B"
|
|
||||||
video_default_resolution: str = "480p"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- `tests/test_video_tools.py` — mock diffusers pipeline, test clip generation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 5: Creative Director — Storyboard & Assembly Pipeline
|
|
||||||
|
|
||||||
**Goal:** Orchestrate multi-persona workflows to produce 3+ minute creative
|
|
||||||
videos with music, narration, and stitched scenes.
|
|
||||||
|
|
||||||
### New module: `src/creative/director.py`
|
|
||||||
|
|
||||||
The Creative Director is a **multi-step pipeline** that coordinates Pixel,
|
|
||||||
Lyra, and Reel to produce complete creative works:
|
|
||||||
|
|
||||||
```
|
|
||||||
User: "Create a 3-minute music video about a sunrise over mountains"
|
|
||||||
│
|
|
||||||
Creative Director
|
|
||||||
┌─────────┼──────────┐
|
|
||||||
│ │ │
|
|
||||||
1. STORYBOARD 2. MUSIC 3. GENERATE
|
|
||||||
(Pixel) (Lyra) (Reel)
|
|
||||||
│ │ │
|
|
||||||
N scene Full song N video clips
|
|
||||||
descriptions with from storyboard
|
|
||||||
+ keyframes vocals frames
|
|
||||||
│ │ │
|
|
||||||
└─────────┼──────────┘
|
|
||||||
│
|
|
||||||
4. ASSEMBLE
|
|
||||||
(MoviePy + FFmpeg)
|
|
||||||
│
|
|
||||||
Final video with
|
|
||||||
music, transitions,
|
|
||||||
titles
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pipeline steps
|
|
||||||
|
|
||||||
1. **Script** — Timmy (or Quill) writes scene descriptions and lyrics
|
|
||||||
2. **Storyboard** — Pixel generates keyframe images for each scene
|
|
||||||
3. **Music** — Lyra generates the soundtrack (vocals + instrumentals)
|
|
||||||
4. **Video clips** — Reel generates video for each scene (image-to-video
|
|
||||||
from storyboard frames, or text-to-video from descriptions)
|
|
||||||
5. **Assembly** — MoviePy stitches clips together with cross-fades,
|
|
||||||
overlays the music track, adds title cards
|
|
||||||
|
|
||||||
### New module: `src/creative/assembler.py`
|
|
||||||
|
|
||||||
Video assembly engine (using **MoviePy** — MIT, `pip install moviepy`):
|
|
||||||
|
|
||||||
| Function | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| `stitch_clips` | Concatenate video clips with transitions |
|
|
||||||
| `overlay_audio` | Mix music track onto video |
|
|
||||||
| `add_title_card` | Prepend/append title/credits |
|
|
||||||
| `add_subtitles` | Burn lyrics/captions onto video |
|
|
||||||
| `export_final` | Encode final video (H.264 + AAC) |
|
|
||||||
|
|
||||||
### New dependency
|
|
||||||
|
|
||||||
```toml
|
|
||||||
dependencies = [
|
|
||||||
...,
|
|
||||||
"moviepy>=2.0.0",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Config
|
|
||||||
|
|
||||||
```python
|
|
||||||
creative_output_dir: str = "data/creative"
|
|
||||||
video_transition_duration: float = 1.0 # seconds
|
|
||||||
default_video_codec: str = "libx264"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dashboard
|
|
||||||
|
|
||||||
- `/creative/ui` — Full Creative Studio with tabs:
|
|
||||||
- **Images** — gallery + generation form
|
|
||||||
- **Music** — player + generation form
|
|
||||||
- **Video** — player + generation form
|
|
||||||
- **Director** — multi-step pipeline builder with storyboard view
|
|
||||||
- `/creative/projects` — saved projects with all assets
|
|
||||||
- `/creative/projects/{id}` — project detail with timeline view
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- `tests/test_assembler.py` — test stitching, audio overlay, title cards
|
|
||||||
- `tests/test_director.py` — test pipeline orchestration with mocks
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 6: Spark Integration for All New Tools
|
|
||||||
|
|
||||||
**Goal:** Every tool invocation and creative pipeline step gets captured by
|
|
||||||
Spark Intelligence for learning and advisory.
|
|
||||||
|
|
||||||
### Changes to `src/spark/engine.py`
|
|
||||||
|
|
||||||
```python
|
|
||||||
def on_tool_executed(
|
|
||||||
self, agent_id: str, tool_name: str,
|
|
||||||
task_id: Optional[str], success: bool,
|
|
||||||
duration_ms: Optional[int] = None,
|
|
||||||
) -> Optional[str]:
|
|
||||||
"""Capture individual tool invocations."""
|
|
||||||
|
|
||||||
def on_creative_step(
|
|
||||||
self, project_id: str, step_name: str,
|
|
||||||
agent_id: str, output_path: Optional[str],
|
|
||||||
) -> Optional[str]:
|
|
||||||
"""Capture creative pipeline progress."""
|
|
||||||
```
|
|
||||||
|
|
||||||
### New advisor patterns
|
|
||||||
|
|
||||||
- "Pixel generates storyboards 40% faster than individual image calls"
|
|
||||||
- "Lyra's pop genre tracks have 85% higher completion rate than jazz"
|
|
||||||
- "Video generation on 480p uses 60% less GPU time than 720p for similar quality"
|
|
||||||
- "Git commits from Forge average 3 files per commit"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Order
|
|
||||||
|
|
||||||
| Phase | What | New Files | Est. Tests |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 1 | Git/DevOps tools | 2 source + 1 test | ~25 |
|
|
||||||
| 2 | Image generation | 2 source + 1 test + 1 template | ~15 |
|
|
||||||
| 3 | Music generation | 1 source + 1 test | ~12 |
|
|
||||||
| 4 | Video generation | 1 source + 1 test | ~12 |
|
|
||||||
| 5 | Creative Director pipeline | 2 source + 2 tests + 1 template | ~20 |
|
|
||||||
| 6 | Spark tool-level capture | 1 modified + 1 test update | ~8 |
|
|
||||||
|
|
||||||
**Total: ~10 new source files, ~6 new test files, ~92 new tests**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## New Dependencies Summary
|
|
||||||
|
|
||||||
**Required (always installed):**
|
|
||||||
```
|
|
||||||
GitPython>=3.1.40
|
|
||||||
moviepy>=2.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
**Optional `creative` extra (GPU features):**
|
|
||||||
```
|
|
||||||
diffusers>=0.30.0
|
|
||||||
transformers>=4.40.0
|
|
||||||
accelerate>=0.30.0
|
|
||||||
torch>=2.2.0
|
|
||||||
safetensors>=0.4.0
|
|
||||||
ace-step>=1.5.0
|
|
||||||
```
|
|
||||||
|
|
||||||
**Install:** `pip install ".[creative]"` for full creative stack
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## New Persona Summary
|
|
||||||
|
|
||||||
| ID | Name | Role | Tools |
|
|
||||||
|---|---|---|---|
|
|
||||||
| pixel | Pixel | Visual Architect | generate_image, generate_storyboard, image_variations |
|
|
||||||
| lyra | Lyra | Sound Weaver | generate_song, generate_instrumental, generate_vocals |
|
|
||||||
| reel | Reel | Motion Director | generate_video_clip, image_to_video |
|
|
||||||
|
|
||||||
These join the existing 6 personas (Echo, Mace, Helm, Seer, Forge, Quill)
|
|
||||||
for a total of **9 specialized agents** in the swarm.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hardware Requirements
|
|
||||||
|
|
||||||
- **CPU only:** Git tools, MoviePy assembly, all tests (mocked)
|
|
||||||
- **8GB VRAM:** FLUX.2 Klein 4B (images)
|
|
||||||
- **4GB VRAM:** ACE-Step 1.5 (music)
|
|
||||||
- **16GB VRAM:** Wan 2.1 1.3B (video at 480p)
|
|
||||||
- **Recommended:** RTX 4090 24GB runs the entire stack comfortably
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
# Timmy Time — Senior Architect Quality Analysis
|
|
||||||
**Date:** 2026-02-21
|
|
||||||
**Branch:** `claude/quality-analysis-mobile-testing-0zgPi`
|
|
||||||
**Test Suite:** 228/228 passing ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
Timmy Time has a strong Python backend skeleton and a working HTMX UI, but the project is at a **critical architectural fork**: a second, fully-detached React frontend was introduced that uses 100% mock/static data with zero API connectivity. This split creates the illusion of a richer app than exists. Completeness against the stated vision is **~35-40%**. The mobile HITL framework is the standout quality asset.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Architecture Coherence — CRITICAL ⚠️
|
|
||||||
|
|
||||||
**Score: 3/10**
|
|
||||||
|
|
||||||
### Finding: Dual Frontend, Zero Integration
|
|
||||||
|
|
||||||
The project ships two separate UIs that both claim to be "Mission Control":
|
|
||||||
|
|
||||||
| UI | Tech | Backend Connected? |
|
|
||||||
|----|------|--------------------|
|
|
||||||
| `src/dashboard/` | FastAPI + Jinja2 + HTMX | ✅ Yes — real Timmy chat, health, history |
|
|
||||||
| `dashboard-web/` | React + TypeScript + Vite | ❌ No — 100% static mock data |
|
|
||||||
|
|
||||||
The React dashboard (`dashboard-web/client/src/lib/data.ts`) exports `MOCK_CHAT`, `MOCK_HEALTH`, `MOCK_NOTIFICATIONS`, `MOCK_TASKS`, `MOCK_WS_EVENTS` — every data source is hardcoded. There is **not a single `fetch()` call** to the FastAPI backend. The `ChatPanel` simulates responses with `setTimeout()`. The `StatusSidebar` shows a hardcoded Ollama status — it never calls `/health/status`.
|
|
||||||
|
|
||||||
**Impact:** The React UI is a clickable mockup, not a product. A new developer would not know which frontend is authoritative.
|
|
||||||
|
|
||||||
### Finding: React App Has No Build Config
|
|
||||||
|
|
||||||
`dashboard-web/client/` contains `src/` and `index.html` but no `package.json`, `vite.config.ts`, or `tsconfig.json` in that directory. The app imports from `@/components/ui/*` (shadcn/ui) but the `components/ui/` directory does not exist in the repo. The React app is **not buildable as committed**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Completeness Against Vision — 35-40%
|
|
||||||
|
|
||||||
**Score: 4/10**
|
|
||||||
|
|
||||||
| Feature | Roadmap | Status |
|
|
||||||
|---------|---------|--------|
|
|
||||||
| Agno + Ollama + SQLite dashboard | v1.0.0 | ✅ Complete |
|
|
||||||
| HTMX chat with history | v1.0.0 | ✅ Complete |
|
|
||||||
| AirLLM big-brain backend | v1.0.0 | ✅ Complete |
|
|
||||||
| CLI (chat/think/status) | v1.0.0 | ✅ Complete |
|
|
||||||
| Swarm registry + coordinator | v2.0.0 | ⚠️ Skeleton only — no real agents |
|
|
||||||
| Agent personas (Echo, Mace, Forge…) | v2.0.0 | ❌ Catalog only — never instantiated |
|
|
||||||
| MCP tools integration | v2.0.0 | ❌ Not started |
|
|
||||||
| Voice NLU | v2.0.0 | ⚠️ Backend module — no live UI |
|
|
||||||
| Push notifications | v2.0.0 | ⚠️ Backend module — never triggered |
|
|
||||||
| Siri Shortcuts | v2.0.0 | ⚠️ Endpoint stub only |
|
|
||||||
| WebSocket live swarm feed | v2.0.0 | ⚠️ Server-side ready — no UI consumer |
|
|
||||||
| L402 / Lightning payments | v3.0.0 | ⚠️ Mock implementation only |
|
|
||||||
| Real LND gRPC backend | v3.0.0 | ❌ Not started |
|
|
||||||
| Single `.app` bundle | v3.0.0 | ❌ Not started |
|
|
||||||
| React dashboard (live data) | — | ❌ All mock data |
|
|
||||||
| Mobile HITL checklist | — | ✅ Complete (27 scenarios) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Mobile UX Audit
|
|
||||||
|
|
||||||
**Score: 7/10 (HTMX UI) / 2/10 (React UI)**
|
|
||||||
|
|
||||||
### HTMX Dashboard — Strong
|
|
||||||
|
|
||||||
The HTMX-served dashboard has solid mobile foundations verified by the automated test suite:
|
|
||||||
|
|
||||||
- ✅ `viewport-fit=cover` — Dynamic Island / notch support
|
|
||||||
- ✅ `apple-mobile-web-app-capable` — Home Screen PWA mode
|
|
||||||
- ✅ `safe-area-inset-top/bottom` — padding clears notch and home indicator
|
|
||||||
- ✅ `overscroll-behavior: none` — no rubber-band on main page
|
|
||||||
- ✅ `-webkit-overflow-scrolling: touch` — momentum scroll in chat
|
|
||||||
- ✅ `dvh` units — correct height on iOS with collapsing chrome
|
|
||||||
- ✅ 44px touch targets on SEND button and inputs
|
|
||||||
- ✅ `font-size: 16px` in mobile query — iOS zoom prevention
|
|
||||||
- ✅ `enterkeyhint="send"` — Send-labelled keyboard key
|
|
||||||
- ✅ HTMX `hx-sync="this:drop"` — double-tap protection
|
|
||||||
- ✅ HTMX `hx-disabled-elt` — in-flight button lockout
|
|
||||||
|
|
||||||
### Gap: Mobile Quick Actions Page (`/mobile`)
|
|
||||||
|
|
||||||
The `/mobile` route template shows a "Mobile only" page with quick action tiles and a JS-based chat — but it uses **CSS `display: none` on desktop** via `.mobile-only` with an `@media (min-width: 769px)` rule. The desktop fallback shows a placeholder. This is a valid progressive enhancement approach but the page is not linked from the main nav bar.
|
|
||||||
|
|
||||||
### React Dashboard — Mobile Not Functional
|
|
||||||
|
|
||||||
The React dashboard uses `hidden lg:flex` for the left sidebar (desktop only) and an `AnimatePresence` slide-in overlay for mobile. The mobile UX architecture is correct. However, because all data is mock, tapping "Chat" produces a simulated response from a setTimeout, not from Ollama. This is not tested and not usable.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Human-in-the-Loop (HITL) Mobile Testing
|
|
||||||
|
|
||||||
**Score: 8/10**
|
|
||||||
|
|
||||||
The `/mobile-test` route is the standout quality feature. It provides:
|
|
||||||
|
|
||||||
- 21 structured test scenarios across 7 categories (Layout, Touch, Chat, Health, Scroll, Notch, Live UI)
|
|
||||||
- PASS/FAIL/SKIP buttons with sessionStorage persistence across scroll
|
|
||||||
- Live pass rate counter and progress bar
|
|
||||||
- Accessible on any phone via local network URL
|
|
||||||
- ← MISSION CONTROL back-link for easy navigation
|
|
||||||
|
|
||||||
**Gaps to improve:**
|
|
||||||
- No server-side results storage — results lost when tab closes
|
|
||||||
- No shareable/exportable report (screenshot required for handoff)
|
|
||||||
- React dashboard has no equivalent HITL page
|
|
||||||
- No automated Playwright/Selenium mobile tests that could catch regressions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Security Assessment
|
|
||||||
|
|
||||||
**Score: 5/10**
|
|
||||||
|
|
||||||
### XSS Vulnerability — `/mobile` template
|
|
||||||
|
|
||||||
`mobile.html` line ~85 uses raw `innerHTML` string interpolation with user-supplied message content:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// mobile.html — VULNERABLE
|
|
||||||
chat.innerHTML += `
|
|
||||||
<div class="chat-message user">
|
|
||||||
<div>${message}</div> <!-- message is user input, not escaped -->
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
```
|
|
||||||
|
|
||||||
If a user types `<img src=x onerror=alert(1)>`, it executes. This is a stored XSS via `innerHTML`. Fix: use `document.createTextNode(message)` or escape HTML before insertion.
|
|
||||||
|
|
||||||
The `swarm_live.html` has the same pattern with WebSocket data:
|
|
||||||
```javascript
|
|
||||||
container.innerHTML = agents.map(agent => `...${agent.name}...`).join('');
|
|
||||||
```
|
|
||||||
If agent names contain `<script>` tags (or any HTML), this executes in context.
|
|
||||||
|
|
||||||
### Hardcoded Secrets
|
|
||||||
|
|
||||||
`l402_proxy.py`: `_MACAROON_SECRET = "timmy-macaroon-secret".encode()` (default)
|
|
||||||
`payment_handler.py`: `_HMAC_SECRET = "timmy-sovereign-sats".encode()` (default)
|
|
||||||
|
|
||||||
Both fall back to env var reads which is correct, but the defaults should not be production-safe strings — they should be None with a startup assertion requiring them to be set.
|
|
||||||
|
|
||||||
### No Route Authentication
|
|
||||||
|
|
||||||
All `/swarm/spawn`, `/swarm/tasks`, `/marketplace`, `/agents/timmy/chat` endpoints have no auth guard. On a `--host 0.0.0.0` server, anyone on the local network can post tasks or clear chat history. Acceptable for v1 local-only use but must be documented and gated before LAN exposure.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Test Coverage
|
|
||||||
|
|
||||||
**Score: 7/10**
|
|
||||||
|
|
||||||
| Suite | Tests | Quality |
|
|
||||||
|-------|-------|---------|
|
|
||||||
| Agent unit | 13 | Good |
|
|
||||||
| Backends | 14 | Good |
|
|
||||||
| Mobile scenarios | 32 | Excellent — covers M1xx-M6xx categories |
|
|
||||||
| Swarm | 29+10+16 | Good |
|
|
||||||
| L402 proxy | 13 | Good |
|
|
||||||
| Voice NLU | 15 | Good |
|
|
||||||
| Dashboard routes | 18+18 | Good |
|
|
||||||
| WebSocket | 3 | Thin — no reconnect or message-type tests |
|
|
||||||
| React components | 0 | Missing entirely |
|
|
||||||
| End-to-end (Playwright) | 0 | Missing |
|
|
||||||
|
|
||||||
**Key gaps:**
|
|
||||||
1. No tests for the XSS vulnerabilities
|
|
||||||
2. No tests for the `/mobile` quick-chat endpoint
|
|
||||||
3. WebSocket tests don't cover reconnection logic or malformed payloads
|
|
||||||
4. React app has zero test coverage
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Code Quality
|
|
||||||
|
|
||||||
**Score: 7/10**
|
|
||||||
|
|
||||||
**Strengths:**
|
|
||||||
- Clean module separation (`timmy/`, `swarm/`, `dashboard/routes/`, `timmy_serve/`)
|
|
||||||
- Consistent use of dataclasses for domain models
|
|
||||||
- Good docstrings on all public functions
|
|
||||||
- SQLite-backed persistence for both Agno memory and swarm registry
|
|
||||||
- pydantic-settings config with `.env` override support
|
|
||||||
|
|
||||||
**Weaknesses:**
|
|
||||||
- Swarm `coordinator.py` uses a module-level singleton `coordinator = SwarmCoordinator()` — not injectable, hard to test in isolation
|
|
||||||
- `swarm/registry.py` opens a new SQLite connection on every call (no connection pool)
|
|
||||||
- `dashboard/routes/swarm.py` creates a new `Jinja2Templates` instance — it should reuse the one from `app.py`
|
|
||||||
- React components import from `@/components/ui/*` which don't exist in the committed tree
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Developer Experience
|
|
||||||
|
|
||||||
**Score: 6/10**
|
|
||||||
|
|
||||||
**Strengths:**
|
|
||||||
- README is excellent — copy-paste friendly, covers Mac quickstart, phone access, troubleshooting
|
|
||||||
- DEVELOPMENT_REPORT.md provides full history of what was built and why
|
|
||||||
- `.env.example` covers all config variables
|
|
||||||
- Self-TDD watchdog CLI is a creative addition
|
|
||||||
|
|
||||||
**Weaknesses:**
|
|
||||||
- No `docker-compose.yml` — setup requires manual Python venv + Ollama install
|
|
||||||
- Two apps (FastAPI + React) with no single `make dev` command to start both
|
|
||||||
- `STATUS.md` says v1.0.0 but development is well past that — version drift
|
|
||||||
- React app missing from the quickstart instructions entirely
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Backend Architecture
|
|
||||||
|
|
||||||
**Score: 7/10**
|
|
||||||
|
|
||||||
The FastAPI backend is well-structured. The swarm subsystem follows a clean coordinator pattern. The L402 mock is architecturally correct (the interface matches what real LND calls would require).
|
|
||||||
|
|
||||||
**Gaps:**
|
|
||||||
- Swarm "agents" are database records — `spawn_agent()` registers a record but no Python process is actually launched. `agent_runner.py` uses `subprocess.Popen` to run `python -m swarm.agent_runner` but no `__main__` block exists in that file.
|
|
||||||
- The bidding system (`bidder.py`) runs an asyncio auction but there are no actual bidder agents submitting bids — auctions will always time out with no winner.
|
|
||||||
- Voice TTS (`voice_tts.py`) requires `pyttsx3` (optional dep) but the voice route offers no graceful fallback message when pyttsx3 is absent.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Prioritized Defects
|
|
||||||
|
|
||||||
| Priority | ID | Issue | File |
|
|
||||||
|----------|----|-------|------|
|
|
||||||
| P0 | SEC-01 | XSS via innerHTML with unsanitized user input | `mobile.html:85`, `swarm_live.html:72` |
|
|
||||||
| P0 | ARCH-01 | React dashboard 100% mock — no backend calls | `dashboard-web/client/src/` |
|
|
||||||
| P0 | ARCH-02 | React app not buildable — missing package.json, shadcn/ui | `dashboard-web/client/` |
|
|
||||||
| P1 | SEC-02 | Hardcoded L402/HMAC secrets without startup assertion | `l402_proxy.py`, `payment_handler.py` |
|
|
||||||
| P1 | FUNC-01 | Swarm spawn creates DB record but no process | `swarm/agent_runner.py` |
|
|
||||||
| P1 | FUNC-02 | Auction always fails — no real bid submitters | `swarm/bidder.py` |
|
|
||||||
| P2 | UX-01 | `/mobile` route not in desktop nav | `base.html`, `index.html` |
|
|
||||||
| P2 | TEST-01 | WebSocket reconnection not tested | `tests/test_websocket.py` |
|
|
||||||
| P2 | DX-01 | No single dev startup command | `README.md` |
|
|
||||||
| P3 | PERF-01 | SQLite connection opened per-query in registry | `swarm/registry.py` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## HITL Mobile Test Session Guide
|
|
||||||
|
|
||||||
To run a complete human-in-the-loop mobile test session right now:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Start the dashboard
|
|
||||||
source .venv/bin/activate
|
|
||||||
uvicorn dashboard.app:app --host 0.0.0.0 --port 8000 --reload
|
|
||||||
|
|
||||||
# 2. Find your local IP
|
|
||||||
ipconfig getifaddr en0 # macOS
|
|
||||||
hostname -I # Linux
|
|
||||||
|
|
||||||
# 3. Open on your phone (same Wi-Fi)
|
|
||||||
http://<YOUR_IP>:8000/mobile-test
|
|
||||||
|
|
||||||
# 4. Work through the 21 scenarios, marking PASS / FAIL / SKIP
|
|
||||||
# 5. Screenshot the SUMMARY section for your records
|
|
||||||
|
|
||||||
# ─── Also test the main dashboard on mobile ───────────────────────
|
|
||||||
http://<YOUR_IP>:8000 # Main Mission Control
|
|
||||||
http://<YOUR_IP>:8000/mobile # Quick Actions (mobile-optimized)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Critical scenarios to test first:**
|
|
||||||
- T01 — iOS zoom prevention (tap input, watch for zoom)
|
|
||||||
- C02 — Multi-turn memory (tell Timmy your name, ask it back)
|
|
||||||
- C04 — Offline graceful error (stop Ollama, send message)
|
|
||||||
- N01/N02 — Notch / home bar clearance (notched iPhone)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommended Next Prompt for Development
|
|
||||||
|
|
||||||
```
|
|
||||||
Connect the React dashboard (dashboard-web/) to the live FastAPI backend.
|
|
||||||
|
|
||||||
Priority order:
|
|
||||||
|
|
||||||
1. FIX BUILD FIRST: Add package.json, vite.config.ts, tailwind.config.ts, and
|
|
||||||
tsconfig.json to dashboard-web/client/. Install shadcn/ui so the existing
|
|
||||||
component imports resolve. Verify `npm run dev` starts the app.
|
|
||||||
|
|
||||||
2. CHAT (highest user value): Replace ChatPanel mock with real fetch to
|
|
||||||
POST /agents/timmy/chat. Show the actual Timmy response from Ollama.
|
|
||||||
Implement loading state (matches the existing isTyping UI).
|
|
||||||
|
|
||||||
3. HEALTH: Replace MOCK_HEALTH in StatusSidebar with a polling fetch to
|
|
||||||
GET /health/status (every 30s, matching HTMX behaviour).
|
|
||||||
|
|
||||||
4. SWARM WEBSOCKET: Open a real WebSocket to ws://localhost:8000/swarm/ws
|
|
||||||
and pipe state updates into SwarmPanel — replacing MOCK_WS_EVENTS.
|
|
||||||
|
|
||||||
5. SECURITY: Fix XSS in mobile.html and swarm_live.html — replace innerHTML
|
|
||||||
string interpolation with safe DOM methods (textContent / createTextNode).
|
|
||||||
|
|
||||||
Use React Query (TanStack) for data fetching with stale-while-revalidate.
|
|
||||||
Keep the existing HTMX dashboard running in parallel — the React app should
|
|
||||||
be the forward-looking UI.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Analysis by Claude Code — Senior Architect Review*
|
|
||||||
*Timmy Time Dashboard | branch: claude/quality-analysis-mobile-testing-0zgPi*
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
# Timmy Time — Quality Analysis Update v2.0
|
|
||||||
**Date:** 2026-02-23
|
|
||||||
**Branch:** `kimi/mission-control-ux`
|
|
||||||
**Test Suite:** 525/525 passing ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
Significant progress since v1 analysis. The swarm system is now functional with real task execution. Lightning payments have a proper abstraction layer. MCP tools are integrated. Test coverage increased from 228 to 525 tests.
|
|
||||||
|
|
||||||
**Overall Progress: ~65-70%** (up from 35-40%)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Major Improvements Since v1
|
|
||||||
|
|
||||||
### 1. Swarm System — NOW FUNCTIONAL ✅
|
|
||||||
|
|
||||||
**Previous:** Skeleton only, agents were DB records with no execution
|
|
||||||
**Current:** Full task lifecycle with tool execution
|
|
||||||
|
|
||||||
| Component | Before | After |
|
|
||||||
|-----------|--------|-------|
|
|
||||||
| Agent bidding | Random bids | Capability-aware scoring |
|
|
||||||
| Task execution | None | ToolExecutor with persona tools |
|
|
||||||
| Routing | Random assignment | Score-based with audit logging |
|
|
||||||
| Tool integration | Not started | Full MCP tools (search, shell, python, file) |
|
|
||||||
|
|
||||||
**Files Added:**
|
|
||||||
- `src/swarm/routing.py` — Capability-based routing with SQLite audit log
|
|
||||||
- `src/swarm/tool_executor.py` — MCP tool execution for personas
|
|
||||||
- `src/timmy/tools.py` — Persona-specific toolkits
|
|
||||||
|
|
||||||
### 2. Lightning Payments — ABSTRACTED ✅
|
|
||||||
|
|
||||||
**Previous:** Mock only, no path to real LND
|
|
||||||
**Current:** Pluggable backend interface
|
|
||||||
|
|
||||||
```python
|
|
||||||
from lightning import get_backend
|
|
||||||
backend = get_backend("lnd") # or "mock"
|
|
||||||
invoice = backend.create_invoice(100, "API access")
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files Added:**
|
|
||||||
- `src/lightning/` — Full backend abstraction
|
|
||||||
- `src/lightning/lnd_backend.py` — LND gRPC stub (ready for protobuf)
|
|
||||||
- `src/lightning/mock_backend.py` — Development backend
|
|
||||||
|
|
||||||
### 3. Sovereignty Audit — COMPLETE ✅
|
|
||||||
|
|
||||||
**New:** `docs/SOVEREIGNTY_AUDIT.md` and live `/health/sovereignty` endpoint
|
|
||||||
|
|
||||||
| Dependency | Score | Status |
|
|
||||||
|------------|-------|--------|
|
|
||||||
| Ollama AI | 10/10 | Local inference |
|
|
||||||
| SQLite | 10/10 | File-based persistence |
|
|
||||||
| Redis | 9/10 | Optional, has fallback |
|
|
||||||
| Lightning | 8/10 | Configurable (local LND or mock) |
|
|
||||||
| **Overall** | **9.2/10** | Excellent sovereignty |
|
|
||||||
|
|
||||||
### 4. Test Coverage — MORE THAN DOUBLED ✅
|
|
||||||
|
|
||||||
**Before:** 228 tests
|
|
||||||
**After:** 525 tests (+297)
|
|
||||||
|
|
||||||
| Suite | Before | After | Notes |
|
|
||||||
|-------|--------|-------|-------|
|
|
||||||
| Lightning | 0 | 36 | Mock + LND backend tests |
|
|
||||||
| Swarm routing | 0 | 23 | Capability scoring, audit log |
|
|
||||||
| Tool executor | 0 | 19 | MCP tool integration |
|
|
||||||
| Scary paths | 0 | 23 | Production edge cases |
|
|
||||||
| Mission Control | 0 | 11 | Dashboard endpoints |
|
|
||||||
| Swarm integration | 0 | 18 | Full lifecycle tests |
|
|
||||||
| Docker agent | 0 | 9 | Containerized workers |
|
|
||||||
| **Total** | **228** | **525** | **+130% increase** |
|
|
||||||
|
|
||||||
### 5. Mission Control Dashboard — NEW ✅
|
|
||||||
|
|
||||||
**New:** `/swarm/mission-control` live system dashboard
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- Sovereignty score with visual progress bar
|
|
||||||
- Real-time dependency health (5s-30s refresh)
|
|
||||||
- System metrics (uptime, agents, tasks, sats earned)
|
|
||||||
- Heartbeat monitor with tick visualization
|
|
||||||
- Health recommendations based on current state
|
|
||||||
|
|
||||||
### 6. Scary Path Tests — PRODUCTION READY ✅
|
|
||||||
|
|
||||||
**New:** `tests/test_scary_paths.py` — 23 edge case tests
|
|
||||||
|
|
||||||
- Concurrent load: 10 simultaneous tasks
|
|
||||||
- Memory persistence across restarts
|
|
||||||
- L402 macaroon expiry handling
|
|
||||||
- WebSocket reconnection resilience
|
|
||||||
- Voice NLU: empty, Unicode, XSS attempts
|
|
||||||
- Graceful degradation: Ollama down, Redis absent, no tools
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture Updates
|
|
||||||
|
|
||||||
### New Module: `src/agent_core/` — Embodiment Foundation
|
|
||||||
|
|
||||||
Abstract base class `TimAgent` for substrate-agnostic agents:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class TimAgent(ABC):
|
|
||||||
async def perceive(self, input: PerceptionInput) -> WorldState
|
|
||||||
async def decide(self, state: WorldState) -> Action
|
|
||||||
async def act(self, action: Action) -> ActionResult
|
|
||||||
async def remember(self, key: str, value: Any) -> None
|
|
||||||
async def recall(self, key: str) -> Any
|
|
||||||
```
|
|
||||||
|
|
||||||
**Purpose:** Enable future embodiments (robot, VR) without architectural changes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security Improvements
|
|
||||||
|
|
||||||
### Issues Addressed
|
|
||||||
|
|
||||||
| Issue | Status | Fix |
|
|
||||||
|-------|--------|-----|
|
|
||||||
| L402/HMAC secrets | ✅ Fixed | Startup warning when defaults used |
|
|
||||||
| Tool execution sandbox | ✅ Implemented | Base directory restriction |
|
|
||||||
|
|
||||||
### Remaining Issues
|
|
||||||
|
|
||||||
| Priority | Issue | File |
|
|
||||||
|----------|-------|------|
|
|
||||||
| P1 | XSS via innerHTML | `mobile.html`, `swarm_live.html` |
|
|
||||||
| P2 | No auth on swarm endpoints | All `/swarm/*` routes |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Updated Feature Matrix
|
|
||||||
|
|
||||||
| Feature | Roadmap | Status |
|
|
||||||
|---------|---------|--------|
|
|
||||||
| Agno + Ollama + SQLite dashboard | v1.0.0 | ✅ Complete |
|
|
||||||
| HTMX chat with history | v1.0.0 | ✅ Complete |
|
|
||||||
| AirLLM big-brain backend | v1.0.0 | ✅ Complete |
|
|
||||||
| CLI (chat/think/status) | v1.0.0 | ✅ Complete |
|
|
||||||
| **Swarm registry + coordinator** | **v2.0.0** | **✅ Complete** |
|
|
||||||
| **Agent personas with tools** | **v2.0.0** | **✅ Complete** |
|
|
||||||
| **MCP tools integration** | **v2.0.0** | **✅ Complete** |
|
|
||||||
| Voice NLU | v2.0.0 | ⚠️ Backend ready, UI pending |
|
|
||||||
| Push notifications | v2.0.0 | ⚠️ Backend ready, trigger pending |
|
|
||||||
| Siri Shortcuts | v2.0.0 | ⚠️ Endpoint ready, needs testing |
|
|
||||||
| **WebSocket live swarm feed** | **v2.0.0** | **✅ Complete** |
|
|
||||||
| **L402 / Lightning abstraction** | **v3.0.0** | **✅ Complete (mock+LND)** |
|
|
||||||
| Real LND gRPC | v3.0.0 | ⚠️ Interface ready, needs protobuf |
|
|
||||||
| **Mission Control dashboard** | **—** | **✅ NEW** |
|
|
||||||
| **Sovereignty audit** | **—** | **✅ NEW** |
|
|
||||||
| **Embodiment interface** | **—** | **✅ NEW** |
|
|
||||||
| Mobile HITL checklist | — | ✅ Complete (27 scenarios) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Test Quality: TDD Adoption
|
|
||||||
|
|
||||||
**Process Change:** Test-Driven Development now enforced
|
|
||||||
|
|
||||||
1. Write test first
|
|
||||||
2. Run test (should fail — red)
|
|
||||||
3. Implement minimal code
|
|
||||||
4. Run test (should pass — green)
|
|
||||||
5. Refactor
|
|
||||||
6. Ensure all tests pass
|
|
||||||
|
|
||||||
**Recent TDD Work:**
|
|
||||||
- Mission Control: 11 tests written before implementation
|
|
||||||
- Scary paths: 23 tests written before fixes
|
|
||||||
- All new features follow this pattern
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Developer Experience
|
|
||||||
|
|
||||||
### New Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Health check
|
|
||||||
make health # Run health/sovereignty report
|
|
||||||
|
|
||||||
# Lightning backend
|
|
||||||
LIGHTNING_BACKEND=lnd make dev # Use real LND
|
|
||||||
LIGHTNING_BACKEND=mock make dev # Use mock (default)
|
|
||||||
|
|
||||||
# Mission Control
|
|
||||||
curl http://localhost:8000/health/sovereignty # JSON audit
|
|
||||||
curl http://localhost:8000/health/components # Component status
|
|
||||||
```
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Lightning
|
|
||||||
LIGHTNING_BACKEND=mock|lnd
|
|
||||||
LND_GRPC_HOST=localhost:10009
|
|
||||||
LND_MACAROON_PATH=/path/to/admin.macaroon
|
|
||||||
LND_TLS_CERT_PATH=/path/to/tls.cert
|
|
||||||
|
|
||||||
# Mock settings
|
|
||||||
MOCK_AUTO_SETTLE=true|false
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Remaining Gaps (v2.1 → v3.0)
|
|
||||||
|
|
||||||
### v2.1 (Next Sprint)
|
|
||||||
1. **XSS Security Fix** — Replace innerHTML with safe DOM methods
|
|
||||||
2. **Chat History Persistence** — SQLite-backed message storage
|
|
||||||
3. **Real LND Integration** — Generate protobuf stubs, test against live node
|
|
||||||
4. **Authentication** — Basic auth for swarm endpoints
|
|
||||||
|
|
||||||
### v3.0 (Revelation)
|
|
||||||
1. **Lightning Treasury** — Agent earns/spends autonomously
|
|
||||||
2. **macOS App Bundle** — Single `.app` with embedded Ollama
|
|
||||||
3. **Robot Embodiment** — First `RobotTimAgent` implementation
|
|
||||||
4. **Federation** — Multi-node swarm discovery
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Metrics Summary
|
|
||||||
|
|
||||||
| Metric | Before | After | Delta |
|
|
||||||
|--------|--------|-------|-------|
|
|
||||||
| Test count | 228 | 525 | +130% |
|
|
||||||
| Test coverage | ~45% | ~65% | +20pp |
|
|
||||||
| Sovereignty score | N/A | 9.2/10 | New |
|
|
||||||
| Backend modules | 8 | 12 | +4 |
|
|
||||||
| Persona agents | 0 functional | 6 with tools | +6 |
|
|
||||||
| Documentation pages | 3 | 5 | +2 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Analysis by Kimi — Architect Sprint*
|
|
||||||
*Timmy Time Dashboard | branch: kimi/mission-control-ux*
|
|
||||||
*Test-Driven Development | 525 tests passing*
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
# Timmy Time — Comprehensive Quality Review Report
|
|
||||||
**Date:** 2026-02-25
|
|
||||||
**Reviewed by:** Claude Code
|
|
||||||
**Test Coverage:** 84.15% (895 tests passing)
|
|
||||||
**Test Result:** ✅ 895 passed, 30 skipped
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
The Timmy Time application is a **functional local-first AI agent system** with a working FastAPI dashboard, Ollama integration, and sophisticated Spark Intelligence engine. The codebase is well-structured with good test coverage, but **critical bugs were found and fixed** during this review that prevented the agent from working properly.
|
|
||||||
|
|
||||||
**Overall Quality Score: 7.5/10**
|
|
||||||
- Architecture: 8/10
|
|
||||||
- Functionality: 8/10 (after fixes)
|
|
||||||
- Test Coverage: 8/10
|
|
||||||
- Documentation: 7/10
|
|
||||||
- Memory/Self-Awareness: 9/10
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Critical Bugs Found & Fixed
|
|
||||||
|
|
||||||
### Bug 1: Toolkit API Mismatch (`CRITICAL`)
|
|
||||||
**Location:** `src/timmy/tools.py`
|
|
||||||
**Issue:** Code used non-existent `Toolkit.add_tool()` method (should be `register()`)
|
|
||||||
|
|
||||||
**Changes Made:**
|
|
||||||
- Changed `toolkit.add_tool(...)` → `toolkit.register(...)` (29 occurrences)
|
|
||||||
- Changed `python_tools.python` → `python_tools.run_python_code` (3 occurrences)
|
|
||||||
- Changed `file_tools.write_file` → `file_tools.save_file` (4 occurrences)
|
|
||||||
- Changed `FileTools(base_dir=str(base_path))` → `FileTools(base_dir=base_path)` (5 occurrences)
|
|
||||||
|
|
||||||
**Impact:** Without this fix, Timmy agent would crash on startup with `AttributeError`.
|
|
||||||
|
|
||||||
### Bug 2: Agent Tools Parameter (`CRITICAL`)
|
|
||||||
**Location:** `src/timmy/agent.py`
|
|
||||||
**Issue:** Tools passed as single Toolkit instead of list
|
|
||||||
|
|
||||||
**Change Made:**
|
|
||||||
- Changed `tools=tools` → `tools=[tools] if tools else None`
|
|
||||||
|
|
||||||
**Impact:** Without this fix, Agno Agent initialization would fail with `TypeError: 'Toolkit' object is not iterable`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Model Inference — ✅ WORKING
|
|
||||||
|
|
||||||
### Test Results
|
|
||||||
|
|
||||||
| Test | Status | Details |
|
|
||||||
|------|--------|---------|
|
|
||||||
| Agent creation | ✅ Pass | Ollama backend initializes correctly |
|
|
||||||
| Basic inference | ✅ Pass | Response type: `RunOutput` with content |
|
|
||||||
| Tool usage | ✅ Pass | File operations, shell commands work |
|
|
||||||
| Streaming | ✅ Pass | Supported via `stream=True` |
|
|
||||||
|
|
||||||
### Inference Example
|
|
||||||
```
|
|
||||||
Input: "What is your name and who are you?"
|
|
||||||
Output: "I am Timmy, a sovereign AI agent running locally on Apple Silicon.
|
|
||||||
I'm committed to your digital sovereignty and powered by Bitcoin economics..."
|
|
||||||
```
|
|
||||||
|
|
||||||
### Available Models
|
|
||||||
- **Ollama:** llama3.2 (default), deepseek-r1:1.5b
|
|
||||||
- **AirLLM:** 8B, 70B, 405B models (optional backend)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Memory & Self-Awareness — ✅ WORKING
|
|
||||||
|
|
||||||
### Conversation Memory Test
|
|
||||||
|
|
||||||
| Test | Status | Result |
|
|
||||||
|------|--------|--------|
|
|
||||||
| Single-turn memory | ✅ Pass | Timmy remembers what user just asked |
|
|
||||||
| Multi-turn context | ✅ Pass | References earlier conversation |
|
|
||||||
| Self-identification | ✅ Pass | "I am Timmy, a sovereign AI agent..." |
|
|
||||||
| Persistent storage | ✅ Pass | SQLite (`timmy.db`) persists across restarts |
|
|
||||||
| History recall | ✅ Pass | Can recall first question from conversation |
|
|
||||||
|
|
||||||
### Memory Implementation
|
|
||||||
- **Storage:** SQLite via `SqliteDb` (Agno)
|
|
||||||
- **Context window:** 10 history runs (`num_history_runs=10`)
|
|
||||||
- **File:** `timmy.db` in project root
|
|
||||||
|
|
||||||
### Self-Awareness Features
|
|
||||||
✅ Agent knows its name ("Timmy")
|
|
||||||
✅ Agent knows it's a sovereign AI
|
|
||||||
✅ Agent knows it runs locally (Apple Silicon detection)
|
|
||||||
✅ Agent references Bitcoin economics and digital sovereignty
|
|
||||||
✅ Agent references Christian faith grounding (per system prompt)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Spark Intelligence Engine — ✅ WORKING
|
|
||||||
|
|
||||||
### Capabilities Verified
|
|
||||||
|
|
||||||
| Feature | Status | Details |
|
|
||||||
|---------|--------|---------|
|
|
||||||
| Event capture | ✅ Working | 550 events captured |
|
|
||||||
| Task predictions | ✅ Working | 235 predictions, 85% avg accuracy |
|
|
||||||
| Memory consolidation | ✅ Working | 6 memories stored |
|
|
||||||
| Advisories | ✅ Working | Failure prevention, performance, bid optimization |
|
|
||||||
| EIDOS loop | ✅ Working | Predict → Observe → Evaluate → Learn |
|
|
||||||
|
|
||||||
### Sample Advisory Output
|
|
||||||
```
|
|
||||||
[failure_prevention] Agent fail-lea has 7 failures (Priority: 1.0)
|
|
||||||
[agent_performance] Agent success- excels (100% success) (Priority: 0.6)
|
|
||||||
[bid_optimization] Wide bid spread (20–94 sats) (Priority: 0.5)
|
|
||||||
[system_health] Strong prediction accuracy (85%) (Priority: 0.3)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Dashboard & UI — ✅ WORKING
|
|
||||||
|
|
||||||
### Route Testing Results
|
|
||||||
|
|
||||||
| Route | Status | Notes |
|
|
||||||
|-------|--------|-------|
|
|
||||||
| `/` | ✅ 200 | Main dashboard loads |
|
|
||||||
| `/health` | ✅ 200 | Health panel |
|
|
||||||
| `/agents` | ✅ 200 | Agent list API |
|
|
||||||
| `/swarm` | ✅ 200 | Swarm coordinator UI |
|
|
||||||
| `/spark` | ✅ 200 | Spark Intelligence dashboard |
|
|
||||||
| `/marketplace` | ✅ 200 | Marketplace UI |
|
|
||||||
| `/mobile` | ✅ 200 | Mobile-optimized layout |
|
|
||||||
| `/agents/timmy/chat` | ✅ 200 | Chat endpoint works |
|
|
||||||
|
|
||||||
### Chat Functionality
|
|
||||||
- HTMX-powered chat interface ✅
|
|
||||||
- Message history persistence ✅
|
|
||||||
- Real-time Ollama inference ✅
|
|
||||||
- Error handling (graceful degradation) ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Swarm System — ⚠️ PARTIAL
|
|
||||||
|
|
||||||
### Working Components
|
|
||||||
- ✅ Registry with SQLite persistence
|
|
||||||
- ✅ Coordinator with task lifecycle
|
|
||||||
- ✅ Agent bidding system
|
|
||||||
- ✅ Task assignment algorithm
|
|
||||||
- ✅ Spark event capture
|
|
||||||
- ✅ Recovery mechanism
|
|
||||||
|
|
||||||
### Limitations
|
|
||||||
- ⚠️ Persona agents are stubbed (not fully functional AI agents)
|
|
||||||
- ⚠️ Most swarm activity is simulated/test data
|
|
||||||
- ⚠️ Docker runner not tested in live environment
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Issues Identified (Non-Critical)
|
|
||||||
|
|
||||||
### Issue 1: SSL Certificate Error with DuckDuckGo
|
|
||||||
**Location:** Web search tool
|
|
||||||
**Error:** `CERTIFICATE_VERIFY_FAILED`
|
|
||||||
**Impact:** Web search tool fails, but agent continues gracefully
|
|
||||||
**Fix:** May need `certifi` package or system certificate update
|
|
||||||
|
|
||||||
### Issue 2: Default Secrets Warning
|
|
||||||
**Location:** L402 payment handler
|
|
||||||
**Message:** `L402_HMAC_SECRET is using the default value`
|
|
||||||
**Impact:** Warning only — production should set unique secrets
|
|
||||||
**Status:** By design (warns at startup)
|
|
||||||
|
|
||||||
### Issue 3: Redis Unavailable Fallback
|
|
||||||
**Location:** SwarmComms
|
|
||||||
**Message:** `Redis unavailable — using in-memory fallback`
|
|
||||||
**Impact:** Falls back to in-memory (acceptable for single-instance)
|
|
||||||
**Status:** By design (graceful degradation)
|
|
||||||
|
|
||||||
### Issue 4: Telemetry to Agno
|
|
||||||
**Observation:** Agno sends telemetry to `os-api.agno.com`
|
|
||||||
**Impact:** Minor — may not align with "sovereign" vision
|
|
||||||
**Note:** Requires further review for truly air-gapped deployments
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Test Coverage Analysis
|
|
||||||
|
|
||||||
| Module | Coverage | Status |
|
|
||||||
|--------|----------|--------|
|
|
||||||
| `spark/memory.py` | 98.3% | ✅ Excellent |
|
|
||||||
| `spark/engine.py` | 92.6% | ✅ Good |
|
|
||||||
| `swarm/coordinator.py` | 92.8% | ✅ Good |
|
|
||||||
| `timmy/agent.py` | 100% | ✅ Excellent |
|
|
||||||
| `timmy/backends.py` | 96.3% | ✅ Good |
|
|
||||||
| `dashboard/` routes | 60-100% | ✅ Good |
|
|
||||||
|
|
||||||
**Overall:** 84.15% coverage (exceeds 60% threshold)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Recommendations
|
|
||||||
|
|
||||||
### High Priority
|
|
||||||
1. ✅ **DONE** Fix toolkit API methods (register vs add_tool)
|
|
||||||
2. ✅ **DONE** Fix agent tools parameter (wrap in list)
|
|
||||||
3. Add tool usage instructions to system prompt to reduce unnecessary tool calls
|
|
||||||
4. Fix SSL certificate issue for DuckDuckGo search
|
|
||||||
|
|
||||||
### Medium Priority
|
|
||||||
5. Add configuration option to disable Agno telemetry
|
|
||||||
6. Implement more sophisticated self-awareness (e.g., knowledge of current tasks)
|
|
||||||
7. Expand persona agent capabilities beyond stubs
|
|
||||||
|
|
||||||
### Low Priority
|
|
||||||
8. Add more comprehensive end-to-end tests with real Ollama
|
|
||||||
9. Optimize tool calling behavior (fewer unnecessary tool invocations)
|
|
||||||
10. Consider adding conversation summarization for very long contexts
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Conclusion
|
|
||||||
|
|
||||||
After fixing the critical bugs identified during this review, **Timmy Time is a functional and well-architected AI agent system** with:
|
|
||||||
|
|
||||||
- ✅ Working model inference via Ollama
|
|
||||||
- ✅ Persistent conversation memory
|
|
||||||
- ✅ Self-awareness capabilities
|
|
||||||
- ✅ Comprehensive Spark Intelligence engine
|
|
||||||
- ✅ Functional web dashboard
|
|
||||||
- ✅ Good test coverage (84%+)
|
|
||||||
|
|
||||||
The core value proposition — a sovereign, local-first AI agent with memory and self-awareness — **is delivered and working**.
|
|
||||||
753
docs/index.html
753
docs/index.html
@@ -2,8 +2,8 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Timmy</title>
|
<title>Timmy Time — Mission Control</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
@@ -12,523 +12,344 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bg: #080412;
|
--bg: #080412;
|
||||||
--bg-msg: #110820;
|
--bg-card: #110820;
|
||||||
--bg-user: #1a0e30;
|
|
||||||
--border: #2a1545;
|
--border: #2a1545;
|
||||||
--text: #c8b0e0;
|
--text: #c8b0e0;
|
||||||
--dim: #6b4a8a;
|
--dim: #6b4a8a;
|
||||||
--bright: #ede0ff;
|
--bright: #ede0ff;
|
||||||
--accent: #ff7a2a;
|
--accent: #ff7a2a;
|
||||||
--green: #00e87a;
|
--green: #00e87a;
|
||||||
--red: #ff4455;
|
|
||||||
--font: 'JetBrains Mono', monospace;
|
--font: 'JetBrains Mono', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-family: var(--font);
|
font-family: var(--font);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.7;
|
||||||
touch-action: pan-y;
|
|
||||||
overscroll-behavior: none;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Full-screen layout ── */
|
.container {
|
||||||
#app {
|
max-width: 760px;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
height: 100dvh;
|
|
||||||
max-width: 720px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
overflow: hidden;
|
padding: 48px 24px 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Header ── */
|
header {
|
||||||
#header {
|
margin-bottom: 48px;
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#header h1 {
|
h1 {
|
||||||
font-size: 13px;
|
font-size: 28px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.2em;
|
letter-spacing: 0.15em;
|
||||||
|
color: var(--accent);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagline {
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--bright);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--dim);
|
||||||
|
}
|
||||||
|
.badge.highlight {
|
||||||
|
border-color: var(--accent);
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
#status {
|
.links {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
gap: 16px;
|
||||||
gap: 6px;
|
flex-wrap: wrap;
|
||||||
font-size: 11px;
|
|
||||||
color: var(--dim);
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#status-dot {
|
.links a {
|
||||||
width: 7px;
|
color: var(--accent);
|
||||||
height: 7px;
|
text-decoration: none;
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--dim);
|
|
||||||
transition: background 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
|
|
||||||
#status-dot.offline { background: var(--red); }
|
|
||||||
#status-dot.trying { background: var(--accent); animation: pulse 1s infinite; }
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.3; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Messages ── */
|
|
||||||
#messages {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg {
|
|
||||||
max-width: 88%;
|
|
||||||
padding: 10px 14px;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.6;
|
letter-spacing: 0.05em;
|
||||||
word-wrap: break-word;
|
border-bottom: 1px solid transparent;
|
||||||
overflow-wrap: break-word;
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
.links a:hover {
|
||||||
|
border-bottom-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg.timmy {
|
h2 {
|
||||||
align-self: flex-start;
|
font-size: 13px;
|
||||||
background: var(--bg-msg);
|
font-weight: 700;
|
||||||
border-left: 2px solid var(--accent);
|
letter-spacing: 0.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent);
|
||||||
|
margin: 48px 0 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat .number {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--bright);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat .label {
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--dim);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-item {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-item strong {
|
||||||
|
color: var(--bright);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-item span {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--dim);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 20px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg.user {
|
.modules {
|
||||||
align-self: flex-end;
|
|
||||||
background: var(--bg-user);
|
|
||||||
border-right: 2px solid var(--dim);
|
|
||||||
color: var(--bright);
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg.system {
|
|
||||||
align-self: center;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--dim);
|
|
||||||
max-width: 90%;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg .label {
|
|
||||||
font-size: 10px;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--dim);
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg.timmy .label { color: var(--accent); }
|
|
||||||
|
|
||||||
.msg .body p { margin-bottom: 0.5em; }
|
|
||||||
.msg .body p:last-child { margin-bottom: 0; }
|
|
||||||
.msg .body code {
|
|
||||||
background: rgba(255, 255, 255, 0.06);
|
|
||||||
padding: 1px 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.msg .body pre {
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
padding: 10px;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-size: 12px;
|
|
||||||
margin: 6px 0;
|
|
||||||
}
|
|
||||||
.msg .body pre code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Thinking indicator */
|
|
||||||
.thinking {
|
|
||||||
align-self: flex-start;
|
|
||||||
padding: 10px 14px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--dim);
|
|
||||||
border-left: 2px solid var(--accent);
|
|
||||||
background: var(--bg-msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.thinking span {
|
|
||||||
animation: blink 1.4s infinite;
|
|
||||||
}
|
|
||||||
.thinking span:nth-child(2) { animation-delay: 0.2s; }
|
|
||||||
.thinking span:nth-child(3) { animation-delay: 0.4s; }
|
|
||||||
|
|
||||||
@keyframes blink {
|
|
||||||
0%, 80%, 100% { opacity: 0.2; }
|
|
||||||
40% { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Input ── */
|
|
||||||
#input-area {
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 12px 16px;
|
|
||||||
padding-bottom: max(12px, env(safe-area-inset-bottom));
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
background: var(--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#input {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--bg-msg);
|
border-collapse: collapse;
|
||||||
border: 1px solid var(--border);
|
|
||||||
color: var(--bright);
|
|
||||||
font-family: var(--font);
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
outline: none;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#input::placeholder {
|
.modules th,
|
||||||
color: var(--dim);
|
.modules td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#input:focus {
|
.modules th {
|
||||||
border-color: var(--accent);
|
color: var(--dim);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Scrollbar ── */
|
.modules td:first-child {
|
||||||
#messages::-webkit-scrollbar { width: 4px; }
|
color: var(--bright);
|
||||||
#messages::-webkit-scrollbar-track { background: transparent; }
|
font-weight: 600;
|
||||||
#messages::-webkit-scrollbar-thumb { background: var(--border); }
|
}
|
||||||
|
|
||||||
/* ── No horizontal anything ── */
|
footer {
|
||||||
* { max-width: 100%; }
|
margin-top: 64px;
|
||||||
|
padding-top: 24px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: var(--dim);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
footer a:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.container { padding: 24px 16px 48px; }
|
||||||
|
h1 { font-size: 22px; }
|
||||||
|
.stats { grid-template-columns: 1fr 1fr; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="app">
|
<div class="container">
|
||||||
<div id="header">
|
<header>
|
||||||
<h1>TIMMY</h1>
|
<h1>TIMMY TIME</h1>
|
||||||
<div id="status">
|
<p class="tagline">A local-first, sovereign AI agent system</p>
|
||||||
<span id="status-text">connecting</span>
|
<div class="badges">
|
||||||
<span id="status-dot" class="trying"></span>
|
<span class="badge highlight">Python 3.11+</span>
|
||||||
|
<span class="badge highlight">FastAPI</span>
|
||||||
|
<span class="badge">HTMX</span>
|
||||||
|
<span class="badge">Ollama</span>
|
||||||
|
<span class="badge">SQLite</span>
|
||||||
|
<span class="badge">MIT License</span>
|
||||||
|
</div>
|
||||||
|
<div class="links">
|
||||||
|
<a href="https://github.com/AlexanderWhitestone/Timmy-time-dashboard">GitHub Repository</a>
|
||||||
|
<a href="https://github.com/AlexanderWhitestone/Timmy-time-dashboard/blob/main/README.md">README</a>
|
||||||
|
<a href="https://github.com/AlexanderWhitestone/Timmy-time-dashboard/blob/main/CLAUDE.md">Developer Guide</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<h2>At a Glance</h2>
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat">
|
||||||
|
<span class="number">8</span>
|
||||||
|
<span class="label">Python Packages</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span class="number">643+</span>
|
||||||
|
<span class="label">Passing Tests</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span class="number">73%+</span>
|
||||||
|
<span class="label">Code Coverage</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span class="number">58</span>
|
||||||
|
<span class="label">API Endpoints</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="messages"></div>
|
<h2>Tech Stack</h2>
|
||||||
|
<div class="tech-grid">
|
||||||
<div id="input-area">
|
<div class="tech-item">
|
||||||
<input id="input"
|
<strong>FastAPI + HTMX</strong>
|
||||||
type="text"
|
<span>Server-rendered UI with real-time updates</span>
|
||||||
placeholder="talk to timmy..."
|
</div>
|
||||||
autocomplete="off"
|
<div class="tech-item">
|
||||||
autocorrect="off"
|
<strong>Agno Framework</strong>
|
||||||
autocapitalize="none"
|
<span>Multi-agent orchestration with tool calling</span>
|
||||||
spellcheck="false"
|
</div>
|
||||||
enterkeyhint="send">
|
<div class="tech-item">
|
||||||
|
<strong>Ollama</strong>
|
||||||
|
<span>Local LLM inference, no cloud dependencies</span>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<strong>SQLite + WAL</strong>
|
||||||
|
<span>Agent memory, swarm registry, task queue</span>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<strong>WebSockets</strong>
|
||||||
|
<span>Live event feeds and push notifications</span>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<strong>Docker</strong>
|
||||||
|
<span>Multi-stage builds, dev and prod configs</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2>Architecture</h2>
|
||||||
|
<pre>
|
||||||
|
Browser / Phone
|
||||||
|
| HTTP + HTMX + WebSocket
|
||||||
|
v
|
||||||
|
+-----------------------------------------+
|
||||||
|
| FastAPI (dashboard.app) |
|
||||||
|
| routes: agents, health, swarm, |
|
||||||
|
| marketplace, voice, mobile |
|
||||||
|
+---+-------------+----------+-----------+
|
||||||
|
| | |
|
||||||
|
v v v
|
||||||
|
Jinja2 Timmy Infrastructure
|
||||||
|
Templates Agent +- LLM Router (cascade)
|
||||||
|
(HTMX) | +- WebSocket manager
|
||||||
|
+- Ollama +- Notifications
|
||||||
|
+- AirLLM +- Events bus
|
||||||
|
|
|
||||||
|
+-- Integrations (voice NLU, Telegram, Siri)
|
||||||
|
+-- WebSocket live feed
|
||||||
|
+-- Push notifications
|
||||||
|
+-- Spark (events, predictions, advisory)
|
||||||
|
|
||||||
|
Persistence: SQLite (Agno memory + swarm registry)
|
||||||
|
External: Ollama :11434, optional Redis, optional LND</pre>
|
||||||
|
|
||||||
|
<h2>Modules</h2>
|
||||||
|
<table class="modules">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Package</th><th>Purpose</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>timmy/</td><td>Core agent, personas, interface, semantic memory</td></tr>
|
||||||
|
<tr><td>dashboard/</td><td>FastAPI web UI, routes, Jinja2 templates</td></tr>
|
||||||
|
<tr><td>infrastructure/</td><td>WebSocket, notifications, events, LLM router</td></tr>
|
||||||
|
<tr><td>integrations/</td><td>Discord, Telegram, Siri Shortcuts, voice NLU</td></tr>
|
||||||
|
<tr><td>spark/</td><td>Event capture, predictions, advisory engine</td></tr>
|
||||||
|
<tr><td>brain/</td><td>Identity system, memory interface</td></tr>
|
||||||
|
<tr><td>timmy_serve/</td><td>API server, L402 Lightning gating</td></tr>
|
||||||
|
<tr><td>config.py</td><td>Pydantic settings — single source for all env vars</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Quality</h2>
|
||||||
|
<div class="tech-grid">
|
||||||
|
<div class="tech-item">
|
||||||
|
<strong>CI/CD</strong>
|
||||||
|
<span>GitHub Actions: lint, test, Docker build</span>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<strong>Testing</strong>
|
||||||
|
<span>tox-managed: unit, integration, functional, e2e</span>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<strong>Linting</strong>
|
||||||
|
<span>black + isort + bandit security scanning</span>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<strong>Security</strong>
|
||||||
|
<span>CSRF, XSS prevention, no hardcoded secrets</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>MIT License © 2026 Alexander Whitestone · <a href="https://github.com/AlexanderWhitestone/Timmy-time-dashboard">View Source</a></p>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
var SERVER = localStorage.getItem('timmy-server') || 'http://localhost:8000';
|
|
||||||
var messages = document.getElementById('messages');
|
|
||||||
var input = document.getElementById('input');
|
|
||||||
var dot = document.getElementById('status-dot');
|
|
||||||
var stxt = document.getElementById('status-text');
|
|
||||||
var connected = false;
|
|
||||||
var sending = false;
|
|
||||||
|
|
||||||
function setStatus(state) {
|
|
||||||
dot.className = state;
|
|
||||||
if (state === 'online') stxt.textContent = 'online';
|
|
||||||
else if (state === 'trying') stxt.textContent = 'connecting';
|
|
||||||
else stxt.textContent = 'offline';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Looping scroll ──
|
|
||||||
// Messages live inside two duplicate containers.
|
|
||||||
// When scroll crosses the midpoint we silently reposition,
|
|
||||||
// creating an infinite one-direction loop.
|
|
||||||
var loopEnabled = false;
|
|
||||||
var suppressLoop = false;
|
|
||||||
|
|
||||||
function buildLoop() {
|
|
||||||
// Clone all messages into a second set so the container is 2x tall
|
|
||||||
var existing = messages.querySelectorAll('.msg, .thinking');
|
|
||||||
if (existing.length < 2) { loopEnabled = false; return; }
|
|
||||||
|
|
||||||
// Remove any previous clone zone
|
|
||||||
var oldClone = document.getElementById('clone-zone');
|
|
||||||
if (oldClone) oldClone.remove();
|
|
||||||
|
|
||||||
var zone = document.createElement('div');
|
|
||||||
zone.id = 'clone-zone';
|
|
||||||
for (var i = 0; i < existing.length; i++) {
|
|
||||||
zone.appendChild(existing[i].cloneNode(true));
|
|
||||||
}
|
|
||||||
messages.appendChild(zone);
|
|
||||||
loopEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.addEventListener('scroll', function() {
|
|
||||||
if (!loopEnabled || suppressLoop) return;
|
|
||||||
var half = messages.scrollHeight / 2;
|
|
||||||
if (messages.scrollTop >= half) {
|
|
||||||
suppressLoop = true;
|
|
||||||
messages.scrollTop -= half;
|
|
||||||
suppressLoop = false;
|
|
||||||
} else if (messages.scrollTop <= 0) {
|
|
||||||
suppressLoop = true;
|
|
||||||
messages.scrollTop += half;
|
|
||||||
suppressLoop = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function scrollDown() {
|
|
||||||
requestAnimationFrame(function() {
|
|
||||||
// Scroll to latest message (just before clone zone)
|
|
||||||
var clone = document.getElementById('clone-zone');
|
|
||||||
if (clone) {
|
|
||||||
messages.scrollTop = clone.offsetTop - messages.clientHeight + 40;
|
|
||||||
} else {
|
|
||||||
messages.scrollTop = messages.scrollHeight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function rebuildAndScroll() {
|
|
||||||
// Small delay so DOM settles, then rebuild loop and scroll
|
|
||||||
requestAnimationFrame(function() {
|
|
||||||
buildLoop();
|
|
||||||
scrollDown();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addMsg(role, text) {
|
|
||||||
// Remove old clone zone before adding new message
|
|
||||||
var oldClone = document.getElementById('clone-zone');
|
|
||||||
if (oldClone) oldClone.remove();
|
|
||||||
|
|
||||||
var div = document.createElement('div');
|
|
||||||
div.className = 'msg ' + role;
|
|
||||||
|
|
||||||
if (role === 'system') {
|
|
||||||
div.textContent = text;
|
|
||||||
} else {
|
|
||||||
var label = document.createElement('div');
|
|
||||||
label.className = 'label';
|
|
||||||
label.textContent = role === 'timmy' ? 'TIMMY' : 'YOU';
|
|
||||||
|
|
||||||
var body = document.createElement('div');
|
|
||||||
body.className = 'body';
|
|
||||||
|
|
||||||
if (role === 'timmy') {
|
|
||||||
body.innerHTML = renderMarkdown(text);
|
|
||||||
} else {
|
|
||||||
body.textContent = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.appendChild(label);
|
|
||||||
div.appendChild(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.appendChild(div);
|
|
||||||
rebuildAndScroll();
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showThinking() {
|
|
||||||
var oldClone = document.getElementById('clone-zone');
|
|
||||||
if (oldClone) oldClone.remove();
|
|
||||||
|
|
||||||
var div = document.createElement('div');
|
|
||||||
div.className = 'thinking';
|
|
||||||
div.id = 'thinking';
|
|
||||||
div.innerHTML = '<span>.</span><span>.</span><span>.</span>';
|
|
||||||
messages.appendChild(div);
|
|
||||||
rebuildAndScroll();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideThinking() {
|
|
||||||
var el = document.getElementById('thinking');
|
|
||||||
if (el) el.remove();
|
|
||||||
// Also remove from clone zone
|
|
||||||
var cloneThinking = document.querySelector('#clone-zone .thinking');
|
|
||||||
if (cloneThinking) cloneThinking.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple markdown rendering (safe — no innerHTML with user content)
|
|
||||||
function renderMarkdown(text) {
|
|
||||||
// Escape HTML first
|
|
||||||
var safe = text
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>');
|
|
||||||
|
|
||||||
// Code blocks
|
|
||||||
safe = safe.replace(/```(\w*)\n?([\s\S]*?)```/g, function(m, lang, code) {
|
|
||||||
return '<pre><code>' + code.trim() + '</code></pre>';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Inline code
|
|
||||||
safe = safe.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
||||||
|
|
||||||
// Bold
|
|
||||||
safe = safe.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
||||||
|
|
||||||
// Italic
|
|
||||||
safe = safe.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
||||||
|
|
||||||
// Paragraphs
|
|
||||||
safe = safe.replace(/\n\n+/g, '</p><p>');
|
|
||||||
safe = '<p>' + safe + '</p>';
|
|
||||||
|
|
||||||
// Line breaks within paragraphs
|
|
||||||
safe = safe.replace(/\n/g, '<br>');
|
|
||||||
|
|
||||||
return safe;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probe the server for connectivity
|
|
||||||
function probe() {
|
|
||||||
setStatus('trying');
|
|
||||||
fetch(SERVER + '/health/status', { mode: 'cors', signal: AbortSignal.timeout(4000) })
|
|
||||||
.then(function(r) {
|
|
||||||
if (r.ok) {
|
|
||||||
connected = true;
|
|
||||||
setStatus('online');
|
|
||||||
} else {
|
|
||||||
connected = false;
|
|
||||||
setStatus('offline');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function() {
|
|
||||||
connected = false;
|
|
||||||
setStatus('offline');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send message to Timmy
|
|
||||||
function send(text) {
|
|
||||||
if (sending) return;
|
|
||||||
|
|
||||||
// Special commands
|
|
||||||
if (text.startsWith('/connect ')) {
|
|
||||||
var url = text.slice(9).trim().replace(/\/+$/, '');
|
|
||||||
SERVER = url;
|
|
||||||
localStorage.setItem('timmy-server', SERVER);
|
|
||||||
addMsg('system', 'server set to ' + SERVER);
|
|
||||||
probe();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text === '/clear') {
|
|
||||||
messages.innerHTML = '';
|
|
||||||
addMsg('system', 'chat cleared');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text === '/help') {
|
|
||||||
addMsg('system', '/connect <url> — set server · /clear — clear chat');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
addMsg('user', text);
|
|
||||||
|
|
||||||
if (!connected) {
|
|
||||||
addMsg('system', 'not connected — start timmy (make dev) or /connect <url>');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sending = true;
|
|
||||||
showThinking();
|
|
||||||
|
|
||||||
var form = new FormData();
|
|
||||||
form.append('message', text);
|
|
||||||
|
|
||||||
fetch(SERVER + '/agents/timmy/chat', {
|
|
||||||
method: 'POST',
|
|
||||||
body: form,
|
|
||||||
mode: 'cors',
|
|
||||||
})
|
|
||||||
.then(function(r) {
|
|
||||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
||||||
return r.text();
|
|
||||||
})
|
|
||||||
.then(function(html) {
|
|
||||||
hideThinking();
|
|
||||||
// Parse the response HTML to extract Timmy's reply
|
|
||||||
var parser = new DOMParser();
|
|
||||||
var doc = parser.parseFromString(html, 'text/html');
|
|
||||||
|
|
||||||
var agentBody = doc.querySelector('.chat-message.agent .msg-body');
|
|
||||||
var errorBody = doc.querySelector('.chat-message.error-msg .msg-body');
|
|
||||||
|
|
||||||
if (agentBody) {
|
|
||||||
addMsg('timmy', agentBody.textContent.trim());
|
|
||||||
} else if (errorBody) {
|
|
||||||
addMsg('system', errorBody.textContent.trim());
|
|
||||||
} else {
|
|
||||||
addMsg('system', 'no response');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function(err) {
|
|
||||||
hideThinking();
|
|
||||||
addMsg('system', 'error: ' + err.message);
|
|
||||||
probe();
|
|
||||||
})
|
|
||||||
.finally(function() {
|
|
||||||
sending = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input handling — Enter to send, no buttons
|
|
||||||
input.addEventListener('keydown', function(e) {
|
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
var text = input.value.trim();
|
|
||||||
if (!text) return;
|
|
||||||
input.value = '';
|
|
||||||
send(text);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initial welcome
|
|
||||||
addMsg('system', 'timmy time — sovereign ai');
|
|
||||||
addMsg('timmy', "What's on your mind?");
|
|
||||||
|
|
||||||
// Probe on load
|
|
||||||
probe();
|
|
||||||
|
|
||||||
// Re-probe periodically
|
|
||||||
setInterval(probe, 15000);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ version = "1.0.0"
|
|||||||
description = "Mission Control for sovereign AI agents"
|
description = "Mission Control for sovereign AI agents"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
authors = ["Alexander Whitestone"]
|
||||||
|
homepage = "https://alexanderwhitestone.github.io/Timmy-time-dashboard/"
|
||||||
|
repository = "https://github.com/AlexanderWhitestone/Timmy-time-dashboard"
|
||||||
packages = [
|
packages = [
|
||||||
{ include = "config.py", from = "src" },
|
{ include = "config.py", from = "src" },
|
||||||
{ include = "brain", from = "src" },
|
{ include = "brain", from = "src" },
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ class Settings(BaseSettings):
|
|||||||
ollama_url: str = "http://localhost:11434"
|
ollama_url: str = "http://localhost:11434"
|
||||||
|
|
||||||
# LLM model passed to Agno/Ollama — override with OLLAMA_MODEL
|
# LLM model passed to Agno/Ollama — override with OLLAMA_MODEL
|
||||||
# llama3.1:8b-instruct is used instead of llama3.2 because it is
|
# qwen2.5:14b is the primary model — better reasoning and tool calling
|
||||||
# specifically fine-tuned for reliable tool/function calling.
|
# than llama3.1:8b-instruct while still running locally on modest hardware.
|
||||||
|
# Fallback: llama3.1:8b-instruct if qwen2.5:14b not available.
|
||||||
# llama3.2 (3B) hallucinated tool output consistently in testing.
|
# llama3.2 (3B) hallucinated tool output consistently in testing.
|
||||||
# Fallback: qwen2.5:14b if llama3.1:8b-instruct not available.
|
|
||||||
ollama_model: str = "qwen2.5:14b"
|
ollama_model: str = "qwen2.5:14b"
|
||||||
|
|
||||||
# Set DEBUG=true to enable /docs and /redoc (disabled by default)
|
# Set DEBUG=true to enable /docs and /redoc (disabled by default)
|
||||||
|
|||||||
Reference in New Issue
Block a user