mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Substantial generated content (full HTML documents, large SVGs, long code) now promotes out of the transcript into versioned, openable artifacts: - lib/artifact-detect.ts: pure detection over fenced blocks — html docs, large standalone svg, and 48+ line / 3k+ char code fences become artifacts; prose/terminal/mermaid fences and small snippets never do. Titles derive from <title>/<h1>, filename comments, or declarations. - store/artifacts.ts: per-session registry with content-hash dedupe and version history (same kind+title = one artifact the model iterates on), persisted to localStorage with per-session/per-artifact/byte caps. - ArtifactCard (transcript): compact openable card replaces the wall of code; streaming shows shimmer + line count; versions accumulate automatically on completion but the rail only opens on click (offer, don't hijack). Reasoning scratchpads never register. - ArtifactPane (right rail): artifact: tabs beside preview/file tabs with version stepper (v2 of 3 / Latest), PREVIEW/SOURCE toggle, copy, download (kind-aware extension), open-in-browser for HTML. - Rendering: HTML runs in a sandbox=allow-scripts iframe (opaque origin, no network to the app, no top-nav); SVG is DOMPurify-sanitized with the same profile as the inline embed; source view reuses the windowed Shiki renderer so 5k-line artifacts scroll smoothly. - Rail integration: artifact tabs participate in tab order, close-others/ close-to-right, ⌘W, pane visibility, and reveal; gateway switches close open artifact tabs; a persisted artifact: active-tab id reconciles to preview on boot (artifact tabs are ephemeral). - i18n: en/zh/zh-hant/ja/ar strings for card + pane. - session-ref-open.test.tsx: mock now spreads the real module — the artifact card imports session-view, which needs $sessionStates. Tests: artifact-detect (15), artifacts store (12), markdown-pipeline integration (3); full desktop suite 3128 passed, electron project 751 passed, tsc no new errors.
44 lines
1.6 KiB
TypeScript
44 lines
1.6 KiB
TypeScript
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { __resetSessionLinkTitleCache } from '@/lib/session-link-title'
|
|
|
|
import { DirectiveContent } from './directive-text'
|
|
import { MarkdownTextContent } from './markdown-text'
|
|
|
|
const openSessionTile = vi.fn()
|
|
|
|
vi.mock('@/store/session-states', async importOriginal => ({
|
|
...(await importOriginal<Record<string, unknown>>()),
|
|
openSessionTile: (...args: unknown[]) => openSessionTile(...args)
|
|
}))
|
|
|
|
afterEach(() => {
|
|
cleanup()
|
|
openSessionTile.mockClear()
|
|
__resetSessionLinkTitleCache()
|
|
})
|
|
|
|
// Both surfaces render a session ref differently — an inline link in agent
|
|
// prose, a chip in the user's own message — but either one opens the session
|
|
// it names, the way its sidebar row would.
|
|
describe('session refs open the session', () => {
|
|
it('opens the session from an agent-written link', async () => {
|
|
render(<MarkdownTextContent isRunning={false} text="Picked up in @session:work/20260101_abc123 last night." />)
|
|
|
|
fireEvent.click(await screen.findByTitle('work/20260101_abc123'))
|
|
|
|
await vi.waitFor(() => expect(openSessionTile).toHaveBeenCalledWith('20260101_abc123', 'center'))
|
|
})
|
|
|
|
it('opens the session from a chip in the user transcript', async () => {
|
|
render(<DirectiveContent text="pick up @session:work/20260101_abc123 please" />)
|
|
|
|
const chip = screen.getByTitle('work/20260101_abc123')
|
|
|
|
expect(chip.tagName).toBe('BUTTON')
|
|
fireEvent.click(chip)
|
|
|
|
await vi.waitFor(() => expect(openSessionTile).toHaveBeenCalledWith('20260101_abc123', 'center'))
|
|
})
|
|
})
|