From 7d0ec942106a96b4a68c627e0562e1ead2018af3 Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Thu, 1 Oct 2026 09:56:55 +0800 Subject: [PATCH] feat: take over host delete so profile dirs are removed --- CHANGELOG.md | 4 +++ SKILL.md | 29 +++++++++--------- assets/README.md | 2 +- assets/actions.json | 33 +++++++++++++++++++++ references/CLI.md | 4 +-- tests/test_account_delete.py | 54 ++++++++++++++++++++++++++++++++++ tests/test_row_open_actions.py | 20 +++++++++++++ 7 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 tests/test_account_delete.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e4b52de..6cbf2a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ 本文件对应技能市场 **「更新日志」** Tab。 发布 CI 会按当前 tag 版本提取匹配的 `##` 小节。 +## 2.0.31 + +- 数据管理「删除」改为技能接管(`replaces: delete`):删账号行和凭证的同时删除硬盘 Profile 目录,避免再建同名账号带回旧登录态 + ## 2.0.30 - 「打开浏览器」为本账号 Profile 附加稳定本机调试口(与科力普等采集技能约定同一端口算法),便于采集挂接已打开的原生窗口;仍使用 `--restore-last-session` 恢复登录态 diff --git a/SKILL.md b/SKILL.md index 8ca2b29..e075b62 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 账号与凭据管理 description: "通用账号 & 凭据 & 登录流程管理器。管理网页/RPA 账号、API Key/Token;支持 9 种登录策略(账密自动、扫码、2FA、SSO 等);提供加密本地存储(Fernet);提供 rpa_helpers 库供业务 skill 同进程调用 ensure_logged_in 完成多平台自动登录。涵盖物流、LLM、内容、开发、API 五类平台。" -version: 2.0.30 +version: 2.0.31 author: 深圳匠厂科技有限公司 metadata: openclaw: @@ -32,19 +32,20 @@ allowed-tools: 3. **登记网页/RPA 账号**:`account add-web` — 平台 + 账号名称 + 可选登录账号即可;环境/角色/登录策略跟平台默认。数据管理「新增」已绑定该能力(`replaces: create`),勿用宿主直写库新建账号。 4. **复制账号**:`account copy ` — 同平台新空 Profile,名称自动加「副本」,登录账号留空。数据管理行内「复制」一点即走。 5. **编辑账号**:`account update --id ` — 只改名称、登录账号、标识类型、入口链接、状态。数据管理「编辑」已绑定(`replaces: update`),不再摊开整张表。 -6. **登记凭据(API Key / Token / 加密密码)**:`account add-secret` — `none` / `env` / `windows_credential` / `local_encrypted`。 -7. **查看/列出账号**:`account get/list` — JSON 输出;list 可按 `--login-id` / `--label` 筛选。 -8. **用人话定位账号**:`account resolve --login-id <手机号/用户名>` 或 `--label` / `--query`(不必先查数字 id)。 -9. **为 RPA 挑选账号**:`account pick-web` — 按平台/环境/角色,也可 `--login-id` / `--label`;业务首启推荐 `account ensure-web`(或 `pick-web --ensure`):无账号则自动 `add-web` 再返回。 -10. **获取 API Key(凭据表)**:`credential pick` — 默认隐藏 secret,`--reveal` 时才返回。 -11. **打开浏览器**:`account open `(或兼容别名 `open `)— **本进程**直接拉起系统 Chrome/Edge(独立 `--user-data-dir`),最大化并跳过 Chrome 首次欢迎页,直接打开平台链接;与「打开目录」同为一点即走,不做登录判定。数据管理「打开目录 / 打开浏览器」成功不弹提示。 -12. **打开目录**:`account reveal-profile ` — 在系统文件管理器中打开该账号 `profile_dir`。 -13. **管理 lease**:`lease release/force-release/list/cleanup` — 防止 RPA 并发;数据管理「账号租约」行右键可强制释放。 -14. **初始化加密**:`account init-master-key` — 生成 Fernet master.key。 -15. **加密备份与迁移**:`account export-credentials --plaintext` / `account import-credentials` — 导出明文 JSON、在新环境重新加密写入。 -16. **按 lease 取明文密码**:`account get-credential --reveal --lease-token ` — 结合 pick-web 返回的 lease 解密 `local_encrypted` 等路径。 -17. **业务 skill 同进程登录**:`from service.rpa_helpers import ensure_logged_in` — 详见 [`references/RPA_HELPERS.md`](references/RPA_HELPERS.md)。 -18. **有则用、无则建(网页账号)**:`account ensure-web --platform

` — 登记账号 ≠ 已登录;登录仍由业务技能 / `ensure_logged_in` 处理。 +6. **删除账号**:`delete id ` — 删除账号行、关联凭证,以及硬盘上的 `profile_dir`。数据管理「删除」已绑定(`replaces: delete`),勿用宿主直写库,否则会留下旧 Profile / 登录态。 +7. **登记凭据(API Key / Token / 加密密码)**:`account add-secret` — `none` / `env` / `windows_credential` / `local_encrypted`。 +8. **查看/列出账号**:`account get/list` — JSON 输出;list 可按 `--login-id` / `--label` 筛选。 +9. **用人话定位账号**:`account resolve --login-id <手机号/用户名>` 或 `--label` / `--query`(不必先查数字 id)。 +10. **为 RPA 挑选账号**:`account pick-web` — 按平台/环境/角色,也可 `--login-id` / `--label`;业务首启推荐 `account ensure-web`(或 `pick-web --ensure`):无账号则自动 `add-web` 再返回。 +11. **获取 API Key(凭据表)**:`credential pick` — 默认隐藏 secret,`--reveal` 时才返回。 +12. **打开浏览器**:`account open `(或兼容别名 `open `)— **本进程**直接拉起系统 Chrome/Edge(独立 `--user-data-dir`),最大化并跳过 Chrome 首次欢迎页,直接打开平台链接;与「打开目录」同为一点即走,不做登录判定。数据管理「打开目录 / 打开浏览器」成功不弹提示。 +13. **打开目录**:`account reveal-profile ` — 在系统文件管理器中打开该账号 `profile_dir`。 +14. **管理 lease**:`lease release/force-release/list/cleanup` — 防止 RPA 并发;数据管理「账号租约」行右键可强制释放。 +15. **初始化加密**:`account init-master-key` — 生成 Fernet master.key。 +16. **加密备份与迁移**:`account export-credentials --plaintext` / `account import-credentials` — 导出明文 JSON、在新环境重新加密写入。 +17. **按 lease 取明文密码**:`account get-credential --reveal --lease-token ` — 结合 pick-web 返回的 lease 解密 `local_encrypted` 等路径。 +18. **业务 skill 同进程登录**:`from service.rpa_helpers import ensure_logged_in` — 详见 [`references/RPA_HELPERS.md`](references/RPA_HELPERS.md)。 +19. **有则用、无则建(网页账号)**:`account ensure-web --platform

` — 登记账号 ≠ 已登录;登录仍由业务技能 / `ensure_logged_in` 处理。 ## CLI 调用 diff --git a/assets/README.md b/assets/README.md index 7b54ec0..2cd74b9 100644 --- a/assets/README.md +++ b/assets/README.md @@ -1,6 +1,6 @@ # assets -- `actions.json`:匠厂宿主 Skill Action 清单(数据管理「账号信息」行内:打开目录 / 打开浏览器)。 +- `actions.json`:匠厂宿主 Skill Action 清单(数据管理「账号信息」:新增/编辑/删除接管,行内打开目录 / 打开浏览器 / 复制)。 - `examples/`:CLI 成功输出形状示例(虚构路径与数据)。 - `schemas/`:`get` / `list-json` / `pick-web` 单条对象的轻量 JSON Schema(`additionalProperties: true` 允许扩展字段)。 diff --git a/assets/actions.json b/assets/actions.json index 0ba994e..337f458 100644 --- a/assets/actions.json +++ b/assets/actions.json @@ -162,6 +162,39 @@ ] } }, + { + "id": "account-delete", + "label": "删除账号", + "description": "删除账号记录、关联凭证,以及硬盘上的浏览器 Profile 目录。再建同名账号会得到空 Profile,不会带回旧登录态。", + "replaces": "delete", + "placements": [], + "executionProfile": "sync", + "interaction": "confirm", + "confirmation": { + "message": "将删除这条账号、关联凭证,以及硬盘上的浏览器 Profile 目录。登录态不可恢复。是否继续?" + }, + "bind": { + "tables": ["accounts"], + "inputMapping": { "accountId": "$row.$pk" } + }, + "inputSchema": { + "type": "object", + "required": ["accountId"], + "properties": { + "accountId": { + "type": "number", + "title": "账号 ID", + "readOnly": true + } + } + }, + "steps": [{ "when": "afterSuccess", "do": "reloadTable" }], + "entrypoint": { + "type": "cli", + "command": "delete", + "args": ["id", "{{accountId}}"] + } + }, { "id": "account-copy", "label": "复制", diff --git a/references/CLI.md b/references/CLI.md index 8784043..45cb4c7 100644 --- a/references/CLI.md +++ b/references/CLI.md @@ -91,7 +91,7 @@ | `pick-web ` | `account pick-web --platform `(无 lease 参数) | | `list [platform\|all]` | 人类可读列表 | | `add ` | 映射到 `account add-web`(`login_id_type=phone`),不传 v2 扩展参数 | -| `delete id\|platform <...>` | 删除账号 | +| `delete id\|platform <...>` | 删除账号(含硬盘 `profile_dir`;数据管理「删除」走 `delete id`) | --- @@ -122,7 +122,7 @@ python main.py account update --id 4 --label "抖音 备用" --login-id 13900001 - `copy`:复制平台、环境、角色、入口链接、登录策略;**新建空 Profile**;登录账号留空;名称自动「副本」/「副本 2」。不复制凭证、设备指纹、会话状态。 - `update`:只改名称、登录账号、标识类型、入口链接、状态。空入口链接回退为平台默认 URL。 -- 数据管理「新增」只填平台 / 账号名称 / 登录账号;「编辑」走 `replaces: update` 短表单;行内「复制」为 `interaction: direct`。 +- 数据管理「新增」只填平台 / 账号名称 / 登录账号;「编辑」走 `replaces: update` 短表单;「删除」走 `replaces: delete`(`delete id `,含硬盘 Profile);行内「复制」为 `interaction: direct`。 --- diff --git a/tests/test_account_delete.py b/tests/test_account_delete.py new file mode 100644 index 0000000..5146685 --- /dev/null +++ b/tests/test_account_delete.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +"""delete id 会删库记录和硬盘 Profile,避免假删除残留登录态。""" +from __future__ import annotations + +import os + +from db.accounts_repo import find_accounts, get_account_by_id + + +def _add_web(**kwargs): + from service.account_service import cmd_account_add_web + + defaults = dict( + platform_input="maersk", + login_id="demo@maersk.com", + login_id_type="email", + label="Maersk delete test", + tenant_id="tenant-demo", + environment="simulator", + role="booking", + provider_code=None, + url=None, + extra_json_str=None, + profile_dir_override=None, + ) + defaults.update(kwargs) + cmd_account_add_web(**defaults) + + +def test_delete_by_id_removes_row_and_profile(clean_db, fake_env): + from service.account_service import cmd_delete_by_id + + _add_web() + acc = find_accounts(platform_key="maersk")[0] + profile_dir = acc["profile_dir"] + marker = os.path.join(profile_dir, "Default", "Cookies") + os.makedirs(os.path.dirname(marker), exist_ok=True) + with open(marker, "w", encoding="utf-8") as handle: + handle.write("session") + assert os.path.isfile(marker) + + cmd_delete_by_id(acc["id"]) + + assert get_account_by_id(acc["id"]) is None + assert find_accounts(platform_key="maersk") == [] + assert not os.path.exists(profile_dir) + + +def test_delete_by_id_missing_account(clean_db, capsys): + from service.account_service import cmd_delete_by_id + + cmd_delete_by_id(99999) + out = capsys.readouterr().out + assert "ERROR:ACCOUNT_NOT_FOUND" in out diff --git a/tests/test_row_open_actions.py b/tests/test_row_open_actions.py index 4562ccf..c780569 100644 --- a/tests/test_row_open_actions.py +++ b/tests/test_row_open_actions.py @@ -72,6 +72,26 @@ class TestRowOpenActions(unittest.TestCase): self.assertIn("enum", props["login_id_type"]) self.assertIn("enum", props["status"]) + def test_delete_takes_over_host_delete(self) -> None: + delete = _by_id(_load_actions(), "account-delete") + self.assertEqual(delete.get("replaces"), "delete") + self.assertEqual(delete.get("placements") or [], []) + self.assertEqual(delete.get("interaction"), "confirm") + self.assertEqual(delete.get("label"), "删除账号") + bind = delete.get("bind") or {} + self.assertEqual(bind.get("tables"), ["accounts"]) + self.assertEqual(bind.get("inputMapping"), {"accountId": "$row.$pk"}) + self.assertEqual( + (delete.get("entrypoint") or {}).get("command"), + "delete", + ) + self.assertEqual( + (delete.get("entrypoint") or {}).get("args"), + ["id", "{{accountId}}"], + ) + steps = delete.get("steps") or [] + self.assertTrue(any(s.get("do") == "reloadTable" for s in steps)) + def test_copy_is_direct_row_action(self) -> None: copy = _by_id(_load_actions(), "account-copy") self.assertEqual(copy.get("label"), "复制")