mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-02 23:33:58 +00:00
feat(webui): 阶段管道改「循环 + 本轮轨迹」,区分工具/输出调用;再砍总览文字
jianf:阶段管道像无记忆的单向滑块,但一轮里会多次 toolcall、也可能多次输出; 且没区分 output_* 调用与普通工具调用;总览仍有一大坨文字。 - 阶段管道不再是单向滑块:# 画成 输入 → 行动 ⇄(工具↻) → 输出 → 结束 的循环结构,当前阶段高亮; 下面用一排 chip 记**本轮真实发生过的序列**(on_input 重置、before_toolcall 追加、 after_output 收尾,最多 24 条)。工具调用会反复出现,循环因此可见。 - 区分调用类型:普通工具 chip 前缀 ⚙(青),output_* 输出通道调用前缀 ⇥(accent 色), 两者配色与图标都不同。 - 文字再收缩:删掉「累计:入队/执行/抢占/挂起/背压」整行;队列标签由 「L4 内核独占…」压成 L4/L3/L2/L1/排队(原描述进 title);各段标题压成 「队列」「栈」「拓扑」;KPI 块标签压成 排队/中断/栈/子代理。 顺带(同类问题):CLI /stop 是人在终端当场下的指令,优先级由默认 L1 提到 L3。
This commit is contained in:
35
third_party/homeagent-sdk/example/qq/plugin.go
vendored
35
third_party/homeagent-sdk/example/qq/plugin.go
vendored
@ -165,7 +165,7 @@ type Plugin struct {
|
||||
batchMax time.Duration // 一批最长等多久(防持续刷屏时永远不投)
|
||||
|
||||
// injectHook 仅供测试:非 nil 时 injectInterrupt 走它而不是真实 SDK。
|
||||
injectHook func(text string)
|
||||
injectHook func(text, level string)
|
||||
}
|
||||
|
||||
// pendingBatch 是一批待投递的消息(同一会话、同一发送者、短时间内的连续消息)。
|
||||
@ -1356,7 +1356,7 @@ func (p *Plugin) enqueueInterrupt(msgType string, userID, groupID, messageID int
|
||||
return
|
||||
}
|
||||
if p.batchWindow <= 0 {
|
||||
p.injectInterrupt(single)
|
||||
p.injectInterrupt(single, p.interruptLevel(owner))
|
||||
return
|
||||
}
|
||||
key := qqBatchKey(msgType, groupID, userID)
|
||||
@ -1392,14 +1392,32 @@ func (p *Plugin) enqueueInterrupt(msgType string, userID, groupID, messageID int
|
||||
p.batchMu.Unlock()
|
||||
}
|
||||
|
||||
// injectInterrupt 投递一条中断提示(NoMemory:HTTP 侧来的不是对话内容;
|
||||
// Priority L1:QQ 消息是低级别中断,既非实时工作也非紧急工作,完全可等)。
|
||||
func (p *Plugin) injectInterrupt(text string) {
|
||||
// interruptLevel 决定一条 QQ 消息的中断级别。
|
||||
//
|
||||
// - Bot 所有者/管理员的消息 → **L2**(一般提醒);
|
||||
// - 其他人的消息 → L1(后台,完全可等)。
|
||||
//
|
||||
// 为什么不能一律 L1:L1 之间可以随时互相抢占、也可以被任何更高一级打断,
|
||||
// 于是「老板发的话」会被路人的闲聊挤到后面,甚至对方持续刷屏时一直排在队尾。
|
||||
// 为什么也不该给 L3:L3 是时钟/终端那类"需要及时处理"的实时工作,QQ 是异步
|
||||
// 消息,抬到 L3 会反过来打断真正实时的事情。
|
||||
func (p *Plugin) interruptLevel(owner bool) string {
|
||||
if owner {
|
||||
return sdk.PriorityL2
|
||||
}
|
||||
return sdk.PriorityL1
|
||||
}
|
||||
|
||||
// injectInterrupt 投递一条中断提示(NoMemory:HTTP 侧来的不是对话内容)。
|
||||
func (p *Plugin) injectInterrupt(text, level string) {
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
if level == "" {
|
||||
level = sdk.PriorityL1
|
||||
}
|
||||
if p.injectHook != nil {
|
||||
p.injectHook(text)
|
||||
p.injectHook(text, level)
|
||||
return
|
||||
}
|
||||
if p.sdk == nil {
|
||||
@ -1407,7 +1425,7 @@ func (p *Plugin) injectInterrupt(text string) {
|
||||
}
|
||||
p.sdk.InjectInterruptTextOpts(p.name, p.name, text, sdk.InjectOptions{
|
||||
NoMemory: true,
|
||||
Priority: sdk.PriorityL1,
|
||||
Priority: level,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1424,7 +1442,8 @@ func (p *Plugin) flushBatch(key string) {
|
||||
if len(b.msgIDs) > 1 {
|
||||
text = p.buildBatchInterrupt(b)
|
||||
}
|
||||
p.injectInterrupt(text)
|
||||
// 一批里只要有一条来自 Bot 所有者,整批按 L2 投递(不因混入路人消息而降低)。
|
||||
p.injectInterrupt(text, p.interruptLevel(b.owner))
|
||||
}
|
||||
|
||||
// flushAllBatches 停机前把未到点的批次立刻投出去(best effort)。
|
||||
|
||||
@ -242,7 +242,7 @@ func TestDowngradedAuthStillAllowsQQOutput(t *testing.T) {
|
||||
// collectInterrupts 用注入钩子收集中断文本(避免测试依赖真实 SDK)。
|
||||
func collectInterrupts(p *Plugin) *[]string {
|
||||
got := []string{}
|
||||
p.injectHook = func(s string) { got = append(got, s) }
|
||||
p.injectHook = func(s, _ string) { got = append(got, s) }
|
||||
return &got
|
||||
}
|
||||
|
||||
@ -312,3 +312,25 @@ func TestSingleMessageKeepsOriginalText(t *testing.T) {
|
||||
t.Fatalf("单条消息应沿用原文(含所有者前缀),实际 %#v", *got)
|
||||
}
|
||||
}
|
||||
|
||||
// Bot 所有者/管理员的消息给 L2,普通人的给 L1 —— 否则所有者的话会被路人
|
||||
// 的 L1 闲聊抢占/挤到队尾。
|
||||
func TestOwnerMessagesGetHigherInterruptLevel(t *testing.T) {
|
||||
p := newPermissionTestPlugin(t)
|
||||
got := []string{}
|
||||
p.injectHook = func(text, level string) { got = append(got, text+"|"+level) }
|
||||
p.batchWindow = 20 * time.Millisecond
|
||||
p.batchMax = time.Second
|
||||
|
||||
p.enqueueInterrupt("private", 1, 0, 1, "owner", "owner-msg", true, false)
|
||||
p.enqueueInterrupt("private", 2, 0, 2, "someone", "other-msg", false, false)
|
||||
time.Sleep(120 * time.Millisecond)
|
||||
|
||||
joined := strings.Join(got, ",")
|
||||
if !strings.Contains(joined, "owner-msg|L2") {
|
||||
t.Fatalf("所有者消息应为 L2,实际 %q", joined)
|
||||
}
|
||||
if !strings.Contains(joined, "other-msg|L1") {
|
||||
t.Fatalf("普通人消息应为 L1,实际 %q", joined)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user