diff --git a/agents/daily_briefing.py b/agents/daily_briefing.py index 9365ea9..31f5f87 100644 --- a/agents/daily_briefing.py +++ b/agents/daily_briefing.py @@ -9,7 +9,7 @@ with a config dict specifying location, wiki parent, and agent ID. import sys from datetime import datetime from shared import ( - MT, WIKI_API, WIKI_COLLECTION_ID, MONTH_NAMES, + MT, DASHBOARD_API, WIKI_API, WIKI_COLLECTION_ID, MONTH_NAMES, api_request, log_run, wiki_headers, find_child_doc, ensure_child_doc, ) @@ -122,6 +122,15 @@ def run(config): """ agent_id = config["agent_id"] + # Fetch live config from dashboard API, merge over defaults + try: + live_config = api_request(f"{DASHBOARD_API}/api/agents/{agent_id}/config") + if live_config.get("location"): + config["location"] = live_config["location"] + print(f"Using live config location: {config['location'].get('name', '?')}") + except Exception as e: + print(f"Could not fetch live config, using defaults: {e}") + try: print(f"Collecting sub-agent data for {config['person']}...") sections = collect_sections(config) diff --git a/dashboard/app.py b/dashboard/app.py index af286eb..3af3e74 100644 --- a/dashboard/app.py +++ b/dashboard/app.py @@ -80,12 +80,14 @@ class AgentCreate(BaseModel): name: str description: str = "" schedule: str = "manual" + config: dict = {} class AgentUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None schedule: Optional[str] = None status: Optional[str] = None + config: Optional[dict] = None class RunCreate(BaseModel): status: str = "running" @@ -108,6 +110,15 @@ def health(): return {"status": "ok", "service": "agent-command-center"} +@app.get("/api/agents/{agent_id}/config") +def get_agent_config(agent_id: str, db: Session = Depends(get_db)): + """Internal endpoint for agents to fetch their config. No auth required.""" + agent = db.query(Agent).filter(Agent.id == agent_id).first() + if not agent: + raise HTTPException(status_code=404, detail="Agent not found") + return agent.config or {} + + @app.get("/api/agents") def list_agents(user: str = Depends(require_auth), db: Session = Depends(get_db)): agents = db.query(Agent).all() @@ -127,6 +138,7 @@ def list_agents(user: str = Depends(require_auth), db: Session = Depends(get_db) "description": a.description, "schedule": a.schedule, "status": a.status, + "config": a.config or {}, "created_at": a.created_at.isoformat() if a.created_at else None, "last_run": { "status": last_run.status, @@ -149,6 +161,7 @@ def create_agent(agent: AgentCreate, db: Session = Depends(get_db)): name=agent.name, description=agent.description, schedule=agent.schedule, + config=agent.config, ) db.add(new_agent) db.commit() @@ -167,6 +180,7 @@ def get_agent(agent_id: str, user: str = Depends(require_auth), db: Session = De "description": agent.description, "schedule": agent.schedule, "status": agent.status, + "config": agent.config or {}, "created_at": agent.created_at.isoformat() if agent.created_at else None, "runs": [{ "id": r.id, @@ -185,7 +199,12 @@ def update_agent(agent_id: str, update: AgentUpdate, db: Session = Depends(get_d agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise HTTPException(status_code=404, detail="Agent not found") - for field, value in update.model_dump(exclude_none=True).items(): + updates = update.model_dump(exclude_none=True) + if "config" in updates: + current_config = agent.config or {} + current_config.update(updates.pop("config")) + agent.config = current_config + for field, value in updates.items(): setattr(agent, field, value) db.commit() return {"id": agent.id, "status": "updated"} diff --git a/dashboard/models.py b/dashboard/models.py index e433c86..1ca9c0f 100644 --- a/dashboard/models.py +++ b/dashboard/models.py @@ -12,6 +12,7 @@ class Agent(Base): description = Column(Text, default="") schedule = Column(String, default="manual") status = Column(String, default="active") + config = Column(JSON, default=dict) created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) runs = relationship("Run", back_populates="agent", order_by="Run.started_at.desc()") diff --git a/dashboard/static/index.html b/dashboard/static/index.html index 6fe28a7..ed2e82c 100644 --- a/dashboard/static/index.html +++ b/dashboard/static/index.html @@ -13,6 +13,7 @@ --text: #e4e6ed; --text-dim: #8b8fa3; --accent: #6c5ce7; + --accent-hover: #7c6ef0; --green: #00b894; --red: #e17055; --yellow: #fdcb6e; @@ -33,79 +34,41 @@ align-items: center; justify-content: space-between; } - .header h1 { - font-size: 1.4rem; - font-weight: 600; - letter-spacing: -0.02em; - } + .header h1 { font-size: 1.4rem; font-weight: 600; letter-spacing: -0.02em; } .header .status { - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 0.85rem; - color: var(--text-dim); - } - .header .status .dot { - width: 8px; height: 8px; - border-radius: 50%; - background: var(--green); + display: flex; align-items: center; gap: 0.5rem; + font-size: 0.85rem; color: var(--text-dim); } + .header .status .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); } .logout-btn { - background: none; - border: 1px solid var(--border); - color: var(--text-dim); - padding: 0.35rem 0.75rem; - border-radius: 6px; - font-size: 0.8rem; - cursor: pointer; - margin-left: 1rem; - transition: border-color 0.2s, color 0.2s; + background: none; border: 1px solid var(--border); color: var(--text-dim); + padding: 0.35rem 0.75rem; border-radius: 6px; font-size: 0.8rem; + cursor: pointer; margin-left: 1rem; transition: border-color 0.2s, color 0.2s; } .logout-btn:hover { border-color: var(--text-dim); color: var(--text); } .container { max-width: 1200px; margin: 0 auto; padding: 1.5rem 2rem; } - /* Agent Cards */ .agents-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); - gap: 1rem; - margin-bottom: 2rem; + display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 1rem; margin-bottom: 0.75rem; } .agent-card { - background: var(--surface); - border: 1px solid var(--border); - border-radius: 10px; - padding: 1.25rem; - cursor: pointer; + background: var(--surface); border: 1px solid var(--border); + border-radius: 10px; padding: 1.25rem; cursor: pointer; transition: border-color 0.2s, transform 0.1s; } - .agent-card:hover { - border-color: var(--accent); - transform: translateY(-1px); - } - .agent-card .card-top { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 0.75rem; - } + .agent-card:hover { border-color: var(--accent); transform: translateY(-1px); } + .agent-card.dimmed { opacity: 0.45; } + .agent-card.dimmed:hover { opacity: 0.7; } + .agent-card .card-top { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.75rem; } .agent-card h3 { font-size: 1.05rem; font-weight: 600; } .agent-card .desc { color: var(--text-dim); font-size: 0.85rem; margin-bottom: 1rem; } - .agent-card .card-stats { - display: flex; - gap: 1.25rem; - font-size: 0.8rem; - color: var(--text-dim); - } + .agent-card .card-stats { display: flex; gap: 1.25rem; font-size: 0.8rem; color: var(--text-dim); } .agent-card .card-stats span { display: flex; align-items: center; gap: 0.3rem; } + .badge { - display: inline-block; - padding: 0.15rem 0.6rem; - border-radius: 12px; - font-size: 0.75rem; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.03em; + display: inline-block; padding: 0.15rem 0.6rem; border-radius: 12px; + font-size: 0.75rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.03em; } .badge.active { background: rgba(0,184,148,0.15); color: var(--green); } .badge.paused { background: rgba(253,203,110,0.15); color: var(--yellow); } @@ -114,104 +77,87 @@ .badge.failed { background: rgba(225,112,85,0.15); color: var(--red); } .badge.running { background: rgba(116,185,255,0.15); color: var(--blue); } - /* Section headers */ - .section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; + .paused-toggle { + background: none; border: none; color: var(--text-dim); font-size: 0.8rem; + cursor: pointer; padding: 0.4rem 0; margin-bottom: 1.5rem; display: none; } + .paused-toggle:hover { color: var(--text); } + + .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; } .section-header h2 { font-size: 1.1rem; font-weight: 600; } - /* Run History Table */ .runs-table { - width: 100%; - border-collapse: collapse; - background: var(--surface); - border-radius: 10px; - overflow: hidden; - border: 1px solid var(--border); + width: 100%; border-collapse: collapse; background: var(--surface); + border-radius: 10px; overflow: hidden; border: 1px solid var(--border); } .runs-table th { - text-align: left; - padding: 0.75rem 1rem; - font-size: 0.75rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--text-dim); - background: var(--surface2); - border-bottom: 1px solid var(--border); - } - .runs-table td { - padding: 0.65rem 1rem; - font-size: 0.85rem; - border-bottom: 1px solid var(--border); + text-align: left; padding: 0.75rem 1rem; font-size: 0.75rem; font-weight: 600; + text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); + background: var(--surface2); border-bottom: 1px solid var(--border); } + .runs-table td { padding: 0.65rem 1rem; font-size: 0.85rem; border-bottom: 1px solid var(--border); } .runs-table tr:last-child td { border-bottom: none; } .runs-table tr:hover td { background: var(--surface2); } - .output-preview { - max-width: 350px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: var(--text-dim); - } + .output-preview { max-width: 350px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-dim); } - /* Detail Modal */ .modal-overlay { - display: none; - position: fixed; - top: 0; left: 0; right: 0; bottom: 0; - background: rgba(0,0,0,0.6); - z-index: 100; - justify-content: center; - align-items: flex-start; - padding-top: 5vh; + display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0,0,0,0.6); z-index: 100; justify-content: center; + align-items: flex-start; padding-top: 5vh; } .modal-overlay.open { display: flex; } .modal { - background: var(--surface); - border: 1px solid var(--border); - border-radius: 12px; - width: 90%; - max-width: 800px; - max-height: 85vh; - overflow-y: auto; - padding: 1.5rem; + background: var(--surface); border: 1px solid var(--border); border-radius: 12px; + width: 90%; max-width: 800px; max-height: 85vh; overflow-y: auto; padding: 1.5rem; } .modal h2 { margin-bottom: 0.5rem; } .modal .meta { color: var(--text-dim); font-size: 0.85rem; margin-bottom: 1.5rem; } .modal .close-btn { - float: right; - background: none; - border: none; - color: var(--text-dim); - font-size: 1.5rem; - cursor: pointer; + float: right; background: none; border: none; color: var(--text-dim); + font-size: 1.5rem; cursor: pointer; } .modal .close-btn:hover { color: var(--text); } .run-output { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 1rem; - font-family: 'SF Mono', Monaco, Consolas, monospace; - font-size: 0.8rem; - white-space: pre-wrap; - max-height: 200px; - overflow-y: auto; - margin-top: 0.5rem; - color: var(--text-dim); + background: var(--bg); border: 1px solid var(--border); border-radius: 6px; + padding: 1rem; font-family: 'SF Mono', Monaco, Consolas, monospace; + font-size: 0.8rem; white-space: pre-wrap; max-height: 200px; overflow-y: auto; + margin-top: 0.5rem; color: var(--text-dim); } - .empty-state { - text-align: center; - padding: 3rem; - color: var(--text-dim); + /* Config form */ + .config-section { + background: var(--bg); border: 1px solid var(--border); border-radius: 8px; + padding: 1rem; margin-bottom: 1.5rem; } + .config-section h3 { font-size: 0.95rem; margin-bottom: 1rem; } + .config-grid { + display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; + } + .config-field label { + display: block; font-size: 0.75rem; font-weight: 500; color: var(--text-dim); + text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.3rem; + } + .config-field input, .config-field select { + width: 100%; padding: 0.5rem 0.65rem; background: var(--surface); + border: 1px solid var(--border); border-radius: 6px; color: var(--text); + font-size: 0.85rem; outline: none; + } + .config-field input:focus, .config-field select:focus { border-color: var(--accent); } + .config-actions { display: flex; gap: 0.5rem; margin-top: 1rem; } + .btn-save { + padding: 0.5rem 1.25rem; background: var(--accent); color: #fff; + border: none; border-radius: 6px; font-size: 0.85rem; cursor: pointer; + } + .btn-save:hover { background: var(--accent-hover); } + .btn-secondary { + padding: 0.5rem 1.25rem; background: none; color: var(--text-dim); + border: 1px solid var(--border); border-radius: 6px; font-size: 0.85rem; cursor: pointer; + } + .btn-secondary:hover { border-color: var(--text-dim); color: var(--text); } + .save-msg { font-size: 0.8rem; color: var(--green); margin-left: 0.75rem; line-height: 2; } + + .empty-state { text-align: center; padding: 3rem; color: var(--text-dim); } .empty-state h3 { margin-bottom: 0.5rem; color: var(--text); } - .time-ago { color: var(--text-dim); } @@ -227,14 +173,11 @@
-
-

Agents

-
+

Agents

+ -
-

Recent Runs

-
+

Recent Runs

@@ -244,12 +187,13 @@