package backthink import ( "fmt" "git.yeij.top/AskaEth/Cyrene_v2/internal/bus" "git.yeij.top/AskaEth/Cyrene_v2/internal/iot" ) // TriggerKind classifies what woke a thinking pass, so the push path can apply the // right policy: a heartbeat is gate-governed (60min≤1 / cooldown / per-flavor), an // event is a real signal the owner wants to hear about and bypasses the rate gate. type TriggerKind int const ( // TriggerHeartbeat is the periodic autonomous reflection. Its proactive push is // throttled by the PushGate so the owner is never spammed by empty reflections. TriggerHeartbeat TriggerKind = iota // TriggerEvent is a real event (IoT device change, reminder, owner-relevant // system event). Its push bypasses the PushGate — "该发就发" — subject only to // same-event dedup. TriggerEvent ) // String returns a stable label for logs and records. func (k TriggerKind) String() string { switch k { case TriggerEvent: return "event" case TriggerHeartbeat: return "heartbeat" default: return fmt.Sprintf("triggerkind(%d)", int(k)) } } // Bus subjects the service subscribes to for event-triggered thinking. They are // the contract defined by design/12 §1: an IoT state change, a reminder firing, // or a generic system event. const ( // SubjectReminderTrigger is the bus subject on which reminders fire. SubjectReminderTrigger = "reminder.trigger" // SubjectSystemEvent is the bus subject for generic system events. SubjectSystemEvent = "system.event" ) // SystemEvent is the optional typed payload a caller may publish to // SubjectSystemEvent to convey a system notice. Import defaults to Normal. Under // design/12 §6 策略一, a low-value system notice (a join/leave ping, generic // platform noise) is downgraded to a registration-only record and does not wake a // full thinking pass; only a high-importance (owner-relevant) system event thinks, // so a busy public platform cannot trigger a flood of expensive reflections. type SystemEvent struct { Summary string Import Importance } // Trigger describes what woke a thinking pass: the source subsystem, a // human-readable summary and the event's estimated importance. type Trigger struct { Source string Summary string Import Importance // Kind records whether this pass was the periodic heartbeat or a real event, so // the push path can bypass the rate gate for a genuine event (owner mandate). Kind TriggerKind } // heartbeatTrigger is the periodic, low-importance trigger. Low importance means // the pass rarely interrupts the user, honouring "默认不发送 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 maps a bus event to a Trigger. An IoT device change is treated // as normal importance, a reminder firing as high importance, and a generic // system event as normal importance. Every event is tagged TriggerEvent so its // proactive push bypasses the heartbeat PushGate. func triggerForEvent(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} } }