新增 feedback-issue Agent skill:把用户反馈整理为上游 Issue (#5799)

This commit is contained in:
InfinityPacer
2026-05-20 20:10:03 +08:00
committed by GitHub
parent c52ccaf75f
commit ad7cce72f4
4 changed files with 1605 additions and 0 deletions
+444
View File
@@ -0,0 +1,444 @@
---
name: feedback-issue
version: 1
description: >-
Use this skill when the user wants to file a bug report against the
MoviePilot upstream backend repository `jxxghp/MoviePilot`. Triggers
include Chinese phrases such as "反馈 issue"、"提 issue"、"报 bug"、
"给 MP 提 issue"、"让上游修一下"、"我要反馈问题"、"提交错误报告"
as well as English phrasings such as "file an issue" / "report a bug" /
"open an upstream issue". The skill collects bug context from the
conversation, drafts an issue payload that matches the upstream
`bug_report.yml` form, asks the user to confirm, then calls the
`submit_feedback_issue` tool which either creates the issue directly
via GitHub REST API (when `GITHUB_TOKEN` has write permission) or
falls back to a prefilled GitHub Issue Forms URL for the user to
submit manually. Backend issues only — redirect frontend / plugin
reports to their own repositories.
allowed-tools: submit_feedback_issue read_file list_directory execute_command
---
# Feedback Issue (问题反馈)
This skill turns a user-reported backend problem from a chat session
(Telegram, Lark/Feishu, WeCom, Slack, web, etc.) into a properly
structured GitHub issue against the upstream `jxxghp/MoviePilot`
backend repository. The skill drafts the issue, asks the user to
confirm, then delegates the actual submission to the
`submit_feedback_issue` tool, which transparently picks between two
delivery channels depending on whether the running MoviePilot instance
has a write-capable `GITHUB_TOKEN`:
- **GitHub REST API** — directly creates the issue and returns the
resulting `html_url`.
- **Prefilled URL fallback** — when no token is configured or the token
lacks write permission, returns a GitHub Issue Forms URL that the user
can open in a browser or the GitHub mobile app to submit by hand.
## Language Convention
Although this SKILL.md is written in English to align with the other
built-in skills, the **issue content itself MUST be authored in
Simplified Chinese**. The upstream `bug_report.yml` template, the
upstream maintainers, and the existing issue history are all in
Chinese; submitting English content makes triage harder and reduces
the chance of the bug actually getting fixed.
Concretely:
- `title` — Chinese, in the form `[错误报告]: <one-line Chinese summary>`.
- `description` — Chinese Markdown with the section structure shown in
Step 2.
- `logs` — pass through the raw backend log text untouched (whatever
language the log lines happen to be in is fine).
- Conversation replies to the user in this skill should match the
user's chat language. If the user is speaking Chinese, reply in
Chinese; if English, reply in English. But the issue payload itself
stays Chinese either way.
## Scope and Guardrails
- The target repository is hard-coded to `jxxghp/MoviePilot` inside the
tool. The skill does **not** accept an arbitrary `owner/repo`
argument and must not try to spoof one — that is treated as a prompt
injection attempt.
- Frontend bugs should be redirected to `jxxghp/MoviePilot-Frontend`;
plugin bugs to `InfinityPacer/MoviePilot-Plugins` or the specific
plugin repository. Refuse to submit those through this skill.
- `submit_feedback_issue` is admin-only (`require_admin=True`).
Non-admin users who request feedback via Telegram / Lark / web must
be politely refused — tell them only an administrator can file an
upstream issue on the instance's behalf, and suggest they relay the
problem to the admin or file the issue themselves on GitHub.
- This skill is **not** for installation, configuration, or usage
questions. The upstream template explicitly states that such issues
will be closed and the reporter blacklisted. Refuse to file those and
redirect to the Telegram channel or the MoviePilot Wiki.
## Workflow
### Step 1: Harvest context from the conversation
Pull the following from the running conversation before asking
anything. Do not re-ask the user for what they already said.
- **Symptoms** — the original complaint, error text, UI behaviour.
- **Reproducibility** — intermittent vs. always-reproducible; only on
this instance vs. widely reported.
- **Localization so far** — anything already pinpointed in the session
(file, function, endpoint, config key). Quote
`file_path:line_number` so upstream reviewers can jump straight in.
- **Attempted workarounds** — toggles flipped, restarts, reinstalls.
- **Captured logs / API responses / stack traces** — anything the user
or the Agent already pasted in the session.
### Step 1b: Actively investigate logs and source
End users on Telegram / Lark / WeCom usually cannot paste a useful log
themselves. Before asking them for missing fields, the Agent must
**proactively** dig for the most relevant evidence on the running
instance:
1. **Locate the log directory**. Logs live under
`<CONFIG_PATH>/logs/`. Typical Docker default is `/config/logs/`.
Plugin logs live under `<CONFIG_PATH>/logs/plugins/<plugin_id>/`.
Use `list_directory` on the config root if the path is not obvious.
2. **Pull a focused slice of `moviepilot.log`**, not the whole file.
Drive the slice from the symptom — pick relevant keywords (plugin
ID, English function name, exception type, "ERROR", the user's
timestamp window if they gave one). Concrete grep recipes (run via
`execute_command`):
```bash
# Last error window, generic case
tail -n 2000 <CONFIG_PATH>/logs/moviepilot.log | \
grep -nE -B 5 -A 30 'ERROR|Traceback|Exception|<keyword>'
# Plugin-specific, both main log and plugin log
tail -n 1500 <CONFIG_PATH>/logs/plugins/<plugin_id>/<plugin_id>.log
```
3. **Cap the captured log at ~3 KB** after redaction (Step 1c). If the
matched window is bigger, keep the single most relevant traceback /
ERROR block rather than truncating mid-line.
4. **Optionally grep source for localization**. When the log points at
a specific function name, module, or API path, the Agent **may**
grep `app/` to find the likely `file_path:line_number`:
```bash
grep -rn '<symbol_or_endpoint>' app/ --include='*.py' | head -20
```
Conclusions drawn from source-only inspection are **speculative**
and must go into the `仅为推测` bucket of `已定位 / 推测`. Do not
promote them to `已经验证` unless an actual run / test confirmed it
in this session.
5. **Skip this step entirely** when the user already pasted a usable
log block, or when the problem is obviously a UI / configuration
complaint with no error-shaped symptom — extra grepping just bloats
the issue.
### Step 1c: Redact sensitive data in the captured log
Auto-redact the log block before showing it in the dry-run or sending
it to the tool. Run a deterministic regex pass over the captured text.
Minimum patterns to redact (case-insensitive):
| Pattern | Replacement |
| --- | --- |
| `Cookie:\s*[^\n]+` | `Cookie: <REDACTED>` |
| `Set-Cookie:\s*[^\n]+` | `Set-Cookie: <REDACTED>` |
| `Authorization:\s*(Bearer|Basic|Token)\s+\S+` | `Authorization: $1 <REDACTED>` |
| `(api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|passkey|pwd|password|secret|token)\s*[:=]\s*['"]?[^\s'"&]+` | `$1=<REDACTED>` |
| `passkey=[0-9a-f]{8,}` (URL query) | `passkey=<REDACTED>` |
| `[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}` (email) | `<EMAIL_REDACTED>` |
| Public IPv4 (skip private 10/172.16/192.168/127) | `<IP_REDACTED>` |
| `/Users/[^/\s]+/` or `/home/[^/\s]+/` | `/Users/<USER>/` / `/home/<USER>/` |
| WeChat / Telegram / Lark webhook URLs containing tokens | host kept, token segment → `<REDACTED>` |
Additional rules:
- If after redaction the log block is empty or trivially small (e.g.
just headers), omit `logs` entirely rather than submitting noise.
- If the captured log still contains a string that **looks** like a
long random base64 / hex value (≥ 24 chars of `[A-Za-z0-9+/=]` after
a `:`/`=`/`Bearer `), treat it as a possible secret and redact it
even if it didn't match any pattern above.
- The redaction is **mandatory** and is part of the dry-run preview —
the user sees the post-redaction logs and decides whether anything
still looks sensitive before confirming.
### Step 1d: Ask the user for the remaining required fields
Only after Step 1 / 1b / 1c, ask the user — in a single batched
question — for the fields you still cannot infer:
| Field | Allowed values | Notes |
| --- | --- | --- |
| `version` | e.g. `v2.12.2` | Required. If the user does not know, point them at the "About" page in the WebUI. |
| `environment` | `Docker` / `Windows` | Required. Exactly one of the two strings. |
| `issue_type` | `主程序运行问题` / `插件问题` / `其他问题` | Required. Must match the upstream `bug_report.yml` dropdown values exactly. |
If the problem is plugin-specific but the user explicitly wants it
filed against the backend, allow it, but make sure
`description` clearly states the plugin ID and plugin version so
maintainers can re-route the issue.
### Step 2: Draft the issue (in Chinese)
Compose the four payload fields below. Use Simplified Chinese for
`title` and `description`. Keep the section headings exactly as shown
so the rendered issue mirrors how `bug_report.yml` would normally
present a submission.
- **`title`** — `[错误报告]: <a single Chinese sentence summarizing the
symptom>`. Always replace the template placeholder `请在此处简单描
述你的问题`; leaving the placeholder triggers auto-close upstream.
- **`description`** — Chinese Markdown using this skeleton (add or omit
sections as needed, but keep the verified-vs-speculation split):
```markdown
## 现象
- 用户观察到的具体行为、报错文字、UI 表现。
## 复现步骤
1. 第一步……
2. 第二步……
3. 出现错误。
## 期望行为
- 正确情况下应该是什么样。
## 已定位 / 推测
- 已经验证:xxx(附 `file_path:line_number`)。
- 仅为推测:xxx。
## 已尝试的处理
- workaround / 关闭/启用某选项 / 重启 / 重装 ……
```
- **`logs`** — the redacted log block from Step 1b / 1c, capped at
~3 KB. Only real log lines — never fabricate. If neither the
conversation nor the active log dig produced anything useful, omit
this field; the tool will fill in
"会话中未捕获到相关后端日志".
- **Speculative localization** drawn from source grep in Step 1b goes
into the `仅为推测` bullet of `已定位 / 推测`, with the
`file_path:line_number` reference. Findings actually verified during
the session (logs that pinpoint the line, behaviour reproduced after
a hypothesis) may go under `已经验证`.
Writing requirements:
- Do not surface meta-information about Claude Code, the Agent runtime,
or "the current session" in `title` / `description`. The maintainer
should read the issue as if a regular user filed it. The tool already
appends a single discreet footer line crediting the Agent.
- Distinguish "verified" from "speculative" findings. Do not let a
guess from the chat become a stated cause.
- Do not invent GitHub usernames, emails, or version numbers.
### Step 3: Mandatory dry-run preview
Before calling the tool, print the six payload fields (`title`,
`version`, `environment`, `issue_type`, `description`, `logs`) back to
the user in full and ask, in the user's chat language:
> Is this draft OK? Reply "confirm" / "确认" to submit, or "edit: ..." /
> "修改:..." to adjust.
The dry-run **must include the post-redaction `logs` block verbatim**
so the user can spot any sensitive data the regex pass missed and
either tell the Agent to drop / re-edit it, or override the
redaction manually. If the user requests further redaction, apply it
and re-show the dry-run.
Do **not** call `submit_feedback_issue` until the user explicitly
confirms.
### Step 4: Call `submit_feedback_issue`
> **MANDATORY: every tool call in this repository requires an
> `explanation` argument.** It is a hard pydantic-required field on
> every MoviePilot agent tool (see `query_subscribes`, `add_download`,
> `search_media`, etc.) — used for activity-log auditing and the
> tool-bubble shown in Telegram / Lark. Omitting it makes the framework
> reject the call **before** the tool runs, so the no-token /
> no-permission fallback inside `submit_feedback_issue` never fires.
> **Always pass a concrete `explanation` string**, e.g.
> `"User authorized submitting a TMDB-identification bug to jxxghp/MoviePilot"`.
Once the user confirms, invoke the tool with the drafted fields:
```
submit_feedback_issue(
explanation="User authorized submitting a bug report to jxxghp/MoviePilot",
title=...,
version=...,
environment=...,
issue_type=...,
description=...,
logs=..., # omit if no real logs
)
```
The tool returns a JSON string. **Important architectural note:** to
avoid LLM verbatim-copy corruption of long URLs (e.g. a single
quantized byte flip mutating `%89` → `%79` and breaking the GitHub
prefill), the tool **delivers `issue_url` / `prefill_url` to the user
directly via a separate notification message** (`send_tool_message`),
not by returning the URL string for the LLM to re-emit. The JSON
returned to the LLM carries only `url_delivered: true|false` and a
short Chinese `message` field that summarizes what to say.
Parse the JSON and branch on `success` + `reason`:
| Result shape | Meaning | How to respond to the user |
| --- | --- | --- |
| `success=true`, `url_delivered=true` | API channel succeeded and the issue URL has already been pushed to the user channel as a separate notification. | Acknowledge briefly: "Issue 已提交到上游,等待 maintainer 跟进。" **Do NOT repeat or paraphrase the URL** — the user already received it as a clickable link. |
| `success=false`, `reason=no_token`, `url_delivered=true` | Instance has no `GITHUB_TOKEN`; prefill URL has been pushed to the user. | Acknowledge briefly: "我没有自动提交权限,已把预填链接单独发给你,点击即可提交。" Optionally remind the admin once to configure a token with `public_repo` scope for next time. **Do NOT repeat the URL.** |
| `success=false`, `reason=no_permission`, `url_delivered=true` | Token lacks write scope; prefill URL pushed. | Acknowledge briefly and remind the admin to regenerate the token with `public_repo` / `repo` scope. **Do NOT repeat the URL.** |
| `success=false`, `reason=rate_limited`, `url_delivered=true` | GitHub returned 403 with `X-RateLimit-Remaining: 0`. Prefill URL pushed. | Ask the user to retry later or click the link that was pushed separately. **Do NOT** tell them to reconfigure the token — this is rate limit, not permission. **Do NOT repeat the URL.** |
| `success=false`, `reason=invalid_payload`, `url_delivered=true` | GitHub returned 422; prefill URL pushed. | Ask the user to revise the title or body (likely forbidden characters), and note that the prefill link was already pushed for manual submission. **Do NOT repeat the URL.** |
| `success=false`, `reason=github_unavailable` / `network_error`, `url_delivered=true` | Transient GitHub failure; prefill URL pushed. | Ask the user to retry later or click the link that was pushed separately. **Do NOT repeat the URL.** |
| `success=false`, `reason=duplicate` | The same feedback was already submitted in the last 60 seconds. Nothing was sent to GitHub or to the user this time. | Acknowledge briefly that the issue was already filed in the previous attempt; ask the user to add a comment to the existing Issue if they have more details. **Do NOT call the tool again for the same payload.** |
| Any of the above with `url_delivered=false` | Notification push failed; the tool returned the URL in `issue_url` / `prefill_url` as a last-resort fallback. | Paste the URL verbatim into the chat reply (single line, no line breaks). This is the **only** scenario in which the LLM should emit the URL. |
| `success=false`, `reason=invalid_input` | Tool rejected the payload before calling GitHub (e.g. `environment` / `issue_type` not in the allowed enum). | Agent-side mistake — silently fix the payload and retry. Do not surface this error to the user. |
Rule of thumb: if `url_delivered=true`, **never put the URL in your
conversation reply**. The link is already in the user's channel. Your
job is to confirm in one or two short Chinese sentences.
#### Error handling — do NOT improvise
If the tool call fails for any reason, the only allowed paths are:
1. **Schema validation error / `reason=invalid_input` / missing
required field (e.g. `explanation`, `environment`, `issue_type`)**
— this is an Agent-side mistake. **Silently fix the payload and
call `submit_feedback_issue` again**, up to 2 retries. Never expose
"tool validation failed" / "system limitation" / "explanation field
missing" to the user. Never substitute a dialog-only "please copy
the following text to GitHub" message as a workaround — the user
is on a mobile chat client and that fallback is unusable.
2. **Tool returned a structured failure with `prefill_url`** (any of
`no_token` / `no_permission` / `invalid_payload` /
`github_unavailable` / `network_error`) — relay the `prefill_url`
per the table above. This is the **only** sanctioned manual-submit
fallback; the URL is engineered to open the upstream form with all
fields prefilled.
3. **Tool returned a real exception (network / unknown)** — log the
error, apologize briefly in one sentence, and offer to retry once
the user reports the same issue again. Do not invent a fallback
that asks the user to copy-paste raw issue text into GitHub.
In short: **never fall back to "here is the issue text, please submit
it yourself"**. Either retry the tool, or relay the tool's own
`prefill_url`. There is no third path.
### Step 5: After submission
- If the tool returned an `issue_url`, tell the user that follow-up
details should go to a comment on that issue in the GitHub web UI —
do not call `submit_feedback_issue` again for the same problem.
- If the user provides more information later in the same session and
the issue is already filed, instruct them to add a GitHub comment
rather than spawning a duplicate issue.
## Refuse / Redirect Scenarios
- User asks to file against `jxxghp/MoviePilot-Frontend`,
`InfinityPacer/MoviePilot-Plugins`, or any other repository — refuse,
explain that this skill only serves the backend upstream, and hand
back the correct repository's issues URL for self-submission.
- Non-admin user invokes the skill — refuse to call the tool, explain
that only an administrator can submit on the instance's behalf, and
suggest relaying the problem to the admin or filing on GitHub
directly.
- User asks to "just submit, skip the preview" — refuse; the dry-run is
mandatory.
- The session lacks enough detail to describe a comprehensible bug
(no symptom, no repro, no logs) — refuse, ask the user to reproduce
or capture logs first.
- The user is actually asking a configuration / installation / usage
question — refuse and redirect to the Telegram channel or Wiki.
## Examples
### Example 1: backend bug already localized
> User: "让 MP 的 Agent 给上游报一下这个问题吧。"
Flow:
1. Pull symptom, root-cause (`file_path:line_number`) and logs from
prior turns in the session.
2. Ask in one batch for the missing fields (`version`, `environment`,
`issue_type`).
3. Print the dry-run draft.
4. On confirmation, call `submit_feedback_issue` and respond per the
result table in Step 4.
### Example 2: user provides everything at once
> User: "2.12.2 Docker 主程序问题:订阅刷新时报错 xxx,日志是 yyy,
> 帮我提一个 issue。"
Flow:
1. Skip straight to Step 2; all six fields are derivable.
2. Print the dry-run and ask if anything else needs adding.
3. On confirmation, call the tool and reply with the outcome.
### Example 3: plugin bug — redirect
> User: "ChineseSubFinder 插件不工作,帮我给上游提 issue。"
Flow:
1. Recognize this as a plugin issue.
2. Refuse to file it through this skill; respond (in Chinese, matching
the user's language) with the plugin's repository issues URL and a
short note that plugin bugs should go to the plugin maintainer.
### Example 4: instance has no GITHUB_TOKEN
Tool returns:
```
{"success": false, "reason": "no_token", "prefill_url": "..."}
```
Reply (Chinese, since user wrote in Chinese):
> 当前 MoviePilot 没有 GitHub Token 的写入权限,我没法直接帮你提交。
> 请点击下面的链接,在浏览器或 GitHub App 中勾选 4 项 ✅ 后提交即可:
>
> <prefill_url>
>
> 如果希望以后让 Agent 直接提交,请管理员到系统设置配置一个具备
> `public_repo` 权限的 GitHub Token。
## Final Checklist
Before calling `submit_feedback_issue`:
- [ ] **`explanation` argument is present and non-empty** (workspace
convention; missing it causes pydantic to reject the call before
the tool runs).
- [ ] `title` no longer contains the placeholder
`请在此处简单描述你的问题`.
- [ ] `title` and `description` are written in Simplified Chinese.
- [ ] `version`, `environment`, `issue_type` are filled in and use
values from the allowed enumerations (else the tool will return
`reason=invalid_input`).
- [ ] `description` follows the section skeleton and separates
verified findings from speculation. Source-grep findings live in
`仅为推测`, not `已经验证`.
- [ ] `logs` is either real log text (post-redaction, ≤ ~3 KB) or
omitted. The full redaction pass from Step 1c has been applied.
- [ ] The user has explicitly confirmed the post-redaction draft in
Step 3.
- [ ] The caller is an admin (non-admin sessions should be refused
earlier).