From dadf89af3df738c68b459ef4201f59a6c67b3cca Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Mon, 11 May 2026 18:34:49 +0800 Subject: [PATCH] docs: update SCHEMA.md / ERRORS.md / PLATFORMS.md / RUNTIME.md for v2 Co-authored-by: Cursor --- references/ERRORS.md | 103 ++++++++++++++++++++++---- references/PLATFORMS.md | 115 +++++++++++++++++++++-------- references/RUNTIME.md | 60 ++++++++++----- references/SCHEMA.md | 160 +++++++++++++++++++++++----------------- 4 files changed, 302 insertions(+), 136 deletions(-) diff --git a/references/ERRORS.md b/references/ERRORS.md index 5bdd7d4..fe3b653 100644 --- a/references/ERRORS.md +++ b/references/ERRORS.md @@ -1,23 +1,27 @@ # 终端错误码(account-manager) -约定:**机器解析只依赖 `stdout` 首行**时,应先判断首行是否以 `ERROR:` 开头;多行说明可忽略或仅作展示。 +约定:**集成解析必须读取 stdout**;多数失败后仍为单行 JSON,`success=false`。老旧别名路径可能出现 **`ERROR:` 文本前缀**——仍以「是否能解析 JSON」为首要判定。 -## 新增机器 JSON 失败格式 +## 机器 JSON 失败格式(常见) ```json { "success": false, "error": { - "code": "ERROR:...", - "message": "..." + "code": "……", + "message": "……" } } ``` +部分 CLI(例如遗留 `ERROR:INVALID_PLATFORM`)仍可能在成功行前额外打印 hint——请以 [`INTEGRATION.md`](INTEGRATION.md) 的建议解析顺序为准。 + +--- + ## ERROR:CLI — CLI 参数 / 路由 | 前缀 | 含义 | -|------|------| +|---|---| | `ERROR:CLI_ADD_MISSING_ARGS` | `add` 参数不足 | | `ERROR:CLI_GET_MISSING_ID` | `get` 缺少 id | | `ERROR:CLI_OPEN_MISSING_ID` | `open` 缺少 id | @@ -28,34 +32,42 @@ | `ERROR:CLI_BAD_ARGS` | 子命令参数缺失或非法 | | `ERROR:CLI_UNKNOWN_OR_BAD_ARGS` | 无法识别的子命令或参数组合 | -## ERROR: — 数据库 +--- + +## ERROR: — 数据库 / 通用 | 前缀 | 含义 | -|------|------| +|---|---| | `ERROR:DB_ERROR` | SQLite 写入失败等 | +--- + ## ERROR: — 平台与账号 | 前缀 | 含义 | -|------|------| +|---|---| | `ERROR:INVALID_PLATFORM` | 平台名无法识别 | | `ERROR:NO_ACCOUNTS_FOUND` | 列表无结果 | | `ERROR:ACCOUNT_NOT_FOUND` | 指定 id 不存在 | | `ERROR:ACCOUNT_DISABLED` | 账号状态为 disabled | -| `ERROR:NO_ACCOUNT` | `pick-web` 无符合条件的可用账号(含:均被 active 未过期 lease 占用时) | +| `ERROR:NO_ACCOUNT` | `pick-web` 无符合条件的可用账号(含均被 active 未过期 lease 占用) | + +--- ## ERROR: — 租约 | 前缀 | 含义 | -|------|------| +|---|---| | `ERROR:LEASE_CONFLICT` | 同账号已有 active 未过期 lease | | `ERROR:LEASE_NOT_FOUND` | 未找到活跃租约 | | `ERROR:LEASE_EXPIRED` | 租约已过期 | +--- + ## ERROR: — 凭据与 Secret | 前缀 | 含义 | -|------|------| +|---|---| | `ERROR:CREDENTIAL_NOT_FOUND` | 凭据不存在 | | `ERROR:CREDENTIAL_DISABLED` | 凭据不可用 | | `ERROR:SECRET_STORAGE_UNSUPPORTED` | 不支持的 secret 存储方式 | @@ -65,15 +77,74 @@ | `ERROR:SECRET_ENV_MISSING` | 环境变量未设置 | | `ERROR:WINDOWS_CREDENTIAL_UNAVAILABLE` | 非 Windows 平台不支持 | +--- + ## ERROR: — 浏览器 / 环境 | 前缀 | 含义 | -|------|------| +|---|---| | `ERROR:需要 playwright Python 包` | 未安装 `playwright` 包或本机无 Chrome/Edge | | `ERROR:PROFILE_DIR_MISSING` | 账号记录中 `profile_dir` 为空 | -## 非前缀输出 +--- -- **`get` / `list-json` / `pick-web`** 成功时:`stdout` 应为**单行 JSON**。 -- 失败时首行 `ERROR:`,可能有后续说明行。 -- **新增机器命令建议统一 JSON 输出**,旧兼容命令可继续输出 `ERROR:` 文本前缀。 +## v2 新增错误码(机器可读 JSON,`code` 字段通常 **无** `ERROR:` 前缀) + +### 加密 / Master Key + +| 错误码 | 触发场景 | +|---|---| +| `ERR_MASTER_KEY_MISSING` | 环境变量与 `master.key` 均未解析到合法密钥 | +| `ERR_MASTER_KEY_EXISTS` | `init-master-key` 时文件已存在且未 `--rotate` | +| `ERR_MASTER_KEY_INVALID` | `--from-env` / 环境变量提供字符串无法归一成 Fernet key | +| `ERR_CREDENTIAL_DECRYPT_FAILED` | Fernet 解密失败(`CredentialDecryptError`,典型为密文损坏或 key 不匹配) | +| `ERR_CREDENTIAL_NOT_ENCRYPTED` | `db.credentials_repo.read_credential_plaintext` 对 `secret_storage='none'` 抛出 **`CredentialNotEncryptedError`**;CLI `get-credential --reveal` **会捕获并返回 `plaintext: null`**(非致命 JSON 错误) | + +### 凭据导入导出 + +| 错误码 | 触发场景 | +|---|---| +| `ERR_EXPORT_REQUIRES_PLAINTEXT_FLAG` | `export-credentials` 未带 `--plaintext` | + +### `add-web` 扩参 + +| 错误码 | 触发场景 | +|---|---| +| `ERR_AUTH_STRATEGY_NOT_SUPPORTED` | `--auth-strategy` 不在允许集合 / 账号缺少策略(同进程 dispatcher 亦使用) | +| `ERR_INVALID_SESSION_PERSISTENT` | `--session-persistent` 不是 `0` 或 `1` | +| `ERR_DEVICE_FINGERPRINT_NOT_APPLICABLE` | 非 `device_bound` 策略却传入 `--device-fingerprint` | + +### `add-secret` + `local_encrypted` + +| 错误码 | 触发场景 | +|---|---| +| `ERR_LOCAL_ENCRYPTED_REQUIRES_STDIN` | `local_encrypted` 未配合 `--secret-stdin` | +| `ERR_LOCAL_ENCRYPTED_TYPE_NOT_ALLOWED` | `credential_type` 不允许加密(如 `browser_profile` / `note`) | +| `ERR_LOCAL_ENCRYPTED_EMPTY_STDIN` | stdin 无内容 | + +### `get-credential` / 访问控制 + +| 错误码 | 触发场景 | +|---|---| +| `ERR_LEASE_INVALID` | `--reveal` 时 lease 缺失、过期或不匹配账号 | +| `ERR_CREDENTIAL_MISSING` | 账号下无 `status=active` 的凭据 | + +--- + +## rpa_helpers(同进程异常,非 CLI) + +详见 [`RPA_HELPERS.md`](RPA_HELPERS.md)。`AuthStrategyError.code` 取值: + +| 错误码 | 触发场景 | +|---|---| +| `ERR_AUTH_STRATEGY_NOT_SUPPORTED` | `auth_strategy` 为空或 dispatcher 不识别的字符串 | +| `ERR_CREDENTIAL_MISSING` | 策略需要 `credentials["plaintext"]` 但未提供 | +| `ERR_AUTH_STRATEGY_NOT_AUTOMATABLE` | `client_certificate` | +| `ERR_DEVICE_FINGERPRINT_MISMATCH` | `device_bound` 机器指纹与账号不一致 | + +--- + +## 非前缀输出提示 + +- **`pick-web` / `account get` / `account list`** 成功:stdout 多为单行 JSON(不一定含 `success` 字段)。 +- **失败**:优先解析 JSON;否则兼容 `ERROR:` 文本。 diff --git a/references/PLATFORMS.md b/references/PLATFORMS.md index 7277894..dfb98ae 100644 --- a/references/PLATFORMS.md +++ b/references/PLATFORMS.md @@ -1,50 +1,101 @@ # 平台与别名(account-manager) -**权威来源**:`scripts/util/platforms.py` 中的 `PLATFORMS`。下表便于文档阅读;若与代码不一致,**以代码为准**。 +**权威来源**:`scripts/util/platforms.py` 中的 `PLATFORMS`(当前 **31** 条)。若本文与代码不一致,**以代码为准**。 -## 物流平台 +解析函数:`resolve_platform_key(name)` — 支持英文键、`display_name`、任一 alias(大小写不敏感)。 -| 英文键 | 展示名 | 供应商代码 | 别名 | -|--------|--------|-----------|------| -| `maersk` | Maersk | maersk | 马士基 | -| `cosco` | COSCO | cosco | 中远海运 | -| `msc` | MSC | msc | 地中海航运 | -| `cma_cgm` | CMA CGM | cma_cgm | 达飞 | -| `evergreen` | Evergreen | evergreen | 长荣 | -| `cargo_wise` | CargoWise | cargo_wise | Cargo Wise | -| `freightos` | Freightos | freightos | — | -| `webcargo` | WebCargo | webcargo | Web Cargo | -| `sinotrans` | Sinotrans | sinotrans | 中外运 | +--- -## LLM / AI 平台 +## 物流平台(`domain = logistics`,9) -| 英文键 | 展示名 | 别名 | 默认 URL | -|--------|--------|------|----------| -| `deepseek` | DeepSeek | — | https://chat.deepseek.com | +| 英文键 | 展示名 | 供应商代码 | 别名(节选) | 默认 URL | +|---|---|---|---|---| +| `maersk` | Maersk | maersk | 马士基 | https://www.maersk.com | +| `cosco` | COSCO | cosco | 中远海运 | (空) | +| `msc` | MSC | msc | 地中海航运 | (空) | +| `cma_cgm` | CMA CGM | cma_cgm | 达飞 | (空) | +| `evergreen` | Evergreen | evergreen | 长荣 | (空) | +| `cargo_wise` | CargoWise | cargo_wise | CargoWise, Cargo Wise | (空) | +| `freightos` | Freightos | freightos | Freightos | (空) | +| `webcargo` | WebCargo | webcargo | WebCargo, web cargo | (空) | +| `sinotrans` | Sinotrans | sinotrans | 中外运 | (空) | + +--- + +## LLM / AI 平台(`domain = llm`,10) + +其中 `openai`、`anthropic` 在注册表中 **`capabilities.web = false`**,以 API Key 场景为主;其余条目多为 Web + API。 + +| 英文键 | 展示名 | 别名(节选) | 默认 URL | +|---|---|---|---| +| `deepseek` | DeepSeek | DeepSeek | https://chat.deepseek.com | | `doubao` | Doubao | 豆包 | https://www.doubao.com | | `kimi` | Kimi | 月之暗面 | https://kimi.moonshot.cn | | `qianwen` | Qianwen | 通义千问、通义、千问 | https://tongyi.aliyun.com | | `yiyan` | YiYan | 文心一言、文心、一言 | https://yiyan.baidu.com | | `yuanbao` | Yuanbao | 腾讯元宝、元宝 | https://yuanbao.tencent.com | -| `gemini` | Gemini | 谷歌Gemini、Google Gemini、Bard | https://gemini.google.com | -| `minimax` | MiniMax | — | — | +| `gemini` | Gemini | Gemini、谷歌Gemini、Google Gemini、Bard | https://gemini.google.com | +| `minimax` | MiniMax | MiniMax | (空) | +| `openai` | OpenAI API | OpenAI、GPT、ChatGPT API | https://platform.openai.com | +| `anthropic` | Anthropic | Anthropic、Claude API | https://console.anthropic.com | -## 内容平台 +--- -| 英文键 | 展示名 | 别名 | 默认 URL | -|--------|--------|------|----------| -| `sohu` | 搜狐号 | 搜狐 | https://mp.sohu.com | -| `toutiao` | 头条号 | 头条 | https://mp.toutiao.com/ | -| `zhihu` | 知乎 | 知乎号 | https://www.zhihu.com | -| `wechat` | 微信公众号 | 公众号、微信 | https://mp.weixin.qq.com | +## 内容平台(`domain = content`,8) -## 解析规则 +| 英文键 | 展示名 | 别名(节选) | 默认 URL | +|---|---|---|---| +| `sohu` | 搜狐号 | 搜狐、搜狐号 | https://mp.sohu.com | +| `toutiao` | 头条号 | 头条、头条号 | https://mp.toutiao.com/ | +| `zhihu` | 知乎 | 知乎、知乎号 | https://www.zhihu.com | +| `wechat` | 微信公众号 | 公众号、微信、微信公众号 | https://mp.weixin.qq.com | +| `douyin` | 抖音 | 抖音、Douyin | https://www.douyin.com | +| `wechat_mp` | 微信公众号 | 微信公众号、公众号、WeChat MP | https://mp.weixin.qq.com | +| `xiaohongshu` | 小红书 | 小红书、Xiaohongshu、RED | https://www.xiaohongshu.com | +| `bilibili` | 哔哩哔哩 | B站、哔哩哔哩、Bilibili | https://www.bilibili.com | -- 英文键本身、各 `display_name`、各 `aliases` 条目(及小写形式)均可解析到对应键。 -- 不区分大小写。 -- 无法识别时输出 `ERROR:INVALID_PLATFORM`,并附带支持的平台列表。 -- 解析函数:`resolve_platform_key(name)` — 支持 key、display_name、aliases。 +--- + +## 开发控制台(`domain = dev`,3) + +| 英文键 | 展示名 | 别名(节选) | 默认 URL | +|---|---|---|---| +| `github` | GitHub | GitHub、github | https://github.com/login | +| `aws` | AWS | AWS、Amazon Web Services、亚马逊云 | https://console.aws.amazon.com | +| `gcp` | GCP | GCP、Google Cloud、谷歌云 | https://console.cloud.google.com | + +--- + +## 仿真平台(`domain = sim`,1) + +| 英文键 | 展示名 | 别名(节选) | 默认 URL | +|---|---|---|---| +| `icbc_sim` | 工行仿真 | 工行仿真、ICBC Simulator、ICBC SIM | http://localhost:5180 | + +--- + +## `platform list --domain` 说明 + +CLI `platform list --domain` **仅接受** `logistics | llm | content`(见 `cmd_platform_list`)。`dev` / `sim` 域平台请 **省略 `--domain`** 获取完整列表后在 JSON 中筛选,或直接 `platform get `。 + +--- + +## 内置默认 `auth_strategy` + +`db/platform_defaults.py` 中的 `PLATFORM_DEFAULT_AUTH_STRATEGY` 提供 **17** 个平台的初始映射(迁移时写入 `platforms.default_auth_strategy`,可被数据库手工覆盖): + +| 平台键 | 默认 `auth_strategy` | +|---|---| +| `maersk`, `cosco`, `msc` | `qr_code_manual` | +| `douyin`, `wechat_mp`, `xiaohongshu`, `bilibili`, `toutiao`, `sohu` | `qr_code_manual` | +| `github`, `aws`, `gcp` | `password_plus_2fa` | +| `icbc_sim` | `password_auto` | +| `openai`, `anthropic`, `deepseek`, `doubao` | `api_token` | + +其余注册平台若 DB 中 `default_auth_strategy` 为空,则在创建账号时回落到 `resolve_auth_strategy_for_platform`(内置映射 → 否则 `password_auto`)。业务可在 `add-web` 显式传入 `--auth-strategy`。 + +--- ## 平台注册到数据库 -`seed_platforms(conn)` 在每次 `init_db()` 后自动执行,将 `PLATFORMS` 字典 upsert 到 `platforms` 表。 +`seed_platforms(conn)` 在业务流程中随 `init_db()` 触发,将 `PLATFORMS` upsert 进 `platforms` 表(仅覆盖定义字段;**不会**自动清空用户自定义的 `default_auth_strategy`,除非迁移逻辑另行更新——详见 [`SCHEMA.md`](SCHEMA.md))。 diff --git a/references/RUNTIME.md b/references/RUNTIME.md index cc0e34d..3aa1f71 100644 --- a/references/RUNTIME.md +++ b/references/RUNTIME.md @@ -2,58 +2,80 @@ ## 数据落盘位置 -`get_data_root()` / `get_user_id()` 来自 **`scripts/jiangchang_skill_core/runtime_env.py`**;`scripts/util/runtime_paths.py` 在此基础上拼技能子目录。 +`get_data_root()` / `get_user_id()` 来自 **`scripts/jiangchang_skill_core/runtime_env.py`**(兼容 **`JIANGCHANG_*` / `CLAW_*` 别名**);`scripts/util/runtime_paths.py` 在此基础上拼接技能子目录。 -- **数据根目录**:优先 **`JIANGCHANG_DATA_ROOT`**;未设置且未走本地注入时,Windows 默认 `D:\jiangchang-data`,其它系统 `~/.jiangchang-data`。 -- **用户/工作空间 id**:优先 **`JIANGCHANG_USER_ID`**;未设置时为 **`_anon`**。 +- **数据根目录**:默认读取宿主注入变量;开发模式下未注入时可能落到 Windows `D:\jiangchang-data` 等(详见 runtime_env 实现)。 +- **用户 / 工作空间 id**:默认 `_anon`(以实际注入为准)。 - **技能数据目录**:`{数据根}/{用户id}/account-manager/` - **SQLite**:`account-manager.db` + - **Master Key**:`master.key`(仅当执行过 `account init-master-key` 且未仅用环境变量场景) - **Profile**:`profiles/{domain}/{platform_key}/{safe_label}[_{login_id_suffix}]/` - **日志**:与其它技能共用 `{数据根}/{用户id}/logs/jiangchang.log` ## 调试:为什么「list 没数据」 -- 若终端与网关注入的 `JIANGCHANG_*` 不一致,会写到**不同用户目录**。 -- 设置 **`JIANGCHANG_ACCOUNT_DEBUG_PATHS=1`** 后,每次 CLI 命令前在 **stderr** 打印实际路径。 +- 若终端与网关注入的数据根 / 用户 id 不一致,会写到**不同目录**。 +- 设置 **`JIANGCHANG_ACCOUNT_DEBUG_PATHS=1`**(或宿主等价开关)后,CLI 会在 **stderr** 打印实际路径。 ## 本地 CLI 默认值 各技能 `main.py` 调用 `apply_cli_local_defaults()`: + - `CLI_LOCAL_DEV_ENABLED = True` 时,未设置环境变量则注入平台默认值。 - 发版前改为 `False` 或依赖宿主注入。 ## Secret 存储 | 方式 | 说明 | 适用场景 | -|------|------|----------| +|---|---|---| | `none` | 不存 secret | 浏览器 profile 登录态 | -| `env` | SQLite 只存环境变量名,运行时从 `os.environ` 读取 | 轻量测试 | -| `windows_credential` | 真实 secret 存 Windows Credential Manager | Windows 本地正式部署 | +| `env` | SQLite 存变量名,运行时从 `os.environ` 读取 | 轻量测试 | +| `windows_credential` | secret 存 Windows Credential Manager | Windows 本地部署 | +| `local_encrypted` | Fernet 密文存 SQLite(`secret_ciphertext`),密钥来自 master key | RPA 密码落地;需先 `account init-master-key` | -- **不把 secret 明文写入 SQLite** -- **不在日志中记录 secret 明文** -- 只有 `credential pick --reveal` 或 `credential resolve --reveal` 才返回 secret +- **禁止**把明文写入日志。 +- `credential pick --reveal` / `credential resolve --reveal` 仍会输出明文 secret——仅限受控环境。 + +## Master Key + +| 变量 | 作用 | +|---|---| +| `ACCOUNT_MANAGER_MASTER_KEY` | Fernet key(典型为 url-safe Base64 **44** 字符);**优先级高于** `master.key` 文件 | + +**加载优先级(解密路径)**:环境变量 → `master.key` 文件 → `ERR_MASTER_KEY_MISSING`。 + +**安全建议** + +- 生产环境优先 **环境变量 / Secret Manager** 注入,不在磁盘留 `master.key`。 +- 桌面环境可使用文件(默认尽力写入私有权限)。 +- 备份 master key 至密码管理器;**丢失将导致所有 `local_encrypted` 凭据不可解密**。 + +详见 [`CLI.md`](CLI.md)(`init-master-key`)、[`ERRORS.md`](ERRORS.md)。 ## 日志 | 变量 | 作用 | -|------|------| +|---|---| | `JIANGCHANG_LOG_LEVEL` | 日志级别,默认 `INFO` | | `JIANGCHANG_LOG_TO_STDERR` | 设为 `1`/`true` 等时,WARNING 及以上同步 stderr | | `JIANGCHANG_LOG_FILE` | 覆盖主日志文件绝对路径 | | `JIANGCHANG_LOG_BACKUP_COUNT` | 轮转保留份数,默认 `30` | | `JIANGCHANG_TRACE_ID` | 可选;跨技能子进程由父进程注入 | -## 浏览器(open) +## 浏览器(`account open` / rpa_helpers) -- 本机已安装 Chrome 或 Edge -- Python 环境需 `playwright` 包(匠厂共享 runtime 已包含) -- 使用 **channel 模式**(`channel=chrome|msedge`),**不下载 Chromium** -- **不要执行 `playwright install chromium`** +- 本机需安装 Chrome 或 Edge。 +- Python 侧依赖 **`playwright`**(匠厂共享 runtime 已内置)。 +- 使用 **channel 模式**(`chrome` / `msedge`),**不下载 Chromium**。 +- **不要执行** `playwright install chromium`。 -### 关键浏览器参数 +### 关键浏览器参数(摘录) - `headless=False` - `no_viewport=True` - `args` 包含 `--start-maximized` -- stealth 模式可选(`OPENCLAW_PLAYWRIGHT_STEALTH` 环境变量控制) +- stealth 是否启用由 `OPENCLAW_PLAYWRIGHT_STEALTH` 控制(见 `launch_browser_with_profile`) + +### rpa_helpers + +登录编排封装见 [`RPA_HELPERS.md`](RPA_HELPERS.md)。 diff --git a/references/SCHEMA.md b/references/SCHEMA.md index ecf9426..60f4f6b 100644 --- a/references/SCHEMA.md +++ b/references/SCHEMA.md @@ -2,121 +2,143 @@ ## 数据库路径 -`{JIANGCHANG_DATA_ROOT}/{JIANGCHANG_USER_ID}/account-manager/account-manager.db` - -路径解析细节见 [RUNTIME.md](RUNTIME.md)。 +`{JIANGCHANG_DATA_ROOT}/{JIANGCHANG_USER_ID}/account-manager/account-manager.db` +(或宿主映射的 `CLAW_DATA_ROOT` / `CLAW_USER_ID`,解析细节见 [`RUNTIME.md`](RUNTIME.md)。) ## Schema 版本 -`PRAGMA user_version = 2` +- `PRAGMA user_version = 2` +- `_schema_meta.schema_version = "2.1"`(字符串;迁移完成后取值) -## 1. platforms — 平台注册表 +启动时 `ensure_schema` 自动执行 `migrate_v1_to_v2` 与 `migrate_v2_to_v2_1`,幂等。 + +--- + +## 1. `platforms` — 平台注册表 | 字段 | 类型 | 说明 | -|------|------|------| -| `platform_key` | TEXT PK | 平台唯一键(maersk / deepseek / sohu 等) | -| `display_name` | TEXT NOT NULL | 展示名(Maersk / DeepSeek / 搜狐号) | -| `domain` | TEXT NOT NULL | 业务域:logistics / llm / content / ecommerce / generic | -| `provider_code` | TEXT | 供应商代码,物流 profile 可复用 | +|---|---|---| +| `platform_key` | TEXT PK | 平台唯一键(`maersk` / `deepseek` / `sohu` …) | +| `display_name` | TEXT NOT NULL | 展示名 | +| `domain` | TEXT NOT NULL | `logistics` / `llm` / `content` / `dev` / `sim` / …(以代码注册为准) | +| `provider_code` | TEXT | 供应商代码 | | `default_url` | TEXT | 默认入口 URL | | `aliases_json` | TEXT | 别名 JSON 数组 | -| `capabilities_json` | TEXT | 能力声明 JSON(`{"web":true,"api_key":true,"rpa":true}`) | -| `enabled` | INTEGER | 是否启用(1=yes 0=no) | +| `capabilities_json` | TEXT | `{"web": bool, "api_key": bool, "rpa": bool}` | +| `enabled` | INTEGER | 是否启用(1/0) | +| `default_auth_strategy` | TEXT | 新账号默认 `auth_strategy`;可由迁移回填 | | `created_at` | INTEGER | Unix 秒 UTC | | `updated_at` | INTEGER | Unix 秒 UTC | -**索引:** `idx_platforms_domain`, `idx_platforms_provider_code` +**索引:** `idx_platforms_domain`,`idx_platforms_provider_code` -## 2. accounts — 账号身份 +--- + +## 2. `accounts` — 账号身份 | 字段 | 类型 | 说明 | -|------|------|------| +|---|---|---| | `id` | INTEGER PK AUTO | 主键 | -| `platform_key` | TEXT NOT NULL | 对应 platforms.platform_key | +| `platform_key` | TEXT NOT NULL | 对应 `platforms.platform_key` | | `account_label` | TEXT NOT NULL | 人可读名称 | -| `login_id` | TEXT | 登录标识(email/phone/username/customer_code),可空 | -| `login_id_type` | TEXT | email / username / phone / customer_code / api_account / unknown | -| `tenant_id` | TEXT | 租户/项目标识,可空 | -| `environment` | TEXT | simulator / staging / production / test | -| `role` | TEXT | booking / tracking / admin / sales / api / default | +| `login_id` | TEXT | 登录标识(可空) | +| `login_id_type` | TEXT | `email` / `username` / `phone` / … | +| `tenant_id` | TEXT | 租户,可空 | +| `environment` | TEXT | `simulator` / `staging` / `production` / … | +| `role` | TEXT | `booking` / `tracking` / `api` / … | | `provider_code` | TEXT | 冗余供应商码 | -| `profile_dir` | TEXT | Playwright 持久化浏览器 profile 目录 | -| `url` | TEXT | 账号默认入口 URL | -| `status` | TEXT | active / disabled / needs_review | -| `session_status` | TEXT | unknown / likely_valid / needs_login / expired | -| `last_used_at` | INTEGER | 最近 pick/使用时间 | -| `last_login_check_at` | INTEGER | 最近登录检查时间 | -| `last_error_code` | TEXT | 最近错误代码 | -| `last_error_message` | TEXT | 最近错误消息 | -| `extra_json` | TEXT | 扩展字段 JSON | +| `profile_dir` | TEXT | Playwright profile 目录 | +| `url` | TEXT | 账号入口 URL | +| `status` | TEXT | `active` / `disabled` / … | +| `session_status` | TEXT | `unknown` / `likely_valid` / … | +| `last_used_at` | INTEGER | Unix 秒 | +| `last_login_check_at` | INTEGER | Unix 秒 | +| `last_error_code` | TEXT | 文本错误码 | +| `last_error_message` | TEXT | 错误说明 | +| `extra_json` | TEXT | 扩展 JSON(可含 `logged_in_url_pattern` / `logged_in_selector` 等 hook) | +| `auth_strategy` | TEXT NOT NULL | 九种策略之一(见 [`PLATFORMS.md`](PLATFORMS.md) / `db/auth_strategy.py`) | +| `session_persistent` | INTEGER NOT NULL DEFAULT 1 | `0`/`1` | +| `device_fingerprint` | TEXT | 仅 `device_bound` | | `created_at` | INTEGER | Unix 秒 UTC | | `updated_at` | INTEGER | Unix 秒 UTC | -**索引:** `idx_accounts_platform`, `idx_accounts_pick_web`, `idx_accounts_provider`, `idx_accounts_status` +**索引:** `idx_accounts_platform`,`idx_accounts_pick_web`,`idx_accounts_provider`,`idx_accounts_status` -**唯一约束说明:** `(platform_key, login_id, tenant_id, environment, role)` 通过应用层检查,因为 SQLite 允许多条 NULL login_id。 +--- -## 3. credentials — 凭据(API Key / Token / Password) - -**核心原则:SQLite 只保存元数据,不保存 secret 明文。** +## 3. `credentials` — 凭据(API Key / Token / Password …) | 字段 | 类型 | 说明 | -|------|------|------| +|---|---|---| | `id` | INTEGER PK AUTO | 主键 | -| `account_id` | INTEGER | 关联 accounts.id,可空(平台级 API Key) | -| `platform_key` | TEXT NOT NULL | 平台唯一键 | -| `credential_label` | TEXT NOT NULL | 人可读名称 | -| `credential_type` | TEXT | api_key / password / bearer_token / oauth_client / refresh_token / browser_profile / note | -| `secret_storage` | TEXT | none / env / windows_credential | -| `secret_ref` | TEXT | env 时为环境变量名;windows_credential 时为 target name | -| `secret_mask` | TEXT | 打码展示(sk-****abcd),不可反推 | -| `expires_at` | INTEGER | 过期时间 | -| `status` | TEXT | active / disabled / expired / needs_rotation | -| `last_used_at` | INTEGER | 最近使用时间 | -| `extra_json` | TEXT | 扩展字段 JSON | -| `created_at` | INTEGER | Unix 秒 UTC | -| `updated_at` | INTEGER | Unix 秒 UTC | +| `account_id` | INTEGER | 关联 `accounts.id`,可空(历史平台级 Key) | +| `platform_key` | TEXT NOT NULL | 平台键 | +| `credential_label` | TEXT NOT NULL | 展示名称 | +| `credential_type` | TEXT NOT NULL | `api_key` / `password` / … | +| `secret_storage` | TEXT NOT NULL | `none` / `env` / `windows_credential` / `local_encrypted` | +| `secret_ref` | TEXT | `env` 变量名 / `windows_credential` target;其它后端可为空 | +| `secret_mask` | TEXT | 打码展示 | +| `secret_ciphertext` | TEXT | `local_encrypted` 专用 Fernet token(v2.1 增加) | +| `expires_at` | INTEGER | Unix 秒 | +| `status` | TEXT | `active` / `disabled` / … | +| `last_used_at` | INTEGER | Unix 秒 | +| `extra_json` | TEXT | JSON | +| `created_at` | INTEGER | Unix 秒 | +| `updated_at` | INTEGER | Unix 秒 | -**索引:** `idx_credentials_platform_type`, `idx_credentials_account`, `idx_credentials_status` +**原则**:不把 **明文** secret 写入 SQLite;`local_encrypted` 仅存 **密文**。外部后端(env / Windows CM)仍在运行时解析。 -## 4. account_leases — RPA 并发锁 +**索引:** `idx_credentials_platform_type`,`idx_credentials_status`,**`idx_credentials_account`(`migrate_v1_to_v2` 中 `CREATE INDEX IF NOT EXISTS`)** -防止多个任务同时打开同一个 profile_dir。 +--- + +## 4. `account_leases` — RPA 并发锁 | 字段 | 类型 | 说明 | -|------|------|------| +|---|---|---| | `id` | INTEGER PK AUTO | 主键 | -| `account_id` | INTEGER NOT NULL | 关联 accounts.id | -| `lease_token` | TEXT UNIQUE | 随机 token,用于 release/heartbeat | +| `account_id` | INTEGER NOT NULL | `accounts.id` | +| `lease_token` | TEXT UNIQUE | 随机 token | | `holder` | TEXT NOT NULL | 持有者标识 | -| `purpose` | TEXT | 用途说明 | -| `expires_at` | INTEGER NOT NULL | 过期时间 | -| `heartbeat_at` | INTEGER | 最近心跳时间 | -| `released_at` | INTEGER | 主动释放时间 | -| `status` | TEXT | active / released / expired | -| `created_at` | INTEGER | Unix 秒 UTC | -| `updated_at` | INTEGER | Unix 秒 UTC | +| `purpose` | TEXT | 用途 | +| `expires_at` | INTEGER NOT NULL | Unix 秒 UTC | +| `heartbeat_at` | INTEGER | Unix 秒 | +| `released_at` | INTEGER | Unix 秒 | +| `status` | TEXT | `active` / `released` / `expired` | +| `created_at` | INTEGER | Unix 秒 | +| `updated_at` | INTEGER | Unix 秒 | -**索引:** `idx_leases_account_status`, `idx_leases_token`, `idx_leases_expires` +**索引:** `idx_leases_account_status`,`idx_leases_token`,`idx_leases_expires` -**约束:** -- acquire 前先把过期 active lease 标记为 expired -- 同一 account_id 同时只能有一个 active 未过期 lease -- release 必须校验 lease_token +--- + +## 5. `_schema_meta` — Schema 元数据 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `key` | TEXT PRIMARY KEY | 键名 | +| `value` | TEXT NOT NULL | 值 | + +**已知键** + +- `schema_version` — 逻辑 schema 版本字符串:`migrate_v1_to_v2` 完成后写入 `"2"`,`migrate_v2_to_v2_1` 完成后更新为 **`"2.1"`**。 + +--- ## Profile 文件布局 ``` {DATA_ROOT}/{USER_ID}/account-manager/ ├── account-manager.db +├── master.key # 若执行过 init-master-key(也可用环境变量代替) └── profiles/ └── {domain}/ └── {platform_key}/ └── {safe_label}[_{login_id_suffix}]/ ``` -删除账号时实现会尽量同时删除对应目录。 +删除账号时实现会尽量同步删除对应目录。 ## 与 JSON 输出的对应关系 -`account get/list/pick-web` 和 `credential pick` 的 JSON 输出字段与上表一一对应。详见 [INTEGRATION.md](INTEGRATION.md) 与 `assets/schemas/`。 +CLI JSON 字段与上表对应;若有封装层(`_ok_json`),详见 [`CLI.md`](CLI.md) / [`INTEGRATION.md`](INTEGRATION.md)。