Conversation
新增主動推送通知功能的完整 openspec artifacts: - proposal:功能範圍定義(Line 預設關閉,Telegram 預設開啟) - design:推送架構設計(caller_context、內部端點、推送服務) - specs:6 個 capability specs(bot-proactive-push 新增 + line-bot/bot-platform/research-skill/media-downloader/media-transcription 修改) - tasks:10 個任務群組共 34 項實作任務 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tasks 5-10: 完成 media-downloader、media-transcription caller_context 整合
- 修正 internal_push.py 欄位名稱(transcript_preview)及群組驗證邏輯
- 更新 build_system_prompt() 提供 caller_context JSON 範本給 AI
- 群組對話加入 platform_group_id(LINE/Telegram 外部 ID)與 platform_user_id
- 非同步任務規則說明附帶 caller_context 的要求
- 後端 API 擴充:GET/PUT /api/admin/bot-settings/{platform} 支援 proactive_push_enabled
- 前端設定介面:Line Bot 與 Telegram Bot 各新增主動推送切換開關
- 更新測試:涵蓋新功能的 mock 與 assertion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AutoCAD 匯出的 PDF 含格式瑕疵(重複 /PageMode 鍵值),RICOH 印表機的 PDF 解譯器會拒絕並導致 processing-to-stop-point 而無法列印。 改以 pdf2ps 轉換為 PostScript 後送印,繞過 PDF 解譯器改走 PS 解譯器, 實測可正常列印。轉換失敗時 fallback 使用原始 PDF 路徑。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tasks 1-4(漏提交): - migrations/014: bot_settings 新增 proactive_push_enabled 預設值 - services/proactive_push_service.py: 推送服務核心實作 - modules.py: 註冊 internal_push router Bug 修正(本次發現): - start-research.py: Claude webtools 成功路徑遺漏 _trigger_proactive_push() 呼叫 - start-research.py: _do_research() status_data 未保存 caller_context,導致推送時讀不到目標對象 - start-research.py: _do_research_local_pipeline() 亦未從前一狀態保留 caller_context Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取請求引入了 Bot 主動推送通知功能,顯著提升了使用者體驗。過去,使用者必須手動查詢長時間運行的背景任務(如研究、影片下載、語音轉錄)的結果。現在,系統能夠在任務完成後自動發送通知,減少了等待時間和操作步驟。這項功能透過在後端新增推送服務、擴充 Bot 設定管理介面,並修改了相關背景任務腳本來實現,同時確保了靈活性,允許管理員根據平台特性(例如 Line 的費用考量)獨立控制推送開關。 Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a proactive push notification system for background tasks, allowing the bot to automatically notify users upon task completion. Key changes include adding a new proactive_push_enabled setting to the bot_settings table, with Line defaulting to disabled and Telegram to enabled, managed via a new Alembic migration. A new internal API endpoint /api/internal/proactive-push is created, accessible only from localhost, which background tasks call upon completion. This endpoint reads caller_context from status.json (which is now passed to and stored by background skills like research, media-downloader, and media-transcription) and dispatches notifications via a new proactive_push_service. The AI's system prompt is updated to instruct it to include caller_context when initiating background tasks. The frontend settings page is updated with a toggle switch for proactive_push_enabled for each platform. Additionally, the PR includes a separate feature to convert PDF files to PostScript before printing for improved compatibility. Review comments highlight concerns about SQL injection risk with f-strings in migrations, suggest optimizing the _find_status_file function to scan only the last 7 days for performance, recommend refactoring duplicate caller_context creation logic in build_system_prompt, advise extracting a repeated success message template in prepare_print_file, and point out redundant initial status writes in background scripts.
Note: Security Review did not run due to the size of the PR.
| op.execute( | ||
| f""" | ||
| INSERT INTO bot_settings (platform, key, value, updated_at) | ||
| VALUES | ||
| ('line', 'proactive_push_enabled', 'false', '{now.isoformat()}'), | ||
| ('telegram', 'proactive_push_enabled', 'true', '{now.isoformat()}') | ||
| ON CONFLICT (platform, key) DO NOTHING | ||
| """ | ||
| ) |
| """依 skill 名稱和 job_id 搜尋 status.json(掃描最近 7 天)""" | ||
| ctos = _get_ctos_mount() | ||
| skill_subdir = { | ||
| "research-skill": "research", | ||
| "media-downloader": "videos", | ||
| "media-transcription": "transcriptions", | ||
| }.get(skill) | ||
|
|
||
| if not skill_subdir: | ||
| return None | ||
|
|
||
| base = Path(ctos) / "linebot" / skill_subdir | ||
| if not base.exists(): | ||
| return None | ||
|
|
||
| for date_dir in sorted(base.iterdir(), reverse=True): | ||
| if not date_dir.is_dir(): | ||
| continue | ||
| status_path = date_dir / job_id / "status.json" | ||
| if status_path.exists(): | ||
| return status_path | ||
|
|
||
| return None |
There was a problem hiding this comment.
函式 _find_status_file 的註解提到「掃描最近 7 天」,但目前的實作是遍歷所有日期的資料夾 (sorted(base.iterdir(), reverse=True))。當累積的資料夾過多時,這可能會有效能問題。建議修改實作,使其符合註解的描述,只掃描最近 7 天的資料夾,以提高效率並避免不必要的 I/O 操作。
from datetime import datetime, timedelta
ctos = _get_ctos_mount()
skill_subdir = {
"research-skill": "research",
"media-downloader": "videos",
"media-transcription": "transcriptions",
}.get(skill)
if not skill_subdir:
return None
base = Path(ctos) / "linebot" / skill_subdir
if not base.exists():
return None
today = datetime.now().date()
for i in range(8): # 掃描今天及過去 7 天
date_to_check = today - timedelta(days=i)
date_dir = base / date_to_check.strftime("%Y-%m-%d")
if not date_dir.is_dir():
continue
status_path = date_dir / job_id / "status.json"
if status_path.exists():
return status_path
return None| # caller_context 範本(供背景任務附帶) | ||
| import json as _json | ||
| _caller_ctx = { | ||
| "platform": platform_type, | ||
| "platform_user_id": line_user_id or "", | ||
| "is_group": True, | ||
| "group_id": platform_group_id or "", | ||
| } | ||
| base_prompt += f"\ncaller_context(呼叫背景任務時附帶此值): {_json.dumps(_caller_ctx, ensure_ascii=False)}" |
| if proc_ps.returncode != 0 or not ps_file.exists(): | ||
| # pdf2ps 失敗則 fallback 使用原始 PDF 路徑 | ||
| return f"""✅ 檔案已準備好,請使用 printer-mcp 的 print_file 工具列印: | ||
|
|
||
| 📄 檔案:{actual_path.name} | ||
| 📂 絕對路徑:{actual_str} | ||
|
|
||
| 下一步:呼叫 print_file(file_path="{actual_str}")""" | ||
|
|
||
| ps_str = str(ps_file) | ||
| return f"""✅ PDF 已轉換為 PostScript,請使用 printer-mcp 的 print_file 工具列印: | ||
|
|
||
| 📄 檔案:{actual_path.name} | ||
| 📂 絕對路徑:{ps_str} | ||
|
|
||
| 下一步:呼叫 print_file(file_path="{ps_str}")""" | ||
|
|
||
| except FileNotFoundError: | ||
| # 沒有 pdf2ps 指令則直接使用原始路徑 | ||
| return f"""✅ 檔案已準備好,請使用 printer-mcp 的 print_file 工具列印: | ||
|
|
||
| 📄 檔案:{actual_path.name} | ||
| 📂 絕對路徑:{actual_str} | ||
|
|
||
| 下一步:呼叫 print_file(file_path="{actual_str}")""" |
| status_path = job_dir / "status.json" | ||
| _write_status( | ||
| status_path, | ||
| { | ||
| "job_id": job_id, | ||
| "status": "queued", | ||
| "status_label": "排隊中", | ||
| "stage": "queued", | ||
| "stage_label": "等待背景程序啟動", | ||
| "progress": 0, | ||
| "query": query, | ||
| "search_provider": "none", | ||
| "provider_trace": [], | ||
| "sources": [], | ||
| "partial_results": [], | ||
| "final_summary": "", | ||
| "error": None, | ||
| "created_at": datetime.now().isoformat(), | ||
| }, | ||
| ) | ||
| initial_status: dict = { | ||
| "job_id": job_id, | ||
| "status": "queued", | ||
| "status_label": "排隊中", | ||
| "stage": "queued", | ||
| "stage_label": "等待背景程序啟動", | ||
| "progress": 0, | ||
| "query": query, | ||
| "search_provider": "none", | ||
| "provider_trace": [], | ||
| "sources": [], | ||
| "partial_results": [], | ||
| "final_summary": "", | ||
| "error": None, | ||
| "created_at": datetime.now().isoformat(), | ||
| } | ||
| if caller_context: | ||
| initial_status["caller_context"] = caller_context | ||
| _write_status(status_path, initial_status) |
新增 test_proactive_push_service.py(12 個測試): - _is_push_enabled:DB 有值/無值/例外 各分支 - notify_job_complete:停用跳過、target 空跳過、Line/Telegram 個人與群組推送 - 不支援平台及推送例外靜默處理 新增 test_api_internal_push.py(16 個測試): - _build_message:research/media-downloader/media-transcription 格式驗證(含截斷邏輯) - _find_status_file:找到/找不到/多日期取最新 - 端點:403 非本機存取、無 caller_context、invalid caller_context、成功推送(個人/群組) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- scheduler: 修正 create_next_month_partitions await_count 2 → 3(ai_logs 分區) - identity_router: 群組中未綁定用戶一律靜默忽略(不受策略影響) - command_handlers: /debug 指令改為 private_only=True,群組靜默忽略 - test_identity_router: 更新群組行為期望(silent) - test_bot_telegram_handler: patch policy 避免受 .env 設定影響 - test_restricted_jfmskin: 加入 pytestmark.skip,避免 pytest 誤收集整合測試 - pyproject.toml: 加入 coverage omit 排除 skill 腳本(獨立子行程不適合單元測試) Tests: 828 passed, 10 skipped | Coverage: 85.55% Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
原修改錯誤地將群組未綁定用戶一律靜默,破壞了群組 restricted_agent_id 功能。 正確行為: - reject 策略 + 群組 → silent(避免廣播「請綁定帳號」給群組) - restricted 策略 + 群組 → restricted(讓群組受限 Agent 如 JFMSKIN 能服務未綁定用戶) test_restricted_jfmskin.py 為整合測試腳本(需真實 DB),維持 skip 不影響 CI。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code reviewFound 2 issues:
ching-tech-os/backend/src/ching_tech_os/api/internal_push.py Lines 61 to 66 in fd53e92
ching-tech-os/frontend/css/settings.css Lines 834 to 838 in fd53e92 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
- internal_push.py: 優先讀取 final_summary 欄位(研究任務完成後的摘要) - settings.css: toggle slider thumb 改用 CSS 變數 --btn-text-on-primary Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
實作內容
資料庫
migrations/014:bot_settings新增proactive_push_enabled預設記錄後端核心
services/proactive_push_service.py: 推送服務,根據設定決定是否推送,分派到 Line / Telegramapi/internal_push.py:POST /api/internal/proactive-push(限 127.0.0.1),從 status.json 讀取結果組裝訊息後觸發推送api/bot_settings.py/services/bot_settings.py: GET/PUT 擴充支援proactive_push_enabled欄位Background Skills 整合
start-research.py: 讀取並保存caller_context,完成時呼叫推送端點(修正 Claude webtools 路徑遺漏 trigger 及 status_data 未保存 caller_context 的 bug)download-video.py: 同上整合transcribe.py: 同上整合AI Prompt
linebot_ai.py:build_system_prompt()在【對話識別】提供caller_contextJSON 範本,指示 AI 呼叫背景任務時附帶此值前端
settings.js/settings.css: Line Bot 與 Telegram Bot 設定卡片各新增主動推送切換開關Test plan
uv run pytest(排除 4 個預存在失敗):759 passed, 8 skippedproactive_push_service單元測試:啟用/停用/缺值預設行為GET/PUT /api/admin/bot-settings/{platform}測試涵蓋proactive_push_enabledbuild_system_prompt測試驗證caller_context出現在 group prompt/research,完成後確認有收到主動推送訊息🤖 Generated with Claude Code