Skip to content

Commit 467d3d9

Browse files
lgc2333codex
andcommitted
feat(picmenu): finalize menu contracts
- Enforce external menu configuration and adapter help visibility contracts - Document current-command help navigation behavior and Python 3.10 compatibility - Reorganize tests by runtime module and regenerate external menu schema Co-authored-by: Codex <codex@openai.com>
1 parent 80e42db commit 467d3d9

29 files changed

Lines changed: 2670 additions & 2347 deletions

CONTEXT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Input supplied by a user to locate a plugin or function item in a help menu.
2727
The deterministic display order of plugin menu items.
2828

2929
**菜单索引**:
30-
The 1-based position of a menu item in the current sorted menu snapshot. It is not a stable identifier.
30+
The 1-based position of a menu item in the current discoverable sorted menu snapshot. A Help view must not advertise an index when its target is absent from that snapshot. It is not a stable identifier.
3131

3232
**同拼音菜单项**:
3333
Plugin menu items with the same pinyin sort key.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@ Telegram:[@lgc2333](https://t.me/lgc2333)
247247
- 修复外部菜单目录扫描异常可能导致菜单收集失败的问题
248248
- 优化菜单排序逻辑,名称拼音相同的插件会按插件 ID 保持稳定顺序
249249
- 限制 `plugin:` 资源路径只能访问目标插件模块内的文件,避免绝对路径、路径越界和符号链接越界访问
250+
- 外部菜单的 `pmn` 配置改为严格校验,未支持的字段将被拒绝。
251+
- 修复 Python 3.10 / Pydantic v1 的兼容性问题
250252

251253
### 0.4.2
252254

defs/ExternalPluginInfo.json

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"$defs": {
33
"ExternalPMNData": {
4+
"additionalProperties": false,
45
"properties": {
56
"hidden": { "default": false, "title": "Hidden", "type": "boolean" },
67
"markdown": { "default": false, "title": "Markdown", "type": "boolean" },
@@ -44,7 +45,11 @@
4445
}
4546
},
4647
"properties": {
47-
"name": { "default": null, "title": "Name", "type": "string" },
48+
"name": {
49+
"anyOf": [{ "type": "string" }, { "type": "null" }],
50+
"default": null,
51+
"title": "Name"
52+
},
4853
"author": {
4954
"anyOf": [{ "type": "string" }, { "type": "null" }],
5055
"default": null,
@@ -66,10 +71,12 @@
6671
"title": "Usage"
6772
},
6873
"funcs": {
74+
"anyOf": [
75+
{ "items": { "$ref": "#/$defs/PMDataItem" }, "type": "array" },
76+
{ "type": "null" }
77+
],
6978
"default": null,
70-
"items": { "$ref": "#/$defs/PMDataItem" },
71-
"title": "Funcs",
72-
"type": "array"
79+
"title": "Funcs"
7380
},
7481
"pmn": {
7582
"$ref": "#/$defs/ExternalPMNData",

docs/adr/0002-resolve-menu-queries-by-snapshot-index-name-and-pinyin.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ status: accepted
66

77
PicMenu Next orders plugin menu items by pinyin sort key and then by plugin ID, removing incidental plugin-load ordering when names share the same pinyin. A numeric query selects the 1-based position in that current sorted snapshot, not a stable identity; the position may change as menu items change.
88

9-
A pure positive number is resolved as an index before name or pinyin matching. Zero and all-zero strings remain fuzzy queries; other queries combine the displayed name at 60% and pinyin at 40%, require a score of 60, and never replace displayed names with pinyin. This keeps direct selection predictable while retaining Chinese and romanized discovery for plugin and function items.
9+
A pure positive number is resolved as an index before name or pinyin matching. Zero and all-zero strings remain fuzzy queries; other queries combine the displayed name at 60% and pinyin at 40%, require a score of 60, and never replace displayed names with pinyin. Any index rendered as navigation guidance must resolve from the same discoverable snapshot under the stated help invocation. Current-command Help first uses the ordinary snapshot and uses a `-H` snapshot only for its hidden-item retry, so its guidance remains resolvable. This keeps direct selection predictable while retaining Chinese and romanized discovery for plugin and function items.

docs/adr/0007-store-adapter-support-as-menu-data.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ status: accepted
66

77
Supported adapters are plugin-level menu data, not transient metadata or PicMenu extension configuration. PicMenu Next stores them internally as `set[str] | None`, accepts an array in external JSON/YAML/TOML config, collects `PluginMetadata.supported_adapters`, and lets external config replace that field as a whole so loaded and external plugin menus share one visibility model.
88

9-
Missing or `null` adapter data means all adapters are supported, while an explicit empty list supports none and hides the plugin from ordinary menu discovery. `-H` or `--show-hidden` includes that adapter-hidden plugin, but does not make it compatible with the current adapter. Unresolvable entries are ignored without failing the menu but do not establish support. Adapter hiding is derived from final menu data before Mixins run, so a Mixin may override it; it affects only ordinary menu visibility, not Help interception for a command the user has already invoked.
9+
Missing or `null` adapter data means all adapters are supported, while an explicit empty list supports none and hides the plugin from ordinary menu discovery. `-H` or `--show-hidden` includes that adapter-hidden plugin, but does not make it compatible with the current adapter. Unresolvable entries are ignored without failing the menu but do not establish support. Adapter hiding is derived from final menu data before Mixins run, so a Mixin may override it. It affects ordinary menu visibility and the first current-command Help attempt; that Help request retries in the `-H` snapshot when ordinary discovery finds no view, so any numeric navigation guidance remains valid.

docs/adr/0013-render-intercepted-alconna-help-without-silence.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ status: accepted
66

77
Normal help-menu queries respect hidden plugins and hidden function items: if a plugin is hidden, users cannot find it through the ordinary menu command, and explicit display of hidden items remains subject to hidden-item permission policy. Adapter support can hide a plugin as part of the menu data transformation path, and later Mixin changes take precedence over that derived hidden state. Alconna Help interception is different because the user has already invoked a concrete command, so PicMenu Next must return Help for that command regardless of plugin or function-item hidden state. It uses the same data path as ordinary plugin and function detail rendering; when the current command has no registered function item, it may create a temporary item for that render.
88

9-
Alconna decides whether a `-h` or `--help` invocation requests output. Once PicMenu Next receives such a request for a recognized command, it must return a response. It first attempts a PicMenu help view with adapter filtering disabled but ordinary hidden-state filtering intact. Only a normal no-result, such as a missing plugin or function item (including an item hidden by that first attempt), triggers one retry with hidden-state filtering relaxed. If that retry still has no view, PicMenu Next returns Alconna's original help output.
9+
Alconna decides whether a `-h` or `--help` invocation requests output. Once PicMenu Next receives such a request for a recognized command, it must return a response. It first attempts a PicMenu help view with the same adapter and hidden-state filtering as ordinary menu discovery. Only a normal no-result, such as a missing plugin or function item (including an item hidden by that first attempt), triggers one retry with hidden-state filtering relaxed. That retry uses the `-H` snapshot, including adapter-hidden plugins, so any numeric menu-navigation guidance remains resolvable. If that retry still has no view, PicMenu Next returns Alconna's original help output.
1010

1111
All PicMenu interception steps are protected as one operation. Any ordinary exception, including command ownership resolution, context injection, Mixin execution, or template rendering, is logged with `logger.exception` together with the command path and plugin ID, then immediately returns Alconna's original help output without a hidden-state retry. A failed interception must never turn an Alconna help request into silence.

nonebot_plugin_picmenu_next/__main__.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ async def render_menu(
222222
q_plugin: str | None = None,
223223
q_function: str | None = None,
224224
show_hidden: bool = False,
225-
check_adapter_support: bool = True,
226225
) -> tuple[UniMessage | None, PMNPluginInfo | None, PMDataItem | None]: ...
227226

228227

@@ -236,7 +235,6 @@ async def render_menu(
236235
alc_command: Alconna | None = None,
237236
alc_detail_des: str | None = None,
238237
show_hidden: bool = False,
239-
check_adapter_support: bool = True,
240238
) -> tuple[UniMessage | None, PMNPluginInfo | None, PMDataItem | None]: ...
241239

242240

@@ -250,11 +248,8 @@ async def render_menu(
250248
alc_command: Alconna | None = None,
251249
alc_detail_des: str | None = None,
252250
show_hidden: bool = False,
253-
check_adapter_support: bool = True,
254251
) -> tuple[UniMessage | None, PMNPluginInfo | None, PMDataItem | None]:
255-
infos = get_infos()
256-
if check_adapter_support:
257-
infos = filter_unsupported_adapters(infos, bot.adapter)
252+
infos = filter_unsupported_adapters(get_infos(), bot.adapter)
258253
infos = await resolve_main_mixin(infos)
259254
if not show_hidden:
260255
infos = [x for x in infos if not x.pmn.hidden]
@@ -447,7 +442,6 @@ async def output_converter(
447442
alc_command=self.command,
448443
alc_detail_des=content,
449444
show_hidden=show_hidden,
450-
check_adapter_support=False,
451445
)
452446
if msg:
453447
return msg

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ build-backend = "hatchling.build"
4646
path = "nonebot_plugin_picmenu_next/__init__.py"
4747

4848
[tool.poe.tasks]
49-
gen-defs.cmd = "uv run scripts/gen_defs.py"
49+
gen-defs.shell = "uv run scripts/gen_defs.py && pnpx prettier -cw defs"
5050
test.cmd = "pytest"
5151
coverage.cmd = "pytest --cov=nonebot_plugin_picmenu_next --cov-branch --cov-report=term-missing"
5252

scripts/gen_defs.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55

66
import nonebot
77

8-
nonebot.init(localstore_use_cwd=True)
8+
nonebot.init(
9+
localstore_cache_dir="temp/cache",
10+
localstore_config_dir="temp/config",
11+
localstore_data_dir="temp/data",
12+
)
913

1014
nonebot.require("nonebot_plugin_picmenu_next")
1115

File renamed without changes.

0 commit comments

Comments
 (0)