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:
2026-10-14 12:14:29 +07:00
parent 0c2f49876d
commit 0cb5e41faf
25 changed files with 2378 additions and 290 deletions
+183 -13
View File
@@ -37,29 +37,199 @@
"chooseDesc": "Featured pets come from codex-pets.net and are downloaded into this install the first time you pick one. Your choice and size are saved to your account.",
"uploadHint": "An image (PNG, JPEG, GIF, WebP — no SVG) is shown still; a .codex-pet.zip (pet.json + a 1536×1872 spritesheet.webp) is animated by mood.",
"panel": {
"label": "Sagi companion",
"label": "{{name}} companion",
"live": "Live",
"offline": "Offline",
"close": "Close Sagi",
"close": "Close {{name}}",
"statLive": "live",
"statWaiting": "waiting",
"statErrored": "errored",
"runClaude": "Run Claude",
"activity": "Activity",
"sessions": "Sessions",
"errored": "Errored",
"mute": "Mute",
"unmute": "Unmute",
"clearAlerts": "Clear alerts",
"askPlaceholder": "Ask Sagi… (e.g. any errors?)",
"ask": "Ask Sagi",
"send": "Send"
"askPlaceholder": "Ask {{name}}… (e.g. any errors?)",
"ask": "Ask {{name}}",
"send": "Send",
"rename": "Click to rename {{name}}",
"renameLabel": "Pet name",
"renameHint": "Enter to save · empty = default name",
"pat": "Pat {{name}}",
"attention": "Needs you",
"attentionError": "errored",
"attentionWaiting": "waiting on you",
"attentionMore": "+{{count}} more in Sessions",
"chat": "Chat with {{name}}"
},
"bubbleDismiss": "Dismiss",
"open": "Open Sagi companion",
"closeAvatar": "Close Sagi",
"avatarTitle": "Sagi — Ctrl/⌘+B · drag or arrow keys to move · right-click for pets",
"startFailed": "Couldn't start that session: {{error}}"
"open": "Open {{name}} companion",
"closeAvatar": "Close {{name}}",
"avatarTitle": "{{name}} — Ctrl/⌘+B · drag or arrow keys to move · stroke to pat · right-click for pets",
"startFailed": "Couldn't start that session: {{error}}",
"bubbleOpen": "Open",
"bubbleMore": "{{list}} +{{count}} more",
"notify": {
"session_done_one": "{{count}} session finished ✨",
"session_done_other": "{{count}} sessions finished ✨",
"session_start_one": "{{count}} new session started 👀",
"session_start_other": "{{count}} new sessions started 👀",
"subagent_spawn_one": "{{count}} subagent joined in 🤝",
"subagent_spawn_other": "{{count}} subagents joined in 🤝",
"waiting_one": "{{count}} session is waiting on you ⏳",
"waiting_other": "{{count}} sessions are waiting on you ⏳",
"error_one": "{{count}} error just popped up 😿",
"error_other": "{{count}} errors just popped up 😿",
"run_done_one": "{{count}} run finished 🐾",
"run_done_other": "{{count}} runs finished 🐾"
},
"greet": {
"morning": [
"good morning! ☀️",
"morning! coffee first? ☕",
"rise and shine 🌤️"
],
"afternoon": [
"good afternoon! 🐾",
"hey, welcome back 😺",
"afternoon! ready when you are ✨"
],
"evening": [
"good evening! 🌆",
"evening shift? I'm in 😼",
"hey there, night owl-to-be 🌙"
],
"night": [
"burning the midnight oil? 🌙",
"late one, huh? I'll keep watch 🦉",
"still up? don't forget to rest 💤"
],
"summary_one": "{{count}} session live",
"summary_other": "{{count}} sessions live",
"summaryWaiting": "{{count}} waiting on you",
"summaryErrors": "{{count}} errored"
},
"pat": [
"purr… 💜",
"hehe, that tickles 😸",
"*happy wiggle* ✨",
"more pats please 🐾",
"best human 😻"
],
"intent": {
"empty": "ask me about your sessions - what's running, any errors, or a quick status.",
"help": "I'm {{name}} - I watch your sessions. Try \"what's running\", \"any errors\", \"what's waiting\" or \"status\". Anything else, I'll hand to Claude.",
"whoami": "I'm {{name}}, your session-watching buddy 🐾 Right-click me to change my look, or click my name in the panel to rename me.",
"hello": "hi there! 😺 {{summary}}.",
"thanks": [
"anytime! 💜",
"happy to help 😸",
"you got it 🐾"
],
"errors_one": "{{count}} session errored{{list}} - open the panel to jump to it.",
"errors_other": "{{count}} sessions errored{{list}} - open the panel to jump to them.",
"errorsNone": "no errors - all clean 🐾",
"waiting_one": "{{count}} session waiting on you{{list}} 👀",
"waiting_other": "{{count}} sessions waiting on you{{list}} 👀",
"waitingNone": "nothing's waiting on you right now 🐾",
"live_one": "{{count}} session live right now 🐾{{tail}}",
"live_other": "{{count}} sessions live right now 🐾{{tail}}",
"liveTail": " ({{count}} waiting on you 👀)",
"liveNone": "nothing's running right now - all quiet.",
"status": "{{live}} live · {{waiting}} waiting · {{errors}} errored · {{conn}}.",
"connected": "connected",
"offline": "offline"
},
"quips": {
"session_done": [
"a session just wrapped up! 🐾",
"a session finished - nice work! ✨",
"that session's all done 😺",
"clean finish on that one 💜"
],
"session_start": [
"a new session started! 👀",
"a fresh session just landed 🐾",
"ooh, a new session to watch 😻",
"something new is cooking 🍲"
],
"subagent_spawn": [
"a subagent just spawned! 🐾",
"a little helper joined in 🤝",
"a subagent's on the job 🚀",
"reinforcements - new subagent! 😺"
],
"waiting": [
"a session needs your input 👀",
"a session is waiting on you ⏳",
"a session paused for your reply 💬",
"your turn - a session's waiting 🐾"
],
"error": [
"uh oh, a session hit an error 😿",
"something broke - wanna peek? 🙀",
"a hook tripped on something ⚠️",
"hiss… an error popped up 💢"
],
"run_done": [
"your run just finished! 🐾",
"the run's all wrapped up ✨",
"run complete - that's a wrap 😸",
"all done with that run 💜"
],
"disconnected": [
"lost the connection… 😴",
"can't reach the server 📡",
"no signal - taking a nap 💤"
],
"worried": [
"that didn't look right 😟",
"keeping an eye out 👀",
"hmm, something's off 🫣"
],
"stuck": [
"a session's been quiet a while… 🤔",
"is something stuck? ⏳",
"still chewing on it… 😾"
],
"happy": [
"great run! 😻",
"love a tidy finish ✨",
"purrfect 💜"
],
"thinking": [
"hmm, let me look… 🤔",
"sniffing around… 🐾",
"one sec, checking 🔍"
],
"watching": [
"on the prowl 👀",
"watching your sessions 😼",
"eyes peeled 🐾"
],
"sleeping": [
"zzz… 💤",
"wake me if something happens 😴",
"curled up, all calm 🐈"
],
"idle": [
"all quiet 😺",
"ready when you are 🐾",
"just vibing ✨"
]
},
"chat": {
"label": "Chat with {{name}}",
"back": "Back",
"close": "Close {{name}}",
"new": "New chat",
"openWorkspace": "Open in workspace",
"typing": "typing…",
"placeholder": "Ask {{name}} anything…",
"emptyTitle": "Hi! I'm {{name}} 🐾",
"emptyHint": "Ask me anything — I'll get {{tool}} to answer right here.",
"starting": "Starting {{tool}}…",
"notStarted": "Waiting for the session to start — if it asks something, open it in the workspace.",
"notSent": "Couldn't reach the session's terminal — try again, or open it in the workspace."
}
},
"pricing": {
"vendor": {
+183 -13
View File
@@ -37,29 +37,199 @@
"chooseDesc": "Pet nổi bật lấy từ codex-pets.net và được tải về máy ở lần đầu bạn chọn. Lựa chọn và kích thước được lưu vào tài khoản của bạn.",
"uploadHint": "Ảnh (PNG, JPEG, GIF, WebP — không nhận SVG) hiển thị tĩnh; file .codex-pet.zip (pet.json + spritesheet.webp 1536×1872) chuyển động theo tâm trạng.",
"panel": {
"label": "Trợ lý Sagi",
"label": "Trợ lý {{name}}",
"live": "Trực tuyến",
"offline": "Ngoại tuyến",
"close": "Đóng Sagi",
"close": "Đóng {{name}}",
"statLive": "đang chạy",
"statWaiting": "đang chờ",
"statErrored": "lỗi",
"runClaude": "Chạy Claude",
"activity": "Hoạt động",
"sessions": "Phiên",
"errored": "Phiên lỗi",
"mute": "Tắt tiếng",
"unmute": "Bật tiếng",
"clearAlerts": "Xoá cảnh báo",
"askPlaceholder": "Hỏi Sagi… (vd: có lỗi nào không?)",
"ask": "Hỏi Sagi",
"send": "Gửi"
"askPlaceholder": "Hỏi {{name}}… (vd: có lỗi nào không?)",
"ask": "Hỏi {{name}}",
"send": "Gửi",
"rename": "Bấm để đổi tên {{name}}",
"renameLabel": "Tên pet",
"renameHint": "Enter để lưu · để trống = tên mặc định",
"pat": "Xoa đầu {{name}}",
"attention": "Cần bạn xem",
"attentionError": "bị lỗi",
"attentionWaiting": "đang chờ bạn",
"attentionMore": "+{{count}} phiên khác trong Phiên",
"chat": "Trò chuyện với {{name}}"
},
"bubbleDismiss": "Đóng",
"open": "Mở trợ lý Sagi",
"closeAvatar": "Đóng Sagi",
"avatarTitle": "Sagi — Ctrl/⌘+B · kéo hoặc dùng phím mũi tên để di chuyển · chuột phải để chọn pet",
"startFailed": "Không khởi động được phiên: {{error}}"
"open": "Mở trợ lý {{name}}",
"closeAvatar": "Đóng {{name}}",
"avatarTitle": "{{name}} — Ctrl/⌘+B · kéo hoặc dùng phím mũi tên để di chuyển · vuốt qua lại để xoa đầu · chuột phải để chọn pet",
"startFailed": "Không khởi động được phiên: {{error}}",
"bubbleOpen": "Mở",
"bubbleMore": "{{list}} và {{count}} phiên khác",
"notify": {
"session_done_one": "{{count}} phiên vừa xong ✨",
"session_done_other": "{{count}} phiên vừa xong ✨",
"session_start_one": "{{count}} phiên mới vừa bắt đầu 👀",
"session_start_other": "{{count}} phiên mới vừa bắt đầu 👀",
"subagent_spawn_one": "{{count}} subagent vừa vào việc 🤝",
"subagent_spawn_other": "{{count}} subagent vừa vào việc 🤝",
"waiting_one": "{{count}} phiên đang chờ bạn ⏳",
"waiting_other": "{{count}} phiên đang chờ bạn ⏳",
"error_one": "Vừa có {{count}} lỗi 😿",
"error_other": "Vừa có {{count}} lỗi 😿",
"run_done_one": "{{count}} lượt chạy vừa xong 🐾",
"run_done_other": "{{count}} lượt chạy vừa xong 🐾"
},
"greet": {
"morning": [
"chào buổi sáng! ☀️",
"sáng rồi! cà phê chưa? ☕",
"ngày mới tốt lành nha 🌤️"
],
"afternoon": [
"chào buổi chiều! 🐾",
"bạn quay lại rồi 😺",
"chiều nay làm gì đây? ✨"
],
"evening": [
"chào buổi tối! 🌆",
"ca tối hả? có mình đây 😼",
"tối rồi, cố lên nhé 🌙"
],
"night": [
"khuya rồi mà vẫn làm à? 🌙",
"để mình canh giúp cho 🦉",
"nhớ nghỉ ngơi nha 💤"
],
"summary_one": "{{count}} phiên đang chạy",
"summary_other": "{{count}} phiên đang chạy",
"summaryWaiting": "{{count}} phiên chờ bạn",
"summaryErrors": "{{count}} phiên lỗi"
},
"pat": [
"grừ grừ… 💜",
"hihi, nhột quá 😸",
"*vẫy đuôi* ✨",
"xoa nữa đi mà 🐾",
"chủ nhân tuyệt nhất 😻"
],
"intent": {
"empty": "hỏi mình về các phiên nhé - đang chạy gì, có lỗi không, hay tình hình chung.",
"help": "Mình là {{name}} - mình canh các phiên của bạn. Thử hỏi \"đang chạy gì\", \"có lỗi không\", \"cái gì đang chờ\" hoặc \"tình hình\". Câu khác mình sẽ chuyển cho Claude.",
"whoami": "Mình là {{name}}, bạn đồng hành canh phiên của bạn 🐾 Chuột phải vào mình để đổi ngoại hình, hoặc bấm vào tên trong bảng để đặt tên mới.",
"hello": "chào bạn! 😺 {{summary}}.",
"thanks": [
"có gì đâu! 💜",
"rất vui được giúp 😸",
"chuyện nhỏ 🐾"
],
"errors_one": "{{count}} phiên bị lỗi{{list}} - mở bảng để xem nhé.",
"errors_other": "{{count}} phiên bị lỗi{{list}} - mở bảng để xem nhé.",
"errorsNone": "không có lỗi nào - sạch sẽ 🐾",
"waiting_one": "{{count}} phiên đang chờ bạn{{list}} 👀",
"waiting_other": "{{count}} phiên đang chờ bạn{{list}} 👀",
"waitingNone": "hiện không có gì chờ bạn cả 🐾",
"live_one": "{{count}} phiên đang chạy 🐾{{tail}}",
"live_other": "{{count}} phiên đang chạy 🐾{{tail}}",
"liveTail": " ({{count}} phiên chờ bạn 👀)",
"liveNone": "hiện không có gì đang chạy - yên ắng.",
"status": "{{live}} đang chạy · {{waiting}} đang chờ · {{errors}} lỗi · {{conn}}.",
"connected": "đã kết nối",
"offline": "mất kết nối"
},
"quips": {
"session_done": [
"một phiên vừa xong! 🐾",
"xong một phiên rồi - giỏi lắm! ✨",
"phiên đó xong xuôi rồi 😺",
"kết thúc gọn gàng 💜"
],
"session_start": [
"có phiên mới bắt đầu! 👀",
"một phiên mới vừa tới 🐾",
"ồ, có phiên mới để canh 😻",
"có gì đó đang được nấu 🍲"
],
"subagent_spawn": [
"một subagent vừa xuất hiện! 🐾",
"có trợ thủ nhỏ vào cuộc 🤝",
"subagent đã nhận việc 🚀",
"viện binh tới - subagent mới! 😺"
],
"waiting": [
"một phiên cần bạn trả lời 👀",
"có phiên đang chờ bạn ⏳",
"một phiên tạm dừng chờ bạn 💬",
"tới lượt bạn - có phiên đang chờ 🐾"
],
"error": [
"ối, một phiên bị lỗi 😿",
"có gì đó hỏng rồi - xem thử không? 🙀",
"một hook vừa vấp phải lỗi ⚠️",
"khè… lỗi xuất hiện 💢"
],
"run_done": [
"lượt chạy của bạn xong rồi! 🐾",
"lượt chạy đã hoàn tất ✨",
"chạy xong - hết phim 😸",
"xong lượt chạy đó rồi 💜"
],
"disconnected": [
"mất kết nối rồi… 😴",
"không gọi được máy chủ 📡",
"mất sóng - đi ngủ chút 💤"
],
"worried": [
"có vẻ không ổn 😟",
"mình đang để mắt đây 👀",
"hừm, có gì đó lạ lạ 🫣"
],
"stuck": [
"một phiên im lặng lâu rồi… 🤔",
"có gì bị kẹt không? ⏳",
"vẫn đang nhai… 😾"
],
"happy": [
"tuyệt vời! 😻",
"kết thúc gọn ghê ✨",
"hoàn hảo 💜"
],
"thinking": [
"để mình xem… 🤔",
"đang đánh hơi… 🐾",
"chờ chút, đang kiểm tra 🔍"
],
"watching": [
"đang tuần tra 👀",
"đang canh các phiên của bạn 😼",
"mắt mở to 🐾"
],
"sleeping": [
"zzz… 💤",
"có gì thì gọi mình dậy nhé 😴",
"cuộn tròn, mọi thứ yên ổn 🐈"
],
"idle": [
"yên ắng quá 😺",
"sẵn sàng khi bạn cần 🐾",
"chill thôi ✨"
]
},
"chat": {
"label": "Trò chuyện với {{name}}",
"back": "Quay lại",
"close": "Đóng {{name}}",
"new": "Cuộc trò chuyện mới",
"openWorkspace": "Mở trong workspace",
"typing": "đang trả lời…",
"placeholder": "Hỏi {{name}} bất cứ điều gì…",
"emptyTitle": "Chào bạn! Mình là {{name}} 🐾",
"emptyHint": "Hỏi gì cũng được — mình sẽ nhờ {{tool}} trả lời ngay tại đây.",
"starting": "Đang khởi động {{tool}}…",
"notStarted": "Đang chờ phiên khởi động — nếu nó hỏi gì, hãy mở trong workspace.",
"notSent": "Không gửi được tới terminal của phiên — thử lại, hoặc mở trong workspace."
}
},
"pricing": {
"vendor": {