Clean up generated files and fix 6 dashboard bugs (#142)

* chore: gitignore local/generated files and remove from tracking

Remove user-specific files (MEMORY.md, user_profile.md, prompts.py)
from source control. Add patterns for credentials, backups, and
generated content to .gitignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve 6 dashboard bugs — chat, /bugs, /swarm/events, WebSocket, marketplace, sidebar

1. Chat non-functional: CSRF middleware silently blocked HTMX POSTs.
   Added CSRF token transmission via hx-headers in base.html.

2. /bugs → 500: Route missing template vars (total, stats, filter_status).

3. /swarm/events → 500: Called .event_type.value on a plain str
   (SparkEvent.event_type is str, not enum). Also fixed timestamp
   and source field mismatches in the template.

4. WebSocket reconnect loop: No WS endpoint existed at /swarm/live,
   only an HTTP GET. Added @router.websocket("/live") using ws_manager.

5. Marketplace "Agent not found": Nav links /marketplace/ui matched
   the /{agent_id} catch-all. Added explicit /marketplace/ui route
   with enriched template context.

6. Agents sidebar "LOADING...": /swarm/agents/sidebar endpoint was
   missing. Added route returning the existing sidebar partial.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore src/timmy/prompts.py to source control

prompts.py is imported by timmy.agent and is production code,
not a user-local file. Re-add to tracking and remove from .gitignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Trip T <trip@local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Whitestone
2026-03-07 21:26:41 -05:00
committed by GitHub
parent b615595100
commit 3bf7187482
8 changed files with 92 additions and 115 deletions

17
.gitignore vendored
View File

@@ -17,6 +17,10 @@ env/
.env .env
.env.* .env.*
!.env.example !.env.example
discord_credentials.txt
# Backup / temp files
*~
# SQLite — never commit databases or WAL/SHM artifacts # SQLite — never commit databases or WAL/SHM artifacts
*.db *.db
@@ -55,6 +59,19 @@ src/data/
*.swo *.swo
.claude/ .claude/
# Local content — user-specific or generated
MEMORY.md
memory/self/user_profile.md
TIMMYTIME
introduction.txt
messages.txt
morning_briefing.txt
markdown_report.md
data/timmy_soul.jsonl
scripts/migrate_to_zeroclaw.py
src/infrastructure/db_pool.py
workspace/
# macOS # macOS
.DS_Store .DS_Store
.AppleDouble .AppleDouble

View File

@@ -1,52 +0,0 @@
# Timmy Hot Memory
> Working RAM — always loaded, ~300 lines max, pruned monthly
> Last updated: 2026-02-26
---
## Current Status
**Agent State:** Operational
**Mode:** Development
**Active Tasks:** 0
**Pending Decisions:** None
---
## Standing Rules
1. **Sovereignty First** — No cloud dependencies
2. **Local-Only Inference** — Ollama on localhost
3. **Privacy by Design** — Telemetry disabled
4. **Tool Minimalism** — Use tools only when necessary
5. **Memory Discipline** — Write handoffs at session end
---
## Agent Roster
| Agent | Role | Status |
|-------|------|--------|
| Timmy | Core | Active |
---
## User Profile
**Name:** Not
## Key Decisions
(none yet)
---
## Pending Actions
- [ ] Learn user's name
---
*Prune date: 2026-02-25*

View File

@@ -1,43 +0,0 @@
# User Profile
> Learned information about the user. Updated continuously.
## Basic Information
**Name:** Not
**Location:** (unknown)
**Occupation:** (unknown)
**Technical Level:** (to be assessed)
## Interests & Expertise
- (to be learned from conversations)
## Preferences
### Communication
- Response style: (default: concise, technical)
- Detail level: (default: medium)
- Humor: (default: minimal)
### Tools
- Auto-tool usage: (default: minimal)
- Confirmation required for: shell commands, file writes
### Memory
- Personalization: Enabled
- Context retention: 20 messages (working), 100 (short-term)
## Important Facts
- (to be extracted from conversations)
## Relationship History
- First session: 2026-02-25
- Total sessions: 1
- Key milestones: (none yet)
---
*Last updated: 2026-02-27*

View File

@@ -47,28 +47,41 @@ async def api_list_agents():
@router.get("/marketplace") @router.get("/marketplace")
async def marketplace_json(request: Request):
"""Marketplace JSON API (backward compat)."""
return await api_list_agents()
@router.get("/marketplace/ui", response_class=HTMLResponse)
async def marketplace_ui(request: Request): async def marketplace_ui(request: Request):
"""Marketplace page — returns JSON for API requests, HTML for browser.""" """Marketplace HTML page."""
# Check if client wants JSON (common test clients don't set Accept header)
accept = request.headers.get("accept", "")
# Return JSON if Accept header indicates JSON OR if no preference (default to JSON for API)
if "application/json" in accept or accept == "*/*" or not accept:
return await api_list_agents()
# Browser request - return HTML
try: try:
brain = BrainClient() brain = BrainClient()
tasks = await brain.get_pending_tasks(limit=20) tasks = await brain.get_pending_tasks(limit=20)
except Exception: except Exception:
tasks = [] tasks = []
# Enrich agents with fields the template expects
enriched = []
for agent in AGENT_CATALOG:
a = dict(agent)
a.setdefault("status", a.get("default_status", "active"))
a.setdefault("tasks_completed", 0)
a.setdefault("total_earned", 0)
enriched.append(a)
active = sum(1 for a in enriched if a["status"] == "active")
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"marketplace.html", "marketplace.html",
{ {
"agents": AGENT_CATALOG, "agents": enriched,
"pending_tasks": tasks, "pending_tasks": tasks,
"message": "Personas deprecated — use Brain Task Queue", "message": "Personas deprecated — use Brain Task Queue",
"page_title": "Agent Marketplace",
"active_count": active,
"planned_count": 0,
} }
) )

View File

@@ -4,11 +4,12 @@ import json
import logging import logging
from typing import Optional from typing import Optional
from fastapi import APIRouter, Request from fastapi import APIRouter, Request, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from spark.engine import spark_engine from spark.engine import spark_engine
from dashboard.templating import templates from dashboard.templating import templates
from infrastructure.ws_manager.handler import ws_manager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -31,13 +32,13 @@ async def swarm_events(
if agent_id: if agent_id:
events = [e for e in events if e.agent_id == agent_id] events = [e for e in events if e.agent_id == agent_id]
if event_type: if event_type:
events = [e for e in events if e.event_type.value == event_type] events = [e for e in events if e.event_type == event_type]
# Prepare summary and event types for template # Prepare summary and event types for template
summary = {} summary = {}
event_types = set() event_types = set()
for e in events: for e in events:
etype = e.event_type.value etype = e.event_type
event_types.add(etype) event_types.add(etype)
summary[etype] = summary.get(etype, 0) + 1 summary[etype] = summary.get(etype, 0) + 1
@@ -60,7 +61,7 @@ async def swarm_live(request: Request):
"""Live swarm activity page.""" """Live swarm activity page."""
status = spark_engine.status() status = spark_engine.status()
events = spark_engine.get_timeline(limit=20) events = spark_engine.get_timeline(limit=20)
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"swarm_live.html", "swarm_live.html",
@@ -69,3 +70,34 @@ async def swarm_live(request: Request):
"events": events, "events": events,
}, },
) )
@router.websocket("/live")
async def swarm_ws(websocket: WebSocket):
"""WebSocket endpoint for live swarm updates."""
await ws_manager.connect(websocket)
try:
while True:
await websocket.receive_text()
except WebSocketDisconnect:
ws_manager.disconnect(websocket)
@router.get("/agents/sidebar", response_class=HTMLResponse)
async def agents_sidebar(request: Request):
"""Sidebar partial showing agent status for the home page."""
from config import settings
agents = [
{
"id": "default",
"name": settings.agent_name,
"status": "idle",
"type": "local",
"capabilities": "chat,reasoning,research,planning",
"last_seen": None,
}
]
return templates.TemplateResponse(
request, "partials/swarm_agents_sidebar.html", {"agents": agents}
)

View File

@@ -89,7 +89,9 @@ async def mission_control(request: Request):
@router.get("/bugs", response_class=HTMLResponse) @router.get("/bugs", response_class=HTMLResponse)
async def bugs_page(request: Request): async def bugs_page(request: Request):
return templates.TemplateResponse(request, "bugs.html", {"bugs": []}) return templates.TemplateResponse(request, "bugs.html", {
"bugs": [], "total": 0, "stats": {}, "filter_status": None,
})
@router.get("/self-coding", response_class=HTMLResponse) @router.get("/self-coding", response_class=HTMLResponse)

View File

@@ -15,6 +15,14 @@
<link rel="stylesheet" href="/static/style.css?v=5" /> <link rel="stylesheet" href="/static/style.css?v=5" />
{% block extra_styles %}{% endblock %} {% block extra_styles %}{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.3/dist/htmx.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.3/dist/htmx.min.js" crossorigin="anonymous"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var match = document.cookie.match(/csrf_token=([^;]+)/);
if (match) {
document.body.setAttribute('hx-headers', JSON.stringify({"X-CSRF-Token": match[1]}));
}
});
</script>
<script defer src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/dompurify@3.2.4/dist/purify.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/dompurify@3.2.4/dist/purify.min.js"></script>
</head> </head>

View File

@@ -63,14 +63,14 @@
</thead> </thead>
<tbody> <tbody>
{% for event in events %} {% for event in events %}
<tr class="event-row" data-type="{{ event.event_type.value }}"> <tr class="event-row" data-type="{{ event.event_type }}">
<td class="event-time">{{ event.timestamp[11:19] }}</td> <td class="event-time">{{ event.created_at[11:19] }}</td>
<td> <td>
<span class="mc-badge mc-badge-{{ event.event_type.value.split('.')[0] }}"> <span class="mc-badge mc-badge-{{ event.event_type.split('.')[0] }}">
{{ event.event_type.value }} {{ event.event_type }}
</span> </span>
</td> </td>
<td>{{ event.source }}</td> <td>{{ event.agent_id or '-' }}</td>
<td> <td>
{% if event.task_id %} {% if event.task_id %}
<a href="/swarm/events?task_id={{ event.task_id }}">{{ event.task_id[:8] }}...</a> <a href="/swarm/events?task_id={{ event.task_id }}">{{ event.task_id[:8] }}...</a>