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 } } // urgencyForImportance 是 importanceForUrgency 的**反向读法**:把一轮思考**实际生效** // 的重要度还原成通用紧急度词汇,供存档携带精确档位(X2a;面板此前只能给出「下限」)。 // // 它复用同一张既有对应关系(Critical↔critical、High↔warning),不引入任何新的门限或 // 折算规则:例行档对应 importanceForUrgency 的 Normal,而 ImportanceLow 只是「比例行 // 还安静」的心跳/启动轮 —— urgency 词汇里没有更低的档,故与 Normal 同归 routine // (无损:routine 本就表示「安静时段照常扣下」这一类)。按档位高低比较而不是逐值 // 相等,是让将来插入更高档时它落在最高档、而不是静默掉回 routine。 // // 取数点在 backthink.go 的 think:入参是该轮**实际生效**的重要度 // (escalatedImport 的结果,她自报的紧急度已折叠在内)。 func urgencyForImportance(i Importance) urgency.Level { switch { case i >= ImportanceCritical: return urgency.LevelCritical case i >= ImportanceHigh: return urgency.LevelWarning default: return urgency.LevelRoutine } } // 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 }