【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。
301 lines
11 KiB
Go
301 lines
11 KiB
Go
package capacity
|
|
|
|
import "strings"
|
|
|
|
// defaultQuietTools is the companion-safe face (design §1): terminal + file edit
|
|
// + memory/emotion companion. It is a function building the slice fresh so no
|
|
// package-level global slice is introduced (project rule §10).
|
|
func defaultQuietTools() []string {
|
|
return []string{"terminal", "file", "knowledge", "text"}
|
|
}
|
|
|
|
// defaultAwakeTools extends the quiet face with web/network retrieval (design
|
|
// §1 唤醒态): a real task wakes the retrieval tools, file and terminal.
|
|
func defaultAwakeTools() []string {
|
|
return []string{"terminal", "file", "knowledge", "text", "web-search", "web-fetch", "http"}
|
|
}
|
|
|
|
// intent classifies the round. It runs L1 on the current turn, falls to L2 on
|
|
// the recent context trend, and folds in the L4 learned-habit verdict (design §2.2)
|
|
// at each layer: L4 may correct a decided result (boost / quiet→awake) or fill in
|
|
// when L1/L2 are undecided. With no matching habit L4 is silent, so a turn with no
|
|
// recall behaves exactly as before; otherwise the result stays conservative (低置信
|
|
// still rests via applyAwakening).
|
|
func (d *Decider) intent(in Input) (State, float64, string) {
|
|
if len(in.Messages) == 0 {
|
|
return "", 0, ""
|
|
}
|
|
current := in.Messages[len(in.Messages)-1]
|
|
l4s, l4c, l4t, l4ok := d.l4(current, in.Recall)
|
|
if s, c, t, ok := l1(current); ok {
|
|
return d.mergeL4(s, c, t, l4s, l4c, l4t, l4ok)
|
|
}
|
|
if s, c, t, ok := l2(in.Messages); ok {
|
|
return d.mergeL4(s, c, t, l4s, l4c, l4t, l4ok)
|
|
}
|
|
if l4ok {
|
|
return l4s, l4c, l4t
|
|
}
|
|
return "", 0.3, "" // 拿不准 → conservative fallback
|
|
}
|
|
|
|
// l1 is the fast heuristic on the current turn (design §2.2 L1): a strong
|
|
// action signal wakes (awake, or full for complex work); a clear companion
|
|
// signal (weak + no task object) stays quiet; otherwise it is undecided and the
|
|
// caller defers to L2.
|
|
func l1(current string) (State, float64, string, bool) {
|
|
if hasStrongSignal(current) {
|
|
if isComplex(current) {
|
|
return StateFull, 0.9, inferTool(current), true
|
|
}
|
|
return StateAwake, 0.9, inferTool(current), true
|
|
}
|
|
if isClearCompanion(current) {
|
|
return StateQuiet, 0.8, "", true
|
|
}
|
|
return "", 0, "", false
|
|
}
|
|
|
|
// l2 looks at the recent context trend (design §2.2 L2) when L1 is undecided:
|
|
// a session firmly leaning task (or a follow-up on the previous task) wakes;
|
|
// a session firmly leaning emotional keeps quiet; a balanced window defers.
|
|
func l2(messages []string) (State, float64, string, bool) {
|
|
window := lastN(messages, 6)
|
|
task, comp := 0, 0
|
|
lastTask := ""
|
|
for _, m := range window {
|
|
switch {
|
|
case hasStrongSignal(m):
|
|
task++
|
|
lastTask = m
|
|
case isClearCompanion(m):
|
|
comp++
|
|
case hasFollowup(m):
|
|
task++
|
|
}
|
|
}
|
|
if task > comp {
|
|
if isComplex(lastTask) {
|
|
return StateFull, 0.66, inferTool(lastTask), true
|
|
}
|
|
return StateAwake, 0.66, inferTool(lastTask), true
|
|
}
|
|
if comp > task {
|
|
return StateQuiet, 0.7, "", true
|
|
}
|
|
return "", 0, "", false
|
|
}
|
|
|
|
// lastN returns the last n elements of messages (or all when shorter).
|
|
func lastN(messages []string, n int) []string {
|
|
if len(messages) <= n {
|
|
return messages
|
|
}
|
|
return messages[len(messages)-n:]
|
|
}
|
|
|
|
// hasStrongSignal reports a task-verb present together with either an
|
|
// imperative marker (帮我/请/去/把/将) or a concrete task object (file path,
|
|
// URL, code, a task noun) — 明确的动作指令 + 任务对象 (design §2.2 L1).
|
|
func hasStrongSignal(text string) bool {
|
|
if !hasStrongVerb(text) {
|
|
return false
|
|
}
|
|
return hasImperativeMarker(text) || hasTaskObject(text)
|
|
}
|
|
|
|
// hasStrongVerb reports an action verb that implies work (查/改/写/搜/跑/部署…).
|
|
func hasStrongVerb(text string) bool {
|
|
return containsAny(text, strongVerbs())
|
|
}
|
|
|
|
// strongVerbs is the L1 action-verb vocabulary.
|
|
func strongVerbs() []string {
|
|
return []string{
|
|
"帮我", "查", "查询", "查一下", "搜索", "搜", "搜一下", "查找", "找一下", "找",
|
|
"看", "看看", "检查", "核对", "改", "修改", "编辑", "写", "编写", "创建",
|
|
"生成", "实现", "配置", "修复", "删除", "移除", "升级", "更新", "搭建",
|
|
"部署", "迁移", "重构", "运行", "执行", "跑", "编译", "构建", "安装",
|
|
"启动", "重启", "停止", "下载", "上传", "翻译", "计算", "对比", "分析",
|
|
"总结", "汇报", "审批", "编排",
|
|
// ASCII
|
|
"help me", "search", "query", "look up", "find", "edit", "modify",
|
|
"write", "create", "generate", "fix", "refactor", "migrate", "deploy",
|
|
"build", "run", "compile", "install", "start", "restart", "stop", "test",
|
|
"analyze", "calculate", "compare", "please",
|
|
}
|
|
}
|
|
|
|
// hasImperativeMarker reports a direct instruction cue (帮我/请/去/把/将/麻烦…).
|
|
func hasImperativeMarker(text string) bool {
|
|
return containsAny(text, imperativeMarkers())
|
|
}
|
|
|
|
func imperativeMarkers() []string {
|
|
return []string{
|
|
"帮我", "请", "给我", "麻烦你", "你能", "你可不可以", "去", "把", "将",
|
|
"帮忙", "拜托", "帮我一下",
|
|
// ASCII
|
|
"please", "help me", "can you", "could you", "would you",
|
|
}
|
|
}
|
|
|
|
// hasTaskObject reports a concrete task object: a file path, URL, code snippet,
|
|
// or an object noun (报告/文档/代码/测试/设备…).
|
|
func hasTaskObject(text string) bool {
|
|
return hasFileObject(text) || hasURLObject(text) || hasCodeObject(text) || hasTaskNoun(text)
|
|
}
|
|
|
|
// hasFileObject reports a path-like target (a known source extension, a drive
|
|
// path, a "./" or "../" relative path, a bare filename such as cx.go).
|
|
func hasFileObject(text string) bool {
|
|
for _, ext := range fileExtensions() {
|
|
if strings.Contains(text, ext) {
|
|
return true
|
|
}
|
|
}
|
|
if strings.Contains(text, "./") || strings.Contains(text, "../") {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// fileExtensions is the source/config extension set used for object detection.
|
|
func fileExtensions() []string {
|
|
return []string{
|
|
".go", ".ts", ".tsx", ".js", ".jsx", ".py", ".rs", ".java", ".c", ".cpp",
|
|
".h", ".yaml", ".yml", ".json", ".toml", ".ini", ".conf", ".mod", ".sum",
|
|
".md", ".txt", ".css", ".html", ".sh", ".sql", ".proto", ".env.example",
|
|
}
|
|
}
|
|
|
|
// hasURLObject reports an http(s) URL or a bare web host.
|
|
func hasURLObject(text string) bool {
|
|
return containsAny(text, []string{"http://", "https://", "www.",
|
|
".com", ".cn", ".net", ".org", ".io", ".dev", ".xyz", ".ai"})
|
|
}
|
|
|
|
// hasCodeObject reports a code snippet (backticks, brace pairs, declarations).
|
|
func hasCodeObject(text string) bool {
|
|
return containsAny(text, []string{"`", "func ", "def ", "return ", ":= ",
|
|
"class ", "import ", "package ", "const ", "var ", "=>", "};"})
|
|
}
|
|
|
|
// hasTaskNoun reports a concrete work object noun.
|
|
func hasTaskNoun(text string) bool {
|
|
return containsAny(text, taskNouns())
|
|
}
|
|
|
|
// taskNouns is the object-noun vocabulary for object detection.
|
|
func taskNouns() []string {
|
|
return []string{
|
|
"报告", "文档", "代码", "脚本", "测试", "文件", "项目", "结果", "数据",
|
|
"接口", "配置", "日志", "表格", "图片", "网页", "页面", "服务器", "服务",
|
|
"设备", "手机", "电脑", "城市", "天气", "新闻", "价格", "股票", "汇率",
|
|
"系统", "数据库", "模型", "论文", "资料", "链接",
|
|
// ASCII
|
|
"report", "doc", "code", "script", "test", "file", "project", "server",
|
|
"device", "config", "log", "url", "api", "database", "page",
|
|
}
|
|
}
|
|
|
|
// isClearCompanion reports a pure companionship turn: a weak (emotional/greeting)
|
|
// signal with no task verb and no task object (设计 §2.2 L1 弱信号→静息).
|
|
func isClearCompanion(text string) bool {
|
|
if !hasWeakSignal(text) {
|
|
return false
|
|
}
|
|
if hasStrongSignal(text) {
|
|
return false
|
|
}
|
|
return !hasTaskObject(text)
|
|
}
|
|
|
|
// hasWeakSignal reports a companion-leaning cue (问候/情绪词/日常寒暄).
|
|
func hasWeakSignal(text string) bool {
|
|
return containsAny(text, weakSignals())
|
|
}
|
|
|
|
// weakSignals is the L1 companion vocabulary.
|
|
func weakSignals() []string {
|
|
return []string{
|
|
"你好", "嗨", "哈喽", "早上好", "早安", "中午好", "下午好", "晚上好",
|
|
"晚安", "在吗", "在不在", "抱抱", "么么", "亲亲", "摸摸", "想你",
|
|
"爱你", "喜欢你", "想念", "我好", "我累", "累了", "疲惫", "困了",
|
|
"无聊", "寂寞", "失眠", "睡不着", "难过", "伤心", "开心", "高兴",
|
|
"哈哈", "嘿嘿", "呜呜", "555", "哭", "笑", "心疼", "安慰", "谢谢",
|
|
"辛苦", "再见", "明天见", "待会见", "怎么了", "在干嘛", "干嘛呢",
|
|
"好呀", "好的", "好哒", "嗯嗯", "嗯", "好", "喔", "哦", "呀",
|
|
// ASCII
|
|
"hi", "hello", "hey", "good morning", "good night", "good evening",
|
|
"hug", "miss you", "love you", "haha", "lol", "aww", "ty", "thanks",
|
|
"bye", "thinking", "sleepy", "tired", "happy", "mood", "nice",
|
|
}
|
|
}
|
|
|
|
// hasFollowup reports a continuation cue pointing at the previous task
|
|
// (设计 §2.2 L2: 上一轮任务的跟进 继续/然后呢/还有吗).
|
|
func hasFollowup(text string) bool {
|
|
return containsAny(text, followups())
|
|
}
|
|
|
|
func followups() []string {
|
|
return []string{
|
|
"继续", "然后呢", "还有吗", "还有呢", "接着", "下一步", "接下来",
|
|
"之后呢", "那然后", "继续做", "继续写", "继续说", "再来", "再继续",
|
|
// ASCII
|
|
"continue", "and then", "next", "what else", "also", "go on", "more",
|
|
}
|
|
}
|
|
|
|
// isComplex reports whether a task is complex enough for the full state
|
|
// (设计 §1 全能态·复杂任务): refactor/migrate/deploy/whole-project/multi-file…
|
|
func isComplex(text string) bool {
|
|
return containsAny(text, complexMarkers())
|
|
}
|
|
|
|
func complexMarkers() []string {
|
|
return []string{
|
|
"重构", "搭建", "部署", "迁移", "架构", "实现一个", "写一个", "做一个",
|
|
"整个项目", "全部", "所有", "批量", "多文件", "多个文件", "全量",
|
|
"综合", "一体化", "测试全部", "修复所有", "上下链路", "系统设计",
|
|
// ASCII
|
|
"refactor", "migrate", "architect", "build a", "full", "all the",
|
|
"multiple files", "entire", "whole", "batch",
|
|
}
|
|
}
|
|
|
|
// inferTool names the tool that a task turn would light up, used for the
|
|
// capacity_awaken event's Tool field (点亮工具). Order: file path/code → file,
|
|
// query/search → web-search, else terminal.
|
|
func inferTool(text string) string {
|
|
switch {
|
|
case hasFileObject(text), hasCodeObject(text):
|
|
return "file"
|
|
case containsAny(text, []string{
|
|
"查", "查询", "搜", "搜索", "查找", "找一下", "search", "query",
|
|
"look up", "find", "查一下", "搜索一下",
|
|
}):
|
|
return "web-search"
|
|
case containsAny(text, []string{
|
|
"跑", "运行", "执行", "部署", "编译", "构建", "安装", "启动", "重启",
|
|
"测试", "run", "deploy", "build", "compile", "install", "start",
|
|
"restart", "exec", "test",
|
|
}):
|
|
return "terminal"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// containsAny reports whether text contains any of words (a simple substring
|
|
// match; every word is a UTF-8 substring).
|
|
func containsAny(text string, words []string) bool {
|
|
for _, w := range words {
|
|
if strings.Contains(text, w) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|