【capacity 唤醒式核心(77)】internal/runtime/capacity:(Decider L1启发式+L2上下文+人格覆盖→三态静息/唤醒/全能→允许工具集+capacity_awaken事件)。loop 外围允许集过滤(不硬阻断=能力≠边界红线)。config 加 Capacity(Enabled/MinConfidence/ToolSets)。wire buildCapacity 接入 loop。 【L4 记忆驱动意图(80)】recall.go:从记忆召回用户习惯(搞一下xx=办事)→唤醒L1/L2漏掉/升置信;群聊陌生人不读owner习惯(隔离红线);RecordIntent闭环(判定写回记忆越用越准);默认保守静息。 【会话消息编排(78)】gateway/session_orchestrator.go:每会话(模式/pending有界5/tail上下文尾巴20);Submit非阻塞(idle立即/busy入队ack/满拒绝);完成回调按序续接(tail+新消息合并增量注入非全量重放);Interrupt中断;REST 202/409+WS session_busy/pending_ack事件;不重造任务引擎复用loop。 【前端集成(79)】ws.ts 加 capacity_awaken/session_busy/pending_ack 解析;store 加 awakenState/sessionBusy;TopBar/ChatShell 加唤醒态徽标+忙态提示(不阻塞输入框);状态组件 status.tsx;Miuix风格。 【管理面板写CRUD(81)】20个写端点(POST/PUT/DELETE /admin/*,owner+审计落库):personas(真持久化YAML)/users apikey(签发掩码+撤销)/iot控制(真下发)/automations(建/触发/删)/llm(test真调用+临时)/channels(connect/close/身份测试actor白名单)/plugins(卸载)/memories(search真检索)/sessions(export)。无存储如实501不伪造。拆4文件满足≤500红线。前端9模块接真写。 【wire收口】buildCapacity接入loop+OnCapacity事件;server.go newOrchestrator接hub。 实机验证:iot ac.living control 200 真实改变 on:true;observability/metrics 200、health ok;全仓 build/vet/test 47包绿+前端tsc/vite。
178 lines
6.7 KiB
Go
178 lines
6.7 KiB
Go
package capacity
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// uRecall builds an Input over messages with the given recall (L4 feed), mirroring
|
|
// the in() helper but adding the recall hook.
|
|
func inRecall(messages []string, recall []RecallItem) Input {
|
|
in := in(messages...)
|
|
in.Recall = recall
|
|
return in
|
|
}
|
|
|
|
// taskRecord is a canonical intent record written back by RecordIntent, carrying a
|
|
// learned task expression for the closed loop.
|
|
func taskRecord(expr string, conf float64) string {
|
|
return formatIntentRecord(Judgment{IsTask: true, TaskKind: taskKindOps, Confidence: conf, Text: expr})
|
|
}
|
|
|
|
func TestDecide_L4_WakesMissedTask_CanonicalRecord(t *testing.T) {
|
|
d := New(activeConfig())
|
|
// "部署" is a strong verb but "搞一下那个部署" carries no imperative marker or
|
|
// object noun, so L1 is undecided and L2 (single turn) is undecided too. A learned
|
|
// habit (canonical record) that "搞一下这个部署" is a task must wake it (L4 fills
|
|
// the gap 设计 §2.2).
|
|
recall := []RecallItem{{
|
|
Content: taskRecord("搞一下这个部署", 0.85),
|
|
Scope: "user",
|
|
Score: 0.8,
|
|
}}
|
|
dec := d.Decide(context.Background(), inRecall([]string{"搞一下那个部署"}, recall))
|
|
if dec.State != StateAwake {
|
|
t.Fatalf("state = %s, want awake (L4 habit learned 搞一下=办事)", dec.State)
|
|
}
|
|
if !contains(dec.AllowedTools, "terminal") {
|
|
t.Fatalf("awake allowed should include terminal (ops habit), got %v", dec.AllowedTools)
|
|
}
|
|
if dec.Confidence < 0.6 {
|
|
t.Fatalf("confidence = %v, want >= 0.6", dec.Confidence)
|
|
}
|
|
if !dec.Judgment.IsTask {
|
|
t.Fatalf("judgment.IsTask = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestDecide_L4_WakesMissedTask_HabitMemory(t *testing.T) {
|
|
d := New(activeConfig())
|
|
// A habit-style memory (not written by RecordIntent) that leans 办事 and shares the
|
|
// phrase must also wake, without L1/L2 support.
|
|
recall := []RecallItem{{
|
|
Content: "用户习惯:他习惯说搞一下就是要我办事",
|
|
Scope: "user",
|
|
Score: 0.5,
|
|
}}
|
|
dec := d.Decide(context.Background(), inRecall([]string{"搞一下那个脚本"}, recall))
|
|
if dec.State != StateAwake {
|
|
t.Fatalf("state = %s, want awake (habit memory leans 办事)", dec.State)
|
|
}
|
|
}
|
|
|
|
func TestDecide_L4_BoostsConfidenceOnAwake(t *testing.T) {
|
|
d := New(activeConfig())
|
|
// L2 already wakes this follow-up at 0.66; a strong learned habit corroborating
|
|
// the same task phrasing must raise the confidence (设计: 越用越准).
|
|
without := d.Decide(context.Background(), in("帮我查一下资料", "还有吗"))
|
|
if without.State != StateAwake || without.Confidence < 0.6 {
|
|
t.Fatalf("baseline = state %s conf %v, want awake >= 0.6", without.State, without.Confidence)
|
|
}
|
|
recall := []RecallItem{{
|
|
Content: taskRecord("还有吗", 0.9),
|
|
Scope: "user",
|
|
}}
|
|
with := d.Decide(context.Background(), inRecall([]string{"帮我查一下资料", "还有吗"}, recall))
|
|
if with.State != StateAwake {
|
|
t.Fatalf("with-recall state = %s, want awake", with.State)
|
|
}
|
|
if with.Confidence <= without.Confidence {
|
|
t.Fatalf("with-recall confidence = %v, want > baseline %v", with.Confidence, without.Confidence)
|
|
}
|
|
}
|
|
|
|
func TestDecide_L4_MissingRecall_FallsBackToL1L2(t *testing.T) {
|
|
d := New(activeConfig())
|
|
// No recall (nil or empty) keeps L1/L2 behavior identical.
|
|
base := d.Decide(context.Background(), in("帮我查一下资料"))
|
|
nilRecall := d.Decide(context.Background(), inRecall([]string{"帮我查一下资料"}, nil))
|
|
emptyRecall := d.Decide(context.Background(), inRecall([]string{"帮我查一下资料"}, []RecallItem{}))
|
|
for _, got := range []Decision{nilRecall, emptyRecall} {
|
|
if got.State != base.State || got.Confidence != base.Confidence {
|
|
t.Fatalf("no-recall = state %s conf %v, want baseline %s %v",
|
|
got.State, got.Confidence, base.State, base.Confidence)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecide_L4_IgnoresNonAllowedScope(t *testing.T) {
|
|
d := New(activeConfig())
|
|
// A recalled item outside the allowed scopes (stranger / secret) must not feed L4
|
|
// even when it leans task and shares the phrase — 只读用户授权范围记忆.
|
|
for _, scope := range []string{"stranger", "secret", "group"} {
|
|
recall := []RecallItem{{
|
|
Content: "用户习惯:他说搞一下就是要我办事",
|
|
Scope: scope,
|
|
Score: 0.9,
|
|
}}
|
|
dec := d.Decide(context.Background(), inRecall([]string{"搞一下那个部署"}, recall))
|
|
if dec.State != StateQuiet {
|
|
t.Fatalf("scope %q leaked into L4: state = %s, want quiet", scope, dec.State)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecide_L4_NonTaskHabit_StaysConservative(t *testing.T) {
|
|
d := New(activeConfig())
|
|
// A recalled preference that is not a task habit (coffee) must not wake a neutral
|
|
// turn — 默认保守 still holds.
|
|
recall := []RecallItem{{
|
|
Content: "用户习惯:他喜欢喝冰美式",
|
|
Scope: "user",
|
|
Score: 0.9,
|
|
}}
|
|
dec := d.Decide(context.Background(), inRecall([]string{"今天喝冰美式了吗"}, recall))
|
|
if dec.State != StateQuiet {
|
|
t.Fatalf("non-task habit woke to %s, want quiet", dec.State)
|
|
}
|
|
}
|
|
|
|
func TestDecide_L4_UndecidedNoHabit_ConservativeQuiet(t *testing.T) {
|
|
d := New(activeConfig())
|
|
dec := d.Decide(context.Background(), inRecall([]string{"嗯", "这个", "然后"}, nil))
|
|
if dec.State != StateQuiet {
|
|
t.Fatalf("undecided state = %s, want quiet (conservative)", dec.State)
|
|
}
|
|
}
|
|
|
|
func TestRecordIntent_TaskFormatsParses(t *testing.T) {
|
|
d := New(activeConfig())
|
|
j := Judgment{IsTask: true, TaskKind: taskKindQuery, Confidence: 0.9, Text: "帮我查一下今天的天气;再跑个测试"}
|
|
rec := d.RecordIntent(j)
|
|
if rec == "" {
|
|
t.Fatal("RecordIntent of a task judgment returned empty")
|
|
}
|
|
got, ok := parseIntentRecord(rec)
|
|
if !ok {
|
|
t.Fatalf("parseIntentRecord failed on %q", rec)
|
|
}
|
|
if !got.isTask || got.taskKind != taskKindQuery || got.text != j.Text {
|
|
t.Fatalf("round-trip mismatch: %+v, want isTask=true kind=query text=%q", got, j.Text)
|
|
}
|
|
if got.confidence != j.Confidence {
|
|
t.Fatalf("confidence = %v, want %v", got.confidence, j.Confidence)
|
|
}
|
|
}
|
|
|
|
func TestRecordIntent_NonTaskReturnsEmpty(t *testing.T) {
|
|
d := New(activeConfig())
|
|
if rec := d.RecordIntent(Judgment{IsTask: false, Text: "你好"}); rec != "" {
|
|
t.Fatalf("non-task judgment persisted %q, want empty (nothing worth learning)", rec)
|
|
}
|
|
}
|
|
|
|
func TestDecide_JudgmentPopulated(t *testing.T) {
|
|
d := New(activeConfig())
|
|
task := d.Decide(context.Background(), in("帮我查一下资料"))
|
|
if !task.Judgment.IsTask || task.Judgment.TaskKind != taskKindQuery {
|
|
t.Fatalf("task judgment = %+v, want IsTask=true kind=query", task.Judgment)
|
|
}
|
|
if task.Judgment.Text != "帮我查一下资料" {
|
|
t.Fatalf("judgment text = %q, want the current turn", task.Judgment.Text)
|
|
}
|
|
quiet := d.Decide(context.Background(), in("你好"))
|
|
if quiet.Judgment.IsTask || quiet.Judgment.TaskKind != "" {
|
|
t.Fatalf("quiet judgment = %+v, want IsTask=false kind empty", quiet.Judgment)
|
|
}
|
|
}
|