Files
hermes-agent/apps/desktop/src/lib/hermes-open-target.test.ts
T
seref 73ddf6665c feat(desktop): rich plugin OS notifications with deeplink activation
Extends ctx.os.notify (the curated plugin OS door from #78685) with icon,
action buttons, and a serializable `activate` target. Body/action clicks
focus the window and navigate to the plugin's screen; activation paths
share one resolver (hermes-open-target.ts) with hermes:// OS deep links,
so `hermes://index-network/intent/1`, `/index-network/intent/1`, and
{ path, params } all land on the same hash-router route. Approval
notifications keep their existing session-scoped channel.

Salvaged from PR #84192 by @serefyarar (net diff of the PR branch applied
onto current main; branch carried merge commits so a single authored
commit preserves attribution).
2026-08-18 14:26:28 -07:00

64 lines
2.6 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
normalizeHermesOpenString,
pathFromHermesDeepLink,
pathFromOpenDeepLink,
resolveHermesOpenPath
} from './hermes-open-target'
describe('normalizeHermesOpenString', () => {
it('accepts hash-router paths and strips a leading hash', () => {
expect(normalizeHermesOpenString('/index-network/intent/1')).toBe('/index-network/intent/1')
expect(normalizeHermesOpenString('#/index-network/intent/1')).toBe('/index-network/intent/1')
})
it('maps plugin-scoped hermes:// deep links to the same path', () => {
expect(normalizeHermesOpenString('hermes://index-network/intent/1')).toBe('/index-network/intent/1')
expect(normalizeHermesOpenString('hermes://index-network/intent/1?focus=true')).toBe(
'/index-network/intent/1?focus=true'
)
})
it('maps hermes://open/… deep links by stripping the open host', () => {
expect(normalizeHermesOpenString('hermes://open/index-network/intent/1')).toBe('/index-network/intent/1')
expect(normalizeHermesOpenString('hermes://open/settings/plugins')).toBe('/settings/plugins')
})
it('rejects reserved hermes kinds and unsafe paths', () => {
expect(normalizeHermesOpenString('hermes://blueprint/morning-brief')).toBeNull()
expect(normalizeHermesOpenString('hermes://plugin/install')).toBeNull()
expect(normalizeHermesOpenString('https://example.com/x')).toBeNull()
expect(normalizeHermesOpenString('/../etc/passwd')).toBeNull()
expect(normalizeHermesOpenString('index-network')).toBeNull()
})
})
describe('resolveHermesOpenPath', () => {
it('merges structured path + params', () => {
expect(resolveHermesOpenPath({ path: '/index-network/intent/1', params: { focus: 'true' } })).toBe(
'/index-network/intent/1?focus=true'
)
})
it('resolves href the same as a bare string', () => {
expect(resolveHermesOpenPath({ href: 'hermes://index-network/intent/1' })).toBe('/index-network/intent/1')
})
})
describe('pathFromHermesDeepLink', () => {
it('builds the navigate path from a plugin-scoped deep-link payload', () => {
expect(pathFromHermesDeepLink('index-network', 'intent/1')).toBe('/index-network/intent/1')
})
it('builds the navigate path from hermes://open/… payloads', () => {
expect(pathFromOpenDeepLink('index-network/intent/1')).toBe('/index-network/intent/1')
expect(pathFromHermesDeepLink('open', 'agent/42')).toBe('/agent/42')
})
it('ignores reserved kinds', () => {
expect(pathFromHermesDeepLink('blueprint', 'morning-brief')).toBeNull()
expect(pathFromHermesDeepLink('plugin', 'install')).toBeNull()
})
})