From 4f4a03d368f9929c73fb9d3d146f564ce32d1738 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 13 Sep 2026 07:00:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(sdk):=20InjectOptions.Priority=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E6=8F=92=E4=BB=B6=E5=A3=B0=E6=98=8E=E8=87=AA?= =?UTF-8?q?=E5=B7=B1=E4=B8=AD=E6=96=AD=E7=9A=84=E7=BA=A7=E5=88=AB=EF=BC=88?= =?UTF-8?q?L1-L3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 内核的输入调度器区分两类别:中断输入(可抢占)与排队输入(可被任何中断打断)。 中断的级别是“这项工作有多不能等”的声明,由插件在注入时给出: p.sdk.InjectInterruptTextOpts(src, ch, text, sdk.InjectOptions{ NoMemory: true, Priority: sdk.PriorityL2, // L1 完全可等 / L2 一般提醒 / L3 需及时 }) - 新增 `InjectOptions.Priority string` 与 `PriorityL1/L2/L3` 常量(纯追加)。 - 空/非法值一律降级为 L1(默认级)——拼写错误不会被静默当成别的级别。 - **L4 由内核独占**(panic 中断、内核事件中断 selfip),插件声明 L4 会被内核 夹到 L3,远端常量的取值域里也不提供 L4。 - 排队注入(InjectText*/InjectInputSync*)没有级别:它们本就是“不需及时处理” 的那一类,可被任何中断打断;传了 Priority 也不会生效。 - 贯通链路:sdk.InjectOptions -> proc RPC 参数(priority)-> 内核 payload; tools/hmapdev 模板同步透传(三个注入的 6 个 Opts 变体共用 applyInjectOpts)。 - example/qq 显式声明 L1:QQ 消息既不是时钟那样的实时工作,也不是紧急工作。 兼容性:零值等价于旧行为(L1),既有插件无需改动。 --- example/qq/plugin.go | 10 ++++++++-- sdk/plugin.go | 16 ++++++++++++++++ tools/hmapdev/templates/proc_main.go.tmpl | 4 ++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/example/qq/plugin.go b/example/qq/plugin.go index 16634bc..1dd7144 100644 --- a/example/qq/plugin.go +++ b/example/qq/plugin.go @@ -1435,7 +1435,12 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) { if p.sdk != nil { // NoMemory:HTTP 侧来的中断提示,不是对话内容。 - p.sdk.InjectInterruptTextOpts(p.name, p.name, interrupt, sdk.InjectOptions{NoMemory: true}) + // Priority:QQ 消息是**低级别中断**——既不是时钟那样的实时工作, + // 也不是紧急工作,所以声明 L1(完全可等)。 + p.sdk.InjectInterruptTextOpts(p.name, p.name, interrupt, sdk.InjectOptions{ + NoMemory: true, + Priority: sdk.PriorityL1, + }) } w.WriteHeader(http.StatusOK) } @@ -2531,9 +2536,10 @@ func (p *Plugin) handleDownloadFile(args map[string]interface{}) (interface{}, e log.Printf("[qq] 文件下载完成: %s", savePath) if p.sdk != nil { // NoMemory:下载完成的状态通知,不是记忆内容。 + // Priority:同上,QQ 侧一律低级别中断(L1)。 p.sdk.InjectInterruptTextOpts(p.name, p.name, fmt.Sprintf("文件下载完成: %s,保存在 %s", filepath.Base(savePath), savePath), - sdk.InjectOptions{NoMemory: true}) + sdk.InjectOptions{NoMemory: true, Priority: sdk.PriorityL1}) } } else { errMsg = "下载失败,文件可能已过期" diff --git a/sdk/plugin.go b/sdk/plugin.go index f8b0c12..cf38db6 100644 --- a/sdk/plugin.go +++ b/sdk/plugin.go @@ -78,8 +78,24 @@ type InjectOptions struct { NoMemory bool ContextPolicy string CleanerName string + + // Priority 声明**中断注入**的优先级(仅 InjectInterrupt* 有意义)。 + // + // 取值 "L1"/"L2"/"L3";空等同 L1。L4 由内核独占(panic / 内核事件 selfip), + // 插件声明 L4 会被内核夹到 L3——内核的调度内部属性不接受外部越权。 + // + // 排队注入(InjectText*/InjectInputSync)没有级别:它们本就是“不需及时处理” + // 的那一类,可被任何中断打断。 + Priority string } +// 中断优先级取值(插件可用范围)。L4 不在其中:它由内核保留。 +const ( + PriorityL1 = "L1" + PriorityL2 = "L2" + PriorityL3 = "L3" +) + // ChannelDef 描述通道在记忆计算层的行为,与 ToolDef.NoMemory/Cleaner 语义一致。 // NoMemory: 此通道输入/输出不参与记忆计算(向量化/关键词提取/蒸馏),但原文保留在上下文中 // Cleaner: 计算层过滤函数,不改原文;仅在向量化/jieba/蒸馏/存档提取关键词时调用 diff --git a/tools/hmapdev/templates/proc_main.go.tmpl b/tools/hmapdev/templates/proc_main.go.tmpl index 7b18d55..272f31f 100644 --- a/tools/hmapdev/templates/proc_main.go.tmpl +++ b/tools/hmapdev/templates/proc_main.go.tmpl @@ -287,6 +287,10 @@ func applyInjectOpts(args map[string]interface{}, opts sdk.InjectOptions) { if opts.CleanerName != "" { args["cleaner_name"] = opts.CleanerName } + // priority 只对中断注入有意义(排队注入没有级别)。 + if opts.Priority != "" { + args["priority"] = opts.Priority + } } // ---- 全局状态 ----