1
0

Wire orchestrator pipe into task runner + pipe-verifying integration tests (#134)

This commit is contained in:
Alexander Whitestone
2026-03-06 01:20:14 -05:00
committed by GitHub
parent d10cff333a
commit 87dc5eadfe
14 changed files with 2617 additions and 0 deletions

View File

@@ -39,6 +39,7 @@ from dashboard.routes.thinking import router as thinking_router
from dashboard.routes.calm import router as calm_router
from dashboard.routes.swarm import router as swarm_router
from dashboard.routes.system import router as system_router
from dashboard.routes.paperclip import router as paperclip_router
from infrastructure.router.api import router as cascade_router
# Import dedicated middleware
@@ -304,6 +305,7 @@ app.include_router(thinking_router)
app.include_router(calm_router)
app.include_router(swarm_router)
app.include_router(system_router)
app.include_router(paperclip_router)
app.include_router(cascade_router)

View File

@@ -0,0 +1,318 @@
"""Paperclip AI integration routes.
Timmy-as-CEO: create issues, delegate to agents, review work, manage goals.
All business logic lives in the bridge — these routes stay thin.
"""
import logging
from typing import Optional
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from config import settings
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/paperclip", tags=["paperclip"])
def _disabled_response() -> JSONResponse:
return JSONResponse({"enabled": False, "detail": "Paperclip integration is disabled"})
# ── Status ───────────────────────────────────────────────────────────────────
@router.get("/status")
async def paperclip_status():
"""Integration health check."""
if not settings.paperclip_enabled:
return _disabled_response()
from integrations.paperclip.bridge import bridge
status = await bridge.get_status()
return status.model_dump()
# ── Issues (CEO creates & manages tickets) ───────────────────────────────────
@router.get("/issues")
async def list_issues(status: Optional[str] = None):
"""List all issues in the company."""
if not settings.paperclip_enabled:
return _disabled_response()
from integrations.paperclip.bridge import bridge
issues = await bridge.client.list_issues(status=status)
return [i.model_dump() for i in issues]
@router.get("/issues/{issue_id}")
async def get_issue(issue_id: str):
"""Get issue details with comments (CEO review)."""
if not settings.paperclip_enabled:
return _disabled_response()
from integrations.paperclip.bridge import bridge
return await bridge.review_issue(issue_id)
@router.post("/issues")
async def create_issue(request: Request):
"""Create a new issue and optionally assign to an agent."""
if not settings.paperclip_enabled:
return _disabled_response()
body = await request.json()
title = body.get("title")
if not title:
return JSONResponse({"error": "title is required"}, status_code=400)
from integrations.paperclip.bridge import bridge
issue = await bridge.create_and_assign(
title=title,
description=body.get("description", ""),
assignee_id=body.get("assignee_id"),
priority=body.get("priority"),
wake=body.get("wake", True),
)
if not issue:
return JSONResponse({"error": "Failed to create issue"}, status_code=502)
return issue.model_dump()
@router.post("/issues/{issue_id}/delegate")
async def delegate_issue(issue_id: str, request: Request):
"""Delegate an issue to an agent (CEO assignment)."""
if not settings.paperclip_enabled:
return _disabled_response()
body = await request.json()
agent_id = body.get("agent_id")
if not agent_id:
return JSONResponse({"error": "agent_id is required"}, status_code=400)
from integrations.paperclip.bridge import bridge
ok = await bridge.delegate_issue(
issue_id=issue_id,
agent_id=agent_id,
message=body.get("message"),
)
if not ok:
return JSONResponse({"error": "Failed to delegate issue"}, status_code=502)
return {"ok": True, "issue_id": issue_id, "agent_id": agent_id}
@router.post("/issues/{issue_id}/close")
async def close_issue(issue_id: str, request: Request):
"""Close an issue (CEO sign-off)."""
if not settings.paperclip_enabled:
return _disabled_response()
body = await request.json()
from integrations.paperclip.bridge import bridge
ok = await bridge.close_issue(issue_id, comment=body.get("comment"))
if not ok:
return JSONResponse({"error": "Failed to close issue"}, status_code=502)
return {"ok": True, "issue_id": issue_id}
@router.post("/issues/{issue_id}/comment")
async def add_comment(issue_id: str, request: Request):
"""Add a CEO comment to an issue."""
if not settings.paperclip_enabled:
return _disabled_response()
body = await request.json()
content = body.get("content")
if not content:
return JSONResponse({"error": "content is required"}, status_code=400)
from integrations.paperclip.bridge import bridge
comment = await bridge.client.add_comment(issue_id, f"[CEO] {content}")
if not comment:
return JSONResponse({"error": "Failed to add comment"}, status_code=502)
return comment.model_dump()
# ── Agents (team management) ─────────────────────────────────────────────────
@router.get("/agents")
async def list_agents():
"""List all agents in the org."""
if not settings.paperclip_enabled:
return _disabled_response()
from integrations.paperclip.bridge import bridge
agents = await bridge.get_team()
return [a.model_dump() for a in agents]
@router.get("/org")
async def org_chart():
"""Get the organizational chart."""
if not settings.paperclip_enabled:
return _disabled_response()
from integrations.paperclip.bridge import bridge
org = await bridge.get_org_chart()
return org or {"error": "Could not retrieve org chart"}
@router.post("/agents/{agent_id}/wake")
async def wake_agent(agent_id: str, request: Request):
"""Wake an agent to start working."""
if not settings.paperclip_enabled:
return _disabled_response()
body = await request.json()
from integrations.paperclip.bridge import bridge
result = await bridge.client.wake_agent(
agent_id,
issue_id=body.get("issue_id"),
message=body.get("message"),
)
if not result:
return JSONResponse({"error": "Failed to wake agent"}, status_code=502)
return result
# ── Goals ────────────────────────────────────────────────────────────────────
@router.get("/goals")
async def list_goals():
"""List company goals."""
if not settings.paperclip_enabled:
return _disabled_response()
from integrations.paperclip.bridge import bridge
goals = await bridge.list_goals()
return [g.model_dump() for g in goals]
@router.post("/goals")
async def create_goal(request: Request):
"""Set a new company goal (CEO directive)."""
if not settings.paperclip_enabled:
return _disabled_response()
body = await request.json()
title = body.get("title")
if not title:
return JSONResponse({"error": "title is required"}, status_code=400)
from integrations.paperclip.bridge import bridge
goal = await bridge.set_goal(title, body.get("description", ""))
if not goal:
return JSONResponse({"error": "Failed to create goal"}, status_code=502)
return goal.model_dump()
# ── Approvals ────────────────────────────────────────────────────────────────
@router.get("/approvals")
async def list_approvals():
"""List pending approvals for CEO review."""
if not settings.paperclip_enabled:
return _disabled_response()
from integrations.paperclip.bridge import bridge
return await bridge.pending_approvals()
@router.post("/approvals/{approval_id}/approve")
async def approve(approval_id: str, request: Request):
"""Approve an agent's action."""
if not settings.paperclip_enabled:
return _disabled_response()
body = await request.json()
from integrations.paperclip.bridge import bridge
ok = await bridge.approve(approval_id, body.get("comment", ""))
if not ok:
return JSONResponse({"error": "Failed to approve"}, status_code=502)
return {"ok": True, "approval_id": approval_id}
@router.post("/approvals/{approval_id}/reject")
async def reject(approval_id: str, request: Request):
"""Reject an agent's action."""
if not settings.paperclip_enabled:
return _disabled_response()
body = await request.json()
from integrations.paperclip.bridge import bridge
ok = await bridge.reject(approval_id, body.get("comment", ""))
if not ok:
return JSONResponse({"error": "Failed to reject"}, status_code=502)
return {"ok": True, "approval_id": approval_id}
# ── Runs (monitoring) ────────────────────────────────────────────────────────
@router.get("/runs")
async def list_runs():
"""List active heartbeat runs."""
if not settings.paperclip_enabled:
return _disabled_response()
from integrations.paperclip.bridge import bridge
return await bridge.active_runs()
@router.post("/runs/{run_id}/cancel")
async def cancel_run(run_id: str):
"""Cancel a running heartbeat execution."""
if not settings.paperclip_enabled:
return _disabled_response()
from integrations.paperclip.bridge import bridge
ok = await bridge.cancel_run(run_id)
if not ok:
return JSONResponse({"error": "Failed to cancel run"}, status_code=502)
return {"ok": True, "run_id": run_id}