fix(pet): each user's pet tells only of their own sessions

An admin/analyst's pet counted every user's sessions (live, waiting, errors)
because their WebSocket carries everyone's. Server mode now marks their
session/agent/event frames `mine: true|false` (scope.broadcastAudience owners,
worked out once per broadcast), and GET /api/sessions?mine=1 lists only the
caller's own sessions (ownSessionWhere). The pet seeds from mine=1 and skips
frames marked mine: false. Developers and local mode are unchanged.
This commit is contained in:
2026-11-03 09:01:57 +07:00
parent 9721941962
commit c229b4b79b
9 changed files with 186 additions and 4 deletions
@@ -200,6 +200,20 @@ describe("Sagi widget", () => {
expect(within(liveChip).getByText("2")).toBeInTheDocument();
});
it("counts only the viewer's own sessions (another user's arrive marked mine: false)", () => {
renderSagi();
act(() => {
eventBus.publish({ ...sessionMsg("mine", "active"), mine: true });
eventBus.publish({ ...sessionMsg("theirs", "active"), mine: false });
eventBus.publish({ ...sessionMsg("theirs-err", "error"), mine: false });
});
fireEvent.click(screen.getByRole("button", { name: /open sagi companion/i }));
const panel = screen.getByRole("dialog", { name: /chat with sagi/i });
const liveChip = within(panel).getByText("live").closest("span.rounded-full")! as HTMLElement;
expect(within(liveChip).getByText("1")).toBeInTheDocument();
expect(screen.queryByText(/theirs/)).toBeNull();
});
it("respects the enabled preference", () => {
localStorage.setItem("agent-dashboard-sagi-enabled", "false");
renderSagi();
+6 -2
View File
@@ -7,7 +7,8 @@
* burst of the same event into one count), the pet's one-shot animation
* "moment", and imperative controls (mute, clear alerts, pat, set thinking)
* for the UI shell. It also greets once per browser session and makes an
* idle pet fidget now and then.
* idle pet fidget now and then. It only ever counts the viewer's OWN sessions
* (`mine=1` seed, and frames marked `mine: false` skipped).
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
/* =============================================================================
@@ -278,7 +279,7 @@ export function useSagiBrain(): SagiBrain {
}, GREET_DELAY_MS);
};
api.sessions
.list({ status: "active", limit: 100 })
.list({ status: "active", limit: 100, mine: true })
.then((res) => {
if (cancelled) return;
update((prev) => seedSessions(prev, res.sessions, Date.now()));
@@ -294,6 +295,9 @@ export function useSagiBrain(): SagiBrain {
// Subscribe to the live stream and connection status.
useEffect(() => {
const unsubMsg = eventBus.subscribe((msg: WSMessage) => {
// Each user's pet tells only of their own sessions: an admin/analyst
// gets every user's, marked `mine: false` when not theirs.
if (msg.mine === false) return;
const prev = stateRef.current;
const { state: next, pulse, subject } = reduceSagi(prev, msg, Date.now());
// Most frames only move lastActivityAt, which the moods read at a 1 s