{
  "$comment": "Bản cho máy đọc của mục /zalo/. Khớp với docs/zalo/*.md và mã nguồn ran/.",
  "updated": "2026-10-13",
  "pages": {
    "/zalo/": "Tổng quan Zalo và chat",
    "/zalo/channels": "Kênh kết nối Zalo",
    "/zalo/zca-js": "zca-js: cơ chế, đánh giá, giới hạn",
    "/zalo/ran": "Ran: kiến trúc",
    "/zalo/ran-guide": "Ran: hướng dẫn sử dụng"
  },
  "channels": [
    {
      "id": "zalo-bot",
      "official": true,
      "transport": [
        "webhook",
        "long-polling"
      ]
    },
    {
      "id": "zalo-personal",
      "official": false,
      "transport": [
        "websocket"
      ],
      "library": "zca-js@2.2.0"
    }
  ],
  "zcaJs": {
    "version": "2.2.0",
    "wsCommands": {
      "501": "user messages",
      "521": "group messages",
      "601": "control events"
    },
    "closeCodes": {
      "1000": "manual",
      "1006": "abnormal",
      "3000": "duplicate connection (Zalo Web mở nơi khác)",
      "3003": "kicked"
    }
  },
  "ran": {
    "path": "ran/",
    "start": "npm start",
    "ui": "http://127.0.0.1:3939",
    "env": {
      "PORT": 3939,
      "HOST": "127.0.0.1",
      "RAN_DATA": "data"
    },
    "files": {
      "data/config.json": "config",
      "data/credentials.json": "Zalo session (secret)",
      "data/threads/<threadId>.json": "{ threadId, messages[], profile, profileUpdatedAt, sinceProfile }"
    },
    "config": {
      "paused": "boolean",
      "ownerName": "string",
      "providers": [
        {
          "id": "string",
          "type": "anthropic | openai",
          "baseUrl": "string",
          "apiKey": "string"
        }
      ],
      "defaults": {
        "providerId": "string",
        "model": "string",
        "temperature": "number",
        "persona": "string",
        "replyDelaySec": "number",
        "historyLimit": "number",
        "profileEvery": "number",
        "takeoverMinutes": "number"
      },
      "targets": {
        "<threadId>": {
          "threadId": "string",
          "type": "user | group",
          "name": "string",
          "enabled": "boolean",
          "mode": "auto | mention | draft",
          "keywords": [
            "string"
          ],
          "providerId": "string (empty = default)",
          "model": "string (empty = default)",
          "temperature": "number | null",
          "persona": "string",
          "context": "string"
        }
      }
    },
    "skipToken": "<SKIP>",
    "api": [
      {
        "method": "GET",
        "path": "/api/status",
        "returns": "zalo state, paused, drafts, unknown threads, activity"
      },
      {
        "method": "GET",
        "path": "/api/backlog",
        "returns": "BACKLOG.md (text/markdown)"
      },
      {
        "method": "POST",
        "path": "/api/login",
        "does": "start QR login"
      },
      {
        "method": "POST",
        "path": "/api/logout",
        "does": "stop listener, delete credentials"
      },
      {
        "method": "GET",
        "path": "/api/config",
        "returns": "config, API keys masked"
      },
      {
        "method": "PUT",
        "path": "/api/config",
        "body": "config without targets; masked key = keep old"
      },
      {
        "method": "GET",
        "path": "/api/zalo/threads",
        "returns": "{ friends[], groups[] }"
      },
      {
        "method": "POST",
        "path": "/api/targets",
        "body": "{ threadId, type, name }"
      },
      {
        "method": "PUT",
        "path": "/api/targets/:id",
        "body": "partial target"
      },
      {
        "method": "DELETE",
        "path": "/api/targets/:id"
      },
      {
        "method": "GET",
        "path": "/api/threads/:id",
        "returns": "last 60 messages + profile"
      },
      {
        "method": "PUT",
        "path": "/api/threads/:id/profile",
        "body": "{ profile }"
      },
      {
        "method": "POST",
        "path": "/api/threads/:id/profile/refresh",
        "returns": "{ profile }"
      },
      {
        "method": "POST",
        "path": "/api/threads/:id/preview",
        "returns": "{ text } (not sent)"
      },
      {
        "method": "POST",
        "path": "/api/drafts/:id/send",
        "body": "{ text? }"
      },
      {
        "method": "DELETE",
        "path": "/api/drafts/:id"
      }
    ],
    "limitations": [
      "never run against real Zalo/Claude",
      "text only (in and out)",
      "no missed-message backfill",
      "no history import",
      "single Zalo account",
      "no send rate limit / quiet hours",
      "no tools",
      "plain-text secrets in data/",
      "UI has no password (localhost only)"
    ],
    "backlog": "ran/BACKLOG.md (đầy đủ; hiện ở tab \"Chưa làm\" và GET /api/backlog)"
  }
}
