- trigger.go: 新增 TriggerKind(TriggerHeartbeat/TriggerEvent) + Trigger.Kind 标记触发来源 - push.go: PushRequest.TriggerKind 分流——event 跳过 gate.Allow/RecordPush(该发就发),heartbeat 仍受 60min≤1/冷却/分类限频;PushResult.Bypassed 标记 + detail=event_bypass 区分 - backthink.go: think() 传 TriggerKind + 过同事件去重门 - event_dedup.go(新): 同事件(Source+Summary)去重防刷,无 60min 节流;不同事件/心跳后重置链 - 测试: event 连续 3 次全推 bypass / heartbeat 第2次 rate_limited / event 不消耗心跳限 / 同事件去重 - build/vet/test 全绿 + windows/darwin 交叉编译通过
106 lines
3.9 KiB
Go
106 lines
3.9 KiB
Go
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}
|
|
}
|
|
|
|
// 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}
|
|
}
|
|
}
|