feat(hardening): virtual-familiar plugin fix, TTS guard, stale placeholder cleanup
- Update familiaros.virtual-familiar manifest id, permissions, and asset key to match the rebrand and the v3 SDK (familiar:* permissions). - Rename plugin asset virtual-pet.svg -> virtual-familiar.svg. - Migrate plugin code from ctx.pet to ctx.familiar and pet:clicked to familiar:clicked so speech/reactions actually work. - Update plugin locale strings from Pet to Familiar. - Add empty-text guard in speakTts to avoid speaking whitespace. - Remove stale "settings.tts.placeholder" key from all locale files.,
This commit is contained in:
parent
4d99cd32fe
commit
648aac86c2
13 changed files with 36 additions and 43 deletions
|
|
@ -279,7 +279,6 @@ export const en = {
|
|||
"settings.tts.eyebrow": "Voice",
|
||||
"settings.tts.title": "Text-to-Speech",
|
||||
"settings.tts.description": "Voice provider and controls for the familiar and floating chat.",
|
||||
"settings.tts.placeholder": "TTS provider settings will appear here in the next build step.",
|
||||
"settings.tts.provider.title": "Provider",
|
||||
"settings.tts.provider.description": "Voice engine used by the familiar and floating chat.",
|
||||
"settings.tts.voice.title": "Voice",
|
||||
|
|
|
|||
|
|
@ -273,7 +273,6 @@ export const es419: Partial<Messages> = {
|
|||
"settings.tts.eyebrow": "Voice",
|
||||
"settings.tts.title": "Text-to-Speech",
|
||||
"settings.tts.description": "Voice provider and controls for the familiar and floating chat.",
|
||||
"settings.tts.placeholder": "TTS provider settings will appear here in the next build step.",
|
||||
"settings.plugins.audio.title": "Los complementos pueden reproducir sonido",
|
||||
"settings.plugins.audio.description": "Permite tonos, alertas y sonidos incluidos de los complementos.",
|
||||
"settings.plugins.voice.title": "Los complementos pueden hablar (voz)",
|
||||
|
|
|
|||
|
|
@ -273,7 +273,6 @@ export const ja: Partial<Messages> = {
|
|||
"settings.tts.eyebrow": "Voice",
|
||||
"settings.tts.title": "Text-to-Speech",
|
||||
"settings.tts.description": "Voice provider and controls for the familiar and floating chat.",
|
||||
"settings.tts.placeholder": "TTS provider settings will appear here in the next build step.",
|
||||
"settings.plugins.audio.title": "プラグインによる音の再生を許可",
|
||||
"settings.plugins.audio.description": "プラグインのチャイム、アラート、付属サウンドを許可します。",
|
||||
"settings.plugins.voice.title": "プラグインによる音声発話を許可",
|
||||
|
|
|
|||
|
|
@ -273,7 +273,6 @@ export const ko: Partial<Messages> = {
|
|||
"settings.tts.eyebrow": "Voice",
|
||||
"settings.tts.title": "Text-to-Speech",
|
||||
"settings.tts.description": "Voice provider and controls for the familiar and floating chat.",
|
||||
"settings.tts.placeholder": "TTS provider settings will appear here in the next build step.",
|
||||
"settings.plugins.audio.title": "플러그인 소리 재생 허용",
|
||||
"settings.plugins.audio.description": "플러그인 알림음, 경고음, 번들 사운드를 허용합니다.",
|
||||
"settings.plugins.voice.title": "플러그인 음성 말하기 허용",
|
||||
|
|
|
|||
|
|
@ -273,7 +273,6 @@ export const ptBR: Partial<Messages> = {
|
|||
"settings.tts.eyebrow": "Voice",
|
||||
"settings.tts.title": "Text-to-Speech",
|
||||
"settings.tts.description": "Voice provider and controls for the familiar and floating chat.",
|
||||
"settings.tts.placeholder": "TTS provider settings will appear here in the next build step.",
|
||||
"settings.plugins.audio.title": "Plugins podem reproduzir som",
|
||||
"settings.plugins.audio.description": "Permitir sinos, alertas e sons incluídos dos plugins.",
|
||||
"settings.plugins.voice.title": "Plugins podem falar (voz)",
|
||||
|
|
|
|||
|
|
@ -273,7 +273,6 @@ export const zhHans: Partial<Messages> = {
|
|||
"settings.tts.eyebrow": "Voice",
|
||||
"settings.tts.title": "Text-to-Speech",
|
||||
"settings.tts.description": "Voice provider and controls for the familiar and floating chat.",
|
||||
"settings.tts.placeholder": "TTS provider settings will appear here in the next build step.",
|
||||
"settings.plugins.audio.title": "允许插件播放声音",
|
||||
"settings.plugins.audio.description": "允许插件播放提示音、警报和内置音效。",
|
||||
"settings.plugins.voice.title": "允许插件说话(语音)",
|
||||
|
|
|
|||
|
|
@ -273,7 +273,6 @@ export const zhHant: Partial<Messages> = {
|
|||
"settings.tts.eyebrow": "Voice",
|
||||
"settings.tts.title": "Text-to-Speech",
|
||||
"settings.tts.description": "Voice provider and controls for the familiar and floating chat.",
|
||||
"settings.tts.placeholder": "TTS provider settings will appear here in the next build step.",
|
||||
"settings.plugins.audio.title": "允許外掛播放聲音",
|
||||
"settings.plugins.audio.description": "允許外掛的提示音、警示音與內建音效。",
|
||||
"settings.plugins.voice.title": "允許外掛說話(語音)",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export async function fetchTtsVoiceList(provider: TtsProviderId): Promise<TtsVoi
|
|||
}
|
||||
|
||||
export async function speakTts(text: string, target?: "familiar" | "prompt" | BrowserWindow): Promise<void> {
|
||||
if (!text || text.trim().length === 0) return;
|
||||
const prefs = getAppStateSnapshot().preferences;
|
||||
const provider = prefs.ttsProvider ?? "system";
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 396 B |
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"manifestVersion": 3,
|
||||
"id": "familiaros.virtual-pet",
|
||||
"id": "familiaros.virtual-familiar",
|
||||
"name": "$t:plugin.name",
|
||||
"description": "$t:plugin.description",
|
||||
"version": "1.0.0",
|
||||
|
|
@ -10,14 +10,13 @@
|
|||
"entry": "index.js",
|
||||
"assets": {
|
||||
"icons": {
|
||||
"virtual-pet": "assets/virtual-pet.svg"
|
||||
"virtual-familiar": "assets/virtual-familiar.svg"
|
||||
}
|
||||
},
|
||||
"permissions": [
|
||||
"pet:speak",
|
||||
"pet:interact",
|
||||
"pet:pin",
|
||||
"pet:reaction",
|
||||
"familiar:speak",
|
||||
"familiar:interact",
|
||||
"familiar:reaction",
|
||||
"schedule",
|
||||
"storage",
|
||||
"commands",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Virtual Pet (familiaros.virtual-pet) — SDK v3 Virtual Pet companion.
|
||||
// Virtual Familiar (familiaros.virtual-familiar) — SDK v3 Virtual Pet companion.
|
||||
|
||||
export const SCHEDULE_ID = "virtual-pet-tick";
|
||||
export const SCHEDULE_ID = "virtual-familiar-tick";
|
||||
|
||||
export function cleanState(state = {}) {
|
||||
const current = state && typeof state === "object" ? state : {};
|
||||
|
|
@ -142,7 +142,7 @@ export async function maybeNudge(ctx, state, now = Date.now()) {
|
|||
else if (isNeglected) speechKey = "nudge.neglected";
|
||||
|
||||
try {
|
||||
await ctx.pet.speak(ctx.t(speechKey));
|
||||
await ctx.familiar.speak(ctx.t(speechKey));
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
|
@ -179,12 +179,12 @@ export async function feed(ctx, now = Date.now()) {
|
|||
await playActionSound(ctx);
|
||||
|
||||
try {
|
||||
await ctx.pet.react("celebrating", { showMessage: false });
|
||||
await ctx.familiar.react("celebrating", { showMessage: false });
|
||||
if (xpInfo.leveledUp) {
|
||||
await ctx.pet.speak(ctx.t("speech.levelup"));
|
||||
await ctx.familiar.speak(ctx.t("speech.levelup"));
|
||||
} else {
|
||||
const idx = Math.floor(Math.random() * 4);
|
||||
await ctx.pet.speak(ctx.t(`speech.feed.${idx}`));
|
||||
await ctx.familiar.speak(ctx.t(`speech.feed.${idx}`));
|
||||
}
|
||||
} catch {}
|
||||
|
||||
|
|
@ -216,12 +216,12 @@ export async function play(ctx, now = Date.now()) {
|
|||
await playActionSound(ctx);
|
||||
|
||||
try {
|
||||
await ctx.pet.react("celebrating", { showMessage: false });
|
||||
await ctx.familiar.react("celebrating", { showMessage: false });
|
||||
if (xpInfo.leveledUp) {
|
||||
await ctx.pet.speak(ctx.t("speech.levelup"));
|
||||
await ctx.familiar.speak(ctx.t("speech.levelup"));
|
||||
} else {
|
||||
const idx = Math.floor(Math.random() * 4);
|
||||
await ctx.pet.speak(ctx.t(`speech.play.${idx}`));
|
||||
await ctx.familiar.speak(ctx.t(`speech.play.${idx}`));
|
||||
}
|
||||
} catch {}
|
||||
|
||||
|
|
@ -253,12 +253,12 @@ export async function pet(ctx, now = Date.now()) {
|
|||
await playActionSound(ctx);
|
||||
|
||||
try {
|
||||
await ctx.pet.react("waving", { showMessage: false });
|
||||
await ctx.familiar.react("waving", { showMessage: false });
|
||||
if (xpInfo.leveledUp) {
|
||||
await ctx.pet.speak(ctx.t("speech.levelup"));
|
||||
await ctx.familiar.speak(ctx.t("speech.levelup"));
|
||||
} else {
|
||||
const idx = Math.floor(Math.random() * 4);
|
||||
await ctx.pet.speak(ctx.t(`speech.pet.${idx}`));
|
||||
await ctx.familiar.speak(ctx.t(`speech.pet.${idx}`));
|
||||
}
|
||||
} catch {}
|
||||
|
||||
|
|
@ -289,12 +289,12 @@ export async function nap(ctx, now = Date.now()) {
|
|||
await playActionSound(ctx);
|
||||
|
||||
try {
|
||||
await ctx.pet.react("waiting", { showMessage: false });
|
||||
await ctx.familiar.react("waiting", { showMessage: false });
|
||||
if (xpInfo.leveledUp) {
|
||||
await ctx.pet.speak(ctx.t("speech.levelup"));
|
||||
await ctx.familiar.speak(ctx.t("speech.levelup"));
|
||||
} else {
|
||||
const idx = Math.floor(Math.random() * 4);
|
||||
await ctx.pet.speak(ctx.t(`speech.nap.${idx}`));
|
||||
await ctx.familiar.speak(ctx.t(`speech.nap.${idx}`));
|
||||
}
|
||||
} catch {}
|
||||
|
||||
|
|
@ -308,7 +308,7 @@ export async function showStatus(ctx, now = Date.now()) {
|
|||
|
||||
const mood = getMood(state, now);
|
||||
try {
|
||||
await ctx.pet.speak(ctx.t(`speech.status.${mood}`));
|
||||
await ctx.familiar.speak(ctx.t(`speech.status.${mood}`));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
|
@ -340,10 +340,10 @@ export function register(FamiliarOSPlugin) {
|
|||
await reconcile(ctx);
|
||||
|
||||
try {
|
||||
ctx.events.on("pet:clicked", () => pet(ctx));
|
||||
ctx.events.on("familiar:clicked", () => pet(ctx));
|
||||
} catch {}
|
||||
|
||||
const icon = ctx.assets.icon("virtual-pet");
|
||||
const icon = ctx.assets.icon("virtual-familiar");
|
||||
|
||||
await ctx.commands.register({ id: "status", title: "$t:command.status.title", description: "$t:command.status.description", icon, placement: "top", priority: 100 }, () => showStatus(ctx));
|
||||
await ctx.commands.register({ id: "pet", title: "$t:command.pet.title", description: "$t:command.pet.description", icon, placement: "top", priority: 80 }, () => pet(ctx));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"plugin.name": "Pet Status",
|
||||
"plugin.description": "Care for a little desktop companion and watch your bond grow through gentle everyday interactions.",
|
||||
"plugin.name": "Familiar Status",
|
||||
"plugin.description": "Care for your Familiar and watch your bond grow through gentle everyday interactions.",
|
||||
"hud.food": "Food",
|
||||
"hud.energy": "Energy",
|
||||
"hud.play": "Play",
|
||||
|
|
@ -9,15 +9,15 @@
|
|||
"config.sound.description": "Optionally play a sound for direct care actions like feeding, playing, petting, or resting.",
|
||||
|
||||
"command.feed.title": "Feed Snack",
|
||||
"command.feed.description": "Feed your pet a snack.",
|
||||
"command.feed.description": "Feed your Familiar a snack.",
|
||||
"command.play.title": "Play Game",
|
||||
"command.play.description": "Play a quick game.",
|
||||
"command.pet.title": "Pet",
|
||||
"command.pet.description": "Give your pet some affection.",
|
||||
"command.pet.title": "Pet Familiar",
|
||||
"command.pet.description": "Give your Familiar some affection.",
|
||||
"command.nap.title": "Let Rest",
|
||||
"command.nap.description": "Let your pet rest for 15 minutes.",
|
||||
"command.status.title": "Check On Pet",
|
||||
"command.status.description": "Check how your pet is feeling.",
|
||||
"command.nap.description": "Let your Familiar rest for 15 minutes.",
|
||||
"command.status.title": "Check On Familiar",
|
||||
"command.status.description": "Check how your Familiar is feeling.",
|
||||
|
||||
"menu.mood": "Mood",
|
||||
"menu.food": "Food",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Golden test for familiaros.virtual-pet.
|
||||
// Golden test for familiaros.virtual-familiar.
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import {
|
||||
|
|
@ -79,7 +79,7 @@ Object.defineProperty(Date, "now", {
|
|||
assert.equal(stateSlept.affection, 50);
|
||||
}
|
||||
|
||||
const PERMISSIONS = ["pet:speak", "pet:interact", "pet:pin", "pet:reaction", "schedule", "storage", "commands", "audio", "events"];
|
||||
const PERMISSIONS = ["familiar:speak", "familiar:interact", "familiar:pin", "familiar:reaction", "schedule", "storage", "commands", "audio", "events"];
|
||||
const LOCALES = { en: JSON.parse(await readFile(new URL("./locales/en.json", import.meta.url), "utf8")) };
|
||||
|
||||
// 2) Start / Reconcile logic
|
||||
|
|
@ -171,7 +171,7 @@ const LOCALES = { en: JSON.parse(await readFile(new URL("./locales/en.json", imp
|
|||
await h.start();
|
||||
|
||||
// Emit click event
|
||||
await h.emit("pet:clicked", {});
|
||||
await h.emit("familiar:clicked", {});
|
||||
h.expectStored("state", (s) => s.affection === 65 && s.careCounts.petted === 1);
|
||||
h.expectSpoke(/Purr|nuzzles|Warm|soft/);
|
||||
h.expectNoErrors();
|
||||
|
|
@ -203,4 +203,4 @@ Object.defineProperty(Date, "now", {
|
|||
configurable: true,
|
||||
});
|
||||
|
||||
console.log("familiaros.virtual-pet: all checks passed.");
|
||||
console.log("familiaros.virtual-familiar: all checks passed.");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue