/**
 * @file ENRICHED OVERRIDE OpenAPI fragments for endpoints that are ALREADY
 * documented in the base spec (server/openapi.js). This fragment does not add
 * any new paths — instead, each entry here re-declares an already-existing path
 * with the *identical* contract (same operationId, tags, parameters,
 * requestBody schemas, and response `$ref`/media-type schemas) but layers on
 * richer `description` text and realistic `example` values for parameters,
 * request bodies, and responses. Because `server/openapi-extra.js` merges
 * fragment `paths` with `Object.assign` (last-writer-wins) and the merge in
 * `createOpenApiSpec()` spreads extras over the base, these override entries
 * supersede the terser base definitions for the same path key.
 *
 * Covered paths (Webhooks / Settings / Import / Updates / Workflows):
 *   POST/GET   /api/webhooks
 *   GET        /api/webhooks/providers
 *   PATCH/DEL  /api/webhooks/{id}
 *   POST       /api/webhooks/{id}/test
 *   GET        /api/webhooks/{id}/deliveries
 *   GET        /api/settings/info
 *   POST       /api/settings/clear-data          ⚠ DESTRUCTIVE
 *   POST       /api/settings/reimport
 *   POST       /api/settings/reinstall-hooks
 *   POST       /api/settings/reset-pricing       ⚠ DESTRUCTIVE
 *   GET        /api/settings/export
 *   POST       /api/settings/import
 *   POST       /api/settings/cleanup             ⚠ DESTRUCTIVE (with purge_days)
 *   GET        /api/import/guide
 *   POST       /api/import/rescan
 *   POST       /api/import/scan-path
 *   POST       /api/import/upload                (multipart/form-data)
 *   GET        /api/updates/status
 *   POST       /api/updates/check
 *   GET        /api/workflows
 *   GET        /api/workflows/session/{id}
 *
 * STRICTLY ADDITIVE: `tags` and `schemas` are intentionally empty — this module
 * introduces no new tags and no new component schemas. Every schema reference
 * reuses a base `$ref` (e.g. ErrorResponse, MessageErrorResponse, WorkflowAggregateResponse,
 * SettingsInfoResponse, CleanupRequest/CleanupResponse, ImportGuideResponse,
 * ImportResultResponse, ExportResponse, …) or an inline schema that matches the
 * base byte-for-byte. The Webhooks/Settings/Import/Updates/Workflows tags are
 * already declared in the base literal. Error bodies preserve the base's split:
 * Webhooks / Settings / Import / Updates use `ErrorResponse` ({ error: { code,
 * message } }) where the base had it (and keep the base's content-less 4xx
 * descriptions where it had none); Workflows use the SHORT `MessageErrorResponse`
 * ({ error: { message } }).
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

const tags = [];

const schemas = {};

// ── Reusable realistic examples ──────────────────────────────────────────────

const WEBHOOK_TARGET_EXAMPLE = {
  id: "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
  name: "Eng on-call (Slack)",
  type: "slack",
  enabled: true,
  url_preview: "https://hooks.slack.com/…BXqZ",
  has_secret: false,
  headers: null,
  config: null,
  rule_ids: ["rule_inactivity_30m"],
  created_at: "2026-06-25T14:03:11.482Z",
  updated_at: "2026-06-25T14:03:11.482Z",
  last_delivery: {
    status: "success",
    status_code: 200,
    attempts: 1,
    error: null,
    created_at: "2026-06-25T18:41:55.117Z",
  },
};

const GENERIC_WEBHOOK_TARGET_EXAMPLE = {
  id: "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  name: "Internal alert sink",
  type: "generic",
  enabled: true,
  url_preview: "https://alerts.internal.example.com/…hook",
  has_secret: true,
  headers: { "X-Api-Key": "••••", Authorization: "••••" },
  config: null,
  rule_ids: null,
  created_at: "2026-06-20T09:15:00.000Z",
  updated_at: "2026-06-24T22:48:30.901Z",
  last_delivery: {
    status: "failed",
    status_code: 503,
    attempts: 3,
    error: "Service Unavailable",
    created_at: "2026-06-24T22:48:30.901Z",
  },
};

const SETTINGS_INFO_EXAMPLE = {
  db: {
    path: "/Users/son/WebstormProjects/Claude-Code-Agent-Monitor/server/data/agent-monitor.db",
    size: 48922624,
    counts: {
      sessions: 412,
      agents: 1893,
      events: 58117,
      model_pricing: 11,
      token_usage: 401,
    },
    pragmas: {
      journal_mode: "wal",
      synchronous: 1,
      auto_vacuum: 0,
      encoding: "UTF-8",
      foreign_keys: 1,
      busy_timeout: 5000,
    },
    load_stats: { m5: 23, m15: 88, h1: 511 },
  },
  hooks: {
    installed: true,
    path: "/Users/son/.claude/settings.json",
    hooks: {
      PreToolUse: true,
      PostToolUse: true,
      Stop: true,
      SubagentStop: true,
      Notification: true,
      SessionStart: true,
      SessionEnd: true,
    },
  },
  server: {
    uptime: 14523.91,
    node_version: "v22.14.0",
    platform: "darwin",
    ws_connections: 2,
    memory: {
      rss: 142802944,
      heapTotal: 71303168,
      heapUsed: 58392104,
      external: 3211884,
      arrayBuffers: 1048576,
    },
    cpu_load: [2.13, 2.45, 2.31],
    arch: "arm64",
    total_mem: 17179869184,
    free_mem: 2147483648,
    cpus: 10,
  },
  transcript_cache: {
    entries: 37,
    paths: [
      "/Users/son/.claude/projects/-Users-son-code-foo/abc123.jsonl",
      "/Users/son/.claude/projects/-Users-son-code-bar/def456.jsonl",
    ],
  },
};

const CLEAR_DATA_EXAMPLE = {
  ok: true,
  cleared: { sessions: 412, agents: 1893, events: 58117, model_pricing: 11, token_usage: 401 },
};

const REIMPORT_EXAMPLE = { ok: true, imported: 38, skipped: 374, errors: 0 };

const REINSTALL_HOOKS_EXAMPLE = {
  ok: true,
  hooks: {
    installed: true,
    path: "/Users/son/.claude/settings.json",
    hooks: {
      PreToolUse: true,
      PostToolUse: true,
      Stop: true,
      SubagentStop: true,
      Notification: true,
      SessionStart: true,
      SessionEnd: true,
    },
  },
};

const RESET_PRICING_EXAMPLE = {
  ok: true,
  pricing: [
    {
      model_pattern: "claude-opus-4*",
      display_name: "Claude Opus 4",
      input_per_mtok: 15,
      output_per_mtok: 75,
      cache_read_per_mtok: 1.5,
      cache_write_per_mtok: 18.75,
      cache_write_1h_per_mtok: 30,
      fast_input_per_mtok: 0,
      fast_output_per_mtok: 0,
      updated_at: "2026-06-26T00:00:00.000Z",
    },
    {
      model_pattern: "claude-sonnet-4*",
      display_name: "Claude Sonnet 4",
      input_per_mtok: 3,
      output_per_mtok: 15,
      cache_read_per_mtok: 0.3,
      cache_write_per_mtok: 3.75,
      cache_write_1h_per_mtok: 6,
      fast_input_per_mtok: 0,
      fast_output_per_mtok: 0,
      updated_at: "2026-06-26T00:00:00.000Z",
    },
  ],
};

const EXPORT_EXAMPLE = {
  format: "ccam-export",
  version: 1,
  exported_at: "2026-06-26T01:12:44.913Z",
  sessions: [
    {
      id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
      name: "Refactor auth flow",
      status: "completed",
      cwd: "/Users/son/code/foo",
      model: "claude-opus-4-8",
      started_at: "2026-06-25T13:00:00.000Z",
      ended_at: "2026-06-25T13:42:18.220Z",
      metadata: null,
      updated_at: "2026-06-25T13:42:18.220Z",
    },
  ],
  agents: [
    {
      id: "agent_main_5f3c0e2a",
      session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
      name: "Main agent",
      type: "main",
      subagent_type: null,
      status: "completed",
      task: null,
      current_tool: null,
      started_at: "2026-06-25T13:00:00.000Z",
      ended_at: "2026-06-25T13:42:18.220Z",
      parent_agent_id: null,
      metadata: null,
      updated_at: "2026-06-25T13:42:18.220Z",
    },
  ],
  events: [
    {
      id: 91021,
      session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
      agent_id: "agent_main_5f3c0e2a",
      event_type: "PreToolUse",
      tool_name: "Edit",
      summary: "Edit server/auth.js",
      data: null,
      created_at: "2026-06-25T13:05:42.001Z",
    },
  ],
  token_usage: [
    {
      session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
      model: "claude-opus-4-8",
      input_tokens: 18422,
      output_tokens: 9120,
      cache_read_tokens: 220184,
      cache_write_tokens: 41002,
    },
  ],
  model_pricing: RESET_PRICING_EXAMPLE.pricing,
};

const CLEANUP_EXAMPLE = {
  ok: true,
  abandoned: 3,
  purged_sessions: 57,
  purged_events: 14820,
  purged_agents: 241,
};

const IMPORT_GUIDE_EXAMPLE = {
  platform: "darwin",
  default_projects_dir: "/Users/son/.claude/projects",
  default_projects_dir_display: "~/.claude/projects",
  default_projects_dir_exists: true,
  default_projects_dir_stats: { projects: 24, jsonl_files: 312 },
  archive_command: "tar -czf claude-history.tar.gz -C ~/.claude projects",
  supported_extensions: [".jsonl", ".meta.json", ".zip", ".tar", ".tar.gz", ".tgz", ".gz"],
  max_upload_bytes: 1073741824,
  max_upload_files: 2000,
  steps: [
    {
      id: "locate",
      title: "Locate your Claude Code history",
      body: "Claude Code stores every session as a JSONL transcript under ~/.claude/projects. Each subdirectory is named after the working directory where the session started (with slashes replaced by dashes).",
    },
    {
      id: "archive",
      title: "Bundle it for transfer (optional)",
      body: "If you're importing from another machine, archive the whole projects folder first:\n\n    tar -czf claude-history.tar.gz -C ~/.claude projects\n\nMove claude-history.tar.gz to this machine however you like (AirDrop, scp, USB, cloud storage).",
    },
    {
      id: "choose",
      title: "Pick an import mode",
      body: "Rescan default: re-read ~/.claude/projects on this machine and import anything new. From folder: point the dashboard at any directory you've extracted history into. Upload: drag-drop JSONL files or an archive directly into the browser.",
    },
    {
      id: "verify",
      title: "Verify tokens and cost",
      body: "Imports are idempotent: re-running is always safe. Token counts are deduplicated per session ID, with compaction baselines preserved so cost never double-counts. After import, open Analytics → Cost to confirm the breakdown.",
    },
  ],
};

const IMPORT_RESCAN_EXAMPLE = {
  ok: true,
  source: "default",
  imported: 14,
  skipped: 298,
  backfilled: 2,
  errors: 0,
  sessions_seen: 312,
  files_scanned: 312,
};

const IMPORT_SCAN_PATH_EXAMPLE = {
  ok: true,
  source: "path",
  path: "/Users/son/Downloads/claude-history/projects",
  imported: 9,
  skipped: 41,
  backfilled: 0,
  errors: 0,
  sessions_seen: 50,
  files_scanned: 50,
};

const IMPORT_UPLOAD_EXAMPLE = {
  ok: true,
  source: "upload",
  files_received: 3,
  rejected_files: ["notes.txt"],
  entries_extracted: 128,
  entries_skipped: 2,
  imported: 12,
  skipped: 116,
  backfilled: 0,
  errors: 0,
  sessions_seen: 128,
  files_scanned: 128,
};

const UPDATE_STATUS_AVAILABLE_EXAMPLE = {
  git_repo: true,
  repo_root: "/Users/son/WebstormProjects/Claude-Code-Agent-Monitor",
  update_available: true,
  commits_behind: 4,
  remote_ref: "origin/master",
  local_sha: "de5d0891a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7",
  remote_sha: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
  manual_command: "npm run self-update",
  checked_at: "2026-06-26T01:20:09.553Z",
};

const UPDATE_STATUS_UP_TO_DATE_EXAMPLE = {
  git_repo: true,
  repo_root: "/Users/son/WebstormProjects/Claude-Code-Agent-Monitor",
  update_available: false,
  commits_behind: 0,
  remote_ref: "origin/master",
  local_sha: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
  remote_sha: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
  manual_command: "npm run self-update",
  checked_at: "2026-06-26T01:25:41.002Z",
};

const UPDATE_STATUS_NON_REPO_EXAMPLE = {
  git_repo: false,
  update_available: false,
  message: "Not a git checkout — update detection is unavailable for this install.",
};

const WORKFLOW_AGGREGATE_EXAMPLE = {
  stats: {
    totalSessions: 412,
    totalAgents: 1893,
    totalSubagents: 1481,
    avgSubagents: 3.6,
    successRate: 94.2,
    avgDepth: 1.4,
    avgDurationSec: 1187,
    totalCompactions: 96,
    avgCompactions: 0.2,
    topFlow: { source: "Read", target: "Edit", count: 3120 },
  },
  orchestration: {
    sessionCount: 412,
    mainCount: 412,
    subagentTypes: [
      { subagent_type: "general-purpose", count: 612, completed: 590, errors: 8 },
      { subagent_type: "Explore", count: 388, completed: 380, errors: 2 },
    ],
    edges: [{ source: "main", target: "general-purpose", weight: 612 }],
    outcomes: [
      { status: "completed", count: 1402 },
      { status: "error", count: 79 },
    ],
    compactions: { total: 96, sessions: 71 },
  },
  toolFlow: {
    transitions: [{ source: "Read", target: "Edit", value: 3120 }],
    toolCounts: [
      { tool_name: "Read", count: 9821 },
      { tool_name: "Edit", count: 5402 },
      { tool_name: "Bash", count: 4810 },
    ],
  },
  effectiveness: [
    {
      subagent_type: "general-purpose",
      total: 612,
      completed: 590,
      errors: 8,
      sessions: 281,
      successRate: 98.7,
      avgDuration: 142,
      trend: [88, 91, 102, 97, 110, 24, 12],
    },
  ],
  patterns: {
    patterns: [{ steps: ["Explore", "general-purpose"], count: 142, percentage: 34.5 }],
    soloSessionCount: 120,
    soloPercentage: 29.1,
  },
  modelDelegation: {
    mainModels: [{ model: "claude-opus-4-8", agent_count: 290, session_count: 290 }],
    subagentModels: [{ model: "claude-opus-4-8", agent_count: 980 }],
    tokensByModel: [
      {
        model: "claude-opus-4-8",
        input_tokens: 4820112,
        output_tokens: 1920334,
        cache_read_tokens: 88201442,
        cache_write_tokens: 12044210,
      },
    ],
  },
  errorPropagation: {
    byDepth: [
      { depth: 0, count: 41 },
      { depth: 1, count: 38 },
    ],
    byType: [{ subagent_type: "general-purpose", count: 8 }],
    eventErrors: [{ summary: "Error in Bash: command not found", count: 5 }],
    sessionsWithErrors: 33,
    totalSessions: 412,
    errorRate: 8,
  },
  concurrency: {
    aggregateLanes: [
      { name: "Main Agent", avgStart: 0, avgEnd: 1, count: 412 },
      { name: "Explore", avgStart: 0.12, avgEnd: 0.31, count: 388 },
    ],
  },
  complexity: [
    {
      id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
      name: "Refactor auth flow",
      status: "completed",
      duration: 2538,
      agentCount: 6,
      subagentCount: 5,
      totalTokens: 940212,
      model: "claude-opus-4-8",
    },
  ],
  compaction: {
    totalCompactions: 96,
    tokensRecovered: 18402991,
    perSession: [{ session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11", compactions: 3 }],
    sessionsWithCompactions: 71,
    totalSessions: 412,
  },
  cooccurrence: [{ source: "Explore", target: "general-purpose", weight: 142 }],
};

const WORKFLOW_SESSION_EXAMPLE = {
  session: {
    id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
    name: "Refactor auth flow",
    status: "completed",
    cwd: "/Users/son/code/foo",
    model: "claude-opus-4-8",
    started_at: "2026-06-25T13:00:00.000Z",
    ended_at: "2026-06-25T13:42:18.220Z",
    metadata: null,
    updated_at: "2026-06-25T13:42:18.220Z",
  },
  tree: [
    {
      id: "agent_main_5f3c0e2a",
      name: "Main agent",
      type: "main",
      subagent_type: null,
      status: "completed",
      task: null,
      started_at: "2026-06-25T13:00:00.000Z",
      ended_at: "2026-06-25T13:42:18.220Z",
      children: [
        {
          id: "agent_sub_a1",
          name: "Explore the auth module",
          type: "subagent",
          subagent_type: "Explore",
          status: "completed",
          task: "Map auth call sites",
          started_at: "2026-06-25T13:02:10.000Z",
          ended_at: "2026-06-25T13:06:55.000Z",
          children: [],
        },
      ],
    },
  ],
  toolTimeline: [
    {
      id: 91021,
      tool_name: "Read",
      event_type: "PreToolUse",
      agent_id: "agent_main_5f3c0e2a",
      created_at: "2026-06-25T13:01:02.500Z",
      summary: "Read server/auth.js",
    },
  ],
  swimLanes: [
    {
      id: "agent_main_5f3c0e2a",
      name: "Main agent",
      type: "main",
      subagent_type: null,
      status: "completed",
      started_at: "2026-06-25T13:00:00.000Z",
      ended_at: "2026-06-25T13:42:18.220Z",
      parent_agent_id: null,
    },
  ],
  events: [
    {
      id: 91021,
      session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
      agent_id: "agent_main_5f3c0e2a",
      event_type: "PreToolUse",
      tool_name: "Read",
      summary: "Read server/auth.js",
      data: null,
      created_at: "2026-06-25T13:01:02.500Z",
    },
  ],
};

const WEBHOOK_ID_PARAM = {
  name: "id",
  in: "path",
  required: true,
  schema: { type: "string" },
  example: "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
};

// ── Paths (enriched overrides — contract preserved) ──────────────────────────

const paths = {
  "/api/webhooks/providers": {
    get: {
      tags: ["Webhooks"],
      summary: "List supported providers + their config fields (for the UI)",
      description:
        "Returns the redacted provider catalog the webhook-target editor renders. For each of the 14 supported providers (Slack, Discord, Teams, Google Chat, Mattermost, Rocket.Chat, Telegram, PagerDuty, Opsgenie, Splunk On-Call, Zapier, Make, n8n, Pipedream) plus the `generic` family, it lists: a human label, the provider family, whether the URL must be https / is user-supplied, and the per-provider config field definitions (key, label, type, required, options, and whether the field is secret). No secret values are ever included — this is purely the *shape* of the form, not stored credentials.",
      operationId: "listWebhookProviders",
      responses: {
        200: {
          description: "Provider catalog: label, family, url requirements, fields",
          content: {
            "application/json": {
              schema: { type: "object", additionalProperties: true },
              example: {
                providers: {
                  slack: {
                    label: "Slack",
                    family: "chat",
                    https: true,
                    urlRequired: true,
                    fields: [],
                  },
                  telegram: {
                    label: "Telegram",
                    family: "chat",
                    https: true,
                    urlRequired: false,
                    fields: [
                      {
                        key: "bot_token",
                        label: "Bot token",
                        type: "string",
                        required: true,
                        secret: true,
                      },
                      {
                        key: "chat_id",
                        label: "Chat ID",
                        type: "string",
                        required: true,
                        secret: false,
                      },
                    ],
                  },
                  pagerduty: {
                    label: "PagerDuty",
                    family: "incident",
                    https: true,
                    urlRequired: false,
                    fields: [
                      {
                        key: "routing_key",
                        label: "Routing key",
                        type: "string",
                        required: true,
                        secret: true,
                      },
                      {
                        key: "severity",
                        label: "Severity",
                        type: "enum",
                        required: false,
                        options: ["critical", "error", "warning", "info"],
                        secret: false,
                      },
                    ],
                  },
                  generic: {
                    label: "Generic webhook",
                    family: "generic",
                    https: false,
                    urlRequired: true,
                    fields: [],
                  },
                },
              },
            },
          },
        },
      },
    },
  },

  "/api/webhooks": {
    get: {
      tags: ["Webhooks"],
      summary: "List webhook targets (URLs masked, secrets redacted)",
      description:
        "Lists every configured webhook target. Responses are always redacted: the full URL is never returned — only `url_preview` (protocol + host + a `…` + the last 4 chars), `has_secret` is a boolean (never the secret itself), and any secret-flagged provider config fields (routing keys, API keys, bot tokens) and custom header values are masked to `••••`. Each target also carries its most recent delivery outcome in `last_delivery` (or null if it has never fired).",
      operationId: "listWebhooks",
      responses: {
        200: {
          description: "All configured webhook targets",
          content: {
            "application/json": {
              schema: { type: "object", additionalProperties: true },
              example: { targets: [WEBHOOK_TARGET_EXAMPLE, GENERIC_WEBHOOK_TARGET_EXAMPLE] },
            },
          },
        },
      },
    },
    post: {
      tags: ["Webhooks"],
      summary: "Create a webhook target",
      description:
        "Creates a webhook target that fires when alerts match. `name` and `type` are required. `url` is required for most providers but is derived or defaulted for a few (Telegram and Opsgenie derive it from config; PagerDuty defaults it) — consult GET /api/webhooks/providers for which fields each provider needs. `config` carries provider-specific params (e.g. `{ chat_id }` for Telegram, `{ routing_key, severity }` for PagerDuty, `{ api_key, region }` for Opsgenie). `secret` (HMAC-SHA256 signing) and custom `headers` apply only to the generic family and are silently ignored for other providers. `rule_ids` optionally scopes the target to specific alert rules; omit it to fire for all rules. The response is the created target, REDACTED the same way as the list endpoint (URL masked, secrets shown only as `has_secret`/`••••`).",
      operationId: "createWebhook",
      requestBody: {
        required: true,
        content: {
          "application/json": {
            schema: {
              type: "object",
              required: ["name", "type"],
              properties: {
                name: { type: "string" },
                type: {
                  type: "string",
                  enum: [
                    "slack",
                    "discord",
                    "teams",
                    "google_chat",
                    "mattermost",
                    "rocketchat",
                    "telegram",
                    "pagerduty",
                    "opsgenie",
                    "splunk_oncall",
                    "zapier",
                    "make",
                    "n8n",
                    "pipedream",
                    "generic",
                  ],
                },
                url: {
                  type: "string",
                  format: "uri",
                  description:
                    "Required for most providers; omit for those that derive their URL (Telegram, Opsgenie) or default it (PagerDuty). See GET /api/webhooks/providers.",
                },
                enabled: { type: "boolean", default: true },
                config: {
                  type: "object",
                  additionalProperties: true,
                  description:
                    "Provider-specific params, e.g. { chat_id } (Telegram), { routing_key, severity } (PagerDuty), { api_key, region } (Opsgenie).",
                },
                secret: {
                  type: "string",
                  description: "Generic family only: HMAC-SHA256 signing secret",
                },
                headers: {
                  type: "object",
                  additionalProperties: { type: "string" },
                  description: "Generic family only: extra request headers",
                },
                rule_ids: {
                  type: "array",
                  items: { type: "string" },
                  description: "Optional: scope to specific alert rules (omit for all)",
                },
              },
            },
            examples: {
              slack: {
                summary: "Slack incoming webhook scoped to one rule",
                value: {
                  name: "Eng on-call (Slack)",
                  type: "slack",
                  url: "https://hooks.slack.com/services/T0000/B0000/XXXXXXXXXXXXXXXXXXXXBXqZ",
                  enabled: true,
                  rule_ids: ["rule_inactivity_30m"],
                },
              },
              pagerduty: {
                summary: "PagerDuty (URL defaulted, routing key in config)",
                value: {
                  name: "Sev1 pager",
                  type: "pagerduty",
                  config: { routing_key: "R0ABCDEF0123456789ABCDEF01", severity: "critical" },
                },
              },
              generic: {
                summary: "Generic webhook with HMAC secret and custom headers",
                value: {
                  name: "Internal alert sink",
                  type: "generic",
                  url: "https://alerts.internal.example.com/cc-monitor/hook",
                  secret: "whsec_4f8c2a9e1b6d7f0a3c5e8b1d2f4a6c8e",
                  headers: { "X-Api-Key": "k_live_abc123", Authorization: "Bearer t0ken" },
                },
              },
            },
          },
        },
      },
      responses: {
        201: {
          description: "Created target (redacted)",
          content: {
            "application/json": {
              schema: { type: "object", additionalProperties: true },
              example: { target: WEBHOOK_TARGET_EXAMPLE },
            },
          },
        },
        400: { description: "Validation error" },
      },
    },
  },

  "/api/webhooks/{id}": {
    patch: {
      tags: ["Webhooks"],
      summary: "Update a webhook target (partial; type is immutable)",
      description:
        "Partially updates a webhook target. Only the keys present in the body are changed; omitted keys are left as-is. The provider `type` is immutable. `config` is MERGED over the existing config and re-validated, so a single field (e.g. `severity`, `region`) can change without re-sending the secret fields. For the generic family, `secret` may be omitted (keep current) or sent as `null` (clear it); `headers` and `rule_ids` replace their stored value when present. The response is the updated target, REDACTED (URL masked, secrets shown only as `has_secret`/`••••`).",
      operationId: "updateWebhook",
      parameters: [WEBHOOK_ID_PARAM],
      requestBody: {
        required: true,
        content: {
          "application/json": {
            schema: {
              type: "object",
              properties: {
                name: { type: "string" },
                url: { type: "string", format: "uri", description: "Omit to keep current" },
                enabled: { type: "boolean" },
                config: {
                  type: "object",
                  additionalProperties: true,
                  description: "Provider params; merged over existing (secrets kept if omitted)",
                },
                secret: {
                  type: ["string", "null"],
                  description: "Generic family only: omit to keep, null to clear",
                },
                headers: { type: "object", additionalProperties: { type: "string" } },
                rule_ids: { type: "array", items: { type: "string" } },
              },
            },
            examples: {
              disable: {
                summary: "Disable a target without touching anything else",
                value: { enabled: false },
              },
              renameAndRescope: {
                summary: "Rename and re-scope to different rules",
                value: {
                  name: "Eng on-call (Slack) — muted weekends",
                  rule_ids: ["rule_token_threshold_5m"],
                },
              },
              mergeConfig: {
                summary: "Change one PagerDuty config field (routing key kept)",
                value: { config: { severity: "warning" } },
              },
              clearSecret: {
                summary: "Generic family: clear the signing secret",
                value: { secret: null },
              },
            },
          },
        },
      },
      responses: {
        200: {
          description: "Updated target (redacted)",
          content: {
            "application/json": {
              schema: { type: "object", additionalProperties: true },
              example: {
                target: { ...WEBHOOK_TARGET_EXAMPLE, name: "Eng on-call (Slack) — muted weekends" },
              },
            },
          },
        },
        400: { description: "Validation error" },
        404: { description: "Target not found" },
      },
    },
    delete: {
      tags: ["Webhooks"],
      summary: "Delete a webhook target and its delivery log",
      description:
        "Permanently deletes a webhook target. Its delivery-log history cascades away with it. Returns `{ ok: true }` on success. This only removes the delivery channel — alert *rules* and the fired-alert feed are untouched.",
      operationId: "deleteWebhook",
      parameters: [WEBHOOK_ID_PARAM],
      responses: {
        200: { description: "Deleted" },
        404: { description: "Target not found" },
      },
    },
  },

  "/api/webhooks/{id}/test": {
    post: {
      tags: ["Webhooks"],
      summary: "Send a synthetic test alert to a target",
      description:
        "Sends a synthetic test alert to the target and reports the delivery outcome synchronously. The HTTP status is always 200 when the target exists — the *request itself* succeeded — and the `ok` flag carries the downstream delivery result. `status` is the HTTP status code returned by the provider (or null if the request never completed), `attempts` is how many tries were made (the delivery layer retries transient failures), and `error` is a human-readable failure reason or null on success.",
      operationId: "testWebhook",
      parameters: [WEBHOOK_ID_PARAM],
      responses: {
        200: {
          description: "Delivery result (ok flag carries the downstream outcome)",
          content: {
            "application/json": {
              schema: { type: "object", additionalProperties: true },
              examples: {
                success: {
                  summary: "Provider accepted the test payload",
                  value: { ok: true, status: 200, attempts: 1, error: null },
                },
                failure: {
                  summary: "Provider rejected or was unreachable after retries",
                  value: { ok: false, status: 503, attempts: 3, error: "Service Unavailable" },
                },
              },
            },
          },
        },
        404: { description: "Target not found" },
      },
    },
  },

  "/api/webhooks/{id}/deliveries": {
    get: {
      tags: ["Webhooks"],
      summary: "Recent delivery log for a target",
      description:
        "Returns the recent delivery log for a target, newest first. Each row records the alert that fired, the resulting HTTP status, the number of attempts, any error text, and a timestamp. `limit` is clamped to 1–200 (default 20) and `offset` to ≥0 (default 0). This is an audit trail of past sends — it does not trigger a new delivery (use POST /{id}/test for that).",
      operationId: "listWebhookDeliveries",
      parameters: [
        WEBHOOK_ID_PARAM,
        {
          name: "limit",
          in: "query",
          schema: { type: "integer", default: 20 },
          description: "Max rows to return (clamped to 1–200).",
          example: 20,
        },
        {
          name: "offset",
          in: "query",
          schema: { type: "integer", default: 0 },
          description: "Pagination offset (clamped to ≥0).",
          example: 0,
        },
      ],
      responses: {
        200: {
          description: "Delivery rows, newest first",
          content: {
            "application/json": {
              schema: { type: "object", additionalProperties: true },
              example: {
                deliveries: [
                  {
                    id: 5012,
                    target_id: "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
                    alert_event_id: 8841,
                    status: "success",
                    status_code: 200,
                    attempts: 1,
                    error: null,
                    created_at: "2026-06-25T18:41:55.117Z",
                  },
                  {
                    id: 5008,
                    target_id: "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
                    alert_event_id: 8839,
                    status: "failed",
                    status_code: 429,
                    attempts: 3,
                    error: "Too Many Requests",
                    created_at: "2026-06-25T18:30:02.004Z",
                  },
                ],
                limit: 20,
                offset: 0,
              },
            },
          },
        },
        404: { description: "Target not found" },
      },
    },
  },

  "/api/settings/info": {
    get: {
      tags: ["Settings"],
      summary: "Get system/database/hook diagnostics",
      description:
        "Returns a diagnostics snapshot used by the Settings page: `db` (database file path, on-disk size in bytes, per-table row counts, SQLite pragmas, and recent event load over the last 5/15/60 minutes), `hooks` (whether the Claude Code hook-handler is installed in `settings.json`, the settings path, and a per-hook-type installed map), `server` (process uptime, Node version, platform, live WebSocket connection count, memory/CPU/host stats), and `transcript_cache` (number of cached transcript entries and the cached paths). Read-only and cheap to poll.",
      operationId: "getSettingsInfo",
      responses: {
        200: {
          description: "Settings and diagnostics",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/SettingsInfoResponse" },
              example: SETTINGS_INFO_EXAMPLE,
            },
          },
        },
      },
    },
  },

  "/api/settings/clear-data": {
    post: {
      tags: ["Settings"],
      summary: "Delete all dashboard data",
      description:
        "⚠ DESTRUCTIVE — IRREVERSIBLE. Deletes ALL sessions, agents, events, token_usage rows, the fired-alert feed (alert_events), and the webhook delivery log. There is no confirmation step and no undo — export first via GET /api/settings/export if you need a backup. User CONFIGURATION survives: alert *rules*, webhook *targets*, and model_pricing are preserved (they're settings, not captured data). The response echoes the row counts that existed BEFORE the wipe so the UI can report what was removed.",
      operationId: "clearData",
      responses: {
        200: {
          description: "Data cleared",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ClearDataResponse" },
              example: CLEAR_DATA_EXAMPLE,
            },
          },
        },
      },
    },
  },

  "/api/settings/reimport": {
    post: {
      tags: ["Settings"],
      summary: "Re-import legacy sessions from ~/.claude",
      description:
        "Re-runs the legacy history importer against the default `~/.claude` projects directory, funneling every transcript through the same parser + importSession pipeline the live server uses. This is IDEMPOTENT and ADDITIVE — already-imported sessions are deduplicated (counted under `skipped`), token counts and compaction baselines are preserved so cost never double-counts, and nothing existing is deleted. The response reports how many sessions were `imported` vs `skipped`, plus an `errors` count for transcripts that failed to parse.",
      operationId: "reimportLegacySessions",
      responses: {
        200: {
          description: "Import completed",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ReimportResponse" },
              example: REIMPORT_EXAMPLE,
            },
          },
        },
        500: {
          description: "Import failed",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              example: {
                error: {
                  code: "IMPORT_FAILED",
                  message:
                    "ENOENT: no such file or directory, scandir '/Users/son/.claude/projects'",
                },
              },
            },
          },
        },
      },
    },
  },

  "/api/settings/reinstall-hooks": {
    post: {
      tags: ["Settings"],
      summary: "Reinstall Claude Code hooks",
      description:
        "Re-runs the hook installer to (re)wire the dashboard's hook-handler into Claude Code's `settings.json` for all seven hook types (PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd). Safe to re-run — it overwrites/repairs the dashboard's own hook entries without touching unrelated user hooks. The response returns the post-install hook status so the UI can confirm every hook type is now `installed: true`.",
      operationId: "reinstallHooks",
      responses: {
        200: {
          description: "Hooks reinstall result",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ReinstallHooksResponse" },
              example: REINSTALL_HOOKS_EXAMPLE,
            },
          },
        },
        500: {
          description: "Hook installation failed",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              example: {
                error: {
                  code: "HOOK_INSTALL_FAILED",
                  message: "EACCES: permission denied, open '/Users/son/.claude/settings.json'",
                },
              },
            },
          },
        },
      },
    },
  },

  "/api/settings/reset-pricing": {
    post: {
      tags: ["Settings"],
      summary: "Reset pricing table to defaults",
      description:
        "⚠ DESTRUCTIVE to pricing customizations. Deletes EVERY row in the model_pricing table and re-seeds it from the dashboard's built-in DEFAULT_PRICING list. Any custom rates or custom model patterns you added are permanently lost — there is no undo. Captured session/token data is untouched (only the pricing rules used to *compute* cost change). The response returns the full freshly-seeded pricing table.",
      operationId: "resetPricing",
      responses: {
        200: {
          description: "Pricing defaults restored",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ResetPricingResponse" },
              example: RESET_PRICING_EXAMPLE,
            },
          },
        },
      },
    },
  },

  "/api/settings/export": {
    get: {
      tags: ["Settings"],
      summary: "Export all dashboard data as JSON",
      description:
        'Exports the entire dataset as a single versioned JSON document — all sessions, agents, events, token_usage rows, workflows, dashboard_runs, alert_rules, and model_pricing — stamped with `format: "ccam-export"`, `version`, and `exported_at`. Served with a `Content-Disposition: attachment` header (filename `agent-monitor-export-YYYY-MM-DD.json`) so browsers download it. Use it to back up before a destructive operation (clear-data / cleanup with purge_days) or to migrate/consolidate data across machines — the bundle is re-importable via POST /api/settings/import. Read-only; nothing is modified.',
      operationId: "exportData",
      responses: {
        200: {
          description: "Export payload (served as attachment)",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ExportResponse" },
              example: EXPORT_EXAMPLE,
            },
          },
        },
      },
    },
  },

  "/api/settings/import": {
    post: {
      tags: ["Settings"],
      summary: "Restore (import) a previously exported data bundle",
      description:
        'Restores a bundle produced by GET /api/settings/export. Supply it either as `multipart/form-data` with a single `file` field (browser upload) or as a JSON body `{ "path": "<absolute path>" }` (the server reads the file from disk — used by the `ccam import-data` CLI, and it also sidesteps the global 1 MB JSON body cap for large bundles). The restore is idempotent and NON-DESTRUCTIVE: it is session-atomic, so a session already present (matched by its UUID) is skipped whole together with its agents/events/token_usage/workflows, and independent config rows (dashboard_runs, alert_rules, model_pricing) are inserted only when absent. Nothing existing is overwritten — ideal for consolidating several machines into one dashboard. The response reports per-table counts.',
      operationId: "importData",
      requestBody: {
        required: true,
        content: {
          "multipart/form-data": {
            schema: {
              type: "object",
              properties: {
                file: { type: "string", format: "binary", description: "The export .json file." },
              },
            },
          },
          "application/json": {
            schema: {
              type: "object",
              required: ["path"],
              properties: {
                path: {
                  type: "string",
                  description:
                    "Absolute path (server-side) to an export .json file. `~` is expanded.",
                },
              },
            },
            example: { path: "/Users/you/ccam-export-2026-06-26.json" },
          },
        },
      },
      responses: {
        200: {
          description: "Per-table restore counts",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ImportResponse" },
              example: {
                ok: true,
                source: "agent-monitor-export-2026-06-26.json",
                format: "ccam-export",
                sessions_imported: 42,
                sessions_skipped: 3,
                agents: 96,
                events: 5120,
                token_usage: 42,
                workflows: 4,
                dashboard_runs: 11,
                alert_rules: 2,
                model_pricing: 0,
                errors: 0,
              },
            },
          },
        },
        400: {
          description: "Missing file, invalid JSON, or unrecognized export format",
        },
      },
    },
  },

  "/api/settings/cleanup": {
    post: {
      tags: ["Settings"],
      summary: "Abandon stale sessions and optionally purge old history",
      description:
        "Two-phase maintenance. Phase 1 (`abandon_hours`, non-destructive): marks any still-`active` session with no events newer than that many hours as `abandoned`, and completes its lingering agents — a tidy-up of crashed/orphaned sessions. Phase 2 (`purge_days`) is ⚠ DESTRUCTIVE and IRREVERSIBLE: it permanently DELETES completed/error/abandoned sessions (and their events, agents, and token_usage) whose `started_at` is older than that many days. Active sessions are NEVER purged. Both fields are optional and independent — send only `abandon_hours` for a safe tidy-up, or include `purge_days` to also reclaim disk. Export first if the purged history matters. The response reports counts for each phase.",
      operationId: "cleanupData",
      requestBody: {
        required: true,
        content: {
          "application/json": {
            schema: { $ref: "#/components/schemas/CleanupRequest" },
            examples: {
              tidyOnly: {
                summary: "Safe: abandon stale sessions only (no deletion)",
                value: { abandon_hours: 12 },
              },
              abandonAndPurge: {
                summary:
                  "Abandon stale sessions, then purge history older than 90 days (destructive)",
                value: { abandon_hours: 12, purge_days: 90 },
              },
            },
          },
        },
      },
      responses: {
        200: {
          description: "Cleanup result",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/CleanupResponse" },
              example: CLEANUP_EXAMPLE,
            },
          },
        },
      },
    },
  },

  "/api/import/guide": {
    get: {
      tags: ["Import"],
      summary: "Import guide with OS-aware defaults and step-by-step instructions",
      description:
        "Returns the OS-aware import guide the Import page renders verbatim: the detected `platform`, the default `~/.claude/projects` directory (raw + display form), whether it exists and how many projects/JSONL files it holds, an OS-specific `archive_command` for bundling history off another machine, the supported file extensions, the upload size/count limits, and four ordered `steps` (locate → archive → choose mode → verify). Read-only; performs no import.",
      operationId: "importGuide",
      responses: {
        200: {
          description: "Guide payload",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ImportGuideResponse" },
              example: IMPORT_GUIDE_EXAMPLE,
            },
          },
        },
      },
    },
  },

  "/api/import/rescan": {
    post: {
      tags: ["Import"],
      summary: "Rescan the default ~/.claude/projects directory",
      description:
        'Re-scans the default `~/.claude/projects` directory and imports anything new through the live ingestion pipeline. IDEMPOTENT and ADDITIVE — re-running is always safe, already-imported sessions are deduplicated (`skipped`), and token/compaction baselines are preserved so cost never double-counts. Progress is broadcast over the WebSocket as `import.progress` frames while it runs. The response reports `imported` / `skipped` / `backfilled` / `errors` plus `sessions_seen` and `files_scanned`, with `source: "default"`.',
      operationId: "importRescan",
      responses: {
        200: {
          description: "Import result",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ImportResultResponse" },
              example: IMPORT_RESCAN_EXAMPLE,
            },
          },
        },
        500: {
          description: "Import failed",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              example: {
                error: { code: "IMPORT_FAILED", message: "Failed to read projects directory" },
              },
            },
          },
        },
      },
    },
  },

  "/api/import/scan-path": {
    post: {
      tags: ["Import"],
      summary: "Import transcripts from an arbitrary absolute directory",
      description:
        'Imports transcripts from an arbitrary directory you point the dashboard at (e.g. history extracted from another machine). The `path` must resolve to an existing directory: a leading `~` is expanded to the home directory, the result must be absolute, and subdirectories are walked recursively for `.jsonl` files. Same idempotent, baseline-preserving pipeline as the default rescan; progress is broadcast as `import.progress`. The response echoes the resolved `path` and the per-run counters with `source: "path"`.',
      operationId: "importScanPath",
      requestBody: {
        required: true,
        content: {
          "application/json": {
            schema: {
              type: "object",
              required: ["path"],
              properties: {
                path: {
                  type: "string",
                  description:
                    "Absolute directory path. Tilde (~) is expanded. Walks subdirectories recursively.",
                },
              },
            },
            examples: {
              absolute: {
                summary: "Absolute directory",
                value: { path: "/Users/son/Downloads/claude-history/projects" },
              },
              tilde: {
                summary: "Tilde expanded to home directory",
                value: { path: "~/Downloads/claude-history/projects" },
              },
            },
          },
        },
      },
      responses: {
        200: {
          description: "Import result",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ImportResultResponse" },
              example: IMPORT_SCAN_PATH_EXAMPLE,
            },
          },
        },
        400: {
          description: "Path validation failed",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              examples: {
                missing: {
                  summary: "path omitted",
                  value: { error: { code: "INVALID_INPUT", message: "`path` is required" } },
                },
                notAbsolute: {
                  summary: "path is not absolute",
                  value: {
                    error: { code: "INVALID_INPUT", message: "`path` must be an absolute path" },
                  },
                },
                notFound: {
                  summary: "path does not exist",
                  value: {
                    error: { code: "PATH_NOT_FOUND", message: "Path does not exist: /tmp/nope" },
                  },
                },
                notDir: {
                  summary: "path is a file, not a directory",
                  value: {
                    error: {
                      code: "NOT_A_DIRECTORY",
                      message: "Path is not a directory: /tmp/foo.jsonl",
                    },
                  },
                },
              },
            },
          },
        },
        500: {
          description: "Import failed",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              example: {
                error: { code: "IMPORT_FAILED", message: "Unexpected end of JSON input" },
              },
            },
          },
        },
      },
    },
  },

  "/api/import/upload": {
    post: {
      tags: ["Import"],
      summary: "Upload JSONL files or archives (.zip, .tar, .tar.gz, .tgz, .gz)",
      description:
        'Imports history uploaded directly from the browser as `multipart/form-data` under the `files` field. Accepts raw `.jsonl` / `.meta.json` transcripts and/or archives (`.zip`, `.tar`, `.tar.gz`, `.tgz`, `.gz`), which are extracted into a temp dir and walked for JSONL content. Unsupported extensions are silently rejected and reported in `rejected_files`. Extraction is bounded to defend against zip bombs — exceeding the limit returns 413. Same idempotent import pipeline; progress is broadcast as `import.progress`. On success the response carries `source: "upload"` plus `files_received`, `rejected_files`, `entries_extracted`, `entries_skipped`, and the standard import counters. (Requires the optional `multer` dependency; a missing install yields a 500.)',
      operationId: "importUpload",
      requestBody: {
        required: true,
        content: {
          "multipart/form-data": {
            schema: {
              type: "object",
              properties: {
                files: {
                  type: "array",
                  items: { type: "string", format: "binary" },
                  description:
                    "Files to import. Supports .jsonl, .meta.json, .zip, .tar, .tar.gz, .tgz, .gz.",
                },
              },
            },
            example: { files: ["claude-history.tar.gz", "session-extra.jsonl"] },
          },
        },
      },
      responses: {
        200: {
          description: "Import result",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ImportResultResponse" },
              example: IMPORT_UPLOAD_EXAMPLE,
            },
          },
        },
        400: {
          description: "No files or no JSONL content",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              examples: {
                noFiles: {
                  summary: "No supported files in the upload",
                  value: {
                    error: {
                      code: "NO_FILES",
                      message:
                        "No supported files in upload. 1 file(s) rejected (unsupported extension).",
                    },
                  },
                },
                noJsonl: {
                  summary: "Archive extracted but contained no .jsonl",
                  value: {
                    error: {
                      code: "NO_JSONL",
                      message:
                        "No .jsonl files were found in the uploaded content. Supported inputs: .jsonl, .meta.json, .zip, .tar, .tar.gz, .tgz, .gz.",
                    },
                  },
                },
              },
            },
          },
        },
        413: {
          description: "Extraction limit exceeded (possible zip bomb)",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              example: {
                error: {
                  code: "EXTRACTION_LIMIT_EXCEEDED",
                  message: "Archive exceeded the extraction size limit (possible zip bomb).",
                },
              },
            },
          },
        },
        500: {
          description: "Upload or import failed",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              examples: {
                noMulter: {
                  summary: "Optional multer dependency not installed",
                  value: {
                    error: {
                      code: "UPLOADER_UNAVAILABLE",
                      message:
                        "File upload requires `multer`. Run `npm install` to pick up new deps.",
                    },
                  },
                },
                importFailed: {
                  summary: "Import pipeline error",
                  value: {
                    error: { code: "IMPORT_FAILED", message: "Unexpected end of JSON input" },
                  },
                },
              },
            },
          },
        },
      },
    },
  },

  "/api/updates/status": {
    get: {
      tags: ["Updates"],
      summary: "Check whether the dashboard git checkout is behind origin",
      description:
        "Reports whether the dashboard's own git checkout is behind its upstream remote, so a user can pull and restart manually (the dashboard never self-restarts). The response is a variant object: when it IS a git checkout it includes `git_repo: true`, `repo_root`, `update_available`, `commits_behind`, `remote_ref`, `local_sha`, `remote_sha`, and a copy-pasteable `manual_command`; when it is NOT a git checkout (e.g. an npm/tarball install) it returns `git_repo: false` with a `message` and no diff fields. Read-only — this only inspects git, it does not fetch destructively or modify the working tree.",
      operationId: "getUpdatesStatus",
      responses: {
        200: {
          description: "Update check result",
          content: {
            "application/json": {
              schema: {
                type: "object",
                additionalProperties: true,
                description:
                  "Includes git_repo, update_available, commits_behind, remote_ref, local_sha, remote_sha, manual_command, and optional error/message fields.",
              },
              examples: {
                updateAvailable: {
                  summary: "Behind origin — update available",
                  value: UPDATE_STATUS_AVAILABLE_EXAMPLE,
                },
                upToDate: {
                  summary: "Up to date with origin",
                  value: UPDATE_STATUS_UP_TO_DATE_EXAMPLE,
                },
                notARepo: {
                  summary: "Not a git checkout — detection unavailable",
                  value: UPDATE_STATUS_NON_REPO_EXAMPLE,
                },
              },
            },
          },
        },
        500: {
          description: "Update status query failed",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              example: {
                error: { code: "UPDATE_STATUS_FAILED", message: "git: command not found" },
              },
            },
          },
        },
      },
    },
  },

  "/api/updates/check": {
    post: {
      tags: ["Updates"],
      summary: "Run an update check immediately and broadcast the result",
      description:
        "Runs the same upstream check as GET /api/updates/status immediately and, in addition to returning the result, broadcasts it to every connected client as an `update_status` WebSocket message — so all open dashboard tabs refresh their update banner at once. The response shape is identical to GET /api/updates/status (the same git_repo / update_available / commits_behind / manual_command variant object). Read-only with respect to the working tree.",
      operationId: "triggerUpdatesCheck",
      responses: {
        200: {
          description: "Fresh update status payload (also broadcast over WebSocket)",
          content: {
            "application/json": {
              schema: {
                type: "object",
                additionalProperties: true,
                description:
                  "Same shape as GET /api/updates/status. Also sent as an update_status WebSocket message to all connected clients.",
              },
              examples: {
                updateAvailable: {
                  summary: "Behind origin — update available",
                  value: UPDATE_STATUS_AVAILABLE_EXAMPLE,
                },
                upToDate: {
                  summary: "Up to date with origin",
                  value: UPDATE_STATUS_UP_TO_DATE_EXAMPLE,
                },
              },
            },
          },
        },
        500: {
          description: "Update check failed",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/ErrorResponse" },
              example: {
                error: {
                  code: "UPDATE_CHECK_FAILED",
                  message: "git fetch failed: network unreachable",
                },
              },
            },
          },
        },
      },
    },
  },

  "/api/workflows": {
    get: {
      tags: ["Workflows"],
      summary: "Get workflow intelligence aggregates",
      description:
        "Returns the full workflow-intelligence aggregate powering the Workflows analytics page — 11 sections in one payload: `stats` (headline counters: sessions, agents, subagents, success rate, avg depth/duration, compactions, top tool flow), `orchestration` (subagent-type breakdown + delegation edges + outcomes), `toolFlow` (tool-to-tool transitions + tool counts), `effectiveness` (per-subagent-type success rate, avg duration, weekly trend), `patterns` (frequent subagent sequences + solo-session share), `modelDelegation` (model usage for main/sub agents + tokens by model), `errorPropagation` (errors by depth/type + error rate), `concurrency` (averaged agent swim-lane start/end), `complexity` (per-session agent/token/duration rows), `compaction` (compaction counts + tokens recovered), and `cooccurrence` (directed subagent-after-subagent pairs). The optional `status` query filter scopes every section to sessions of one status. Errors use the SHORT `{ error: { message } }` shape.",
      operationId: "getWorkflowIntelligence",
      parameters: [{ $ref: "#/components/parameters/WorkflowStatusQuery" }],
      responses: {
        200: {
          description: "Workflow aggregate data",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/WorkflowAggregateResponse" },
              example: WORKFLOW_AGGREGATE_EXAMPLE,
            },
          },
        },
        500: {
          description: "Internal server error",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/MessageErrorResponse" },
              example: { error: { message: "no such table: agents" } },
            },
          },
        },
      },
    },
  },

  "/api/workflows/session/{id}": {
    get: {
      tags: ["Workflows"],
      summary: "Get workflow drill-in for one session",
      description:
        "Returns the workflow drill-in for a single session, used by the session-level Workflow view: `session` (the session row), `tree` (the recursive parent→child agent tree rooted at the main agent), `toolTimeline` (chronological tool events with tool_name/event_type/agent_id/summary), `swimLanes` (a flat per-agent start/end lane list for the Gantt-style view), and `events` (the chronological event stream, capped at the first 500 rows). Returns 404 with the SHORT `{ error: { message } }` shape when the session id is unknown.",
      operationId: "getWorkflowSession",
      parameters: [{ $ref: "#/components/parameters/SessionIdPath" }],
      responses: {
        200: {
          description: "Workflow session detail",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/WorkflowSessionResponse" },
              example: WORKFLOW_SESSION_EXAMPLE,
            },
          },
        },
        404: {
          description: "Session not found",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/MessageErrorResponse" },
              example: { error: { message: "Session not found" } },
            },
          },
        },
        500: {
          description: "Internal server error",
          content: {
            "application/json": {
              schema: { $ref: "#/components/schemas/MessageErrorResponse" },
              example: { error: { message: "no such table: agents" } },
            },
          },
        },
      },
    },
  },
};

module.exports = { tags, schemas, paths };
