docs: update SCHEMA.md / ERRORS.md / PLATFORMS.md / RUNTIME.md for v2

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-11 18:34:49 +08:00
parent 1792ba2a0d
commit dadf89af3d
4 changed files with 302 additions and 136 deletions

View File

@@ -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:` 文本。

View File

@@ -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 <key>`
---
## 内置默认 `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)

View File

@@ -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)。

View File

@@ -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 tokenv2.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)。