feat(pet): cleared alerts stay cleared, contextual bubbles, i18n, moments, chat window
- Fix: an errored session alerted on every update, and Clear alerts only dropped it in memory, so its next update (or a reload) brought the alert straight back. An error now alerts on the transition into error only; cleared sessions are remembered (agenthub.pet.dismissedErrors) and stay quiet until they recover and fail anew. - Quips, greetings, notifications and Ask answers move to i18n (EN/VI); Ask understands Vietnamese keywords and names the sessions it counts. - The pet goes by the worn pet's name; click it in the panel to rename (agenthub.pet.names, synced to the account). - Bubbles name the session they are about, with an Open button (never a resume); a burst of the same event coalesces into one count. - Moments: a new session waves, a finish hops, a subagent peeks, a wait taps, an error flinches — on Sagi, Codex sprites and still images. Idle fidgets, a once-per-session greeting, a "Needs you" list, an amber waiting dot. - Pat (heart button, Ask, or stroking the pet) reacts on the pet itself, so it shows while the panel or chat is open. - Panel drops Run Claude / Activity / Sessions / Errored. - Ask hands off to a chat window (PetChat) on the default AI tool (Claude/Codex): transcript as bubbles, follow-ups and CLI menus through an off-screen attached terminal; no new server route. Verified on HEAD + this diff in a clean worktree: tsc -b, the full client suite (1334/1335; the one failure, ConversationView.follow, is flaky under load and passes 3/3 alone) and server me-prefs.test.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -225,6 +225,43 @@ describe("clearErrors", () => {
|
||||
expect(statusOf(s).liveCount).toBe(1);
|
||||
expect(s.worriedUntil).toBe(0);
|
||||
});
|
||||
|
||||
it("keeps a cleared session quiet while it stays errored", () => {
|
||||
let s = initialSagiState(T0);
|
||||
({ state: s } = reduceSagi(s, sessionMsg("b", "error"), T0));
|
||||
s = clearErrors(s);
|
||||
const again = reduceSagi(s, sessionMsg("b", "error", T0 + 1), T0 + 1);
|
||||
expect(again.pulse).toBeNull();
|
||||
expect(statusOf(again.state).errorCount).toBe(0);
|
||||
expect(again.state.worriedUntil).toBe(0);
|
||||
const ev = reduceSagi(
|
||||
again.state,
|
||||
{ type: "new_event", data: { session_id: "b", event_type: "toolError" }, timestamp: "" } as WSMessage,
|
||||
T0 + 2
|
||||
);
|
||||
expect(ev.pulse).toBeNull();
|
||||
expect(seedSessions(s, [{ id: "b", status: "error" }], T0).sessions.b).toBeUndefined();
|
||||
});
|
||||
|
||||
it("alerts again once a cleared session recovers and fails anew", () => {
|
||||
let s = initialSagiState(T0);
|
||||
({ state: s } = reduceSagi(s, sessionMsg("b", "error"), T0));
|
||||
s = clearErrors(s);
|
||||
({ state: s } = reduceSagi(s, sessionMsg("b", "active"), T0));
|
||||
expect(s.dismissed).not.toHaveProperty("b");
|
||||
const r = reduceSagi(s, sessionMsg("b", "error"), T0);
|
||||
expect(r.pulse).toBe("error");
|
||||
});
|
||||
});
|
||||
|
||||
describe("error alerts fire on the transition only", () => {
|
||||
it("does not re-alert on each update of a still-errored session", () => {
|
||||
let s = initialSagiState(T0);
|
||||
({ state: s } = reduceSagi(s, sessionMsg("a", "error"), T0));
|
||||
const r = reduceSagi(s, sessionMsg("a", "error"), T0 + 10_000);
|
||||
expect(r.pulse).toBeNull();
|
||||
expect(statusOf(r.state).errorCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// Reference the imported constant so it is exercised and tsc-clean.
|
||||
|
||||
Reference in New Issue
Block a user