fmt(js): npm run fix on merge (#95299)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
hermes-seaeye[bot]
2026-08-26 06:20:59 +00:00
committed by GitHub
parent 71d804d005
commit bc943d5672
17 changed files with 204 additions and 216 deletions
+1 -7
View File
@@ -61,10 +61,4 @@ async function resolveTerminalConnectionForSender(webContentsId, getTarget, ensu
)
}
export {
applyConnectionChange,
commitConnectionFailure,
resolveTerminalConnection,
resolveTerminalConnectionForSender
}
export { applyConnectionChange, commitConnectionFailure, resolveTerminalConnection, resolveTerminalConnectionForSender }
@@ -232,7 +232,6 @@ test('URL route fails closed for different token, headers, kind, or Cloud org',
}
})
test('profile remote wins over a registry-backed global SSH route', () => {
const route = resolveDesktopRemoteRoute({
config: {
+5 -17
View File
@@ -356,10 +356,7 @@ import {
import { fetchMarketplaceThemes, searchMarketplaceThemes } from './vscode-marketplace'
import { createWakeIndicatorWindowController } from './wake-indicator-window'
import { enumerateWindowsFrontToBack, enumerationFailed, readWindowBelow } from './window-below'
import {
registrySshScopeForWindowRoute,
WindowConnectionRouteRegistry
} from './window-connection-route'
import { registrySshScopeForWindowRoute, WindowConnectionRouteRegistry } from './window-connection-route'
import { installWindowRendererLifecycle } from './window-renderer-lifecycle'
import { createWindowRevealController } from './window-reveal'
import {
@@ -9522,14 +9519,10 @@ async function teardownSshConnection(profile) {
// SSH connection — so if the active profile resolves to a NON-SSH backend, the
// terminal must NOT fall through to a global SSH host.
function activeSshTerminalTarget(webContentsId?: number) {
const windowRoute =
typeof webContentsId === 'number' ? windowConnectionRoutes.get(webContentsId) : null
const windowRoute = typeof webContentsId === 'number' ? windowConnectionRoutes.get(webContentsId) : null
if (windowRoute?.registryScoped && windowRoute.connectionId) {
const scope = registrySshScopeForWindowRoute(
windowRoute,
readDesktopConnectionsRegistry()
)
const scope = registrySshScopeForWindowRoute(windowRoute, readDesktopConnectionsRegistry())
if (!scope) {
return null
@@ -9579,10 +9572,7 @@ async function ensureTerminalBackend(webContentsId: number) {
// Loopback reach for the browser pane. Scoped to the SSH connection that
// authorized it: a different host (or none) must never inherit live forwards
// into somebody else's machine.
const previewReachByWebContents = new Map<
number,
{ registry: PreviewReachRegistry; scope: string }
>()
const previewReachByWebContents = new Map<number, { registry: PreviewReachRegistry; scope: string }>()
async function resetPreviewReach(webContentsId?: number) {
if (typeof webContentsId === 'number') {
@@ -15300,9 +15290,7 @@ ipcMain.handle('hermes:stop-find-in-page', event => {
// The renderer can't know whether a loopback URL is reachable — only main
// knows which transport backs this gateway. Ask before loading one.
ipcMain.handle('hermes:preview:reach', async (event, url) =>
reachablePreviewUrl(event.sender.id, String(url || ''))
)
ipcMain.handle('hermes:preview:reach', async (event, url) => reachablePreviewUrl(event.sender.id, String(url || '')))
ipcMain.handle('hermes:openPreviewInBrowser', async (_event, url) => {
if (!(await openPreviewInBrowser(url))) {
+2 -4
View File
@@ -2,10 +2,7 @@ import assert from 'node:assert/strict'
import { test } from 'vitest'
import {
resolveTerminalConnection,
resolveTerminalConnectionForSender
} from './connection-apply'
import { resolveTerminalConnection, resolveTerminalConnectionForSender } from './connection-apply'
const ssh = {
host: 'registry-box.test',
@@ -49,6 +46,7 @@ test('terminal start re-reads the SSH target after backend startup', async () =>
ssh,
scope: 'connection:registry-ssh'
}
let ready = false
const resolved = await resolveTerminalConnection(
+2 -5
View File
@@ -292,11 +292,8 @@ export function registerTerminalIpc({
const cols = Math.max(2, Number.parseInt(String(payload?.cols || 80), 10) || 80)
const rows = Math.max(2, Number.parseInt(String(payload?.rows || 24), 10) || 24)
const sshTarget = await resolveTerminalConnectionForSender(
event.sender.id,
activeSshTerminalTarget,
ensureBackend
)
const sshTarget = await resolveTerminalConnectionForSender(event.sender.id, activeSshTerminalTarget, ensureBackend)
const remote = Boolean(sshTarget)
const remoteState = remote ? getSshConnectionState(sshTarget.scope) : null
@@ -95,10 +95,7 @@ test('routes a non-primary SSH connection independently from another window', ()
registryScoped: true
})
assert.equal(
registrySshScopeForWindowRoute(routes.get(11), registry),
'conn:source-b::worker'
)
assert.equal(registrySshScopeForWindowRoute(routes.get(11), registry), 'conn:source-b::worker')
assert.equal(registrySshScopeForWindowRoute(routes.get(22), registry), null)
})
@@ -123,4 +120,3 @@ test('uses the canonical default profile scope when a registry SSH route has no
'conn:source-b::default'
)
})
@@ -13,10 +13,8 @@ export function normalizeWindowConnectionRoute(value: unknown): WindowConnection
const input = value as Record<string, unknown>
const connectionId = typeof input.connectionId === 'string' ? input.connectionId.trim() : ''
const profile =
typeof input.profile === 'string' && input.profile.trim()
? input.profile.trim()
: undefined
const profile = typeof input.profile === 'string' && input.profile.trim() ? input.profile.trim() : undefined
return {
connectionId: connectionId || null,
@@ -50,10 +48,12 @@ export class WindowConnectionRouteRegistry {
if (!route) {
this.routes.delete(webContentsId)
return null
}
this.routes.set(webContentsId, route)
return route
}
@@ -580,8 +580,7 @@ export function useGatewayBoot({
bootCompleted = true
} catch (err) {
const mayPublishFailure =
!cancelled &&
(switchToken === null ? !$gatewaySwitching.get() : isCurrentGatewaySwitch(switchToken))
!cancelled && (switchToken === null ? !$gatewaySwitching.get() : isCurrentGatewaySwitch(switchToken))
if (mayPublishFailure) {
const message = err instanceof Error ? err.message : String(err)
@@ -390,13 +390,13 @@ describe('useRouteResume', () => {
expect(resumeSession).toHaveBeenCalledWith('session-2', true)
})
it("does not re-resume the old session when the new profile gateway opens before /new commits (#68594)", () => {
it('does not re-resume the old session when the new profile gateway opens before /new commits (#68594)', () => {
const resumeSession = vi.fn(async () => undefined)
const startFreshSessionDraft = vi.fn()
const activeSessionIdRef: MutableRefObject<null | string> = { current: "runtime-a" }
const activeSessionIdRef: MutableRefObject<null | string> = { current: 'runtime-a' }
const creatingSessionRef = { current: false }
const runtimeIdByStoredSessionIdRef = { current: new Map([["session-a", "runtime-a"]]) }
const selectedStoredSessionIdRef: MutableRefObject<null | string> = { current: "session-a" }
const runtimeIdByStoredSessionIdRef = { current: new Map([['session-a', 'runtime-a']]) }
const selectedStoredSessionIdRef: MutableRefObject<null | string> = { current: 'session-a' }
const { rerender } = render(
<RouteResumeHarness
@@ -674,4 +674,3 @@ describe('useRouteResume bounded auto-retry after a failed resume', () => {
expect($resumeExhaustedSessionId.get()).toBeNull()
})
})
@@ -23,7 +23,13 @@ import { clearSessionDraft, stashSessionDraft, takeSessionDraft } from '@/store/
import { requestGatewayForAgent, requestGatewayForProfile } from '@/store/gateway'
import { $pinnedSessionIds } from '@/store/layout'
import { $activeGatewayProfile, $newChatProfile, $newChatRoute, $profiles, ensureGatewayProfile } from '@/store/profile'
import { $projectScope, $projectTree, $removedSessionIds, $sessionMutationsInFlight, ALL_PROJECTS } from '@/store/projects'
import {
$projectScope,
$projectTree,
$removedSessionIds,
$sessionMutationsInFlight,
ALL_PROJECTS
} from '@/store/projects'
import {
$activeSessionId,
$activeSessionStoredIdRotation,
@@ -817,7 +817,8 @@ export function useSessionActions({
// gateway can be untagged, so retain the captured ambient connection too.
// Either way, route by the composite (connection, profile), never by a
// same-named profile alone.
const sessionOwner: SessionOwnerScope = ownerRoute ||
const sessionOwner: SessionOwnerScope =
ownerRoute ||
(resolvedConnectionId
? {
connectionId: resolvedConnectionId,
@@ -2094,6 +2095,7 @@ export function useSessionActions({
const archived = listed?.session
const stampedProfile = archived?.profile?.trim()
const profile = stampedProfile || (await resolveSessionProfile(storedSessionId))
if (
listed &&
!stampedProfile &&
@@ -2101,8 +2103,10 @@ export function useSessionActions({
$profiles.get().filter(item => item.name.trim()).length > 1
) {
notifyError(new Error('Session ownership could not be resolved'), copy.archiveFailed)
return
}
const wasSelected = selectedStoredSessionId === storedSessionId
const previousPinned = $pinnedSessionIds.get()
// Pins are keyed on the durable lineage-root id; the stored id may be the
@@ -224,128 +224,131 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg
}, [profileScope])
/** Refresh every sidebar session slice without committing an obsolete profile response. */
const refreshSessions = useCallback(async (shouldPublish: () => boolean = () => true) => {
const sessionProfile = sidebarProfileForScope(profileScope)
const activationEpoch = gatewayActivationEpoch()
const refreshSessions = useCallback(
async (shouldPublish: () => boolean = () => true) => {
const sessionProfile = sidebarProfileForScope(profileScope)
const activationEpoch = gatewayActivationEpoch()
if (!shouldPublish() || sidebarProfileForScope(profileScopeRef.current) !== sessionProfile) {
return
}
const requestId = refreshSessionsRequestRef.current + 1
refreshSessionsRequestRef.current = requestId
// The loading flag exists to drive the initial skeletons (they only render
// while the list is empty). Turn-complete / reconnect refreshes over a
// populated list used to flip it true→false anyway, churning every
// $sessionsLoading subscriber twice per turn for no visible change.
const showLoading = $sessions.get().length === 0
if (showLoading && shouldPublish()) {
setSessionsLoading(true)
}
try {
const limit = $sessionsLimit.get()
// Require at least one message so abandoned/empty "Untitled" drafts (one
// was created per TUI/desktop launch before the lazy-create fix) don't
// clutter the sidebar.
// Unified cross-profile list (served read-only off each profile's
// state.db; no per-profile backend is spawned). Single-profile users get
// the same rows tagged profile="default".
// Scope every sidebar slice to the active profile (not always 'all') so a profile
// with few recent sessions isn't windowed out of the cross-profile
// recency page and never inherits another profile's cron or messaging
// sections. ALL_PROFILES remains the explicit unified view.
// Batched: one request opens each profile DB once and returns all three
// source-scoped slices, instead of three separate listAllProfileSessions
// calls that each reopened + re-counted every profile DB per refresh.
const result = await listSidebarSessions({
recentsProfile: sessionProfile,
recentsLimit: limit,
recentsExclude: SIDEBAR_EXCLUDED_SOURCES,
cronLimit: CRON_SECTION_LIMIT,
messagingLimit: MESSAGING_SECTION_LIMIT,
messagingExclude: MESSAGING_EXCLUDED_SOURCES
})
if (
shouldPublish() &&
refreshSessionsRequestRef.current === requestId &&
sidebarProfileForScope(profileScopeRef.current) === sessionProfile &&
gatewayActivationEpoch() === activationEpoch
) {
const recents = result.recents
// Drop rows the user just deleted/archived: a refresh can race an
// in-flight mutation and the backend page still carries the doomed row.
// Honoring the optimistic tombstone keeps the removal from flashing back
// (the tombstone self-clears once projects.tree confirms the delete).
const incoming = dropTombstoned(recents.sessions)
// Signature-gate the swap (same pattern as cron/messaging): a refresh
// that returns content-identical rows must keep the previous array
// identity, or every sidebar memo keyed on $sessions recomputes and the
// whole list re-renders once per turn/broadcast for nothing.
setSessions(prev => {
const next = mergeSessionPage(prev, incoming, sessionsToKeep())
return sameCronSignature(prev, next) ? prev : next
})
// "Is there another page?" instead of an exact total: the backend
// reports which profiles filled their window, which costs nothing on
// top of the rows it already read (the old exact totals ran a COUNT(*)
// per profile DB on every refresh). Reference-stable when unchanged so
// the sidebar's group memos don't recompute per refresh.
setSessionProfilesTruncated(prev => {
const next = recents.profiles_truncated ?? {}
const prevKeys = Object.keys(prev)
return prevKeys.length === Object.keys(next).length && prevKeys.every(key => prev[key] === next[key])
? prev
: next
})
// Same identity gate: these totals only move when a session bills, and
// a fresh object every refresh would repaint every profile header.
setSessionProfilesUsage(prev => {
const next = recents.profiles_usage ?? {}
const prevKeys = Object.keys(prev)
return prevKeys.length === Object.keys(next).length &&
prevKeys.every(
key => prev[key]?.tokens === next[key]?.tokens && prev[key]?.cost_usd === next[key]?.cost_usd
)
? prev
: next
})
// Cron section: latest N cron sessions (kept so a pinned cron run still
// resolves via sessionByAnyId), signature-gated like above.
setCronSessions(prev => (sameCronSignature(prev, result.cron.sessions) ? prev : result.cron.sessions))
// Messaging sections: drop any non-messaging source the broad exclude
// didn't catch (custom sources stay in local recents), then split per
// platform in the UI.
const messagingRows = dropTombstoned(result.messaging.sessions.filter(s => isMessagingSource(s.source)))
setMessagingSessions(prev => (sameCronSignature(prev, messagingRows) ? prev : messagingRows))
// Hit the cap → at least one platform may have more on disk than loaded.
setMessagingTruncated(result.messaging.sessions.length >= MESSAGING_SECTION_LIMIT)
if (!shouldPublish() || sidebarProfileForScope(profileScopeRef.current) !== sessionProfile) {
return
}
} finally {
// Request identity preserves the zero-argument refresh contract across a
// failed activation epoch; an explicit owner predicate is stronger and
// must never release a newer switch's loading barrier.
if (showLoading && shouldPublish() && refreshSessionsRequestRef.current === requestId) {
setSessionsLoading(false)
}
}
// Cron *jobs* are a distinct API (getCronJobs), not a session slice.
if (shouldPublish() && sidebarProfileForScope(profileScopeRef.current) === sessionProfile) {
void refreshCronJobs()
}
}, [profileScope, refreshCronJobs])
const requestId = refreshSessionsRequestRef.current + 1
refreshSessionsRequestRef.current = requestId
// The loading flag exists to drive the initial skeletons (they only render
// while the list is empty). Turn-complete / reconnect refreshes over a
// populated list used to flip it true→false anyway, churning every
// $sessionsLoading subscriber twice per turn for no visible change.
const showLoading = $sessions.get().length === 0
if (showLoading && shouldPublish()) {
setSessionsLoading(true)
}
try {
const limit = $sessionsLimit.get()
// Require at least one message so abandoned/empty "Untitled" drafts (one
// was created per TUI/desktop launch before the lazy-create fix) don't
// clutter the sidebar.
// Unified cross-profile list (served read-only off each profile's
// state.db; no per-profile backend is spawned). Single-profile users get
// the same rows tagged profile="default".
// Scope every sidebar slice to the active profile (not always 'all') so a profile
// with few recent sessions isn't windowed out of the cross-profile
// recency page and never inherits another profile's cron or messaging
// sections. ALL_PROFILES remains the explicit unified view.
// Batched: one request opens each profile DB once and returns all three
// source-scoped slices, instead of three separate listAllProfileSessions
// calls that each reopened + re-counted every profile DB per refresh.
const result = await listSidebarSessions({
recentsProfile: sessionProfile,
recentsLimit: limit,
recentsExclude: SIDEBAR_EXCLUDED_SOURCES,
cronLimit: CRON_SECTION_LIMIT,
messagingLimit: MESSAGING_SECTION_LIMIT,
messagingExclude: MESSAGING_EXCLUDED_SOURCES
})
if (
shouldPublish() &&
refreshSessionsRequestRef.current === requestId &&
sidebarProfileForScope(profileScopeRef.current) === sessionProfile &&
gatewayActivationEpoch() === activationEpoch
) {
const recents = result.recents
// Drop rows the user just deleted/archived: a refresh can race an
// in-flight mutation and the backend page still carries the doomed row.
// Honoring the optimistic tombstone keeps the removal from flashing back
// (the tombstone self-clears once projects.tree confirms the delete).
const incoming = dropTombstoned(recents.sessions)
// Signature-gate the swap (same pattern as cron/messaging): a refresh
// that returns content-identical rows must keep the previous array
// identity, or every sidebar memo keyed on $sessions recomputes and the
// whole list re-renders once per turn/broadcast for nothing.
setSessions(prev => {
const next = mergeSessionPage(prev, incoming, sessionsToKeep())
return sameCronSignature(prev, next) ? prev : next
})
// "Is there another page?" instead of an exact total: the backend
// reports which profiles filled their window, which costs nothing on
// top of the rows it already read (the old exact totals ran a COUNT(*)
// per profile DB on every refresh). Reference-stable when unchanged so
// the sidebar's group memos don't recompute per refresh.
setSessionProfilesTruncated(prev => {
const next = recents.profiles_truncated ?? {}
const prevKeys = Object.keys(prev)
return prevKeys.length === Object.keys(next).length && prevKeys.every(key => prev[key] === next[key])
? prev
: next
})
// Same identity gate: these totals only move when a session bills, and
// a fresh object every refresh would repaint every profile header.
setSessionProfilesUsage(prev => {
const next = recents.profiles_usage ?? {}
const prevKeys = Object.keys(prev)
return prevKeys.length === Object.keys(next).length &&
prevKeys.every(
key => prev[key]?.tokens === next[key]?.tokens && prev[key]?.cost_usd === next[key]?.cost_usd
)
? prev
: next
})
// Cron section: latest N cron sessions (kept so a pinned cron run still
// resolves via sessionByAnyId), signature-gated like above.
setCronSessions(prev => (sameCronSignature(prev, result.cron.sessions) ? prev : result.cron.sessions))
// Messaging sections: drop any non-messaging source the broad exclude
// didn't catch (custom sources stay in local recents), then split per
// platform in the UI.
const messagingRows = dropTombstoned(result.messaging.sessions.filter(s => isMessagingSource(s.source)))
setMessagingSessions(prev => (sameCronSignature(prev, messagingRows) ? prev : messagingRows))
// Hit the cap → at least one platform may have more on disk than loaded.
setMessagingTruncated(result.messaging.sessions.length >= MESSAGING_SECTION_LIMIT)
}
} finally {
// Request identity preserves the zero-argument refresh contract across a
// failed activation epoch; an explicit owner predicate is stronger and
// must never release a newer switch's loading barrier.
if (showLoading && shouldPublish() && refreshSessionsRequestRef.current === requestId) {
setSessionsLoading(false)
}
}
// Cron *jobs* are a distinct API (getCronJobs), not a session slice.
if (shouldPublish() && sidebarProfileForScope(profileScopeRef.current) === sessionProfile) {
void refreshCronJobs()
}
},
[profileScope, refreshCronJobs]
)
const loadMoreSessions = useCallback(async () => {
bumpSessionsLimit()
+7 -5
View File
@@ -415,11 +415,13 @@ declare global {
write: (id: string, data: string) => Promise<boolean>
}
reachPreviewUrl?: (url: string) => Promise<string>
setActiveConnectionRoute?: (route: {
connectionId?: null | string
profile?: string
registryScoped?: boolean
} | null) => void
setActiveConnectionRoute?: (
route: {
connectionId?: null | string
profile?: string
registryScoped?: boolean
} | null
) => void
onClosePreviewRequested?: (callback: () => void) => () => void
onPreviewNav?: (callback: (command: 'back' | 'forward' | 'reload') => void) => () => void
onOpenFolderRequested?: (callback: () => void) => () => void
+2 -3
View File
@@ -625,9 +625,8 @@ export const host = {
// the deleted profile.
const ambientConnectionId = route ? null : String(activeGatewayConnectionId() || '').trim()
const ambientRemoteConnectionId = ambientConnectionId && ambientConnectionId !== 'local'
? ambientConnectionId
: null
const ambientRemoteConnectionId =
ambientConnectionId && ambientConnectionId !== 'local' ? ambientConnectionId : null
if (!name) {
throw new Error('deleteProfile: profile name required')
+6 -1
View File
@@ -224,7 +224,12 @@ export function setPrimaryGatewayConnection(connection: Pick<HermesConnection, '
function isPrimaryRegistryRoute(connectionId: null | string, profile: string): boolean {
const id = String(connectionId ?? '').trim()
return normKey(profile) === g.primaryProfile && Boolean(id) && Boolean(g.primaryConnectionId) && id === g.primaryConnectionId
return (
normKey(profile) === g.primaryProfile &&
Boolean(id) &&
Boolean(g.primaryConnectionId) &&
id === g.primaryConnectionId
)
}
export function isActivePrimary(): boolean {
@@ -23,7 +23,8 @@ let promptAckStatus: null | string = null
vi.mock('@/hermes', () => ({
HermesGateway: class {
connectionState = 'closed'
eventHandler: ((event: { payload?: Record<string, unknown>; session_id?: string; type: string }) => void) | null = null
eventHandler: ((event: { payload?: Record<string, unknown>; session_id?: string; type: string }) => void) | null =
null
stateHandler: ((state: string) => void) | null = null
connect = vi.fn(async () => {
this.connectionState = 'open'
@@ -36,15 +37,18 @@ vi.mock('@/hermes', () => ({
return method === 'prompt.submit' && promptAckStatus ? { status: promptAckStatus } : { method, params }
})
close = vi.fn()
emit = (event: { payload?: Record<string, unknown>; session_id?: string; type: string }) => this.eventHandler?.(event)
emit = (event: { payload?: Record<string, unknown>; session_id?: string; type: string }) =>
this.eventHandler?.(event)
emitState = (state: string) => this.stateHandler?.(state)
onEvent = vi.fn((handler: (event: { payload?: Record<string, unknown>; session_id?: string; type: string }) => void) => {
this.eventHandler = handler
onEvent = vi.fn(
(handler: (event: { payload?: Record<string, unknown>; session_id?: string; type: string }) => void) => {
this.eventHandler = handler
return () => {
this.eventHandler = null
return () => {
this.eventHandler = null
}
}
})
)
onState = vi.fn((handler: (state: string) => void) => {
this.stateHandler = handler
@@ -338,12 +342,10 @@ describe('requestForSessionProfile', () => {
installDesktop()
const ambient = vi.fn(async () => ({ ambient: true }))
await requestForSessionProfile(
{ connectionId: 'local', profile: 'default' },
ambient as never,
'prompt.submit',
{ session_id: 'rt-bot-chat', text: 'research this' }
)
await requestForSessionProfile({ connectionId: 'local', profile: 'default' }, ambient as never, 'prompt.submit', {
session_id: 'rt-bot-chat',
text: 'research this'
})
// prompt.submit ACKs immediately while the model keeps running. Releasing
// the request-scoped socket here detaches the runtime session; the backend
@@ -375,23 +377,24 @@ describe('requestForSessionProfile', () => {
vi.useRealTimers()
})
it.each(['queued', 'redirected', 'future-nonterminal'])('retains a routed socket for non-terminal ACK status %s', async status => {
const primary = makePrimary()
setPrimaryGateway(primary as never, 'default')
installDesktop()
const ambient = vi.fn(async () => ({ ambient: true }))
it.each(['queued', 'redirected', 'future-nonterminal'])(
'retains a routed socket for non-terminal ACK status %s',
async status => {
const primary = makePrimary()
setPrimaryGateway(primary as never, 'default')
installDesktop()
const ambient = vi.fn(async () => ({ ambient: true }))
promptAckStatus = status
promptAckStatus = status
await requestForSessionProfile(
'loki',
ambient as never,
'prompt.submit',
{ session_id: `rt-${status}`, text: 'continue' }
)
await requestForSessionProfile('loki', ambient as never, 'prompt.submit', {
session_id: `rt-${status}`,
text: 'continue'
})
expect(secondaryGateways[0].close).not.toHaveBeenCalled()
})
expect(secondaryGateways[0].close).not.toHaveBeenCalled()
}
)
it.each(['complete', 'completed', 'error'])('releases a routed socket for terminal ACK status %s', async status => {
const primary = makePrimary()
@@ -401,12 +404,10 @@ describe('requestForSessionProfile', () => {
promptAckStatus = status
await requestForSessionProfile(
'loki',
ambient as never,
'prompt.submit',
{ session_id: `rt-${status}`, text: 'finish' }
)
await requestForSessionProfile('loki', ambient as never, 'prompt.submit', {
session_id: `rt-${status}`,
text: 'finish'
})
expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
})
+1 -3
View File
@@ -228,9 +228,7 @@ export type NewChatWorkspaceTarget = null | string | undefined
export const getConfiguredDefaultProjectDir = (): string => configuredDefaultProjectDir
export async function syncConfiguredDefaultProjectDir(
shouldPublish: () => boolean = () => true
): Promise<string> {
export async function syncConfiguredDefaultProjectDir(shouldPublish: () => boolean = () => true): Promise<string> {
const settings = window.hermesDesktop?.settings?.getDefaultProjectDir
if (!settings) {