feat(backthink): 事件驱动主动消息绕过PushGate——IoT等事件该发就发(heartbeat仍受60min节流)
- 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 交叉编译通过
This commit is contained in:
1 parent
3e4efa0e41
commit
d09b25071d
6 files changed
+266
-22
No files matched your search
@@ -12,10 +12,12 @@
|
||||
// Each pass produces a conclusion, then consults Decide to judge whether the user
|
||||
// should be interrupted at all (event importance / time-of-day / user state such
|
||||
// as heart rate, sleep, work, busy / boredom). When it is worth interrupting the
|
||||
// push goes to the active client first and falls back to the IM bridge, bounded
|
||||
// by the proactive-push rate limit of DevDocs/design/11 §6. Every pass is
|
||||
// persisted to SQLite via Store so the M9/M10 monitor panel can render the
|
||||
// background-thinking page (design/12 §4).
|
||||
// push goes to the active client first and falls back to the IM bridge. The
|
||||
// proactive-push rate gate of DevDocs/design/11 §6 governs the periodic heartbeat
|
||||
// only; an event-driven push bypasses it (owner mandate: "该发就发") subject to
|
||||
// same-event dedup (see event_dedup.go). Every pass is persisted to SQLite via
|
||||
// Store so the M9/M10 monitor panel can render the background-thinking page
|
||||
// (design/12 §4).
|
||||
//
|
||||
// The package is concurrency-safe: a mutex guards the lifecycle state, the
|
||||
// injected store/pusher/thinker are required to be concurrency-safe by contract,
|
||||
@@ -110,6 +112,11 @@ type Backthink struct {
|
||||
cancel context.CancelFunc
|
||||
subs []func()
|
||||
wg sync.WaitGroup
|
||||
|
||||
// eventMu guards the same-event dedup guard (event_dedup.go). It is separate
|
||||
// from the lifecycle lock so a dedup decision never blocks a sink swap.
|
||||
eventMu sync.Mutex
|
||||
lastEventKey string
|
||||
}
|
||||
|
||||
// New builds a Backthink from cfg and deps, applying defaults and validating the
|
||||
@@ -356,10 +363,18 @@ func (b *Backthink) think(parent context.Context, tr Trigger) {
|
||||
|
||||
pushRes := PushResult{Route: RouteNone, Detail: b.interruptReason(push, at, state, tr.Import, lastActivity, hasActivity)}
|
||||
if shouldPush {
|
||||
pushRes = b.currentPusher().Push(cctx, PushRequest{
|
||||
Message: b.buildProactiveMessage(message, channelHint),
|
||||
ChannelHint: channelHint,
|
||||
})
|
||||
// Same-event dedup: a repeating identical event is suppressed after the
|
||||
// first push (防刷), while a genuinely new event still goes out. Heartbeats
|
||||
// are never deduped (they reset the chain) and stay gate-governed.
|
||||
if b.eventRedundant(tr) {
|
||||
pushRes = PushResult{Route: RouteNone, Detail: "duplicate event push suppressed"}
|
||||
} else {
|
||||
pushRes = b.currentPusher().Push(cctx, PushRequest{
|
||||
Message: b.buildProactiveMessage(message, channelHint),
|
||||
ChannelHint: channelHint,
|
||||
TriggerKind: tr.Kind,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
b.record(Record{
|
||||
@@ -371,7 +386,7 @@ func (b *Backthink) think(parent context.Context, tr Trigger) {
|
||||
})
|
||||
|
||||
if shouldPush {
|
||||
b.log.Info("backthink proactive push", "source", tr.Source, "route", pushRes.Route, "pushed", pushRes.Pushed, "reason", pushRes.Detail)
|
||||
b.log.Info("backthink proactive push", "source", tr.Source, "route", pushRes.Route, "pushed", pushRes.Pushed, "bypass", pushRes.Bypassed, "reason", pushRes.Detail)
|
||||
} else {
|
||||
b.log.Debug("backthink did not interrupt", "source", tr.Source, "reason", pushRes.Detail)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user