remove the desktop-pet system
The pet feature was three loosely coupled subsystems that no core flow depended on: the Petdex catalog page, the pet install/preference store, and the pet-state socket. All three are removed. The only cross-boundary touch point was a try/catch-wrapped observer that mapped chat-run events to pet animations. Removing it leaves the /chat-run event contract unchanged: no event name, field name, field type, or emission timing changed. Removing petdexPublicRoutes also closes the unauthenticated outbound proxy at /api/hermes/petdex/asset, which shrinks the SSRF surface.
This commit is contained in:
@@ -1,58 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { fetchPetdexAsset, fetchPetdexManifest } from '../../packages/server/src/services/hermes/petdex'
|
||||
|
||||
describe('petdex service', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('adds local preview proxy URLs to manifest pets', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
||||
generatedAt: '2026-06-30T00:00:00.000Z',
|
||||
pets: [{
|
||||
slug: 'desk-cat',
|
||||
displayName: 'Desk Cat',
|
||||
kind: 'cat',
|
||||
submittedBy: 'petdex',
|
||||
spritesheetUrl: 'https://assets.petdex.dev/pets/desk-cat/spritesheet.webp',
|
||||
}],
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})))
|
||||
|
||||
const manifest = await fetchPetdexManifest({ force: true })
|
||||
|
||||
expect(manifest.pets[0].spritesheetUrl).toBe('https://assets.petdex.dev/pets/desk-cat/spritesheet.webp')
|
||||
expect(manifest.pets[0].previewUrl).toBe(
|
||||
'/api/hermes/petdex/asset?url=https%3A%2F%2Fassets.petdex.dev%2Fpets%2Fdesk-cat%2Fspritesheet.webp',
|
||||
)
|
||||
})
|
||||
|
||||
it('only proxies petdex https assets', async () => {
|
||||
const fetchMock = vi.fn()
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
await expect(fetchPetdexAsset('https://example.com/pet.webp')).rejects.toThrow('Unsupported petdex asset host')
|
||||
await expect(fetchPetdexAsset('http://assets.petdex.dev/pet.webp')).rejects.toThrow('Unsupported petdex asset host')
|
||||
expect(fetchMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('fetches allowed petdex image assets with a size limit', async () => {
|
||||
const body = new Uint8Array([1, 2, 3, 4])
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'image/webp',
|
||||
'content-length': String(body.byteLength),
|
||||
},
|
||||
})))
|
||||
|
||||
const asset = await fetchPetdexAsset('https://assets.petdex.dev/pets/desk-cat/spritesheet.webp')
|
||||
|
||||
expect(asset.mime).toBe('image/webp')
|
||||
expect([...asset.buffer]).toEqual([1, 2, 3, 4])
|
||||
expect(asset.maxAgeSeconds).toBe(3600)
|
||||
})
|
||||
})
|
||||
@@ -1,77 +0,0 @@
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'fs/promises'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { getActivePet, updateActivePetPreferences } from '../../packages/server/src/services/hermes/pets'
|
||||
|
||||
const originalWebUiHome = process.env.HERMES_WEB_UI_HOME
|
||||
|
||||
let hermesHome = ''
|
||||
|
||||
function profilePetsDir(profile: string): string {
|
||||
const segment = Buffer.from(profile || 'default', 'utf-8').toString('base64url')
|
||||
return join(hermesHome, 'profile-metadata', segment, 'pets')
|
||||
}
|
||||
|
||||
async function writeInstalledPet(profile: string, slug: string): Promise<string> {
|
||||
const petsDir = profilePetsDir(profile)
|
||||
const petDir = join(petsDir, slug)
|
||||
await mkdir(petDir, { recursive: true })
|
||||
await writeFile(join(petDir, 'spritesheet.webp'), Buffer.from([1, 2, 3]))
|
||||
await writeFile(join(petDir, 'pet.json'), `${JSON.stringify({
|
||||
slug,
|
||||
displayName: 'Desk Cat',
|
||||
kind: 'cat',
|
||||
submittedBy: 'petdex',
|
||||
source: 'petdex',
|
||||
spritesheetUrl: 'https://assets.petdex.dev/pets/desk-cat/spritesheet.webp',
|
||||
petJsonUrl: '',
|
||||
zipUrl: '',
|
||||
spritesheetFile: 'spritesheet.webp',
|
||||
mime: 'image/webp',
|
||||
installedAt: 1,
|
||||
updatedAt: 1,
|
||||
}, null, 2)}\n`)
|
||||
await writeFile(join(petsDir, 'active.json'), `${JSON.stringify({
|
||||
enabled: true,
|
||||
slug,
|
||||
scale: 0.42,
|
||||
position: { x: 15, y: 30 },
|
||||
updatedAt: 2,
|
||||
}, null, 2)}\n`)
|
||||
return join(petsDir, 'active.json')
|
||||
}
|
||||
|
||||
describe('pets service', () => {
|
||||
beforeEach(async () => {
|
||||
hermesHome = await mkdtemp(join(tmpdir(), 'hermes-pets-service-'))
|
||||
process.env.HERMES_WEB_UI_HOME = hermesHome
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(hermesHome, { recursive: true, force: true })
|
||||
if (originalWebUiHome === undefined) delete process.env.HERMES_WEB_UI_HOME
|
||||
else process.env.HERMES_WEB_UI_HOME = originalWebUiHome
|
||||
})
|
||||
|
||||
it('persists disabled active pet state and stops returning it as active', async () => {
|
||||
const profile = 'default'
|
||||
const activePath = await writeInstalledPet(profile, 'desk-cat')
|
||||
|
||||
await expect(getActivePet(profile)).resolves.toMatchObject({
|
||||
enabled: true,
|
||||
slug: 'desk-cat',
|
||||
scale: 0.42,
|
||||
})
|
||||
|
||||
await expect(updateActivePetPreferences(profile, { enabled: false })).resolves.toBeNull()
|
||||
await expect(getActivePet(profile)).resolves.toBeNull()
|
||||
|
||||
const active = JSON.parse(await readFile(activePath, 'utf-8'))
|
||||
expect(active).toMatchObject({
|
||||
enabled: false,
|
||||
slug: 'desk-cat',
|
||||
scale: 0.42,
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user