Files
Cyrene_v2/internal/services/backthink/trigger.go
T
AskaEth f91a83ff81
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(urgency): 通用紧急度通道(Declarer/Level + 只抬不压折算)+ IoT 首个生产者 + 她自己的结论走结构化标记穿透安静时段
2026-10-05 13:11:04 +08:00

166 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package backthink
import (
"fmt"
"git.yeij.top/AskaEth/Cyrene_v2/internal/bus"
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
"git.yeij.top/AskaEth/Cyrene_v2/internal/urgency"
)
// TriggerKind 归类唤醒一轮思考的原因,好让推送路径套用
// 正确的策略:心跳受门控管辖(60min≤1 / 冷却 / 按风格),
// 事件是主人确实想听到的真实信号、绕过频率门。
type TriggerKind int
const (
// TriggerHeartbeat 是周期性自主反思。它的主动推送受
// PushGate 限流,使主人绝不会被空洞的反思刷屏。
TriggerHeartbeat TriggerKind = iota
// TriggerEvent 是真实事件(IoT 设备变化、提醒、与主人相关的
// 系统事件)。它的推送绕过 PushGate——"该发就发"——只受
// 同一事件去重约束。
TriggerEvent
)
// String 返回用于日志与记录的稳定标签。
func (k TriggerKind) String() string {
switch k {
case TriggerEvent:
return "event"
case TriggerHeartbeat:
return "heartbeat"
default:
return fmt.Sprintf("triggerkind(%d)", int(k))
}
}
// 本服务为事件驱动思考订阅的总线主题。它们是
// design/12 §1 定义的契约:一次 IoT 状态变化、一次提醒触发,
// 或一次泛化的系统事件。
const (
// SubjectReminderTrigger 是提醒触发所用的总线主题。
SubjectReminderTrigger = "reminder.trigger"
// SubjectSystemEvent 是泛化系统事件的总线主题。
SubjectSystemEvent = "system.event"
)
// SystemEvent 是调用方可发布到 SubjectSystemEvent 以传达
// 系统通知的可选类型化负载。Import 默认为 Normal。在
// design/12 §6 策略一之下,低价值的系统通知(加入/退出 ping、泛化的
// 平台噪声)被降级为仅登记一条记录,不唤醒
// 一整轮思考;只有高重要度(与主人相关)的系统事件才思考,
// 因此繁忙的公开平台无法触发昂贵反思的洪流。
//
// 它是「载荷自带重要度」这条通用通道的既有形式;生产者的紧急度若以
// urgency.Declarer 声明(IoT 的设备跃迁即如此),由 triggerForEvent 折算到
// 同一个 Trigger.Import 字段上,两条形式因此落到同一套判定。
type SystemEvent struct {
Summary string
Import Importance
}
// Trigger 描述唤醒一轮思考的原因:来源子系统、
// 可读摘要与事件的估计重要度。
type Trigger struct {
Source string
Summary string
Import Importance
// Kind 记录本轮是周期性心跳还是真实事件,好让
// 推送路径能为真实事件绕过频率门(主人要求)。
Kind TriggerKind
}
// heartbeatTrigger 是周期性、低重要度的触发。低重要度意味着
// 该轮很少打扰用户,遵守 "默认不发送 chat 类型消息"。
func heartbeatTrigger() Trigger {
return Trigger{Source: "heartbeat", Summary: "periodic autonomous reflection", Import: ImportanceLow, Kind: TriggerHeartbeat}
}
// StartupSummary 是启动轮思考(裁决 07.4.4,design/12 §1)的触发摘要。它同时是
// context.go 里切换「拟人化启动消息」专属引导提示词的标识。
const StartupSummary = "process startup"
// startupTrigger 是进程启动后的第一轮思考(裁决 07.4.4):不等满一个心跳周期,
// 启动立即触发,并引导她以角色口吻产出一条拟人化启动问候(如「我回来啦」)。
// Kind 仍为 TriggerHeartbeat:启动轮的推送与心跳走完全相同的门与限频
// (PushGate / 打扰级联 / 活动门),进程反复重启也不会刷屏主人。
func startupTrigger() Trigger {
return Trigger{Source: "startup", Summary: StartupSummary, Import: ImportanceLow, Kind: TriggerHeartbeat}
}
// triggerForEvent 把总线事件映射为 Trigger:主题给出可读摘要与主题级默认重要度,
// 随后**通用通道**统一解出重要度 —— 任何事件载荷只要实现 urgency.Declarer
// (IoT 的 DeviceUpdate 是第一个使用者),它声明的紧急度就被承认;泛化系统事件沿用
// 它既有的 SystemEvent.Import 口径。两条形式最终都落在同一个 Trigger.Import 字段上,
// 判定侧(decide.go)因此只认档位、不认生产者。每个事件都打上 TriggerEvent,
// 因此其主动推送绕过心跳的 PushGate。
func triggerForEvent(e bus.Event) Trigger {
tr := triggerForSubject(e)
if d, ok := e.Payload.(urgency.Declarer); ok {
tr.Import = escalateImport(tr.Import, d.Urgency())
}
return tr
}
// triggerForSubject 给出各主题的可读摘要与主题级默认重要度(未声明紧急度的事件就
// 停在这一档)。它刻意不对任何生产者做专有判定:重要度已由通用通道统一解出。
func triggerForSubject(e bus.Event) Trigger {
switch e.Subject {
case iot.DeviceUpdateSubject:
if d, ok := e.Payload.(iot.DeviceUpdate); ok {
return Trigger{
Source: e.Subject,
Summary: fmt.Sprintf("device %s (%s) %s", d.Device.ID, d.Device.Type, d.Action),
Import: ImportanceNormal,
Kind: TriggerEvent,
}
}
return Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceNormal, Kind: TriggerEvent}
case SubjectReminderTrigger:
return Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceHigh, Kind: TriggerEvent}
case SubjectSystemEvent:
tr := Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceNormal, Kind: TriggerEvent}
if sy, ok := e.Payload.(SystemEvent); ok {
if sy.Summary != "" {
tr.Summary = sy.Summary
}
tr.Import = sy.Import
}
return tr
default:
return Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceNormal, Kind: TriggerEvent}
}
}
// importanceForUrgency 把生产者声明的紧急度折算为思考轮的重要度。它是通用通道与
// 判定侧之间**唯一**的换算点(design/12 §6 安全域豁免,裁决 1.8 / 07.4.2):零值
// Routine 折算 Normal —— 默认档永不获得穿透安静时段的资格;只有生产者显式声明
// critical 的安全域事件拿到 ImportanceCritical,从而穿透安静时段与工作 / 忙碌。
func importanceForUrgency(l urgency.Level) Importance {
switch l {
case urgency.LevelCritical:
return ImportanceCritical
case urgency.LevelWarning:
return ImportanceHigh
default:
return ImportanceNormal
}
}
// escalateImport 把声明的紧急度叠到基础重要度上。声明只能**上抬**、绝不压低,因此
// 主题级默认(如提醒的 High)与安全域事件既得的档位不会被一句 routine 抹掉;
// 未声明(零值 LevelRoutine)时原样返回 base,没标紧急度的事件行为逐字不变。
//
// 两个调用方共用它:总线事件的载荷声明(triggerForEvent),以及她自己在主动思考轮
// 结论里声明「我认为这条紧急」(backthink.go 的 escalatedImport)。
func escalateImport(base Importance, declared urgency.Level) Importance {
if declared == urgency.LevelRoutine {
return base
}
if up := importanceForUrgency(declared); up > base {
return up
}
return base
}