style: eslint --fix across src/ + electron/ (CI lints the full tree); drop unused destructure
This commit is contained in:
@@ -156,6 +156,7 @@ test('resolveRouteProfile preserves a primary-backend route from another routing
|
||||
test('explicit registered local DELETE holds one gate through teardown and dispatch', async () => {
|
||||
const events: string[] = []
|
||||
const gate = new ProfileDeletionGate()
|
||||
|
||||
const result = await dispatchConnectionScopedProfileDelete(
|
||||
{ connectionId: 'local', method: 'DELETE', path: '/api/profiles/worker', profile: 'worker' },
|
||||
{
|
||||
@@ -163,6 +164,7 @@ test('explicit registered local DELETE holds one gate through teardown and dispa
|
||||
acquire: profile => {
|
||||
events.push(`gate:${profile}`)
|
||||
const release = gate.acquire(profile)
|
||||
|
||||
return () => {
|
||||
events.push(`release:${profile}`)
|
||||
release()
|
||||
@@ -172,6 +174,7 @@ test('explicit registered local DELETE holds one gate through teardown and dispa
|
||||
dispatch: async routeProfile => {
|
||||
assert.equal(gate.blocks('worker'), true)
|
||||
events.push(`dispatch:${routeProfile ?? 'primary'}`)
|
||||
|
||||
return 'deleted'
|
||||
},
|
||||
prepareLocal: async request => {
|
||||
@@ -193,17 +196,20 @@ test('explicit registered local DELETE holds one gate through teardown and dispa
|
||||
|
||||
test('explicit registered SSH DELETE tears down its source backend before dispatch', async () => {
|
||||
const events: string[] = []
|
||||
|
||||
const result = await dispatchConnectionScopedProfileDelete(
|
||||
{ connectionId: 'build-host', method: 'DELETE', path: '/api/profiles/worker', profile: 'worker' },
|
||||
{
|
||||
...deps,
|
||||
acquire: profile => {
|
||||
events.push(`gate:${profile}`)
|
||||
|
||||
return () => events.push(`release:${profile}`)
|
||||
},
|
||||
connectionKind: () => 'ssh',
|
||||
dispatch: async routeProfile => {
|
||||
events.push(`dispatch:${routeProfile ?? 'primary'}`)
|
||||
|
||||
return 'deleted'
|
||||
},
|
||||
prepareLocal: async () => assert.fail('SSH deletion must not use local profile teardown'),
|
||||
@@ -224,6 +230,7 @@ test('explicit registered SSH DELETE tears down its source backend before dispat
|
||||
|
||||
test('non-identity connection DELETE tears down the logical pool and deletes the backend target', async () => {
|
||||
const events: string[] = []
|
||||
|
||||
const request = {
|
||||
connectionId: 'source-a',
|
||||
method: 'DELETE',
|
||||
@@ -235,11 +242,13 @@ test('non-identity connection DELETE tears down the logical pool and deletes the
|
||||
...deps,
|
||||
acquire: profile => {
|
||||
events.push(`gate:${profile}`)
|
||||
|
||||
return () => events.push(`release:${profile}`)
|
||||
},
|
||||
connectionKind: () => 'ssh',
|
||||
dispatch: async routeProfile => {
|
||||
events.push(`delete:${request.path}:${routeProfile ?? 'primary'}`)
|
||||
|
||||
return 'deleted'
|
||||
},
|
||||
prepareLocal: async () => assert.fail('remote deletion must not use local profile teardown'),
|
||||
@@ -266,11 +275,13 @@ test('connection-scoped default DELETE rejects before gate, preparation, teardow
|
||||
{
|
||||
acquire: profile => {
|
||||
events.push(`gate:${profile}`)
|
||||
|
||||
return () => events.push(`release:${profile}`)
|
||||
},
|
||||
connectionKind: () => 'ssh',
|
||||
dispatch: async () => {
|
||||
events.push('dispatch')
|
||||
|
||||
return 'deleted'
|
||||
},
|
||||
isDefaultProfile: profile => profile === 'default',
|
||||
@@ -298,11 +309,13 @@ test('connection-scoped invalid-name DELETE rejects before gate, preparation, te
|
||||
{
|
||||
acquire: profile => {
|
||||
events.push(`gate:${profile}`)
|
||||
|
||||
return () => events.push(`release:${profile}`)
|
||||
},
|
||||
connectionKind: () => 'local',
|
||||
dispatch: async () => {
|
||||
events.push('dispatch')
|
||||
|
||||
return 'deleted'
|
||||
},
|
||||
isDefaultProfile: profile => profile === 'default',
|
||||
|
||||
@@ -33,8 +33,8 @@ import { $petActive } from '@/store/pet'
|
||||
import { $petOverlayActive } from '@/store/pet-overlay'
|
||||
import { $activeGatewayProfile, $gatewaySwapTarget, $profiles } from '@/store/profile'
|
||||
import {
|
||||
$contextSuggestions,
|
||||
$connection,
|
||||
$contextSuggestions,
|
||||
$freshDraftReady,
|
||||
$gatewayState,
|
||||
$introPersonality,
|
||||
@@ -272,12 +272,15 @@ function ChatRuntimeBoundary({
|
||||
// appears/retires as tail hydrations and backfill pages record their state.
|
||||
const transcriptTailStates = useStore($transcriptTailBySessionId)
|
||||
const connectionId = connection?.connectionId || (connection?.mode === 'local' ? 'local' : '')
|
||||
|
||||
const ownerRoute = storedId
|
||||
? getSessionOwnerHint(storedId, connectionId ? { connectionId, profile: activeProfile } : undefined)
|
||||
: undefined
|
||||
|
||||
const tailProfile = ownerRoute
|
||||
? { connectionId: ownerRoute.connectionId, profile: ownerRoute.targetProfile || ownerRoute.profile }
|
||||
: undefined
|
||||
|
||||
const tailState = storedId && transcriptTailStates ? transcriptTailState(storedId, tailProfile) : undefined
|
||||
const restBackfillAvailable = Boolean(tailState?.possiblyTruncated)
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ describe('transcript tail bookkeeping', () => {
|
||||
messages: [row(1, 'tail')],
|
||||
pagination: { limit: 120, offset: 0, order: 'latest' as const, returned: 1 }
|
||||
}
|
||||
|
||||
const sourceA = { connectionId: 'source-a', profile: 'backend' }
|
||||
const sourceB = { connectionId: 'source-b', profile: 'backend' }
|
||||
|
||||
@@ -91,6 +92,7 @@ describe('transcript tail bookkeeping', () => {
|
||||
messages: [row(1, 'tail')],
|
||||
pagination: { limit: 120, offset: 0, order: 'latest' as const, returned: 1 }
|
||||
}
|
||||
|
||||
const scope = { connectionId: 'source-a', profile: 'backend' }
|
||||
|
||||
for (let index = 0; index < 257; index += 1) {
|
||||
@@ -202,10 +204,12 @@ describe('backfillOlderTranscriptPage', () => {
|
||||
it('backfills the matching connection when two owners share one session id', async () => {
|
||||
const sourceA = { connectionId: 'source-a', profile: 'backend-a' }
|
||||
const sourceB = { connectionId: 'source-b', profile: 'backend-b' }
|
||||
|
||||
const page = {
|
||||
messages: Array.from({ length: 120 }, (_, index) => row(index, `tail${index}`)),
|
||||
pagination: { limit: 120, offset: 0, order: 'latest' as const, returned: 120 }
|
||||
}
|
||||
|
||||
recordTranscriptTail('same-session', page, sourceA)
|
||||
recordTranscriptTail('same-session', page, sourceB)
|
||||
vi.mocked(getOlderSessionMessages).mockResolvedValue({
|
||||
|
||||
@@ -352,13 +352,16 @@ describe('useRouteResume', () => {
|
||||
const creatingSessionRef = { current: false }
|
||||
const runtimeIdByStoredSessionIdRef = { current: new Map([['session-1', 'runtime-1']]) }
|
||||
const selectedStoredSessionIdRef: MutableRefObject<null | string> = { current: 'session-1' }
|
||||
|
||||
const ownerRoute: SessionProfileRoute = {
|
||||
connectionId: 'source-a',
|
||||
mode: 'remote',
|
||||
profile: 'worker',
|
||||
targetProfile: 'backend-worker'
|
||||
}
|
||||
|
||||
const request = { ownerRoute, sequence: 1, sessionId: 'session-1' }
|
||||
|
||||
const props = {
|
||||
activeSessionId: 'runtime-1',
|
||||
activeSessionIdRef,
|
||||
|
||||
@@ -178,8 +178,10 @@ export function useRouteResume({
|
||||
}
|
||||
|
||||
bootResumeRef.current = false
|
||||
|
||||
const ownerRoute =
|
||||
sessionResumeRequest?.sessionId === routedSessionId ? sessionResumeRequest.ownerRoute : undefined
|
||||
|
||||
if (ownerRoute) {
|
||||
void resumeSession(routedSessionId, true, ownerRoute)
|
||||
} else {
|
||||
|
||||
@@ -16,14 +16,13 @@ import {
|
||||
} from '@/hermes'
|
||||
import { createClientSessionState } from '@/lib/chat-runtime'
|
||||
import { clearSessionDraft, stashSessionDraft, takeSessionDraft } from '@/store/composer'
|
||||
import { requestGatewayForAgent } from '@/store/gateway'
|
||||
import {
|
||||
$activeGatewayProfile,
|
||||
$newChatProfile,
|
||||
$newChatRoute,
|
||||
ensureGatewayProfile
|
||||
} from '@/store/profile'
|
||||
import { requestGatewayForAgent } from '@/store/gateway'
|
||||
import type { SessionProfileRoute } from '@/store/session-request-router'
|
||||
import { $projectScope, $projectTree, ALL_PROJECTS } from '@/store/projects'
|
||||
import {
|
||||
$activeSessionId,
|
||||
@@ -54,6 +53,7 @@ import {
|
||||
setSessions,
|
||||
setTurnStartedAt
|
||||
} from '@/store/session'
|
||||
import type { SessionProfileRoute } from '@/store/session-request-router'
|
||||
import { $sessionTiles } from '@/store/session-states'
|
||||
|
||||
import sessionResumeActiveTurn from '../../../../../../tests/fixtures/session-resume-active-turn.json'
|
||||
@@ -600,6 +600,7 @@ describe('createBackendSessionForSend profile routing', () => {
|
||||
profile: 'default',
|
||||
targetProfile: 'backend-default'
|
||||
}
|
||||
|
||||
const ambientRequest = vi.fn(async () => ({}) as never)
|
||||
vi.mocked(requestGatewayForAgent).mockResolvedValueOnce({
|
||||
session_id: RUNTIME_SESSION_ID,
|
||||
@@ -1812,12 +1813,15 @@ describe('resumeSession warm-cache mapping integrity', () => {
|
||||
profile: 'default',
|
||||
targetProfile: 'backend-default'
|
||||
}
|
||||
|
||||
const runtimeIdByStoredSessionIdRef: MutableRefObject<Map<string, string>> = {
|
||||
current: new Map([['stored-warm', 'runtime-warm']])
|
||||
}
|
||||
|
||||
const sessionStateByRuntimeIdRef: MutableRefObject<Map<string, ClientSessionState>> = {
|
||||
current: new Map([['runtime-warm', clientState('stored-warm')]])
|
||||
}
|
||||
|
||||
// Same-name rows without a source tag are not authoritative for an
|
||||
// explicit owner. Metadata must be re-read from the captured connection.
|
||||
setSessions([storedSession({ id: 'stored-warm', profile: 'default' })])
|
||||
@@ -1845,11 +1849,13 @@ describe('resumeSession warm-cache mapping integrity', () => {
|
||||
})
|
||||
|
||||
const ambientRequest = vi.fn(async () => ({}) as never)
|
||||
|
||||
let resume: null | ((
|
||||
storedSessionId: string,
|
||||
replaceRoute?: boolean,
|
||||
ownerRoute?: SessionProfileRoute
|
||||
) => Promise<unknown>) = null
|
||||
|
||||
render(
|
||||
<ResumeHarness
|
||||
onReady={ready => (resume = ready)}
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
$newChatWorkspaceTarget,
|
||||
$sessions,
|
||||
$yoloActive,
|
||||
getSessionOwnerHint,
|
||||
type NewChatWorkspaceTarget,
|
||||
resolveComposerSessionKey,
|
||||
sessionPinId,
|
||||
@@ -67,7 +68,6 @@ import {
|
||||
setSelectedStoredSessionId,
|
||||
setSessions,
|
||||
setSessionStartedAt,
|
||||
getSessionOwnerHint,
|
||||
setTurnStartedAt,
|
||||
setWorkspaceCwdOwner,
|
||||
setYoloActive
|
||||
@@ -473,6 +473,7 @@ export function useSessionActions({
|
||||
|
||||
const capturedRoute = $newChatRoute.get()
|
||||
const params = await desktopSessionCreateParams(cwd, capturedRoute)
|
||||
|
||||
const created = capturedRoute
|
||||
? await requestGatewayForAgent<SessionCreateResponse>(
|
||||
capturedRoute.connectionId,
|
||||
@@ -481,6 +482,7 @@ export function useSessionActions({
|
||||
params
|
||||
)
|
||||
: await requestGateway<SessionCreateResponse>('session.create', params)
|
||||
|
||||
const stored = created.stored_session_id ?? null
|
||||
|
||||
// Only a genuine move to a DIFFERENT chat mid-create should orphan the
|
||||
@@ -597,6 +599,7 @@ export function useSessionActions({
|
||||
// (project scope → configured default).
|
||||
const capturedRoute = $newChatRoute.get()
|
||||
const params = await desktopSessionCreateParams((options?.cwd || resolveNewSessionCwd()).trim(), capturedRoute)
|
||||
|
||||
const created = capturedRoute
|
||||
? await requestGatewayForAgent<SessionCreateResponse>(
|
||||
capturedRoute.connectionId,
|
||||
@@ -605,6 +608,7 @@ export function useSessionActions({
|
||||
params
|
||||
)
|
||||
: await requestGateway<SessionCreateResponse>('session.create', params)
|
||||
|
||||
const stored = created.stored_session_id
|
||||
|
||||
if (!stored) {
|
||||
@@ -1785,12 +1789,14 @@ export function useSessionActions({
|
||||
const closingRuntimeId = wasSelected ? activeSessionId : null
|
||||
const previousMessages = $messages.get()
|
||||
const previousPinned = $pinnedSessionIds.get()
|
||||
|
||||
const removedOwner: SessionOwnerScope = removed?.connection_id
|
||||
? {
|
||||
connectionId: removed.connection_id,
|
||||
profile: removed.profile || 'default'
|
||||
}
|
||||
: removed?.profile
|
||||
|
||||
const previousArchived = $archivedSessions.get()
|
||||
// Pins are keyed on the durable lineage-root id; the stored id may be the
|
||||
// live tip after compression. Drop both so the pin can't linger.
|
||||
|
||||
@@ -1317,6 +1317,7 @@ export async function resolveStoredSession(
|
||||
connectionId: ownerRoute.connectionId,
|
||||
profile: ownerRoute.targetProfile || ownerRoute.profile
|
||||
}
|
||||
|
||||
const cachedOwnerMatches = cached &&
|
||||
cached.connection_id === ownerRoute.connectionId &&
|
||||
(!cached.profile || normalizeProfileKey(cached.profile) === normalizeProfileKey(ownerRoute.profile))
|
||||
|
||||
@@ -16,7 +16,7 @@ function deferred() {
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
|
||||
function load({ requestProfile, agents, profileRoutes, storage } = {}) {
|
||||
function load({ requestProfile, agents, profileRoutes } = {}) {
|
||||
const values = new Map()
|
||||
const atom = initial => {
|
||||
const slot = {
|
||||
|
||||
@@ -274,6 +274,7 @@ describe('connection-aware plugin host APIs', () => {
|
||||
profile: 'worker',
|
||||
targetProfile: 'backend-worker'
|
||||
}
|
||||
|
||||
vi.mocked(deleteProfile).mockResolvedValueOnce({ ok: true, path: '/profiles/backend-worker' })
|
||||
|
||||
await host.deleteProfile(route)
|
||||
@@ -319,6 +320,7 @@ describe('connection-aware plugin host APIs', () => {
|
||||
profile: 'worker',
|
||||
targetProfile: 'backend-worker'
|
||||
}
|
||||
|
||||
vi.mocked(deleteProfile).mockResolvedValueOnce({ ok: true, path: '/profiles/backend-worker' })
|
||||
|
||||
await host.deleteProfile(route)
|
||||
|
||||
@@ -149,15 +149,19 @@ describe('requestForSessionProfile', () => {
|
||||
setPrimaryGateway(primary as never, 'default')
|
||||
installDesktop()
|
||||
await ensureGatewayForProfile('other')
|
||||
|
||||
const desktop = (window as unknown as {
|
||||
hermesDesktop: { getConnectionFor: ReturnType<typeof vi.fn> }
|
||||
}).hermesDesktop
|
||||
|
||||
const ambient = vi.fn(async () => ({ ambient: true }))
|
||||
|
||||
const routeA = {
|
||||
connectionId: 'source-a',
|
||||
profile: 'default',
|
||||
targetProfile: 'backend-a'
|
||||
}
|
||||
|
||||
const routeB = {
|
||||
connectionId: 'source-b',
|
||||
profile: 'default',
|
||||
@@ -168,9 +172,11 @@ describe('requestForSessionProfile', () => {
|
||||
profile: 'default',
|
||||
session_id: 'stored-a'
|
||||
})
|
||||
|
||||
routeA.connectionId = 'source-b'
|
||||
routeA.targetProfile = 'mutated-after-dispatch'
|
||||
await ensureGatewayForProfile('default')
|
||||
|
||||
const fromB = requestForSessionProfile(routeB, ambient as never, 'session.resume', {
|
||||
profile: 'default',
|
||||
session_id: 'stored-b'
|
||||
|
||||
@@ -39,8 +39,8 @@ import {
|
||||
setCurrentCwdTransient,
|
||||
setRememberedRoute,
|
||||
setRememberedSessionId,
|
||||
setSessionOwnerHint,
|
||||
setSelectedStoredSessionId,
|
||||
setSessionOwnerHint,
|
||||
setSessions,
|
||||
shouldMigrateComposerScope,
|
||||
touchSessionActivity,
|
||||
|
||||
Reference in New Issue
Block a user