diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 2523798..fb681b2 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -12,5 +12,6 @@ jobs:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
+ - run: bun run test
- run: bun run lint
- run: bun run format:check
diff --git a/migrations/0010_slippery_mattie_franklin.sql b/migrations/0010_slippery_mattie_franklin.sql
new file mode 100644
index 0000000..bccf6d5
--- /dev/null
+++ b/migrations/0010_slippery_mattie_franklin.sql
@@ -0,0 +1,25 @@
+CREATE TABLE `ai_events` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `kind` text NOT NULL,
+ `incident_id` integer,
+ `monitor_id` integer,
+ `model` text,
+ `outcome` text NOT NULL,
+ `reason` text,
+ `latency_ms` integer,
+ `prompt_tokens` integer,
+ `completion_tokens` integer,
+ `context_preview` text,
+ `output_preview` text,
+ `created_at` integer NOT NULL,
+ FOREIGN KEY (`incident_id`) REFERENCES `incidents`(`id`) ON UPDATE no action ON DELETE set null,
+ FOREIGN KEY (`monitor_id`) REFERENCES `monitors`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE INDEX `ai_events_created_at_idx` ON `ai_events` (`created_at`);--> statement-breakpoint
+ALTER TABLE `ai_settings` ADD `autopilot_enabled` integer DEFAULT false NOT NULL;--> statement-breakpoint
+ALTER TABLE `ai_settings` ADD `autopilot_followup_minutes` integer DEFAULT 15 NOT NULL;--> statement-breakpoint
+ALTER TABLE `ai_settings` ADD `autopilot_max_updates` integer DEFAULT 6 NOT NULL;--> statement-breakpoint
+ALTER TABLE `ai_settings` ADD `autopilot_advance_status` integer DEFAULT false NOT NULL;--> statement-breakpoint
+ALTER TABLE `ai_settings` ADD `autopilot_degraded_incidents` integer DEFAULT false NOT NULL;--> statement-breakpoint
+ALTER TABLE `incidents` ADD `kind` text DEFAULT 'down' NOT NULL;
\ No newline at end of file
diff --git a/migrations/meta/0010_snapshot.json b/migrations/meta/0010_snapshot.json
new file mode 100644
index 0000000..3fd0ef9
--- /dev/null
+++ b/migrations/meta/0010_snapshot.json
@@ -0,0 +1,1464 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "3722898d-5812-4cc0-9466-aea6cb2ffc57",
+ "prevId": "fdb7ba86-a907-43e1-bcbb-9cf7cbd1cb15",
+ "tables": {
+ "admin_credentials": {
+ "name": "admin_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ai_events": {
+ "name": "ai_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "incident_id": {
+ "name": "incident_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "latency_ms": {
+ "name": "latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "prompt_tokens": {
+ "name": "prompt_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "completion_tokens": {
+ "name": "completion_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "context_preview": {
+ "name": "context_preview",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "output_preview": {
+ "name": "output_preview",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ai_events_created_at_idx": {
+ "name": "ai_events_created_at_idx",
+ "columns": [
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ai_events_incident_id_incidents_id_fk": {
+ "name": "ai_events_incident_id_incidents_id_fk",
+ "tableFrom": "ai_events",
+ "tableTo": "incidents",
+ "columnsFrom": [
+ "incident_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ai_events_monitor_id_monitors_id_fk": {
+ "name": "ai_events_monitor_id_monitors_id_fk",
+ "tableFrom": "ai_events",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ai_settings": {
+ "name": "ai_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "base_url": {
+ "name": "base_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "api_key": {
+ "name": "api_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autopilot_enabled": {
+ "name": "autopilot_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "autopilot_followup_minutes": {
+ "name": "autopilot_followup_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 15
+ },
+ "autopilot_max_updates": {
+ "name": "autopilot_max_updates",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 6
+ },
+ "autopilot_advance_status": {
+ "name": "autopilot_advance_status",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "autopilot_degraded_incidents": {
+ "name": "autopilot_degraded_incidents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "checks": {
+ "name": "checks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ok": {
+ "name": "ok",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status_code": {
+ "name": "status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "latency_ms": {
+ "name": "latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "checked_at": {
+ "name": "checked_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "maintenance": {
+ "name": "maintenance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "degraded": {
+ "name": "degraded",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {
+ "checks_monitor_id_checked_at_idx": {
+ "name": "checks_monitor_id_checked_at_idx",
+ "columns": [
+ "monitor_id",
+ "checked_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "checks_monitor_id_monitors_id_fk": {
+ "name": "checks_monitor_id_monitors_id_fk",
+ "tableFrom": "checks",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "incident_monitors": {
+ "name": "incident_monitors",
+ "columns": {
+ "incident_id": {
+ "name": "incident_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "incident_monitors_monitor_id_idx": {
+ "name": "incident_monitors_monitor_id_idx",
+ "columns": [
+ "monitor_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "incident_monitors_incident_id_incidents_id_fk": {
+ "name": "incident_monitors_incident_id_incidents_id_fk",
+ "tableFrom": "incident_monitors",
+ "tableTo": "incidents",
+ "columnsFrom": [
+ "incident_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "incident_monitors_monitor_id_monitors_id_fk": {
+ "name": "incident_monitors_monitor_id_monitors_id_fk",
+ "tableFrom": "incident_monitors",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "incident_monitors_incident_id_monitor_id_pk": {
+ "columns": [
+ "incident_id",
+ "monitor_id"
+ ],
+ "name": "incident_monitors_incident_id_monitor_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "incident_updates": {
+ "name": "incident_updates",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "incident_id": {
+ "name": "incident_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'manual'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "incident_updates_incident_id_created_at_idx": {
+ "name": "incident_updates_incident_id_created_at_idx",
+ "columns": [
+ "incident_id",
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "incident_updates_incident_id_incidents_id_fk": {
+ "name": "incident_updates_incident_id_incidents_id_fk",
+ "tableFrom": "incident_updates",
+ "tableTo": "incidents",
+ "columnsFrom": [
+ "incident_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "incidents": {
+ "name": "incidents",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'investigating'"
+ },
+ "impact": {
+ "name": "impact",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'major'"
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'auto'"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'down'"
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "start_status_code": {
+ "name": "start_status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "start_error": {
+ "name": "start_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "incidents_started_at_idx": {
+ "name": "incidents_started_at_idx",
+ "columns": [
+ "started_at"
+ ],
+ "isUnique": false
+ },
+ "incidents_resolved_at_idx": {
+ "name": "incidents_resolved_at_idx",
+ "columns": [
+ "resolved_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "login_attempts": {
+ "name": "login_attempts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attempted_at": {
+ "name": "attempted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "login_attempts_ip_attempted_at_idx": {
+ "name": "login_attempts_ip_attempted_at_idx",
+ "columns": [
+ "ip_address",
+ "attempted_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "maintenance_window_monitors": {
+ "name": "maintenance_window_monitors",
+ "columns": {
+ "window_id": {
+ "name": "window_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "maintenance_window_monitors_monitor_id_idx": {
+ "name": "maintenance_window_monitors_monitor_id_idx",
+ "columns": [
+ "monitor_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "maintenance_window_monitors_window_id_maintenance_windows_id_fk": {
+ "name": "maintenance_window_monitors_window_id_maintenance_windows_id_fk",
+ "tableFrom": "maintenance_window_monitors",
+ "tableTo": "maintenance_windows",
+ "columnsFrom": [
+ "window_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "maintenance_window_monitors_monitor_id_monitors_id_fk": {
+ "name": "maintenance_window_monitors_monitor_id_monitors_id_fk",
+ "tableFrom": "maintenance_window_monitors",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "maintenance_window_monitors_window_id_monitor_id_pk": {
+ "columns": [
+ "window_id",
+ "monitor_id"
+ ],
+ "name": "maintenance_window_monitors_window_id_monitor_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "maintenance_windows": {
+ "name": "maintenance_windows",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "start_minute": {
+ "name": "start_minute",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "duration_minutes": {
+ "name": "duration_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'UTC'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "maintenance_windows_enabled_idx": {
+ "name": "maintenance_windows_enabled_idx",
+ "columns": [
+ "enabled"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "monitor_daily_stats": {
+ "name": "monitor_daily_stats",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "day": {
+ "name": "day",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "total_checks": {
+ "name": "total_checks",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "up_checks": {
+ "name": "up_checks",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "avg_latency_ms": {
+ "name": "avg_latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "min_latency_ms": {
+ "name": "min_latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "max_latency_ms": {
+ "name": "max_latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "monitor_daily_stats_monitor_id_day_uidx": {
+ "name": "monitor_daily_stats_monitor_id_day_uidx",
+ "columns": [
+ "monitor_id",
+ "day"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "monitor_daily_stats_monitor_id_monitors_id_fk": {
+ "name": "monitor_daily_stats_monitor_id_monitors_id_fk",
+ "tableFrom": "monitor_daily_stats",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "monitors": {
+ "name": "monitors",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'GET'"
+ },
+ "expected_status": {
+ "name": "expected_status",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 200
+ },
+ "expect_keyword": {
+ "name": "expect_keyword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "keyword_inverted": {
+ "name": "keyword_inverted",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "request_headers": {
+ "name": "request_headers",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "request_body": {
+ "name": "request_body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "degraded_latency_ms": {
+ "name": "degraded_latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "interval_seconds": {
+ "name": "interval_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 300
+ },
+ "timeout_ms": {
+ "name": "timeout_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 10000
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "alerts_enabled": {
+ "name": "alerts_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "retry_count": {
+ "name": "retry_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "failure_threshold": {
+ "name": "failure_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 2
+ },
+ "consecutive_failures": {
+ "name": "consecutive_failures",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "consecutive_slow": {
+ "name": "consecutive_slow",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_ok": {
+ "name": "last_ok",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_degraded": {
+ "name": "last_degraded",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "last_status_code": {
+ "name": "last_status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_latency_ms": {
+ "name": "last_latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_checked_at": {
+ "name": "last_checked_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "monitors_enabled_last_checked_at_idx": {
+ "name": "monitors_enabled_last_checked_at_idx",
+ "columns": [
+ "enabled",
+ "last_checked_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_channel_monitors": {
+ "name": "notification_channel_monitors",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "notification_channel_monitors_monitor_id_idx": {
+ "name": "notification_channel_monitors_monitor_id_idx",
+ "columns": [
+ "monitor_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "notification_channel_monitors_channel_id_notification_channels_id_fk": {
+ "name": "notification_channel_monitors_channel_id_notification_channels_id_fk",
+ "tableFrom": "notification_channel_monitors",
+ "tableTo": "notification_channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_channel_monitors_monitor_id_monitors_id_fk": {
+ "name": "notification_channel_monitors_monitor_id_monitors_id_fk",
+ "tableFrom": "notification_channel_monitors",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "notification_channel_monitors_channel_id_monitor_id_pk": {
+ "columns": [
+ "channel_id",
+ "monitor_id"
+ ],
+ "name": "notification_channel_monitors_channel_id_monitor_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_channels": {
+ "name": "notification_channels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "notify_manual": {
+ "name": "notify_manual",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "notification_channels_enabled_idx": {
+ "name": "notification_channels_enabled_idx",
+ "columns": [
+ "enabled"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_deliveries": {
+ "name": "notification_deliveries",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "incident_id": {
+ "name": "incident_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "event": {
+ "name": "event",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ok": {
+ "name": "ok",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status_code": {
+ "name": "status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "notification_deliveries_channel_id_created_at_idx": {
+ "name": "notification_deliveries_channel_id_created_at_idx",
+ "columns": [
+ "channel_id",
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "notification_deliveries_channel_id_notification_channels_id_fk": {
+ "name": "notification_deliveries_channel_id_notification_channels_id_fk",
+ "tableFrom": "notification_deliveries",
+ "tableTo": "notification_channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_deliveries_incident_id_incidents_id_fk": {
+ "name": "notification_deliveries_incident_id_incidents_id_fk",
+ "tableFrom": "notification_deliveries",
+ "tableTo": "incidents",
+ "columnsFrom": [
+ "incident_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_deliveries_monitor_id_monitors_id_fk": {
+ "name": "notification_deliveries_monitor_id_monitors_id_fk",
+ "tableFrom": "notification_deliveries",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sessions": {
+ "name": "sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ "expires_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json
index da385e6..0ab1107 100644
--- a/migrations/meta/_journal.json
+++ b/migrations/meta/_journal.json
@@ -71,6 +71,13 @@
"when": 1788062139636,
"tag": "0009_shocking_butterfly",
"breakpoints": true
+ },
+ {
+ "idx": 10,
+ "version": "6",
+ "when": 1788183346274,
+ "tag": "0010_slippery_mattie_franklin",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/src/client/api/settings.ts b/src/client/api/settings.ts
index f884458..3fe7bc0 100644
--- a/src/client/api/settings.ts
+++ b/src/client/api/settings.ts
@@ -5,6 +5,11 @@ export type AiSettings = {
enabled: boolean;
baseUrl: string | null;
model: string | null;
+ autopilotEnabled: boolean;
+ autopilotFollowupMinutes: number;
+ autopilotMaxUpdates: number;
+ autopilotAdvanceStatus: boolean;
+ autopilotDegradedIncidents: boolean;
apiKeySet: boolean;
apiKeyPreview: string | null;
createdAt: string | null;
@@ -16,6 +21,35 @@ export type AiSettingsInput = {
baseUrl: string | null;
model: string | null;
apiKey?: string | null;
+ autopilotEnabled: boolean;
+ autopilotFollowupMinutes: number;
+ autopilotMaxUpdates: number;
+ autopilotAdvanceStatus: boolean;
+ autopilotDegradedIncidents: boolean;
+};
+
+export type AiEvent = {
+ id: number;
+ kind: string;
+ incidentId: number | null;
+ monitorId: number | null;
+ model: string | null;
+ outcome: string;
+ reason: string | null;
+ latencyMs: number | null;
+ promptTokens: number | null;
+ completionTokens: number | null;
+ contextPreview: string | null;
+ outputPreview: string | null;
+ createdAt: string;
+};
+
+export type AiEventSummary = {
+ total: number;
+ ok: number;
+ averageLatencyMs: number | null;
+ promptTokens: number;
+ completionTokens: number;
};
export function getAiSettings(signal?: AbortSignal) {
@@ -32,3 +66,10 @@ export function updateAiSettings(input: AiSettingsInput) {
export function testAiSettings() {
return postJson<{ ok: true; message: string }>('/api/settings/ai/test');
}
+
+export function getAiEvents(signal?: AbortSignal) {
+ return getJson<{ events: AiEvent[]; summary: AiEventSummary }>('/api/settings/ai/events?limit=50', {
+ signal,
+ credentials: 'same-origin',
+ });
+}
diff --git a/src/client/components/settings/AiActivityPanel.tsx b/src/client/components/settings/AiActivityPanel.tsx
new file mode 100644
index 0000000..bd124d0
--- /dev/null
+++ b/src/client/components/settings/AiActivityPanel.tsx
@@ -0,0 +1,66 @@
+import { Badge } from '@/components/ui/badge';
+import { Empty, EmptyDescription, EmptyTitle } from '@/components/ui/empty';
+import { useAiEventsQuery } from '../../queries/settings';
+
+function formatTokens(value: number) {
+ return new Intl.NumberFormat(undefined, { notation: value >= 10_000 ? 'compact' : 'standard' }).format(value);
+}
+
+export function AiActivityPanel() {
+ const query = useAiEventsQuery();
+ if (query.isPending) return
Loading AI activity…
;
+ if (query.isError) {
+ return (
+
+ Unable to load AI activity
+
+ );
+ }
+ const { events, summary } = query.data;
+ const tokenTotal = summary.promptTokens + summary.completionTokens;
+ return (
+
+
+
+ {summary.total ? Math.round((summary.ok / summary.total) * 100) : 0}%
+ Success rate
+
+
+ {summary.total}
+ Calls · 7 days
+
+
+ {summary.averageLatencyMs === null ? '—' : `${summary.averageLatencyMs} ms`}
+ Average latency
+
+
+ {formatTokens(tokenTotal)}
+ Total tokens
+
+
+ {events.length === 0 ? (
+
+ No AI activity yet
+ Attempts will appear here after AI generation or autopilot runs.
+
+ ) : (
+
+ {events.map((event) => (
+
+
+ {event.outcome.replaceAll('_', ' ')}
+
+ {event.kind.replaceAll('_', ' ')}
+ {new Date(event.createdAt).toLocaleString()}
+ {event.reason ?? event.model ?? '—'}
+
+ {event.latencyMs === null ? '—' : `${event.latencyMs} ms`} ·{' '}
+ {formatTokens((event.promptTokens ?? 0) + (event.completionTokens ?? 0))} tokens
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/client/pages/SettingsPage.tsx b/src/client/pages/SettingsPage.tsx
index dad4431..54c22a1 100644
--- a/src/client/pages/SettingsPage.tsx
+++ b/src/client/pages/SettingsPage.tsx
@@ -1,5 +1,5 @@
import { type FormEvent, useState } from 'react';
-import { ArrowLeft, BellRing, Sparkles, Wrench } from 'lucide-react';
+import { Activity, ArrowLeft, BellRing, Sparkles, Wrench } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Empty, EmptyTitle } from '@/components/ui/empty';
@@ -7,6 +7,7 @@ import { Input } from '@/components/ui/input';
import { Switch } from '@/components/ui/switch';
import type { AiSettings } from '../api/settings';
import { AppHeader } from '../components/AppHeader';
+import { AiActivityPanel } from '../components/settings/AiActivityPanel';
import { MaintenanceWindowsPanel } from '../components/settings/MaintenanceWindowsPanel';
import { NotificationChannelsPanel } from '../components/settings/NotificationChannelsPanel';
import { navigate } from '../lib/router';
@@ -17,6 +18,11 @@ function AiSettingsForm({ settings }: { settings: AiSettings }) {
const [apiKey, setApiKey] = useState('');
const [model, setModel] = useState(settings.model ?? 'gpt-4o-mini');
const [enabled, setEnabled] = useState(settings.enabled);
+ const [autopilotEnabled, setAutopilotEnabled] = useState(settings.autopilotEnabled);
+ const [followupMinutes, setFollowupMinutes] = useState(settings.autopilotFollowupMinutes);
+ const [maxUpdates, setMaxUpdates] = useState(settings.autopilotMaxUpdates);
+ const [advanceStatus, setAdvanceStatus] = useState(settings.autopilotAdvanceStatus);
+ const [degradedIncidents, setDegradedIncidents] = useState(settings.autopilotDegradedIncidents);
const updateMutation = useUpdateAiSettingsMutation();
const testMutation = useTestAiSettingsMutation();
@@ -27,6 +33,11 @@ function AiSettingsForm({ settings }: { settings: AiSettings }) {
baseUrl: baseUrl.trim() || null,
model: model.trim() || null,
apiKey: apiKey.trim() || null,
+ autopilotEnabled,
+ autopilotFollowupMinutes: followupMinutes,
+ autopilotMaxUpdates: maxUpdates,
+ autopilotAdvanceStatus: advanceStatus,
+ autopilotDegradedIncidents: degradedIncidents,
});
}
@@ -65,6 +76,54 @@ function AiSettingsForm({ settings }: { settings: AiSettings }) {
Generate one sanitized public update when an incident opens.
+