feat(presence): F-5b presence-sync — mirror sm.Machine into Fabric
Adds an internal/presence package that ticks every 30s (configurable
via presence_interval_seconds), reads each bound agent's sm.Machine
state through host.ReadAgentState, maps to Fabric's 6-status enum,
and PUTs diffs to /api/agents/:userId/presence on every guild the
agent belongs to.
Semantic mapping (the part flagged "needed" in the prior README):
idle → idle
working → on_call
busy → busy
offline → offline
exhausted/unknown reserved for backend-side fallbacks; we don't push.
Tick is mutex-guarded (avoids the upsert race the openclaw incident
called out in agent-presence.service.ts) and diff-gated so writes are
sparse. Token-cache invalidation on PUT failure handles guild JWT
rotation.
fabric.Client gains SetAgentPresence helper. README marks F-5b ✅.
This commit is contained in:
21
internal/presence/presence_test.go
Normal file
21
internal/presence/presence_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package presence
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStateToFabricMapping(t *testing.T) {
|
||||
cases := []struct {
|
||||
in, want string
|
||||
}{
|
||||
{"idle", "idle"},
|
||||
{"working", "on_call"},
|
||||
{"busy", "busy"},
|
||||
{"offline", "offline"},
|
||||
{"", ""},
|
||||
{"weird", ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := StateToFabric(c.in); got != c.want {
|
||||
t.Errorf("StateToFabric(%q) = %q, want %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user