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": "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": {