From 718aeefd44a81435051d9c9bf8e980b4a3a19e08 Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Sat, 22 Aug 2026 13:15:26 +0800 Subject: [PATCH] fix: unify row open labels and silent success MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename 打开文件位置 to 打开目录, and print empty SAS message so the host skips 执行成功 after opening Explorer or the browser. Co-authored-by: Cursor --- SKILL.md | 6 ++-- assets/README.md | 2 +- assets/actions.json | 4 +-- references/CLI.md | 4 +-- scripts/service/browser_service.py | 8 +++++ tests/test_browser_open.py | 3 ++ tests/test_reveal_profile.py | 3 ++ tests/test_row_open_actions.py | 50 ++++++++++++++++++++++++++++++ 8 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 tests/test_row_open_actions.py diff --git a/SKILL.md b/SKILL.md index 7701e4e..8b93238 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.20 +version: 2.0.21 author: 深圳匠厂科技有限公司 metadata: openclaw: @@ -34,8 +34,8 @@ allowed-tools: 5. **用人话定位账号**:`account resolve --login-id <手机号/用户名>` 或 `--label` / `--query`(不必先查数字 id)。 6. **为 RPA 挑选账号**:`account pick-web` — 按平台/环境/角色,也可 `--login-id` / `--label`;业务首启推荐 `account ensure-web`(或 `pick-web --ensure`):无账号则自动 `add-web` 再返回。 7. **获取 API Key(凭据表)**:`credential pick` — 默认隐藏 secret,`--reveal` 时才返回。 -8. **浏览器打开查看**:`account open `(或兼容别名 `open `)— **本进程**直接拉起系统 Chrome/Edge(`--user-data-dir`),与「打开文件位置」同模式;不做登录判定。数据管理行内按钮均为 `interaction: direct` + `sync`。 -9. **打开 Profile 目录**:`account reveal-profile ` — 在系统文件管理器中打开该账号 `profile_dir`。 +8. **打开浏览器**:`account open `(或兼容别名 `open `)— **本进程**直接拉起系统 Chrome/Edge(`--user-data-dir`),与「打开目录」同模式;不做登录判定。数据管理行内按钮均为 `interaction: direct` + `sync`,成功不弹提示。 +9. **打开目录**:`account reveal-profile ` — 在系统文件管理器中打开该账号 `profile_dir`。 10. **管理 lease**:`lease release/list/cleanup` — 防止 RPA 并发。 11. **初始化加密**:`account init-master-key` — 生成 Fernet master.key。 12. **加密备份与迁移**:`account export-credentials --plaintext` / `account import-credentials` — 导出明文 JSON、在新环境重新加密写入。 diff --git a/assets/README.md b/assets/README.md index b50a978..7b54ec0 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 5ae23a4..6667fa4 100644 --- a/assets/actions.json +++ b/assets/actions.json @@ -78,7 +78,7 @@ }, { "id": "reveal-profile", - "label": "打开文件位置", + "label": "打开目录", "description": "在系统文件管理器中打开该账号的浏览器 Profile 目录。", "placements": ["row"], "executionProfile": "sync", @@ -108,7 +108,7 @@ { "id": "open-browser", "label": "打开浏览器", - "description": "用该账号持久化 Profile 直接打开系统 Chrome/Edge,仅供肉眼核对(不做登录判定)。与打开文件位置同为一点即开。", + "description": "用该账号持久化 Profile 直接打开系统 Chrome/Edge,仅供肉眼核对(不做登录判定)。与打开目录同为一点即开。", "placements": ["row"], "executionProfile": "sync", "interaction": "direct", diff --git a/references/CLI.md b/references/CLI.md index 8f679e7..b7437f3 100644 --- a/references/CLI.md +++ b/references/CLI.md @@ -75,8 +75,8 @@ | `lease release ` | 释放租约 | | `lease list [--active]` | 列出租约 | | `lease cleanup` | 清理过期租约 | -| `account open ` / `open ` | 浏览器查看 | -| `account reveal-profile ` | 打开 Profile 目录 | +| `account open ` / `open ` | 打开浏览器 | +| `account reveal-profile ` | 打开目录 | ### 兼容入口(旧版别名) diff --git a/scripts/service/browser_service.py b/scripts/service/browser_service.py index 09868d1..7868186 100644 --- a/scripts/service/browser_service.py +++ b/scripts/service/browser_service.py @@ -147,6 +147,10 @@ def cmd_reveal_profile(account_id): print( json.dumps( { + "type": "result", + "schema_version": 1, + "status": "success", + "message": "", "success": True, "data": { "account_id": int(target["id"]) if target.get("id") is not None else account_id, @@ -245,6 +249,10 @@ def cmd_open(account_id): print( json.dumps( { + "type": "result", + "schema_version": 1, + "status": "success", + "message": "", "success": True, "data": { "account_id": int(target["id"]) if target.get("id") is not None else account_id, diff --git a/tests/test_browser_open.py b/tests/test_browser_open.py index a5fd785..267ab5f 100644 --- a/tests/test_browser_open.py +++ b/tests/test_browser_open.py @@ -70,6 +70,9 @@ class TestCmdOpenDirectBrowser: payload = json.loads(out.splitlines()[-1]) assert payload["success"] is True + assert payload["message"] == "" + assert payload["type"] == "result" + assert payload["status"] == "success" assert payload["data"]["pid"] == 5252 assert payload["data"]["browser_exe"] == fake_exe assert spawned["cmd"][0] == fake_exe diff --git a/tests/test_reveal_profile.py b/tests/test_reveal_profile.py index 470e5a7..f886f5d 100644 --- a/tests/test_reveal_profile.py +++ b/tests/test_reveal_profile.py @@ -54,6 +54,9 @@ def test_reveal_profile_opens_dir(clean_db, tmp_path, capsys): out = capsys.readouterr().out.strip() payload = json.loads(out.splitlines()[-1]) assert payload["success"] is True + assert payload["message"] == "" + assert payload["type"] == "result" + assert payload["status"] == "success" assert payload["data"]["account_id"] == aid assert payload["data"]["profile_dir"] == called_path diff --git a/tests/test_row_open_actions.py b/tests/test_row_open_actions.py new file mode 100644 index 0000000..a1f8811 --- /dev/null +++ b/tests/test_row_open_actions.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +"""数据管理行内打开类:label 对齐模板,不与宿主 CRUD 重复。""" +from __future__ import annotations + +import json +import os +import unittest + + +def _skill_root() -> str: + return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +def _load_actions() -> list[dict]: + path = os.path.join(_skill_root(), "assets", "actions.json") + with open(path, encoding="utf-8") as f: + manifest = json.load(f) + return list(manifest.get("actions") or []) + + +def _by_id(actions: list[dict], action_id: str) -> dict: + for item in actions: + if item.get("id") == action_id: + return item + raise AssertionError(f"missing action {action_id}") + + +class TestRowOpenActions(unittest.TestCase): + def test_reveal_and_open_use_template_labels(self) -> None: + actions = _load_actions() + reveal = _by_id(actions, "reveal-profile") + open_browser = _by_id(actions, "open-browser") + self.assertEqual(reveal.get("label"), "打开目录") + self.assertEqual(open_browser.get("label"), "打开浏览器") + self.assertEqual(reveal.get("placements"), ["row"]) + self.assertEqual(open_browser.get("placements"), ["row"]) + self.assertEqual(reveal.get("interaction"), "direct") + self.assertEqual(open_browser.get("interaction"), "direct") + self.assertFalse(reveal.get("replaces")) + self.assertFalse(open_browser.get("replaces")) + + def test_create_takes_over_host_add_without_row_duplicate(self) -> None: + create = _by_id(_load_actions(), "account-add-web") + self.assertEqual(create.get("replaces"), "create") + self.assertEqual(create.get("placements") or [], []) + self.assertEqual(create.get("label"), "新增账号") + + +if __name__ == "__main__": + unittest.main()