Chào Tiến, anh Phong đây. Anh đã xem qua mã nguồn của em về hệ thống Handoff Automation. Nhìn chung là có ý tưởng hay và đã đi được một bước khá xa, tuy nhiên, vẫn còn một số điểm cần mài giũa để hệ thống hoạt động ổn định và chuyên nghiệp hơn. Đây là báo cáo đánh giá chi tiết: --- ### **BÁO CÁO ĐÁNH GIÁ MÃ NGUỒN & LUỒNG LOGIC HỆ THỐNG HANDOFF AUTOMATION** **Người đánh giá:** Anh Phong (Tech Lead) **Người thực hiện:** Tiến **Ngày:** 11/07/2026 **Mã nguồn được xem xét:** * `/root/.hermes/scripts/auto_accept_worker.py` (phiên bản cũ, có thể đã deprecated) * `/opt/ai-os/products/ceo/lib/auto_accept_worker.py` (phiên bản hiện tại của worker) * `/opt/ai-os/core/plugin/ai-os/handoff.py` (Plugin Telegram) * Các file liên quan khác (`tg_topics.py`, `whereami.py`, `topic_handoff.py`, `auto_watcher.py`, `auto_trigger_verify.py`, `handoff_auto_listener.py`) --- #### **1. Tổng quan Hệ thống** Hệ thống Handoff Automation được thiết kế để tự động hóa quy trình bàn giao công việc giữa các phòng ban. Luồng chính hoạt động như sau: * **Telegram Plugin (`handoff.py`):** Nhận lệnh `/handoff`, `/block`, `/done`, `/accept` từ người dùng, xử lý logic cơ bản và tương tác với Lark Base. * **Worker (`auto_accept_worker.py`):** Chạy định kỳ (cronjob), đọc các task ở trạng thái "Handed off" từ Lark Base, tự động chấp nhận, chạy xử lý (qua NotebookLM CLI), cập nhật kết quả và trạng thái lên Lark Base, đồng thời gửi thông báo lên Telegram. * **Các module phụ trợ:** `tg_topics.py` (quản lý topic Telegram), `whereami.py` (xác định phòng ban), `topic_handoff.py` (CLI gửi handoff), `auto_watcher.py` (kích hoạt worker), `auto_trigger_verify.py` (tự động verify), `handoff_auto_listener.py` (nghe phản hồi trên Telegram). #### **2. Phân tích Mã nguồn & Luồng Logic** **2.1. `auto_accept_worker.py` (phiên bản `/opt/ai-os/products/ceo/lib/`)** * **Ưu điểm:** * Tích hợp tốt với Lark Base để lấy và cập nhật task. * Sử dụng `notebooklm-mcp-cli` để chạy verify task. * Thông báo rõ ràng lên Telegram ở các bước Accept, Report. * Sử dụng `timezone.utc` để đảm bảo tính nhất quán về thời gian. * Có cơ chế fallback cho `DEPARTMENTS`. * **Điểm yếu & Khuyến nghị:** 1. **Xử lý NotebookLM:** * **Vấn đề:** Logic `run_nlm_describe` hiện tại chỉ gọi CLI mà chưa có cơ chế tự động refresh token hay xử lý lỗi auth hiệu quả như phiên bản cũ (lấy notebook list có tự auth lại). Phiên bản mới này `real_result` có thể trả về lỗi auth mà worker không xử lý được, dẫn đến trạng thái "Reported" sai. * **Khuyến nghị:** Tích hợp lại cơ chế tự động re-authenticate cho NotebookLM CLI (tương tự như trong `auto_accept_worker.py` cũ hoặc module `nlm_describe_uuid` cũ) ngay trong hàm `run_nlm_describe`. Nếu sau khi re-auth vẫn lỗi thì mới báo Blocked/Reported lỗi. * **Vấn đề:** Kết quả `real_result` được gán trực tiếp vào `Output thực tế` và `Note` khi status là "Reported". Nếu `real_result` là lỗi dài, nó sẽ làm tràn field trên Lark Base. * **Khuyến nghị:** Cần truncate (cắt bớt) `real_result` khi cập nhật lên Lark Base (`Output thực tế`) hoặc bổ sung thêm trường `Error Details` riêng biệt nếu `real_result` chứa thông báo lỗi. Hiện tại, `Output thực tế` đang bị truncate có điều kiện ở bản cũ (`[:500]`), bản mới đã bỏ đi. Nên giữ lại logic truncate này. 2. **Xử lý Input/Output:** * **Vấn đề:** Task ID và `from_dept` trên Lark Base đang bị map sai. `task_id` từ `get_lark_tasks_by_status` đang lấy giá trị từ cột "Task ID", nhưng trong code lại gán vào `task_desc` (`fields.get("Task ID")`). Tương tự, `from_dept` đang lấy từ "Phòng gửi", nhưng trên Lark Base `get_lark_tasks_by_status` lại lấy "Phòng gửi" là "Phòng gửi", còn `task_id` lại lấy từ "Task ID" - có vẻ đang bị nhầm lẫn hoặc thiếu mapping. * **Vấn đề:** Trường `Input` trong `task` dict mà worker lấy từ Lark Base đang bị map sai từ cột `Note` (`\"input\": fields.get(\"Note\")`). Điều này là không đúng với ý định ban đầu là `Input` chứa notebook name. * **Khuyến nghị:** Rà soát lại mapping giữa các cột trên Lark Base và các key trong dict `rec` mà hàm `get_lark_tasks_by_status` trả về. Đảm bảo `task_id`, `from_dept`, `input`, `output_desc` được lấy đúng từ các cột tương ứng trên Lark Base. Cụ thể: `task_id` phải là ID duy nhất, `input` nên là Notebook name hoặc hint, `output_desc` là mong muốn. 3. **Trạng thái & Thông báo:** * **Vấn đề:** Worker chỉ xử lý các task ở trạng thái "Handed off". Tuy nhiên, luồng logic hệ thống lại có cả trạng thái "Ready for Handoff" (trong `AUTO_PROCESS_STATUSES` của bản cũ) và "Accepted", "Blocked", "Reported", "Done". Worker hiện tại chỉ cập nhật lên "Reported". * **Khuyến nghị:** * Cần định nghĩa rõ ràng luồng trạng thái (status flow) trên Lark Base và trong code. Ví dụ: `Handed off` (từ plugin) -> `Accepted` (worker accept) -> `Processing` (worker đang chạy verify) -> `Reported` (worker xong verify, có kết quả) HOẶC `Blocked` (worker gặp lỗi). * Cần cập nhật trạng thái tương ứng lên Lark Base sau khi worker chạy xong. Hiện tại chỉ có `Reported` hoặc lỗi trong `real_result` không làm thay đổi status. * Nên xem xét lại mục đích của `AUTO_PROCESS_STATUSES` trong bản cũ. Nếu worker chỉ quét "Handed off", thì list này không còn ý nghĩa. 4. **Xử lý lỗi:** * **Vấn đề:** Khi `run_nlm_describe` gặp lỗi (trừ timeout), worker vẫn cố gắng cập nhật Lark Base với status "Reported" và `real_result` chứa message lỗi. Điều này có thể gây nhầm lẫn. * **Khuyến nghị:** Nếu `run_nlm_describe` trả về lỗi (bắt đầu bằng "⚠️" hoặc "❌"), worker nên cập nhật trạng thái Task trên Lark Base thành "Blocked" và thêm chi tiết lỗi vào `Note`. Thông báo trên Telegram cũng cần rõ ràng là "LỖI THỰC THI THẤT BẠI" thay vì "BÁO CÁO HOÀN TẤT". (Lưu ý: Phiên bản cũ có logic này, phiên bản mới đã lược bỏ). 5. **Cấu trúc đường dẫn:** * **Vấn đề:** Worker bản mới (`/opt/ai-os/products/ceo/lib/auto_accept_worker.py`) hardcode đường dẫn tới NLM CLI và các thư viện phụ trợ. * **Khuyến nghị:** Sử dụng `sys.path.insert` hoặc các biến môi trường linh hoạt hơn, giống như cách `auto_accept_worker.py` bản cũ đã làm. Cần đảm bảo các đường dẫn này là chính xác và không phụ thuộc cứng vào cấu trúc thư mục hiện tại, ví dụ như dùng `Path(__file__).resolve().parent` để trỏ về thư mục lib của chính nó. 6. **Xử lý `DEPARTMENTS`:** * **Vấn đề:** `DEPARTMENTS` chỉ chứa 3 phòng ban (`policy-lab`, `research-inno`, `it-ai`), trong khi plugin `handoff.py` lại định nghĩa 5 phòng ban (`policy-lab`, `research-inno`, `it-ai`, `marketing`, `grill-brainstorm`). * **Khuyến nghị:** Đồng bộ `DEPARTMENTS` giữa `auto_accept_worker.py` và `handoff.py` (hoặc các module khác cùng sử dụng). 7. **Bảo mật (Tiềm ẩn):** * **Vấn đề:** Token Telegram và Lark Base được hardcode trực tiếp trong file. * **Khuyến nghị:** Nên đọc các token này từ biến môi trường hoặc file cấu hình an toàn (ví dụ `.env`), giống như cách `tg_topics.py` đang làm (`load_token`). **2.2. `handoff.py` (Plugin Telegram)** * **Ưu điểm:** * Xử lý tốt các lệnh `/handoff`, `/block`, `/done`, `/accept`. * Sử dụng `lark_api` để tương tác với Lark Base. * Có cơ chế `parse_args_after_command` để tách lệnh và tham số. * Xử lý tách input/output từ lệnh `/handoff` khá thông minh (dựa vào `→` hoặc `->`). * Có `get_sender_dept` dựa vào `whereami.py`. * Phân loại phòng nhận, gửi và gửi thông báo tới các phòng liên quan. * **Điểm yếu & Khuyến nghị:** 1. **Hardcode Token & ID:** * **Vấn đề:** `BASE_TOKEN`, `TABLE_ID`, `CHAT_ID` và `MAPPING` (tương đương `DEPARTMENTS` ở worker) đang hardcode trực tiếp trong file. * **Khuyến nghị:** Tương tự như worker, nên đọc các thông tin này từ biến môi trường hoặc file cấu hình để dễ quản lý và bảo mật. Đặc biệt là `BASE_TOKEN` của Lark. 2. **Sử dụng `lark_api`:** * **Vấn đề:** Hàm `lark_api` hiện tại chỉ bao bọc `subprocess.run` và log lỗi. Nếu `lark-cli` không tồn tại hoặc có vấn đề, khó debug. * **Khuyến nghị:** Cần xử lý lỗi chặt chẽ hơn. Nếu `lark-cli` không tìm thấy, nên báo lỗi rõ ràng. Khi chạy `subprocess.run`, nên bắt các exception cụ thể (ví dụ `FileNotFoundError` nếu `lark-cli` không có). 3. **Xử lý `Task ID`:** * **Vấn đề:** Lệnh `/block`, `/done`, `/accept` đều tìm kiếm task trên Lark Base bằng `Task ID` và strip bỏ tiền tố "HO-". Tuy nhiên, logic tạo `task_id` trong worker lại là `f\"HO-{datetime.now().strftime(\'%Y%m%d-%H%M%S\')}\"`. Điều này có thể gây xung đột hoặc khó khăn khi tìm kiếm nếu tiền tố "HO-" bị strip ở một nơi mà không strip ở nơi khác. * **Khuyến nghị:** Nên thống nhất cách format `Task ID`. Hoặc là luôn dùng `HO-YYYYMMDD-HHMMSS` và strip bỏ ở tất cả các lệnh tìm kiếm/update, hoặc là không dùng tiền tố "HO-" nữa mà chỉ dùng ID duy nhất. Quan trọng là sự nhất quán. Hiện tại, hàm `find_lark_record` có comment `Loại bỏ kiểm tra HO- để khớp với chuẩn Task ID hiện tại` - cần làm rõ "chuẩn hiện tại" là gì và áp dụng đồng bộ. 4. **Xử lý Input/Output trong `/handoff`:** * **Vấn đề:** Logic tách `inp` và `out` dựa trên `→` hoặc `->` là tốt, nhưng việc gán `inp = body` nếu không có `→` có thể chưa đúng ý. * **Khuyến nghị:** Nếu không tách được `input` và `output`, thì `task_desc` nên là toàn bộ `body`, và `input`/`output` nên để trống hoặc có giá trị mặc định rõ ràng. Hiện tại, `inp` đang được gán là `body` khi không tách được, và `out` là trống. Nếu `body` chứa mô tả task, thì `inp` lại bị gán là mô tả task thay vì là input thực sự. * **Vấn đề:** Việc ưu tiên link URL cho `inp` và loại bỏ khỏi `task_desc` là tốt, nhưng cần làm rõ logic này ảnh hưởng đến `output_desc` như thế nào. 5. **Thông báo Blocked/Done:** * **Vấn đề:** Khi `/block` hoặc `/done`, thông báo gửi đi Telegram chỉ có `Phòng nhận` và `Phòng gửi`. Nếu `Phòng gửi` không có trong `MAPPING`, nó sẽ mặc định là `IT & AI`. * **Khuyến nghị:** Cần có cơ chế fallback mạnh mẽ hơn hoặc thông báo lỗi rõ ràng nếu không xác định được `Phòng gửi` hoặc `Phòng nhận` chính xác từ Lark Base. Hiện tại, nó vẫn gửi vào `IT & AI`. 6. **Lệnh `/accept`:** * **Vấn đề:** Lệnh `/accept` chỉ thay đổi status trên Lark Base thành "Accepted" và gửi thông báo. Nó không kích hoạt worker xử lý tiếp. Worker chỉ tự động chạy cho task ở trạng thái "Handed off". * **Khuyến nghị:** Cần làm rõ vai trò của `/accept`. Nếu người dùng nhấn `/accept`, có nghĩa là họ đã nhận thủ công. Hệ thống nên ghi nhận lại trạng thái này và có thể cần một cơ chế để worker xử lý các task đã được `Accepted` thủ công này, hoặc thông báo cho người dùng biết rằng worker chỉ xử lý task `Handed off`. Hoặc đơn giản là `/accept` chỉ nên là hành động thông báo trạng thái, còn việc trigger worker sẽ do cronjob định kỳ. 7. **Sử dụng `sys.path.insert`:** * **Vấn đề:** Plugin sử dụng `sys.path.insert` để import các module như `tg_topics.py`, `whereami.py`, `topic_handoff.py`. Điều này có thể gây ra các vấn đề về dependency nếu cấu trúc thư mục thay đổi hoặc có nhiều version của cùng một module. * **Khuyến nghị:** Nên sử dụng các cách quản lý module Python hiện đại hơn, ví dụ: tạo package cho các module dùng chung, hoặc đảm bảo môi trường Python (venv) được cấu hình đúng để tìm thấy các module này mà không cần chỉnh sửa `sys.path` thủ công. Tuy nhiên, với kịch bản hiện tại, việc này có thể chấp nhận được nếu cấu trúc thư mục được cố định. **2.3. Các module phụ trợ** * **`tg_topics.py`:** * **Ưu điểm:** Hỗ trợ tốt cho việc tạo/quản lý topic trên Telegram, đọc token từ `.env`. * **Điểm yếu:** Hàm `list` không thực sự liệt kê topic mà chỉ dùng để kiểm quyền. Việc tạo topic có hardcode màu icon, cần cơ chế linh hoạt hơn nếu muốn tùy chỉnh icon emoji custom. * **Khuyến nghị:** Sử dụng `getForumTopicIconStickers` để lấy danh sách icon và `custom_emoji_id` để tạo topic với icon tuỳ chọn, thay vì chỉ `icon_color`. * **`whereami.py`:** * **Ưu điểm:** Xác định phòng ban dựa trên `sessions.json`, tránh lỗi từ env. * **Điểm yếu:** Logic `_latest_group` có thể bị ảnh hưởng nếu `sessions.json` không được cập nhật đúng cách. * **Khuyến nghị:** Đảm bảo `sessions.json` luôn phản ánh đúng phiên làm việc hiện tại. * **`topic_handoff.py`:** * **Ưu điểm:** Là CLI riêng để gửi handoff, tích hợp với Lark Base và Telegram. * **Điểm yếu:** Cũng hardcode token/ID và có thể cải thiện xử lý lỗi khi gọi `lark_cli`. Logic `do_handoff` gọi worker bằng `subprocess.Popen` — nên có cách quản lý process tốt hơn. * **Khuyến nghị:** Đọc token từ env. Sử dụng `process.run` thay vì `os.system` để kiểm soát tốt hơn. * **`auto_watcher.py` & `auto_trigger_verify.py` & `handoff_auto_listener.py`:** * **Vấn đề:** Có vẻ như các module này đang hoạt động độc lập hoặc có sự chồng chéo về logic. Ví dụ: `auto_accept_worker.py` tự chạy cronjob, nhưng lại có `auto_watcher.py` để "trigger_auto_handoff". `auto_trigger_verify.py` lại có vẻ làm nhiệm vụ tương tự worker là trigger verify. `handoff_auto_listener.py` lại nghe phản hồi trên Telegram. * **Khuyến nghị:** Cần làm rõ trách nhiệm của từng script. * Worker (`auto_accept_worker.py`) nên là tâm điểm: đọc Lark Base, trigger xử lý, cập nhật Lark Base và Telegram. * `auto_watcher.py`: Có thể gộp logic vào worker hoặc loại bỏ nếu worker đã chạy định kỳ. * `auto_trigger_verify.py`: Logic này nên được tích hợp thẳng vào worker (`auto_accept_worker.py`) sau khi nó nhận task, thay vì chạy 1 script riêng biệt. Hoặc nếu nó dùng để "verify" lệnh `/handoff_verify` từ người dùng, thì cần làm rõ mục đích. * `handoff_auto_listener.py`: Có vẻ nó nghe phản hồi trên Telegram cho các handoff đã gửi. Logic này có thể được tích hợp vào plugin `handoff.py` (nếu plugin có thể chạy nền) hoặc làm thành 1 dịch vụ lắng nghe Telegram riêng biệt và cập nhật trạng thái task (ví dụ: ghi vào file state hoặc gọi API backend). Nếu worker xử lý toàn bộ luồng, thì listener này có thể thừa. **3. Đề xuất Cải tiến Tổng thể** 1. **Tập trung hóa Logic:** Gom các chức năng liên quan vào một module hoặc một class để dễ quản lý và tránh trùng lặp. Ví dụ, toàn bộ logic xử lý task, giao tiếp với Lark Base, Telegram và NotebookLM nên nằm gọn trong `auto_accept_worker.py`. 2. **Quản lý Cấu hình:** Đưa tất cả các token, ID, chat ID, Lark Base token/table ID vào file cấu hình hoặc biến môi trường thay vì hardcode. 3. **Luồng Trạng Thái (Status Flow):** Định nghĩa rõ ràng luồng trạng thái của một task trên Lark Base và trong code (ví dụ: `Handed off` -> `Accepted` -> `Processing` -> `Reported` / `Blocked`). Đảm bảo worker cập nhật chính xác các trạng thái này. 4. **Xử lý Lỗi Mạnh Mẽ:** Cải thiện cơ chế bắt và xử lý lỗi ở mọi khâu: API Telegram, API Lark Base, NotebookLM CLI, các lệnh shell. Đảm bảo hệ thống báo lỗi rõ ràng và chuyển sang trạng thái phù hợp (ví dụ: Blocked). 5. **Xử lý Worker & Cronjob:** * Worker chỉ nên làm 1 việc: kiểm tra Lark Base, xử lý các task đủ điều kiện, và cập nhật kết quả. * Cronjob chỉ nên có nhiệm vụ *gọi* worker này định kỳ. * Loại bỏ các script độc lập chỉ để "trigger" worker hoặc "verify", trừ khi chúng có vai trò đặc biệt và rõ ràng. 6. **Tách biệt Trách nhiệm:** * **Plugin (`handoff.py`):** Chỉ chịu trách nhiệm nhận lệnh từ người dùng (Telegram), xử lý logic cơ bản, validation đầu vào, và tạo task ban đầu trên Lark Base. Plugin không nên tự kích hoạt worker hay xử lý logic verify phức. * **Worker (`auto_accept_worker.py`):** Chạy nền (cronjob), chịu trách nhiệm lấy task từ Lark Base, thực thi tự động (NotebookLM), cập nhật kết quả và trạng thái lên Lark Base/Telegram. * **Các script lắng nghe/theo dõi:** Nên có vai trò rõ ràng, ví dụ `handoff_auto_listener.py` có thể chỉ để ghi nhận phản hồi thủ công từ người dùng trên Telegram và cập nhật trạng thái task (vd: `/accept`, `/block` do người dùng gõ). --- Tiến xem kỹ các điểm anh Phong góp ý nhé. Có gì khó hiểu hay cần làm rõ thêm, cứ hỏi anh ngay. Cần làm gọn lại để hệ thống chạy mượt mà, dễ bảo trì hơn.