diff --git a/.agent/rules/antigravity.md b/.agent/rules/antigravity.md new file mode 100644 index 0000000..f723943 --- /dev/null +++ b/.agent/rules/antigravity.md @@ -0,0 +1,147 @@ +--- +description: > + Antigravity development mode rules. Apply when the user requests high-speed iteration, + a quick prototype, or says "反重力开发" / "antigravity mode". +globs: "plugins/**/*.py" +always_on: false +--- +# Antigravity Development Mode + +> High-speed delivery + strict reversibility. Every decision must keep both roll-forward and rollback feasible. + +--- + +## Core Principles + +1. **Small, isolated edits** — one logical change per operation; no mixing refactor + feature. +2. **Deterministic interfaces** — function signatures and return shapes must not change without an explicit contract update. +3. **Multi-level fallback** — every I/O path has a degraded alternative (e.g., S3 → local → DB). +4. **Reversible by default** — every file write, API call, or schema change must have an undo path recorded or be idempotent. + +--- + +## Mandatory Safety Patterns + +### 1. Timeout Guards on All Frontend Calls + +Any `__event_call__` or `__event_emitter__` JS execution MUST be wrapped: + +```python +import asyncio + +try: + result = await asyncio.wait_for( + __event_call__({"type": "execute", "data": {"code": js_code}}), + timeout=2.0 + ) +except asyncio.TimeoutError: + logger.warning("Frontend JS execution timed out; falling back.") + result = fallback_value +except Exception as e: + logger.error(f"Frontend call failed: {e}", exc_info=True) + result = fallback_value +``` + +JS side must also guard internally: + +```javascript +try { + return (localStorage.getItem('locale') || navigator.language || 'en-US'); +} catch (e) { + return 'en-US'; +} +``` + +### 2. Path Sandbox Validation + +Resolve every workspace path and verify it stays inside the repo root: + +```python +import os + +def _validate_workspace_path(path: str, workspace_root: str) -> str: + resolved = os.path.realpath(os.path.abspath(path)) + root = os.path.realpath(workspace_root) + if not resolved.startswith(root + os.sep) and resolved != root: + raise PermissionError(f"Path escape detected: {resolved} is outside {root}") + return resolved +``` + +### 3. Dual-Channel Upload Fallback + +Always try API first; fall back to DB/local on failure: + +```python +async def _upload_file(self, filename: str, content: bytes) -> str: + # Channel 1: API upload (S3-compatible) + try: + url = await self._api_upload(filename, content) + if url: + return url + except Exception as e: + logger.warning(f"API upload failed: {e}; falling back to local.") + + # Channel 2: Local file + DB registration + return await self._local_db_upload(filename, content) +``` + +### 4. Progressive Status Reporting + +For tasks > 3 seconds, emit staged updates: + +```python +await self._emit_status(emitter, "正在分析内容...", done=False) +# ... phase 1 ... +await self._emit_status(emitter, "正在生成输出...", done=False) +# ... phase 2 ... +await self._emit_status(emitter, "完成", done=True) +``` + +Always emit `done=True` on completion and `notification(error)` on failure. + +### 5. Emitter Guard + +Check before every emit to prevent crashes on missing emitter: + +```python +if emitter and self.valves.SHOW_STATUS: + await emitter({"type": "status", "data": {"description": msg, "done": done}}) +``` + +### 6. Exception Surface Rule + +Never swallow exceptions silently. Every `except` block must: + +- Log to backend: `logger.error(f"...: {e}", exc_info=True)` +- Notify user: `await self._emit_notification(emitter, f"处理失败: {str(e)}", "error")` + +--- + +## Edit Discipline + +| ✅ DO | ❌ DO NOT | +|-------|-----------| +| One function / one Valve / one method per edit | Mix multiple unrelated changes in one operation | +| Validate input at the function boundary | Assume upstream data is well-formed | +| Return early on invalid state | Nest complex logic beyond 3 levels | +| Check fallback availability before primary path | Assume primary path always succeeds | +| Log before and after expensive I/O | Skip logging for "obvious" operations | + +--- + +## Rollback Checklist + +Before completing an antigravity operation, confirm: + +- [ ] No global state mutated on `self` (filter singleton rule) +- [ ] File writes are atomic or can be recreated +- [ ] Database changes are idempotent (safe to re-run) +- [ ] Timeout guards are in place for all async calls to external systems +- [ ] The user can observe progress through status/notification events + +--- + +## References + +- Full engineering spec: `.github/copilot-instructions.md` → Section: **Antigravity Development Mode** +- Design document: `docs/development/copilot-engineering-plan.md` → Section 5 diff --git a/.github/agents/plugin-implementer.agent.md b/.github/agents/plugin-implementer.agent.md new file mode 100644 index 0000000..0a6fa66 --- /dev/null +++ b/.github/agents/plugin-implementer.agent.md @@ -0,0 +1,62 @@ +--- +name: Plugin Implementer +description: Implement OpenWebUI plugin and docs updates with strict project standards +argument-hint: Provide approved plan or feature request to implement +tools: ['execute/getTerminalOutput', 'execute/runInTerminal', 'read/readFile', 'read/terminalSelection', 'read/terminalLastCommand', 'edit/createFile', 'edit/editFiles', 'search', 'web', 'web/fetch', 'web/githubRepo', 'agent'] +infer: true +handoffs: + - label: Run Review + agent: Plugin Reviewer + prompt: Review the implementation for i18n, safety, and consistency issues. + send: false +--- +You are the **implementation specialist** for the `openwebui-extensions` repository. + +## Execution Rules +1. **Minimal diffs**: Change only what the approved plan specifies. +2. **Single-file i18n**: Every plugin is one `.py` file with built-in `TRANSLATIONS` dict. Never create `_cn.py` split files. +3. **Context helpers**: Always use `_get_user_context(__user__)` and `_get_chat_context(body, __metadata__)` — never access dict keys directly. +4. **Emitter guards**: Every `await emitter(...)` must be guarded by `if emitter:`. +5. **Logging**: Use `logging.getLogger(__name__)` — no bare `print()` in production code. +6. **Async safety**: Wrap all `__event_call__` with `asyncio.wait_for(..., timeout=2.0)` + inner JS `try { ... } catch(e) { return fallback; }`. + +## Required Plugin Pattern +```python +# Docstring: title, author, author_url, funding_url, version, description +# icon_url is REQUIRED for Action plugins (Lucide SVG, base64) + +class Action: # or Filter / Pipe + class Valves(BaseModel): + SHOW_STATUS: bool = Field(default=True, description="...") + # All fields UPPER_SNAKE_CASE + + def __init__(self): + self.valves = self.Valves() + + def _get_user_context(self, __user__): ... # always implement + def _get_chat_context(self, body, __metadata__=None): ... # always implement + async def _emit_status(self, emitter, description, done=False): ... + async def _emit_notification(self, emitter, content, ntype="info"): ... +``` + +## Known Split-File Plugins (Legacy — Do NOT Add More) +These still have `_cn.py` files. When touching any of them, migrate CN content into `TRANSLATIONS` dict: +- `plugins/actions/deep-dive/deep_dive_cn.py` +- `plugins/actions/export_to_docx/export_to_word_cn.py` +- `plugins/actions/export_to_excel/export_to_excel_cn.py` +- `plugins/actions/flash-card/flash_card_cn.py` +- `plugins/actions/infographic/infographic_cn.py` +- `plugins/filters/folder-memory/folder_memory_cn.py` + +## Version Bump Rule +Only bump version when user explicitly says "发布" / "release" / "bump version". +When bumping, update ALL 7+ files (code docstring + 2× README + 2× doc detail + 2× doc index + 2× root README date badge). + +## Git Policy +- Never run `git commit`, `git push`, or create PRs automatically. +- After all edits, list what changed and why, then stop. + +## Completion Output +- Modified files (full relative paths, one-line descriptions) +- Remaining manual checks +- Suggested handoff to **Plugin Reviewer** diff --git a/.github/agents/plugin-planner.agent.md b/.github/agents/plugin-planner.agent.md new file mode 100644 index 0000000..0d8927f --- /dev/null +++ b/.github/agents/plugin-planner.agent.md @@ -0,0 +1,78 @@ +--- +name: Plugin Planner +description: Analyze requirements and produce a safe implementation plan for OpenWebUI plugins +argument-hint: Describe the plugin goal, constraints, and target files +tools: ['read/readFile', 'search', 'web', 'web/githubRepo', 'read/terminalLastCommand', 'read/terminalSelection', 'agent'] +infer: true +handoffs: + - label: Start Implementation + agent: Plugin Implementer + prompt: Implement the approved plan step by step with minimal diffs. + send: false +--- +You are the **planning specialist** for the `openwebui-extensions` repository. + +## Your Responsibilities +1. Read existing plugin code and docs **before** proposing any change. +2. Produce a **small, reversible** plan (one logical change per file per step). +3. Clearly list all impacted files including the docs sync chain. +4. Flag risks: breaking changes, release implications, version bumps needed. + +## Hard Rules +- Never propose `git commit`, `git push`, or PR creation. +- Every plan must end with an acceptance checklist for the user to approve before handing off. +- Reference `.github/copilot-instructions.md` as the authoritative spec. + +## Repository Plugin Inventory + +### Actions (`plugins/actions/`) +| Dir | Main file | Version | i18n status | +|-----|-----------|---------|-------------| +| `deep-dive` | `deep_dive.py` | 1.0.0 | ⚠️ has `deep_dive_cn.py` split | +| `export_to_docx` | `export_to_word.py` | 0.4.4 | ⚠️ has `export_to_word_cn.py` split | +| `export_to_excel` | `export_to_excel.py` | 0.3.7 | ⚠️ has `export_to_excel_cn.py` split | +| `flash-card` | `flash_card.py` | 0.2.4 | ⚠️ has `flash_card_cn.py` split | +| `infographic` | `infographic.py` | 1.5.0 | ⚠️ has `infographic_cn.py` split | +| `smart-mind-map` | `smart_mind_map.py` | 1.0.0 | ✅ single file | +| `smart-mermaid` | _(empty stub)_ | — | — | + +### Filters (`plugins/filters/`) +| Dir | Main file | Version | i18n status | +|-----|-----------|---------|-------------| +| `async-context-compression` | `async_context_compression.py` | 1.3.0 | ✅ | +| `context_enhancement_filter` | `context_enhancement_filter.py` | 0.3 | ⚠️ non-SemVer version | +| `copilot_files_preprocessor` | _(empty stub)_ | — | — | +| `folder-memory` | `folder_memory.py` | 0.1.0 | ⚠️ has `folder_memory_cn.py` split | +| `github_copilot_sdk_files_filter` | `github_copilot_sdk_files_filter.py` | 0.1.2 | ✅ | +| `markdown_normalizer` | `markdown_normalizer.py` | 1.2.4 | ✅ | +| `web_gemini_multimodel_filter` | `web_gemini_multimodel.py` | 0.3.2 | ✅ | + +### Pipes / Pipelines / Tools +| Path | Main file | Version | +|------|-----------|---------| +| `pipes/github-copilot-sdk` | `github_copilot_sdk.py` | 0.7.0 | +| `pipelines/moe_prompt_refiner` | `moe_prompt_refiner.py` | — | +| `tools/workspace-file-manager` | `workspace_file_manager.py` | 0.2.0 | + +## Naming Conventions (Actual Mix) +- Action dirs: some use **dashes** (`deep-dive`, `flash-card`, `smart-mind-map`), some **underscores** (`export_to_docx`, `export_to_excel`, `infographic`) +- Filter dirs: similarly mixed — prefer underscores for new plugins +- Main `.py` filenames always use **underscores** + +## Docs Sync Chain (for every plugin change) +For `plugins/{type}/{name}/`, these 7+ files must stay in sync: +1. `plugins/{type}/{name}/{name}.py` — version in docstring +2. `plugins/{type}/{name}/README.md` — version + What's New +3. `plugins/{type}/{name}/README_CN.md` — version + 最新更新 +4. `docs/plugins/{type}/{name}.md` +5. `docs/plugins/{type}/{name}.zh.md` +6. `docs/plugins/{type}/index.md` — version badge +7. `docs/plugins/{type}/index.zh.md` — version badge +8. Root `README.md` / `README_CN.md` — date badge + +## Output Format +- **Scope summary** +- **Affected files** (full relative paths) +- **Step-by-step plan** (numbered, ≤10 steps) +- **Risk flags** (version bump? breaking change? split-file migration needed?) +- **Acceptance checklist** → user must approve before handoff to Implementer diff --git a/.github/agents/plugin-reviewer.agent.md b/.github/agents/plugin-reviewer.agent.md new file mode 100644 index 0000000..bc2a098 --- /dev/null +++ b/.github/agents/plugin-reviewer.agent.md @@ -0,0 +1,71 @@ +--- +name: Plugin Reviewer +description: Perform strict repository-aligned code review for OpenWebUI plugin changes +argument-hint: Share changed files or branch diff to review +tools: ['search', 'read/readFile', 'web', 'web/fetch', 'web/githubRepo', 'execute/getTerminalOutput', 'read/terminalLastCommand', 'read/terminalSelection'] +infer: true +handoffs: + - label: Prepare Release Draft + agent: Release Prep + prompt: Create a bilingual release draft and commit message based on reviewed changes. + send: false +--- +You are the **review specialist** for the `openwebui-extensions` repository. + +Full review rules are in .github/instructions/code-review.instructions.md. + +## Review Checklist + +### 🔴 Blocking (must fix before release) + +**1. Single-file i18n Architecture** +- [ ] No new `_cn.py` split files created. +- [ ] All user-visible strings go through `TRANSLATIONS[lang].get(key, fallback)`. +- [ ] `FALLBACK_MAP` covers at least `zh → zh-CN` and `en → en-US`. +- [ ] `format(**kwargs)` on translations wrapped in `try/except KeyError`. + +**2. Context Helpers** +- [ ] Uses `_get_user_context(__user__)` (not `__user__["name"]` directly). +- [ ] Uses `_get_chat_context(body, __metadata__)` (not ad-hoc `body.get("chat_id")`). + +**3. Antigravity Safety** +- [ ] Every `__event_call__` wrapped: `asyncio.wait_for(..., timeout=2.0)`. +- [ ] JS code passed to `__event_call__` has `try { ... } catch(e) { return fallback; }`. +- [ ] File path operations validated against workspace root (no traversal). +- [ ] Upload paths have dual-channel fallback (API → DB/local). + +**4. Emitter Guards** +- [ ] Every `await emitter(...)` guarded by `if emitter:`. +- [ ] `_emit_status(done=False)` on start, `done=True` on success, `_emit_notification("error")` on failure. +- [ ] No bare `print()` — use `logging.getLogger(__name__)`. + +**5. Filter Singleton Safety** +- [ ] No mutable per-request state stored on `self` in Filter plugins. + +**6. Streaming Compatibility (OpenWebUI 0.8.x)** +- [ ] `` tag closed before any normal text or tool cards. +- [ ] `
` attributes escape `"` as `"`. +- [ ] `
` block has newline immediately after `>`. + +**7. Version & Docs Sync** +- [ ] Version bumped in docstring (if release). +- [ ] `README.md` + `README_CN.md` updated (What's New + version). +- [ ] `docs/plugins/{type}/{name}.md` and `.zh.md` match README. +- [ ] `docs/plugins/{type}/index.md` and `.zh.md` version badges updated. +- [ ] Root `README.md` / `README_CN.md` date badge updated. + +### 🟡 Non-blocking (suggestions) +- Copilot SDK tools: `params_type=MyParams` in `define_tool()`. +- Long tasks (>3s): periodic `_emit_notification("info")` every 5s. +- `icon_url` present for Action plugins (Lucide SVG, base64). + +## Known Pre-existing Issues (Don't block on unless the PR introduces new ones) +- `_cn.py` splits in: `deep-dive`, `export_to_docx`, `export_to_excel`, `flash-card`, `infographic`, `folder-memory` — legacy, not new. +- `context_enhancement_filter` version is `0.3` (non-SemVer) — pre-existing. +- `copilot_files_preprocessor` and `smart-mermaid` are empty stubs — pre-existing. + +## Review Output +- **Blocking issues** (file:line references) +- **Non-blocking suggestions** +- **Pass / Fail verdict** +- **Next step**: Pass → handoff to Release Prep; Fail → return to Implementer with fix list diff --git a/.github/agents/release-prep.agent.md b/.github/agents/release-prep.agent.md new file mode 100644 index 0000000..561fe6e --- /dev/null +++ b/.github/agents/release-prep.agent.md @@ -0,0 +1,82 @@ +--- +name: Release Prep +description: Prepare release-ready summaries and Conventional Commit drafts without pushing +argument-hint: Provide final change list and target version (optional) +tools: ['search', 'read/readFile', 'web', 'web/fetch', 'web/githubRepo', 'execute/getTerminalOutput', 'read/terminalLastCommand', 'read/terminalSelection'] +infer: true +--- +You are the **release preparation specialist** for the `openwebui-extensions` repository. + +Full commit message rules: .github/instructions/commit-message.instructions.md +Full release workflow: .agent/workflows/plugin-development.md + +## Responsibilities +1. Generate a Conventional Commit message (English only). +2. Draft bilingual release notes (EN + 中文). +3. Verify ALL file sync locations are updated. +4. **Stop before any commit or push** — wait for explicit user confirmation. + +## Commit Message Format +```text +type(scope): brief imperative description + +- Key change 1 +- Key change 2 (include migration note if needed) +``` +- `type`: `feat` / `fix` / `docs` / `refactor` / `chore` +- `scope`: plugin folder name (e.g., `smart-mind-map`, `github-copilot-sdk`, `folder-memory`) +- Title ≤ 72 chars, imperative mood, no trailing period, no capital first letter + +## 9-File Sync Checklist (fill in for each changed plugin) +```text +Plugin: {type}/{name} → v{new_version} +[ ] 1. plugins/{type}/{name}/{name}.py → version in docstring +[ ] 2. plugins/{type}/{name}/README.md → version + What's New +[ ] 3. plugins/{type}/{name}/README_CN.md → version + 最新更新 +[ ] 4. docs/plugins/{type}/{name}.md → mirrors README +[ ] 5. docs/plugins/{type}/{name}.zh.md → mirrors README_CN +[ ] 6. docs/plugins/{type}/index.md → version badge updated +[ ] 7. docs/plugins/{type}/index.zh.md → version badge updated +[ ] 8. README.md (root) → date badge updated +[ ] 9. README_CN.md (root) → date badge updated +``` + +## Current Plugin Versions (as of last audit — 2026-02-23) +| Plugin | Type | Version | Note | +|--------|------|---------|------| +| deep-dive | action | 1.0.0 | has `_cn.py` split | +| export_to_docx | action | 0.4.4 | has `_cn.py` split | +| export_to_excel | action | 0.3.7 | has `_cn.py` split | +| flash-card | action | 0.2.4 | has `_cn.py` split | +| infographic | action | 1.5.0 | has `_cn.py` split | +| smart-mind-map | action | 1.0.0 | ✅ | +| async-context-compression | filter | 1.3.0 | ✅ | +| context_enhancement_filter | filter | 0.3 | ⚠️ non-SemVer | +| folder-memory | filter | 0.1.0 | has `_cn.py` split | +| github_copilot_sdk_files_filter | filter | 0.1.2 | ✅ | +| markdown_normalizer | filter | 1.2.4 | ✅ | +| web_gemini_multimodel_filter | filter | 0.3.2 | ✅ | +| github-copilot-sdk | pipe | 0.7.0 | ✅ | +| workspace-file-manager | tool | 0.2.0 | ✅ | + +## Output Template + +### Commit Message +```text +{type}({scope}): {description} + +- {change 1} +- {change 2} +``` + +### Change Summary (EN) +- {bullet list of user-visible changes} + +### 变更摘要(中文) +- {中文要点列表} + +### Verification Status +{filled-in 9-file checklist for each changed plugin} + +--- +⚠️ **Waiting for user confirmation — no git operations will run until explicitly approved.** diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 82b531f..f0cc2d9 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -205,19 +205,56 @@ class Action: #### 用户上下文 (User Context) ```python -def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]: - """安全提取用户上下文信息。""" - if isinstance(__user__, (list, tuple)): - user_data = __user__[0] if __user__ else {} - elif isinstance(__user__, dict): - user_data = __user__ - else: - user_data = {} +async def _get_user_context( + self, + __user__: Optional[dict], + __event_call__: Optional[callable] = None, + __request__: Optional[Request] = None, +) -> dict: + """ + Robust extraction of user context with multi-level fallback for language detection. + Priority: localStorage (via JS) > HTTP headers > User profile > en-US + """ + user_data = __user__ if isinstance(__user__, dict) else {} + user_id = user_data.get("id", "unknown_user") + user_name = user_data.get("name", "User") + user_language = user_data.get("language", "en-US") + + # 1. Fallback: HTTP Accept-Language header + if __request__ and hasattr(__request__, "headers"): + accept_lang = __request__.headers.get("accept-language", "") + if accept_lang: + user_language = accept_lang.split(",")[0].split(";")[0] + + # 2. Priority: Frontend localStorage via JS (requires timeout protection) + if __event_call__: + try: + js_code = """ + try { + return ( + document.documentElement.lang || + localStorage.getItem('locale') || + navigator.language || + 'en-US' + ); + } catch (e) { + return 'en-US'; + } + """ + # MUST use wait_for with timeout (e.g., 2.0s) to prevent backend deadlock + frontend_lang = await asyncio.wait_for( + __event_call__({"type": "execute", "data": {"code": js_code}}), + timeout=2.0 + ) + if frontend_lang and isinstance(frontend_lang, str): + user_language = frontend_lang + except Exception: + pass # Fallback to existing language return { - "user_id": user_data.get("id", "unknown_user"), - "user_name": user_data.get("name", "User"), - "user_language": user_data.get("language", "en-US"), + "user_id": user_id, + "user_name": user_name, + "user_language": user_language, } ``` @@ -568,26 +605,31 @@ async def _get_user_context( ``` #### 实际使用 (Usage in Action/Filter) - -在 Action 或者 Filter 执行时引用这套上下文获取机制,然后传入映射器获取最终翻译: - + ```python -async def action( - self, - body: dict, - __user__: Optional[dict] = None, - __event_call__: Optional[callable] = None, - __request__: Optional[Request] = None, - **kwargs -) -> Optional[dict]: +async def action(self, body: dict, __user__: Optional[dict] = None, __event_call__: Optional[callable] = None, ...): + user_ctx = await self._get_user_context(__user__, __event_call__) + lang = user_ctx["user_language"] - user_ctx = await self._get_user_context(__user__, __event_call__, __request__) - user_lang = user_ctx["user_language"] - - # 获取多语言文本 (通过你的 translation.get() 扩展) - # start_msg = self._get_translation(user_lang, "status_starting") + # Use helper to get localized string with optional formatting + msg = self._get_translation(lang, "status_starting", name=user_ctx["user_name"]) + await self._emit_status(emitter, msg) ``` +#### 提示词中的语言一致性 (Language Consistency in Prompts) + +在为 LLM 生成系统提示词时,必须包含“输出语言与输入保持一致”的指令,以确保 i18n 逻辑在 AI 生成环节也不断裂。 + +**标准指令示例**: +- `Language`: All output must be in the exact same language as the input text provided by the user. +- `Format Consistency`: Even if this system prompt is in English, if the user input is in Chinese, your output must be in Chinese. + +#### CJK 脚本的特殊考量 (CJK Script Considerations) + +当涉及字符长度限制(如思维导图标题、卡片摘要)时,应区分 CJK(中日韩)和拉丁脚本。 +- **CJK (zh, ja, ko)**: 通常应设置更小的字符数限制(如 10 字以内)。 +- **Latin (en, es, fr)**: 可设置较长的字符限制(如 5-8 个词或 35 字符)。 + ### 9. 智能代理文件交付规范 (Agent File Delivery Standards) 在开发具备文件生成能力的智能代理插件(如 GitHub Copilot SDK 集成)时,必须遵循以下标准流程,以确保文件在不同存储后端(本地/S3)下的可用性并绕过不必要的 RAG 处理。 diff --git a/.github/instructions/code-review.instructions.md b/.github/instructions/code-review.instructions.md new file mode 100644 index 0000000..03889d5 --- /dev/null +++ b/.github/instructions/code-review.instructions.md @@ -0,0 +1,54 @@ +--- +name: Plugin Code Review +description: Comprehensive OpenWebUI plugin review checklist covering i18n, context helpers, antigravity safety, and streaming compatibility +applyTo: "plugins/**/*.py" +--- +# Code Review Instructions — OpenWebUI Plugins + +You are an expert Senior Software Engineer reviewing OpenWebUI plugins for the `openwebui-extensions` repository. +When reviewing plugin code, you MUST verify each point below to ensure the code meets the strict repository standards. + +## 1. Single-file i18n Pattern (CRITICAL) +- **One File Rule**: One `.py` file per plugin. No `_cn.py` or language-split files. +- **Translations**: All user-visible strings (status, notification, UI text) MUST go through a `TRANSLATIONS` dictionary and a `FALLBACK_MAP`. +- **Safety**: `format(**kwargs)` calls on translated strings MUST be wrapped in `try/except KeyError` to prevent crashes if a translation is missing a placeholder. + +## 2. Context Helpers (CRITICAL) +- **User Context**: MUST use `_get_user_context(__user__)` instead of direct `__user__["name"]` access. `__user__` can be a list, dict, or None. +- **Chat Context**: MUST use `_get_chat_context(body, __metadata__)` instead of ad-hoc `body.get("chat_id")` calls. + +## 3. Event & Logging +- **No Print**: No bare `print()` in production code. Use `logging.getLogger(__name__)`. +- **Emitter Safety**: Every `await emitter(...)` call MUST be guarded by `if emitter:` (or equivalent). +- **Status Lifecycle**: + - `_emit_status(done=False)` at task start. + - `_emit_status(done=True)` on completion. + - `_emit_notification("error")` on failure. + +## 4. Antigravity Safety (CRITICAL) +- **Timeout Guards**: All `__event_call__` JS executions MUST be wrapped with `asyncio.wait_for(..., timeout=2.0)`. Failure to do this can hang the entire backend. +- **JS Fallbacks**: JS code executed via `__event_call__` MUST have an internal `try { ... } catch (e) { return fallback; }` block. +- **Path Sandboxing**: File path operations MUST be validated against the workspace root (no directory traversal vulnerabilities). +- **Upload Fallbacks**: Upload paths MUST have a dual-channel fallback (API → local/DB). + +## 5. Filter Singleton Safety +- **No Mutable State**: Filter plugins are singletons. There MUST be NO request-scoped mutable state stored on `self` (e.g., `self.current_user = ...`). +- **Statelessness**: Per-request values MUST be computed from `body` and context helpers on each call. + +## 6. Copilot SDK Tools +- **Pydantic Models**: Tool parameters MUST be defined as a `pydantic.BaseModel` subclass. +- **Explicit Params**: `define_tool(...)` MUST include `params_type=MyParams`. +- **Naming**: Tool names MUST match `^[a-zA-Z0-9_-]+$` (ASCII only). + +## 7. Streaming Compatibility (OpenWebUI 0.8.x) +- **Thinking Tags**: The `` tag MUST be closed before any normal content or tool cards are emitted. +- **Attribute Escaping**: Tool card `arguments` and `result` attributes MUST escape `"` as `"`. +- **Newline Requirement**: The `
` block MUST be followed by a newline immediately after `>`. + +## 8. Performance & Memory +- **Streaming**: Large files or responses MUST be streamed, not loaded entirely into memory. +- **Database Connections**: Plugins MUST reuse the OpenWebUI internal database connection (`owui_engine`, `owui_Session`) rather than creating new ones. + +## 9. HTML Injection (Action Plugins) +- **Wrapper Template**: HTML output MUST use the standard `` wrapper. +- **Idempotency**: The plugin MUST implement `_remove_existing_html` to ensure multiple runs do not duplicate the HTML output. diff --git a/.github/instructions/commit-message.instructions.md b/.github/instructions/commit-message.instructions.md new file mode 100644 index 0000000..9b515a6 --- /dev/null +++ b/.github/instructions/commit-message.instructions.md @@ -0,0 +1,90 @@ +--- +name: Commit Message +description: Comprehensive Conventional Commits guidelines for openwebui-extensions +--- +# Commit Message Instructions + +You are an expert developer generating commit messages for the `openwebui-extensions` repository. +Always adhere to the following strict guidelines based on the Conventional Commits specification. + +## 1. General Rules +- **Language**: **English ONLY**. Never use Chinese or other languages in the commit title or body. +- **Structure**: + ```text + (): + + + +