Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/desktop/src/app/desktop-controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export function DesktopController() {
currentView,
openAgents,
openCommandCenterSection,
openStarmap,
profilesOpen,
settingsOpen,
starmapOpen,
Expand Down Expand Up @@ -739,6 +740,7 @@ export function DesktopController() {
busyRef,
createBackendSessionForSend,
handleSkinCommand,
openMemoryGraph: openStarmap,
refreshSessions,
requestGateway,
resumeStoredSession: resumeSession,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function Harness({
busyRef,
onReady,
onSeedState,
openMemoryGraph,
refreshSessions,
requestGateway,
resumeStoredSession,
Expand All @@ -63,6 +64,7 @@ function Harness({
busyRef?: MutableRefObject<boolean>
onReady: (handle: HarnessHandle) => void
onSeedState?: (state: Record<string, unknown>) => void
openMemoryGraph?: () => void
refreshSessions: () => Promise<void>
requestGateway: <T>(method: string, params?: Record<string, unknown>) => Promise<T>
resumeStoredSession?: (storedSessionId: string) => Promise<void> | void
Expand Down Expand Up @@ -91,6 +93,7 @@ function Harness({
busyRef: localBusyRef,
createBackendSessionForSend: async () => RUNTIME_SESSION_ID,
handleSkinCommand: () => '',
openMemoryGraph: openMemoryGraph ?? (() => undefined),
refreshSessions,
requestGateway,
resumeStoredSession: resumeStoredSession ?? (() => undefined),
Expand Down Expand Up @@ -369,6 +372,29 @@ describe('usePromptActions desktop slash pickers', () => {
expect(requestGateway).not.toHaveBeenCalledWith('slash.exec', expect.anything())
})

it('opens the memory graph overlay for /journey and its aliases instead of hitting the backend', async () => {
const openMemoryGraph = vi.fn()
const requestGateway = vi.fn(async () => ({}) as never)

let handle: HarnessHandle | null = null
render(
<Harness
onReady={h => (handle = h)}
openMemoryGraph={openMemoryGraph}
refreshSessions={async () => undefined}
requestGateway={requestGateway}
/>
)

await handle!.submitText('/journey')
await handle!.submitText('/memory-graph')
await handle!.submitText('/learning')

expect(openMemoryGraph).toHaveBeenCalledTimes(3)
expect(requestGateway).not.toHaveBeenCalledWith('slash.exec', expect.anything())
expect(requestGateway).not.toHaveBeenCalledWith('command.dispatch', expect.anything())
})

it('marks a timed-out handoff as failed so the next attempt can retry', async () => {
vi.useFakeTimers()
const calls: { method: string; params?: Record<string, unknown> }[] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { AppendMessage, ThreadMessage } from '@assistant-ui/react'
import { useStore } from '@nanostores/react'
import { type MutableRefObject, useCallback, useEffect, useRef } from 'react'

import { transcribeAudio, PROMPT_SUBMIT_REQUEST_TIMEOUT_MS } from '@/hermes'
import { PROMPT_SUBMIT_REQUEST_TIMEOUT_MS, transcribeAudio } from '@/hermes'
import { useI18n } from '@/i18n'
import { stripAnsi } from '@/lib/ansi'
import { branchGroupForUser, type ChatMessage, chatMessageText, textPart } from '@/lib/chat-messages'
Expand Down Expand Up @@ -158,6 +158,7 @@ interface PromptActionsOptions {
branchCurrentSession: () => Promise<boolean>
createBackendSessionForSend: (preview?: string | null) => Promise<string | null>
handleSkinCommand: (arg: string) => string
openMemoryGraph: () => void
refreshSessions: () => Promise<void>
requestGateway: <T>(method: string, params?: Record<string, unknown>, timeoutMs?: number) => Promise<T>
resumeStoredSession: (storedSessionId: string) => Promise<void> | void
Expand Down Expand Up @@ -185,6 +186,7 @@ export function usePromptActions({
branchCurrentSession,
createBackendSessionForSend,
handleSkinCommand,
openMemoryGraph,
refreshSessions,
requestGateway,
resumeStoredSession,
Expand Down Expand Up @@ -447,6 +449,7 @@ export function usePromptActions({
createBackendSessionForSend,
handleSkinCommand,
handoffSession,
openMemoryGraph,
refreshSessions,
requestGateway,
resumeStoredSession,
Expand Down
10 changes: 10 additions & 0 deletions apps/desktop/src/app/session/hooks/use-prompt-actions/slash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ interface SlashCommandDeps {
platform: string,
options?: { onProgress?: (state: string) => void; sessionId?: string }
) => Promise<{ ok: boolean; error?: string }>
openMemoryGraph: () => void
refreshSessions: () => Promise<void>
requestGateway: GatewayRequest
resumeStoredSession: (storedSessionId: string) => Promise<void> | void
Expand All @@ -75,6 +76,7 @@ export function useSlashCommand(deps: SlashCommandDeps) {
createBackendSessionForSend,
handleSkinCommand,
handoffSession,
openMemoryGraph,
refreshSessions,
requestGateway,
resumeStoredSession,
Expand Down Expand Up @@ -388,6 +390,13 @@ export function useSlashCommand(deps: SlashCommandDeps) {
renderSlashOutput(`error: ${err instanceof Error ? err.message : String(err)}`)
}
},
// /journey (aliases /learning, /memory-graph) opens the memory graph
// overlay — the desktop's visual counterpart of the TUI journey
// timeline — instead of printing a text rendering into the transcript.
// Args are ignored, matching the TUI overlay behavior.
journey: async () => {
openMemoryGraph()
},
// /hatch opens the pet generator overlay (the desktop's rich, multi-step
// generate→pick→hatch→adopt flow). A typed description seeds the prompt
// so `/hatch a cyber fox` lands on the composer step prefilled.
Expand Down Expand Up @@ -612,6 +621,7 @@ export function useSlashCommand(deps: SlashCommandDeps) {
createBackendSessionForSend,
handleSkinCommand,
handoffSession,
openMemoryGraph,
refreshSessions,
requestGateway,
resumeStoredSession,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type MutableRefObject, useCallback } from 'react'

import type { Translations } from '@/i18n'
import { PROMPT_SUBMIT_REQUEST_TIMEOUT_MS } from '@/hermes'
import type { Translations } from '@/i18n'
import { type ChatMessage, textPart } from '@/lib/chat-messages'
import { optimisticAttachmentRef } from '@/lib/chat-runtime'
import { setMutableRef } from '@/lib/mutable-ref'
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/src/lib/desktop-slash-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ describe('desktop slash command curation', () => {
expect(resolveDesktopCommand('/browser')?.args).toBe(true)
})

it('routes /journey (and aliases) to the memory graph overlay action', () => {
expect(resolveDesktopCommand('/journey')?.surface).toEqual({ kind: 'action', action: 'journey' })
expect(resolveDesktopCommand('/memory-graph')?.surface).toEqual({ kind: 'action', action: 'journey' })
expect(resolveDesktopCommand('/learning')?.surface).toEqual({ kind: 'action', action: 'journey' })
expect(isDesktopSlashCommand('/journey')).toBe(true)
expect(isDesktopSlashCommand('/memory-graph')).toBe(true)
expect(isDesktopSlashSuggestion('/journey')).toBe(true)
// Aliases execute but stay out of the popover.
expect(isDesktopSlashSuggestion('/memory-graph')).toBe(false)
expect(desktopSlashUnavailableMessage('/journey')).toBeNull()
})

it('allows aliases to execute without cluttering the popover', () => {
expect(isDesktopSlashSuggestion('/reset')).toBe(false)
expect(isDesktopSlashCommand('/reset')).toBe(true)
Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/src/lib/desktop-slash-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type DesktopActionId =
| 'handoff'
| 'hatch'
| 'help'
| 'journey'
| 'new'
| 'pet'
| 'profile'
Expand Down Expand Up @@ -122,6 +123,12 @@ const DESKTOP_COMMAND_SPECS: readonly DesktopCommandSpec[] = [
surface: action('browser'),
args: true
},
{
name: '/journey',
description: 'Open the memory graph — skills + memories over time',
aliases: ['/learning', '/memory-graph'],
surface: action('journey')
},

// Overlay pickers
{ name: '/model', description: 'Switch the model for this session', surface: picker('model'), hidden: true },
Expand Down
Loading