Files
Cyrene_v2/internal/runtime/capacity/signals.go
T
Aska 6dbb4a2e18
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat: 唤醒式架构批次——capacity/L4/会话编排/管理面板写CRUD/前端集成(77-81)
【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。
2026-09-06 21:54:40 +08:00

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
}