Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions AGENT_ACTIVITY_DASHBOARD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Agent Activity Dashboard

## Overview

The Agent Activity Dashboard provides real-time visibility into agent operations, security findings, report generation, and notification management across all companies in the Paperclip platform.

## Features

### Live Telemetry (Overview Tab)
- Active agent count
- Findings discovered today
- Success rate percentage
- Stuck agents count
- Auto-refreshes via WebSocket live events

### Run History
- Sortable/filterable table of all agent runs
- Status badges (succeeded, failed, stuck, running)
- Duration and timestamp display
- Pagination support

### Findings
- Severity-filtered findings list (critical, high, medium, low, info)
- Category badges
- Verification toggle
- Detail sheet for full finding information

### Reports
- Report type filter (EOD, import summary, custom)
- PDF generation and download
- Create/delete reports with confirmation
- Content preview

### Notifications
- Full CRUD for notification configurations
- Event type multi-select (run failed, run completed, finding created, etc.)
- Test button for verifying delivery
- Enable/disable toggle
- Supports Telegram and generic webhooks

### Bulk Operations
- Import agents from multiple GitHub repos
- Bulk enable/disable/terminate agents
- Cross-repo agent comparison

## API Endpoints

### Findings
- `GET /companies/:companyId/findings` - List with filters
- `GET /companies/:companyId/findings/summary` - Severity summary
- `POST /companies/:companyId/findings` - Create finding
- `GET /companies/:companyId/findings/:id` - Get single finding
- `PATCH /companies/:companyId/findings/:id` - Update finding
- `POST /companies/:companyId/findings/:id/verify` - Verify finding
- `DELETE /companies/:companyId/findings/:id` - Delete finding

### Reports
- `GET /companies/:companyId/reports` - List reports
- `POST /companies/:companyId/reports` - Create report
- `GET /companies/:companyId/reports/:id` - Get single report
- `DELETE /companies/:companyId/reports/:id` - Delete report
- `POST /companies/:companyId/reports/eod` - Generate EOD PDF
- `POST /companies/:companyId/reports/import-summary` - Generate import summary PDF
- `GET /companies/:companyId/reports/:id/download` - Download PDF

### Notifications
- `GET /companies/:companyId/notifications/config` - List configs
- `POST /companies/:companyId/notifications/config` - Create config
- `GET /companies/:companyId/notifications/config/:id` - Get config
- `PATCH /companies/:companyId/notifications/config/:id` - Update config
- `DELETE /companies/:companyId/notifications/config/:id` - Delete config
- `POST /companies/:companyId/notifications/config/:id/test` - Send test notification

### Agent Runs
- `GET /companies/:companyId/runs` - List runs with filters
- `GET /companies/:companyId/runs/stats` - Run statistics
- `GET /companies/:companyId/runs/:id` - Get single run
- `GET /companies/:companyId/runs/:id/tags` - Get run tags
- `POST /companies/:companyId/runs/:id/tags` - Add tag to run
- `DELETE /companies/:companyId/runs/:id/tags/:tagId` - Remove tag from run

### Bulk Operations
- `POST /companies/:companyId/agents/bulk-import` - Import from GitHub repos
- `POST /companies/:companyId/agents/bulk` - Bulk enable/disable/terminate
- `GET /companies/:companyId/agents/comparison` - Cross-repo comparison

## WebSocket Events

The dashboard subscribes to live events for real-time updates:

- `agent.run.started` - New run initiated
- `agent.run.completed` - Run finished successfully
- `agent.run.failed` - Run failed or timed out
- `agent.run.stuck` - Run stuck/unresponsive
- `agent.finding.created` - New security finding discovered
- `agent.report.generated` - New report available

## Database Schema

### New Tables

- `agent_findings` - Security findings with severity, category, verification
- `agent_reports` - Generated reports with type and PDF URL
- `notification_configs` - Notification destinations and event subscriptions
- `agent_run_tags` - Many-to-many tags for runs

## Testing

Run the test suite:

```bash
cd server
npx vitest run src/__tests__/findings-routes.test.ts
npx vitest run src/__tests__/reports-routes.test.ts
npx vitest run src/__tests__/notifications-routes.test.ts
npx vitest run src/__tests__/agent-runs-routes.test.ts
npx vitest run src/__tests__/findings-service.test.ts
npx vitest run src/__tests__/notifications-service.test.ts
npx vitest run src/__tests__/webhook-sender.test.ts
```

## Architecture

```
Frontend (React + TanStack Query)
API Client (ui/src/api/*.ts)
REST API (server/src/routes/*.ts)
Services (server/src/services/*.ts)
Database (Drizzle ORM + SQLite)
WebSocket Events (server/src/services/live-events.ts)
```

## Implementation Phases

1. **DB Schema** - Created 4 new tables with proper indexing
2. **Backend Services** - Implemented CRUD services with activity logging
3. **API Routes** - Added REST endpoints with company access control
4. **WebSocket Events** - Added 6 new live event types for real-time updates
5. **Frontend Dashboard** - Built tabbed UI with 6 panels
6. **Report Generation** - Added puppeteer-core PDF generation with templates
7. **Notifications** - Added Telegram webhook sender and daily digest scheduler
8. **Bulk Operations** - Added GitHub import, bulk actions, and comparison
9. **Testing** - Added 50 unit and integration tests
10. **Polish** - Added JSDoc comments and documentation
Empty file.
73 changes: 73 additions & 0 deletions packages/db/src/migrations/0094_lumpy_black_bird.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
CREATE TABLE "agent_findings" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"agent_id" uuid NOT NULL,
"run_id" uuid,
"severity" text DEFAULT 'info' NOT NULL,
"category" text,
"title" text NOT NULL,
"description" text,
"cvss_score" integer,
"verified" boolean DEFAULT false NOT NULL,
"verified_by" uuid,
"verified_at" timestamp with time zone,
"metadata" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "agent_reports" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"type" text DEFAULT 'summary' NOT NULL,
"title" text NOT NULL,
"content_json" jsonb,
"pdf_url" text,
"logo_asset_id" uuid,
"generated_by" uuid,
"generated_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "agent_run_tags" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"run_id" uuid NOT NULL,
"tag" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "notification_configs" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"type" text NOT NULL,
"target_url" text NOT NULL,
"events" jsonb DEFAULT '[]'::jsonb NOT NULL,
"enabled" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "agent_findings" ADD CONSTRAINT "agent_findings_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "agent_findings" ADD CONSTRAINT "agent_findings_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "agent_findings" ADD CONSTRAINT "agent_findings_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "agent_reports" ADD CONSTRAINT "agent_reports_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "agent_reports" ADD CONSTRAINT "agent_reports_logo_asset_id_assets_id_fk" FOREIGN KEY ("logo_asset_id") REFERENCES "public"."assets"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "agent_run_tags" ADD CONSTRAINT "agent_run_tags_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "agent_run_tags" ADD CONSTRAINT "agent_run_tags_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "notification_configs" ADD CONSTRAINT "notification_configs_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "agent_findings_company_agent_idx" ON "agent_findings" USING btree ("company_id","agent_id");--> statement-breakpoint
CREATE INDEX "agent_findings_company_severity_idx" ON "agent_findings" USING btree ("company_id","severity");--> statement-breakpoint
CREATE INDEX "agent_findings_run_id_idx" ON "agent_findings" USING btree ("run_id");--> statement-breakpoint
CREATE INDEX "agent_findings_verified_idx" ON "agent_findings" USING btree ("verified");--> statement-breakpoint
CREATE INDEX "agent_findings_created_at_idx" ON "agent_findings" USING btree ("created_at");--> statement-breakpoint
CREATE INDEX "agent_reports_company_type_idx" ON "agent_reports" USING btree ("company_id","type");--> statement-breakpoint
CREATE INDEX "agent_reports_generated_at_idx" ON "agent_reports" USING btree ("generated_at");--> statement-breakpoint
CREATE INDEX "agent_reports_created_at_idx" ON "agent_reports" USING btree ("created_at");--> statement-breakpoint
CREATE INDEX "agent_run_tags_company_run_idx" ON "agent_run_tags" USING btree ("company_id","run_id");--> statement-breakpoint
CREATE INDEX "agent_run_tags_company_tag_idx" ON "agent_run_tags" USING btree ("company_id","tag");--> statement-breakpoint
CREATE INDEX "agent_run_tags_created_at_idx" ON "agent_run_tags" USING btree ("created_at");--> statement-breakpoint
CREATE INDEX "notification_configs_company_type_idx" ON "notification_configs" USING btree ("company_id","type");--> statement-breakpoint
CREATE INDEX "notification_configs_company_enabled_idx" ON "notification_configs" USING btree ("company_id","enabled");--> statement-breakpoint
CREATE INDEX "notification_configs_created_at_idx" ON "notification_configs" USING btree ("created_at");
Loading