feat: Mission Control v2 — swarm, L402, voice, marketplace, React dashboard
Major expansion of the Timmy Time Dashboard:
Backend modules:
- Swarm subsystem: registry, manager, bidder, coordinator, agent_runner, swarm_node, tasks, comms
- L402/Lightning: payment_handler, l402_proxy with HMAC macaroons
- Voice NLU: regex-based intent detection (chat, status, swarm, task, help, voice)
- Notifications: push notifier for swarm events
- Shortcuts: Siri Shortcuts iOS integration endpoints
- WebSocket: live dashboard event manager
- Inter-agent: agent-to-agent messaging layer
Dashboard routes:
- /swarm/* — swarm management and agent registry
- /marketplace — agent catalog with sat pricing
- /voice/* — voice command processing
- /mobile — mobile status endpoint
- /swarm/live — WebSocket live feed
React web dashboard (dashboard-web/):
- Sovereign Terminal design — dark theme with Bitcoin orange accents
- Three-column layout: status sidebar, workspace tabs, context panel
- Chat, Swarm, Tasks, Marketplace tab views
- JetBrains Mono typography, terminal aesthetic
- Framer Motion animations throughout
Tests: 228 passing (expanded from 93)
Includes Kimi's additional templates and QA work.
2026-02-21 12:57:38 -05:00
|
|
|
"""Push notification system for swarm events.
|
|
|
|
|
|
|
|
|
|
Collects notifications from swarm events (task completed, agent joined,
|
|
|
|
|
auction won, etc.) and makes them available to the dashboard via polling
|
|
|
|
|
or WebSocket. On macOS, can optionally trigger native notifications
|
|
|
|
|
via osascript.
|
|
|
|
|
|
|
|
|
|
No cloud push services — everything stays local.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
import platform
|
2026-03-08 12:50:44 -04:00
|
|
|
import subprocess
|
feat: Mission Control v2 — swarm, L402, voice, marketplace, React dashboard
Major expansion of the Timmy Time Dashboard:
Backend modules:
- Swarm subsystem: registry, manager, bidder, coordinator, agent_runner, swarm_node, tasks, comms
- L402/Lightning: payment_handler, l402_proxy with HMAC macaroons
- Voice NLU: regex-based intent detection (chat, status, swarm, task, help, voice)
- Notifications: push notifier for swarm events
- Shortcuts: Siri Shortcuts iOS integration endpoints
- WebSocket: live dashboard event manager
- Inter-agent: agent-to-agent messaging layer
Dashboard routes:
- /swarm/* — swarm management and agent registry
- /marketplace — agent catalog with sat pricing
- /voice/* — voice command processing
- /mobile — mobile status endpoint
- /swarm/live — WebSocket live feed
React web dashboard (dashboard-web/):
- Sovereign Terminal design — dark theme with Bitcoin orange accents
- Three-column layout: status sidebar, workspace tabs, context panel
- Chat, Swarm, Tasks, Marketplace tab views
- JetBrains Mono typography, terminal aesthetic
- Framer Motion animations throughout
Tests: 228 passing (expanded from 93)
Includes Kimi's additional templates and QA work.
2026-02-21 12:57:38 -05:00
|
|
|
from collections import deque
|
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class Notification:
|
|
|
|
|
id: int
|
|
|
|
|
title: str
|
|
|
|
|
message: str
|
|
|
|
|
category: str # swarm | task | agent | system | payment
|
2026-03-08 12:50:44 -04:00
|
|
|
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
feat: Mission Control v2 — swarm, L402, voice, marketplace, React dashboard
Major expansion of the Timmy Time Dashboard:
Backend modules:
- Swarm subsystem: registry, manager, bidder, coordinator, agent_runner, swarm_node, tasks, comms
- L402/Lightning: payment_handler, l402_proxy with HMAC macaroons
- Voice NLU: regex-based intent detection (chat, status, swarm, task, help, voice)
- Notifications: push notifier for swarm events
- Shortcuts: Siri Shortcuts iOS integration endpoints
- WebSocket: live dashboard event manager
- Inter-agent: agent-to-agent messaging layer
Dashboard routes:
- /swarm/* — swarm management and agent registry
- /marketplace — agent catalog with sat pricing
- /voice/* — voice command processing
- /mobile — mobile status endpoint
- /swarm/live — WebSocket live feed
React web dashboard (dashboard-web/):
- Sovereign Terminal design — dark theme with Bitcoin orange accents
- Three-column layout: status sidebar, workspace tabs, context panel
- Chat, Swarm, Tasks, Marketplace tab views
- JetBrains Mono typography, terminal aesthetic
- Framer Motion animations throughout
Tests: 228 passing (expanded from 93)
Includes Kimi's additional templates and QA work.
2026-02-21 12:57:38 -05:00
|
|
|
read: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PushNotifier:
|
|
|
|
|
"""Local push notification manager."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, max_history: int = 200, native_enabled: bool = True) -> None:
|
|
|
|
|
self._notifications: deque[Notification] = deque(maxlen=max_history)
|
|
|
|
|
self._counter = 0
|
|
|
|
|
self._native_enabled = native_enabled and platform.system() == "Darwin"
|
|
|
|
|
self._listeners: list = []
|
|
|
|
|
|
|
|
|
|
def notify(
|
|
|
|
|
self,
|
|
|
|
|
title: str,
|
|
|
|
|
message: str,
|
|
|
|
|
category: str = "system",
|
|
|
|
|
native: bool = False,
|
|
|
|
|
) -> Notification:
|
|
|
|
|
"""Create and store a notification."""
|
|
|
|
|
self._counter += 1
|
|
|
|
|
notif = Notification(
|
|
|
|
|
id=self._counter,
|
|
|
|
|
title=title,
|
|
|
|
|
message=message,
|
|
|
|
|
category=category,
|
|
|
|
|
)
|
|
|
|
|
self._notifications.appendleft(notif)
|
|
|
|
|
logger.info("Notification [%s]: %s — %s", category, title, message[:60])
|
|
|
|
|
|
|
|
|
|
# Trigger native macOS notification if requested
|
|
|
|
|
if native and self._native_enabled:
|
|
|
|
|
self._native_notify(title, message)
|
|
|
|
|
|
|
|
|
|
# Notify listeners (for WebSocket push)
|
|
|
|
|
for listener in self._listeners:
|
|
|
|
|
try:
|
|
|
|
|
listener(notif)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.error("Notification listener error: %s", exc)
|
|
|
|
|
|
|
|
|
|
return notif
|
|
|
|
|
|
|
|
|
|
def _native_notify(self, title: str, message: str) -> None:
|
|
|
|
|
"""Send a native macOS notification via osascript."""
|
|
|
|
|
try:
|
2026-03-08 12:50:44 -04:00
|
|
|
safe_message = message.replace("\\", "\\\\").replace('"', '\\"')
|
|
|
|
|
safe_title = title.replace("\\", "\\\\").replace('"', '\\"')
|
feat: Mission Control v2 — swarm, L402, voice, marketplace, React dashboard
Major expansion of the Timmy Time Dashboard:
Backend modules:
- Swarm subsystem: registry, manager, bidder, coordinator, agent_runner, swarm_node, tasks, comms
- L402/Lightning: payment_handler, l402_proxy with HMAC macaroons
- Voice NLU: regex-based intent detection (chat, status, swarm, task, help, voice)
- Notifications: push notifier for swarm events
- Shortcuts: Siri Shortcuts iOS integration endpoints
- WebSocket: live dashboard event manager
- Inter-agent: agent-to-agent messaging layer
Dashboard routes:
- /swarm/* — swarm management and agent registry
- /marketplace — agent catalog with sat pricing
- /voice/* — voice command processing
- /mobile — mobile status endpoint
- /swarm/live — WebSocket live feed
React web dashboard (dashboard-web/):
- Sovereign Terminal design — dark theme with Bitcoin orange accents
- Three-column layout: status sidebar, workspace tabs, context panel
- Chat, Swarm, Tasks, Marketplace tab views
- JetBrains Mono typography, terminal aesthetic
- Framer Motion animations throughout
Tests: 228 passing (expanded from 93)
Includes Kimi's additional templates and QA work.
2026-02-21 12:57:38 -05:00
|
|
|
script = (
|
2026-03-08 12:50:44 -04:00
|
|
|
f'display notification "{safe_message}" '
|
|
|
|
|
f'with title "Agent Dashboard" subtitle "{safe_title}"'
|
feat: Mission Control v2 — swarm, L402, voice, marketplace, React dashboard
Major expansion of the Timmy Time Dashboard:
Backend modules:
- Swarm subsystem: registry, manager, bidder, coordinator, agent_runner, swarm_node, tasks, comms
- L402/Lightning: payment_handler, l402_proxy with HMAC macaroons
- Voice NLU: regex-based intent detection (chat, status, swarm, task, help, voice)
- Notifications: push notifier for swarm events
- Shortcuts: Siri Shortcuts iOS integration endpoints
- WebSocket: live dashboard event manager
- Inter-agent: agent-to-agent messaging layer
Dashboard routes:
- /swarm/* — swarm management and agent registry
- /marketplace — agent catalog with sat pricing
- /voice/* — voice command processing
- /mobile — mobile status endpoint
- /swarm/live — WebSocket live feed
React web dashboard (dashboard-web/):
- Sovereign Terminal design — dark theme with Bitcoin orange accents
- Three-column layout: status sidebar, workspace tabs, context panel
- Chat, Swarm, Tasks, Marketplace tab views
- JetBrains Mono typography, terminal aesthetic
- Framer Motion animations throughout
Tests: 228 passing (expanded from 93)
Includes Kimi's additional templates and QA work.
2026-02-21 12:57:38 -05:00
|
|
|
)
|
|
|
|
|
subprocess.Popen(
|
|
|
|
|
["osascript", "-e", script],
|
|
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
|
stderr=subprocess.DEVNULL,
|
|
|
|
|
)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.debug("Native notification failed: %s", exc)
|
|
|
|
|
|
|
|
|
|
def recent(self, limit: int = 20, category: Optional[str] = None) -> list[Notification]:
|
|
|
|
|
"""Get recent notifications, optionally filtered by category."""
|
|
|
|
|
notifs = list(self._notifications)
|
|
|
|
|
if category:
|
|
|
|
|
notifs = [n for n in notifs if n.category == category]
|
|
|
|
|
return notifs[:limit]
|
|
|
|
|
|
|
|
|
|
def unread_count(self) -> int:
|
|
|
|
|
return sum(1 for n in self._notifications if not n.read)
|
|
|
|
|
|
|
|
|
|
def mark_read(self, notification_id: int) -> bool:
|
|
|
|
|
for n in self._notifications:
|
|
|
|
|
if n.id == notification_id:
|
|
|
|
|
n.read = True
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def mark_all_read(self) -> int:
|
|
|
|
|
count = 0
|
|
|
|
|
for n in self._notifications:
|
|
|
|
|
if not n.read:
|
|
|
|
|
n.read = True
|
|
|
|
|
count += 1
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
def clear(self) -> None:
|
|
|
|
|
self._notifications.clear()
|
|
|
|
|
|
2026-03-08 12:50:44 -04:00
|
|
|
def add_listener(self, callback: "Callable[[Notification], None]") -> None:
|
feat: Mission Control v2 — swarm, L402, voice, marketplace, React dashboard
Major expansion of the Timmy Time Dashboard:
Backend modules:
- Swarm subsystem: registry, manager, bidder, coordinator, agent_runner, swarm_node, tasks, comms
- L402/Lightning: payment_handler, l402_proxy with HMAC macaroons
- Voice NLU: regex-based intent detection (chat, status, swarm, task, help, voice)
- Notifications: push notifier for swarm events
- Shortcuts: Siri Shortcuts iOS integration endpoints
- WebSocket: live dashboard event manager
- Inter-agent: agent-to-agent messaging layer
Dashboard routes:
- /swarm/* — swarm management and agent registry
- /marketplace — agent catalog with sat pricing
- /voice/* — voice command processing
- /mobile — mobile status endpoint
- /swarm/live — WebSocket live feed
React web dashboard (dashboard-web/):
- Sovereign Terminal design — dark theme with Bitcoin orange accents
- Three-column layout: status sidebar, workspace tabs, context panel
- Chat, Swarm, Tasks, Marketplace tab views
- JetBrains Mono typography, terminal aesthetic
- Framer Motion animations throughout
Tests: 228 passing (expanded from 93)
Includes Kimi's additional templates and QA work.
2026-02-21 12:57:38 -05:00
|
|
|
"""Register a callback for real-time notification delivery."""
|
|
|
|
|
self._listeners.append(callback)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Module-level singleton
|
|
|
|
|
notifier = PushNotifier()
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
async def notify_briefing_ready(briefing) -> None:
|
2026-02-26 09:35:59 -05:00
|
|
|
"""Notify the owner that a new morning briefing is ready.
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
|
2026-02-26 09:35:59 -05:00
|
|
|
Only triggers a native macOS popup when there are pending approval items.
|
|
|
|
|
Briefings with 0 approvals are still logged but don't interrupt the user
|
|
|
|
|
with a notification that leads to an empty-looking page.
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
briefing: A timmy.briefing.Briefing instance.
|
|
|
|
|
"""
|
|
|
|
|
n_approvals = len(briefing.approval_items) if briefing.approval_items else 0
|
2026-02-26 09:35:59 -05:00
|
|
|
|
|
|
|
|
if n_approvals == 0:
|
|
|
|
|
logger.info("Briefing ready but no pending approvals — skipping native notification")
|
|
|
|
|
return
|
|
|
|
|
|
2026-03-08 12:50:44 -04:00
|
|
|
message = f"Your morning briefing is ready. " f"{n_approvals} item(s) await your approval."
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
notifier.notify(
|
|
|
|
|
title="Morning Briefing Ready",
|
|
|
|
|
message=message,
|
|
|
|
|
category="briefing",
|
|
|
|
|
native=True,
|
|
|
|
|
)
|
|
|
|
|
logger.info("Briefing push notification dispatched (%d approval(s))", n_approvals)
|