From 8394fd7c1fa53bf38626708cdf025b1bb1058085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 31 Aug 2026 12:01:34 +0200 Subject: [PATCH] fix: show named sessions in scoped inventory --- .../__tests__/request-lock-policy.test.ts | 2 +- .../__tests__/request-router-open.test.ts | 4 + .../request-router-session-address.test.ts | 6 +- src/daemon/__tests__/session-routing.test.ts | 36 ++++++- .../session-inventory-scoped-paths.test.ts | 99 +++++++++++++++++++ .../__tests__/session-open-surface.test.ts | 1 + .../__tests__/session-test-harness.ts | 1 + src/daemon/handlers/record-runtime.ts | 4 +- src/daemon/handlers/session-inventory.ts | 6 +- src/daemon/handlers/session-open-execution.ts | 14 ++- src/daemon/handlers/session-open-surface.ts | 4 +- .../handlers/session-replay-action-runtime.ts | 5 +- src/daemon/handlers/snapshot-session.ts | 6 +- src/daemon/request-lock-policy.ts | 4 +- src/daemon/session-routing.ts | 58 +++++++---- src/daemon/snapshot-command-runtime.ts | 14 ++- src/daemon/types.ts | 11 ++- 17 files changed, 229 insertions(+), 46 deletions(-) diff --git a/src/daemon/__tests__/request-lock-policy.test.ts b/src/daemon/__tests__/request-lock-policy.test.ts index 52d31482ff..e5c0642201 100644 --- a/src/daemon/__tests__/request-lock-policy.test.ts +++ b/src/daemon/__tests__/request-lock-policy.test.ts @@ -142,7 +142,7 @@ test('rejects existing-session selector conflicts under request lock policy', () }, ref(IOS_SESSION), ), - /--serial=emulator-5554/i, + /Session "qa-ios" is already bound to apple device "iPhone 16" \(SIM-001\), but snapshot selected --serial=emulator-5554/i, ); }); diff --git a/src/daemon/__tests__/request-router-open.test.ts b/src/daemon/__tests__/request-router-open.test.ts index eb196ba2bb..c95ede4c21 100644 --- a/src/daemon/__tests__/request-router-open.test.ts +++ b/src/daemon/__tests__/request-router-open.test.ts @@ -351,6 +351,10 @@ test('open stores admitted lease metadata on the session', async () => { ); expect(response.ok).toBe(true); + expect(sessionStore.get('tenant-a:default')?.sessionScope).toEqual({ + kind: 'tenant', + id: 'tenant-a', + }); expect(sessionStore.get('tenant-a:default')?.lease).toEqual({ leaseId: lease.leaseId, tenantId: 'tenant-a', diff --git a/src/daemon/__tests__/request-router-session-address.test.ts b/src/daemon/__tests__/request-router-session-address.test.ts index 0fe344d529..7ef2bc46b9 100644 --- a/src/daemon/__tests__/request-router-session-address.test.ts +++ b/src/daemon/__tests__/request-router-session-address.test.ts @@ -198,7 +198,7 @@ test('lock-policy conflict on an implicit session names its store key, not "defa expect(response.ok).toBe(false); if (response.ok) return; expect(response.error.code).toBe('INVALID_ARGS'); - expect(response.error.message).toContain(`session "${address}"`); + expect(response.error.message).toContain(`Session "${address}"`); expect(response.error.details?.session).toBe(address); expectAddressableRecovery(errorHint(response), address); }); @@ -221,4 +221,8 @@ test('session list reports the address an implicit session answers to alongside expect(sessions?.[0]?.name).toBe('default'); expect(sessions?.[0]?.address).toBe(address); expect(sessions?.[0]?.address).toMatch(SCOPED_ADDRESS_PATTERN); + expect(sessionStore.get(address)?.sessionScope).toEqual({ + kind: 'cwd', + id: address.split(':')[1], + }); }); diff --git a/src/daemon/__tests__/session-routing.test.ts b/src/daemon/__tests__/session-routing.test.ts index 73fbafc782..75f4f92b17 100644 --- a/src/daemon/__tests__/session-routing.test.ts +++ b/src/daemon/__tests__/session-routing.test.ts @@ -3,8 +3,8 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { SessionStore } from '../session-store.ts'; -import { resolveEffectiveSessionName } from '../session-routing.ts'; -import type { SessionState } from '../types.ts'; +import { resolveEffectiveSessionName, resolveSessionScope } from '../session-routing.ts'; +import type { DaemonRequest, SessionState } from '../types.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; function makeSession(name: string): SessionState { @@ -111,3 +111,35 @@ test('keeps explicitly configured default session global', (t) => { assert.equal(resolved, 'default'); }); + +test('classifies every persisted session provenance without parsing its address', (t) => { + const cwd = mkdtempForTestSync('agent-device-cwd-scope-'); + t.onTestFinished(() => { + fs.rmSync(cwd, { recursive: true, force: true }); + }); + const request: DaemonRequest = { + token: 't', + session: 'default', + command: 'open', + positionals: ['com.example.app'], + flags: {}, + }; + + const cwdScope = resolveSessionScope({ ...request, session: 'default', meta: { cwd } }); + assert.equal(cwdScope.kind, 'cwd'); + if (cwdScope.kind === 'cwd') assert.match(cwdScope.id, /^[a-f0-9]{16}$/); + assert.deepEqual(resolveSessionScope({ ...request, session: 'tenant-a:qa' }), { + kind: 'named-local', + }); + assert.deepEqual(resolveSessionScope({ ...request, session: 'default' }), { + kind: 'global-default', + }); + assert.deepEqual( + resolveSessionScope({ + ...request, + session: 'tenant-a:default', + meta: { tenantId: 'tenant-a', sessionIsolation: 'tenant' }, + }), + { kind: 'tenant', id: 'tenant-a' }, + ); +}); diff --git a/src/daemon/handlers/__tests__/session-inventory-scoped-paths.test.ts b/src/daemon/handlers/__tests__/session-inventory-scoped-paths.test.ts index 70eae157b1..4b6a48e692 100644 --- a/src/daemon/handlers/__tests__/session-inventory-scoped-paths.test.ts +++ b/src/daemon/handlers/__tests__/session-inventory-scoped-paths.test.ts @@ -4,6 +4,8 @@ import { handleSessionInventoryCommands } from '../session-inventory.ts'; import { makeSessionStore } from '../../../__tests__/test-utils/store-factory.ts'; import type { DaemonRequest, DaemonResponse, SessionState } from '../../types.ts'; import { IOS_SIMULATOR } from '../../../__tests__/test-utils/device-fixtures.ts'; +import { resolveImplicitSessionScope } from '../../session-routing.ts'; +import { tenantScopedSessionName } from '../../session-tenant-scope.ts'; // A session opened without an explicit --session is NAMED `default` and STORED under // `cwd::default`. `session list` resolved its paths from the name, so it answered with @@ -39,6 +41,103 @@ async function runSessionList(): Promise { }); } +test('session list returns the caller cwd and local named sessions without crossing cwd or tenant ownership', async () => { + const sessionStore = makeSessionStore('agent-device-inventory-scoped-'); + const req: DaemonRequest = { + token: 't', + session: 'default', + command: 'session_list', + positionals: [], + flags: {}, + meta: { cwd: '/tmp/shop-app' }, + }; + const callerScope = resolveImplicitSessionScope(req)!; + const callerSessionAddress = `cwd:${callerScope.id}:default`; + sessionStore.set(callerSessionAddress, { + ...scopedSession(), + sessionScope: callerScope, + }); + sessionStore.set('qa-cart-integrity', { + ...scopedSession(), + name: 'qa-cart-integrity', + sessionScope: { kind: 'named-local' }, + }); + sessionStore.set('tenant-a:qa', { + ...scopedSession(), + name: 'tenant-a:qa', + sessionScope: { kind: 'named-local' }, + }); + sessionStore.set('default', { + ...scopedSession(), + sessionScope: { kind: 'global-default' }, + }); + sessionStore.set('cwd:other:default', { + ...scopedSession(), + sessionScope: { kind: 'cwd', id: 'other' }, + }); + const tenantSessionAddress = tenantScopedSessionName('tenant-a', 'remote-recording'); + sessionStore.set(tenantSessionAddress, { + ...scopedSession(), + name: tenantSessionAddress, + sessionScope: { kind: 'tenant', id: 'tenant-a' }, + }); + + const response = await handleSessionInventoryCommands({ + req, + sessionName: callerSessionAddress, + sessionStore, + }); + + expect(response?.ok).toBe(true); + if (!response?.ok) return; + const sessions = response.data?.sessions as { name: string; address: string }[]; + expect(sessions).toEqual([ + expect.objectContaining({ name: 'default', address: callerSessionAddress }), + expect.objectContaining({ name: 'qa-cart-integrity', address: 'qa-cart-integrity' }), + expect.objectContaining({ name: 'tenant-a:qa', address: 'tenant-a:qa' }), + ]); +}); + +test('session list returns only sessions owned by the requesting tenant', async () => { + const sessionStore = makeSessionStore('agent-device-inventory-tenant-'); + for (const tenantId of ['tenant-a', 'tenant-b']) { + const address = tenantScopedSessionName(tenantId, 'default'); + sessionStore.set(address, { + ...scopedSession(), + name: address, + sessionScope: { kind: 'tenant', id: tenantId }, + }); + } + for (const address of ['qa-cart-integrity', 'tenant-a:qa']) { + sessionStore.set(address, { + ...scopedSession(), + name: address, + sessionScope: { kind: 'named-local' }, + }); + } + const req: DaemonRequest = { + token: 't', + session: tenantScopedSessionName('tenant-a', 'default'), + command: 'session_list', + positionals: [], + flags: {}, + meta: { tenantId: 'tenant-a', sessionIsolation: 'tenant' }, + }; + + const response = await handleSessionInventoryCommands({ + req, + sessionName: req.session, + sessionStore, + }); + + expect(response?.ok).toBe(true); + if (!response?.ok) return; + const sessions = response.data?.sessions as { address: string }[]; + expect(sessions).toEqual([ + expect.objectContaining({ address: tenantScopedSessionName('tenant-a', 'default') }), + ]); +}); + test('session list resolves a cwd-scoped session directory from its store key', async () => { const response = await runSessionList(); diff --git a/src/daemon/handlers/__tests__/session-open-surface.test.ts b/src/daemon/handlers/__tests__/session-open-surface.test.ts index b2b47e5fd1..650fa8a350 100644 --- a/src/daemon/handlers/__tests__/session-open-surface.test.ts +++ b/src/daemon/handlers/__tests__/session-open-surface.test.ts @@ -95,6 +95,7 @@ function reopen(existingSession: ReturnType) { return buildNextOpenSession({ existingSession, sessionName: existingSession.name, + sessionScope: existingSession.sessionScope ?? { kind: 'named-local' }, device: IOS_SIMULATOR, surface: 'app', appBundleId: 'com.example.other', diff --git a/src/daemon/handlers/__tests__/session-test-harness.ts b/src/daemon/handlers/__tests__/session-test-harness.ts index c0c1b0b70e..a49cd6f8ad 100644 --- a/src/daemon/handlers/__tests__/session-test-harness.ts +++ b/src/daemon/handlers/__tests__/session-test-harness.ts @@ -196,6 +196,7 @@ export function makeSessionStore(): SessionStore { export function makeSession(name: string, device: SessionState['device']): SessionState { return { name, + sessionScope: { kind: 'named-local' }, device, createdAt: Date.now(), actions: [], diff --git a/src/daemon/handlers/record-runtime.ts b/src/daemon/handlers/record-runtime.ts index 9caafd7bd9..7ad07dda98 100644 --- a/src/daemon/handlers/record-runtime.ts +++ b/src/daemon/handlers/record-runtime.ts @@ -20,7 +20,7 @@ import { screenRecordingDurableResource, } from '../screen-recording-session-resource.ts'; import { createScreenRecordingRecoveryControl } from '../screen-recording-resource-recovery.ts'; -import { resolveImplicitSessionScope } from '../session-routing.ts'; +import { resolveSessionScope } from '../session-routing.ts'; import type { SessionStore } from '../session-store.ts'; import type { BindDeviceRuntime, BindExactDeviceRuntime } from '../request-runtime-binding.ts'; import type { DaemonRequest, DaemonResponse, SessionState } from '../types.ts'; @@ -281,7 +281,7 @@ function createRecordOnlySession( ): SessionState { return { name: params.sessionName, - sessionScope: resolveImplicitSessionScope(params.req), + sessionScope: resolveSessionScope(params.req), device, createdAt: Date.now(), recordOnlySession: true, diff --git a/src/daemon/handlers/session-inventory.ts b/src/daemon/handlers/session-inventory.ts index 192b0ff85f..6bb95628ec 100644 --- a/src/daemon/handlers/session-inventory.ts +++ b/src/daemon/handlers/session-inventory.ts @@ -27,7 +27,7 @@ import { selectorTargetsSessionDevice, } from './session-device-utils.ts'; import { errorResponse } from './response.ts'; -import { resolveImplicitSessionScope, sessionMatchesScope } from '../session-routing.ts'; +import { resolveSessionScope, sessionMatchesInventoryScope } from '../session-routing.ts'; import type { BoundDeviceRuntime, RuntimeFacts, @@ -77,13 +77,13 @@ function sessionListInventoryResponse( req: DaemonRequest, sessionStore: SessionStore, ): DaemonResponse { - const scope = resolveImplicitSessionScope(req); + const scope = resolveSessionScope(req); return { ok: true, data: { sessions: sessionStore .listRefs() - .filter((ref) => sessionMatchesScope(ref.session, scope)) + .filter((ref) => sessionMatchesInventoryScope(ref.session, scope)) .map((ref) => publicSessionInfo(ref, sessionStore)), }, }; diff --git a/src/daemon/handlers/session-open-execution.ts b/src/daemon/handlers/session-open-execution.ts index 64554fcadc..ab41da8c5b 100644 --- a/src/daemon/handlers/session-open-execution.ts +++ b/src/daemon/handlers/session-open-execution.ts @@ -10,7 +10,13 @@ import { import type { BoundDeviceRuntime } from '@agent-device/contracts/platform-runtime'; import type { SessionSurface } from '@agent-device/contracts/session'; import type { DeviceInfo } from '@agent-device/kernel/device'; -import type { DaemonRequest, DaemonResponse, SessionRef, SessionState } from '../types.ts'; +import type { + DaemonRequest, + DaemonResponse, + SessionRef, + SessionScope, + SessionState, +} from '../types.ts'; import { abortAuthoringOnSecondOpen, armAuthoringOnOpen, @@ -40,7 +46,7 @@ import { errorResponse } from './response.ts'; import { buildSessionRecoveryHint } from '../session-recovery-hints.ts'; import { isImplicitSessionScopeConflict, - resolveImplicitSessionScope, + resolveSessionScope, resolvePublicSessionName, } from '../session-routing.ts'; import { resolveSessionLeaseForRequest } from '../lease-lifecycle.ts'; @@ -79,8 +85,8 @@ export type RuntimeHintClearOperation = BoundDeviceRuntime< typeof openApplicationWithRuntimeHintClearUse >['operations']['clearRuntimeHints']; -function resolveOpenSessionScope(req: DaemonRequest): SessionState['sessionScope'] | undefined { - return req.internal?.resolvedSessionScope ?? resolveImplicitSessionScope(req); +function resolveOpenSessionScope(req: DaemonRequest): SessionScope { + return req.internal?.resolvedSessionScope ?? resolveSessionScope(req); } function applyOrdinaryScriptRecordingOpenOutcome(params: { diff --git a/src/daemon/handlers/session-open-surface.ts b/src/daemon/handlers/session-open-surface.ts index fc7262a300..a5495642f7 100644 --- a/src/daemon/handlers/session-open-surface.ts +++ b/src/daemon/handlers/session-open-surface.ts @@ -5,7 +5,7 @@ import { publicPlatformString, type DeviceInfo, } from '@agent-device/kernel/device'; -import type { SessionRuntimeHints, SessionState } from '../types.ts'; +import type { SessionRuntimeHints, SessionScope, SessionState } from '../types.ts'; import { successText } from '@agent-device/kernel/success-text'; import type { StartupPerfSample } from './session-startup-metrics.ts'; import type { DeviceSelectionResult } from '../../core/device-selection-resolver.ts'; @@ -92,7 +92,7 @@ function selectionResponseData( export function buildNextOpenSession(params: { existingSession?: SessionState; sessionName: string; - sessionScope?: SessionState['sessionScope']; + sessionScope: SessionScope; device: DeviceInfo; surface: SessionSurface; appBundleId?: string; diff --git a/src/daemon/handlers/session-replay-action-runtime.ts b/src/daemon/handlers/session-replay-action-runtime.ts index 2723220a2d..b524d93c46 100644 --- a/src/daemon/handlers/session-replay-action-runtime.ts +++ b/src/daemon/handlers/session-replay-action-runtime.ts @@ -11,7 +11,7 @@ import { buildDisplayPositionals } from '../session-event-action.ts'; import { appendReplayTraceEvent } from './session-replay-trace.ts'; import { inferFillText } from '../action-utils.ts'; import { readRecordedInputVariableName } from '@agent-device/ad-script'; -import { resolveImplicitSessionScope } from '../session-routing.ts'; +import { resolveSessionScope } from '../session-routing.ts'; type ReplayBaseRequest = Omit; @@ -155,8 +155,7 @@ async function invokeResolvedReplayAction(params: { replayPlanStep: true, ...(resolved.command === 'open' ? { - resolvedSessionScope: - req.internal?.resolvedSessionScope ?? resolveImplicitSessionScope(req), + resolvedSessionScope: req.internal?.resolvedSessionScope ?? resolveSessionScope(req), } : {}), }, diff --git a/src/daemon/handlers/snapshot-session.ts b/src/daemon/handlers/snapshot-session.ts index e93410713e..6cdc9cdc55 100644 --- a/src/daemon/handlers/snapshot-session.ts +++ b/src/daemon/handlers/snapshot-session.ts @@ -1,6 +1,6 @@ import { resolveTargetDevice } from '../../core/dispatch-resolve.ts'; import type { PlatformResourceCleanup } from '@agent-device/contracts/platform-resource-cleanup'; -import type { DaemonRequest, SessionState } from '../types.ts'; +import type { DaemonRequest, SessionScope, SessionState } from '../types.ts'; import { ensureDeviceReady } from '../device-ready.ts'; import { SessionStore } from '../session-store.ts'; @@ -49,11 +49,12 @@ export function recordIfSession( export function buildSnapshotSession(params: { session: SessionState | undefined; sessionName: string; + sessionScope: SessionScope; device: SessionState['device']; snapshot: SessionState['snapshot']; appBundleId?: string; }): SessionState { - const { session, sessionName, device, snapshot, appBundleId } = params; + const { session, sessionName, sessionScope, device, snapshot, appBundleId } = params; if (session) { return { ...session, @@ -64,6 +65,7 @@ export function buildSnapshotSession(params: { } return { name: sessionName, + sessionScope, device, createdAt: Date.now(), appBundleId, diff --git a/src/daemon/request-lock-policy.ts b/src/daemon/request-lock-policy.ts index 512be48c10..616386d16b 100644 --- a/src/daemon/request-lock-policy.ts +++ b/src/daemon/request-lock-policy.ts @@ -115,8 +115,8 @@ function buildLockPolicyConflictMessage( const conflictList = conflicts.map(formatSessionSelectorConflict).join(', '); if (existingRef) { return ( - `${req.command} is already bound to session "${existingRef.address}" on ${describeSessionDevice(existingRef.session)}, ` + - `but this request selected ${conflictList}.` + `Session "${existingRef.address}" is already bound to ${describeSessionDevice(existingRef.session)}, ` + + `but ${req.command} selected ${conflictList}.` ); } const lockPlatform = req.meta?.lockPlatform; diff --git a/src/daemon/session-routing.ts b/src/daemon/session-routing.ts index 3097437de1..4e16f840a4 100644 --- a/src/daemon/session-routing.ts +++ b/src/daemon/session-routing.ts @@ -2,7 +2,8 @@ import type { CommandFlags } from '@agent-device/contracts/command'; import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; -import type { DaemonRequest, SessionState } from './types.ts'; +import { AppError } from '@agent-device/kernel/errors'; +import type { DaemonRequest, SessionScope, SessionState } from './types.ts'; import { SessionStore } from './session-store.ts'; const DEFAULT_SESSION_NAME = 'default'; @@ -14,8 +15,8 @@ export function resolveEffectiveSessionName( ): string { const requested = req.session || DEFAULT_SESSION_NAME; if (hasExplicitSessionFlag(req)) return requested; - const scope = resolveImplicitSessionScope(req); - if (scope) return formatScopedSessionName(scope.id, requested); + const scope = resolveSessionScope(req); + if (scope.kind === 'cwd') return formatScopedSessionName(scope.id, requested); return requested; } @@ -25,32 +26,53 @@ export function resolvePublicSessionName(req: DaemonRequest): string { export function resolveImplicitSessionScope( req: DaemonRequest, -): SessionState['sessionScope'] | undefined { - if (req.meta?.sessionExplicit === true) return undefined; - if ((req.session || DEFAULT_SESSION_NAME) !== DEFAULT_SESSION_NAME) return undefined; +): Extract | undefined { + const scope = resolveSessionScope(req); + return scope.kind === 'cwd' ? scope : undefined; +} + +export function resolveSessionScope(req: DaemonRequest): SessionScope { if (req.meta?.sessionIsolation === 'tenant' || req.flags?.sessionIsolation === 'tenant') { - return undefined; + const tenantId = req.meta?.tenantId; + if (!tenantId) { + throw new AppError( + 'INTERNAL_ERROR', + 'Tenant-scoped request reached session routing without an admitted tenant id', + ); + } + return { kind: 'tenant', id: tenantId }; + } + if ( + hasExplicitSessionFlag(req) || + (req.session || DEFAULT_SESSION_NAME) !== DEFAULT_SESSION_NAME + ) { + return { kind: 'named-local' }; } const scopeRoot = resolveCallerScopeRoot(req.meta?.cwd); - if (!scopeRoot) return undefined; - return { - kind: 'cwd', - id: hashScopeRoot(scopeRoot), - }; + return scopeRoot ? { kind: 'cwd', id: hashScopeRoot(scopeRoot) } : { kind: 'global-default' }; } -export function sessionMatchesScope( +export function sessionMatchesInventoryScope( session: SessionState, - scope: SessionState['sessionScope'] | undefined, + requestScope: SessionScope, ): boolean { - if (!scope) return true; - return session.sessionScope?.kind === scope.kind && session.sessionScope.id === scope.id; + const sessionScope = session.sessionScope; + if (!sessionScope) return false; + if (requestScope.kind === 'tenant') { + return sessionScope.kind === 'tenant' && sessionScope.id === requestScope.id; + } + if (sessionScope.kind === 'tenant') return false; + if (requestScope.kind !== 'cwd') return true; + return ( + sessionScope.kind === 'named-local' || + (sessionScope.kind === 'cwd' && sessionScope.id === requestScope.id) + ); } export function isImplicitSessionScopeConflict(req: DaemonRequest, session: SessionState): boolean { const scope = resolveImplicitSessionScope(req); - if (!scope || !session.sessionScope) return false; - return !sessionMatchesScope(session, scope); + if (!scope || session.sessionScope?.kind !== 'cwd') return false; + return session.sessionScope.id !== scope.id; } export function hasExplicitSessionFlag(req: DaemonRequest): boolean { diff --git a/src/daemon/snapshot-command-runtime.ts b/src/daemon/snapshot-command-runtime.ts index 4f767e65e0..91aa1d7e4a 100644 --- a/src/daemon/snapshot-command-runtime.ts +++ b/src/daemon/snapshot-command-runtime.ts @@ -14,6 +14,7 @@ import type { RuntimeAdmissionBindings } from './request-runtime-binding.ts'; import { maybeBuildAndroidSnapshotTimeoutFailure } from './android-snapshot-timeout-evidence.ts'; import { captureSnapshot } from './handlers/snapshot-capture.ts'; import { buildSnapshotSession, withSessionlessRunnerCleanup } from './handlers/snapshot-session.ts'; +import { resolveSessionScope } from './session-routing.ts'; import { activateCompleteRefFrame } from './ref-frame.ts'; import { applyRecoveredWarningLatch, @@ -28,7 +29,13 @@ import { resolveBoundSnapshotCaptureRuntime, type SnapshotRuntimeRouteParams, } from './snapshot-runtime-binding.ts'; -import type { DaemonRequest, DaemonResponse, DaemonResponseData, SessionState } from './types.ts'; +import type { + DaemonRequest, + DaemonResponse, + DaemonResponseData, + SessionScope, + SessionState, +} from './types.ts'; export type SnapshotRuntimeRecord = | { kind: 'snapshot'; nodes: number; truncated: boolean | undefined } @@ -152,6 +159,7 @@ function createSnapshotRuntime( buildNextSnapshotSession({ current, sessionName, + sessionScope: resolveSessionScope(req), device, record: snapshotRecord, refScopedSnapshot: isRefScopedSnapshot(req), @@ -169,17 +177,19 @@ function createSnapshotRuntime( function buildNextSnapshotSession(params: { current: SessionState | undefined; sessionName: string; + sessionScope: SessionScope; device: SessionState['device']; record: CommandSessionRecord & { snapshot: NonNullable }; refScopedSnapshot: boolean; issuesRefsToClient: boolean; }): SessionState { - const { current, sessionName, device, record, refScopedSnapshot } = params; + const { current, sessionName, sessionScope, device, record, refScopedSnapshot } = params; const keepCurrentSnapshot = shouldKeepCurrentSnapshot(current, record, refScopedSnapshot); const snapshot = keepCurrentSnapshot ? current.snapshot : record.snapshot; const nextSession = buildSnapshotSession({ session: current, sessionName, + sessionScope, device, snapshot, appBundleId: record.appBundleId, diff --git a/src/daemon/types.ts b/src/daemon/types.ts index 19a433e71e..7e78f05e69 100644 --- a/src/daemon/types.ts +++ b/src/daemon/types.ts @@ -240,12 +240,15 @@ export type SessionRef = { session: SessionState; }; +export type SessionScope = + | { kind: 'cwd'; id: string } + | { kind: 'tenant'; id: string } + | { kind: 'named-local' } + | { kind: 'global-default' }; + export type SessionState = { name: string; - sessionScope?: { - kind: 'cwd'; - id: string; - }; + sessionScope?: SessionScope; lease?: { leaseId: string; tenantId: string;