Skip to content
11 changes: 0 additions & 11 deletions apps/cli/ai/sessions/paths.ts

This file was deleted.

4 changes: 2 additions & 2 deletions apps/cli/ai/sessions/pi-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path';
import { SessionManager } from '@earendil-works/pi-coding-agent';
import { migrateLegacyFileInPlace } from '@studio/common/ai/sessions/migration';
import { getAiSessionsDirectoryForDate } from '@studio/common/ai/sessions/paths';
import { getAiSessionsRootDirectory } from 'cli/ai/sessions/paths';
import { getSessionsDirectory } from '@studio/common/lib/well-known-paths';
import { STUDIO_SITES_ROOT } from 'cli/lib/site-paths';

export interface CreateStudioSessionOptions {
Expand All @@ -14,7 +14,7 @@ export async function createStudioSession(
options: CreateStudioSessionOptions = {}
): Promise< SessionManager > {
const startedAt = options.startedAt ?? new Date();
const sessionDir = getAiSessionsDirectoryForDate( getAiSessionsRootDirectory(), startedAt );
const sessionDir = getAiSessionsDirectoryForDate( getSessionsDirectory(), startedAt );
await fs.mkdir( sessionDir, { recursive: true } );
return SessionManager.create( STUDIO_SITES_ROOT, sessionDir );
}
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/commands/ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DEFAULT_MODEL, resolveSessionModel, type AiModelId } from '@studio/comm
import { getAgentEndTurnResult } from '@studio/common/ai/session-events';
import { buildSkillInvocationPrompt } from '@studio/common/ai/slash-commands';
import { readAuthToken } from '@studio/common/lib/shared-config';
import { getSessionsDirectory } from '@studio/common/lib/well-known-paths';
import { __, sprintf } from '@wordpress/i18n';
import {
getAvailableAiProviders,
Expand All @@ -26,7 +27,6 @@ import { AI_PROVIDERS, getAiProviderDefinition, type AiProviderId } from 'cli/ai
import { runStudioAgentTurn } from 'cli/ai/runtimes/pi';
import { setScreenshotDirectoryProvider } from 'cli/ai/screenshot-storage';
import { resolveResumeSessionContext } from 'cli/ai/sessions/context';
import { getAiSessionsRootDirectory } from 'cli/ai/sessions/paths';
import {
createStudioSession,
listStudioSessionFiles,
Expand Down Expand Up @@ -140,7 +140,7 @@ export async function runCommand( options: {
if ( options.resumeSession ) {
session = await openStudioSession( options.resumeSession.summary.filePath );
} else if ( options.resumeSessionId ) {
const files = await listStudioSessionFiles( getAiSessionsRootDirectory() );
const files = await listStudioSessionFiles( getSessionsDirectory() );
let match: string | undefined;
for ( const file of files ) {
try {
Expand Down
9 changes: 3 additions & 6 deletions apps/cli/commands/ai/sessions/delete.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { deleteAiSession, listAiSessions } from '@studio/common/ai/sessions/store';
import { getSessionsDirectory } from '@studio/common/lib/well-known-paths';
import { __ } from '@wordpress/i18n';
import { getAiSessionsRootDirectory } from 'cli/ai/sessions/paths';
import { chooseSessionForAction } from 'cli/commands/ai/sessions/helpers';
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';
Expand All @@ -23,18 +23,15 @@ export async function runCommand( sessionIdOrPrefix?: string ): Promise< void >
}

if ( resolvedSessionIdOrPrefix.toLowerCase() === 'latest' ) {
const sessions = await listAiSessions( getAiSessionsRootDirectory() );
const sessions = await listAiSessions( getSessionsDirectory() );
if ( sessions.length === 0 ) {
throw new Error( __( 'No code sessions found' ) );
}

resolvedSessionIdOrPrefix = sessions[ 0 ].id;
}

const deletedSession = await deleteAiSession(
getAiSessionsRootDirectory(),
resolvedSessionIdOrPrefix
);
const deletedSession = await deleteAiSession( getSessionsDirectory(), resolvedSessionIdOrPrefix );
console.log( `${ __( 'Deleted code session' ) }: ${ deletedSession.id }` );
}

Expand Down
4 changes: 2 additions & 2 deletions apps/cli/commands/ai/sessions/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
import { select } from '@inquirer/prompts';
import { listAiSessions } from '@studio/common/ai/sessions/store';
import chalk from '@studio/common/lib/chalk';
import { getSessionsDirectory } from '@studio/common/lib/well-known-paths';
import { __ } from '@wordpress/i18n';
import { getAiSessionsRootDirectory } from 'cli/ai/sessions/paths';
import type { AiSessionSummary } from '@studio/common/ai/sessions/types';

function formatSessionTimestamp( timestamp: string ): string {
Expand Down Expand Up @@ -201,7 +201,7 @@ export async function chooseSessionForAction(
actionLabel: string,
noSessionsMessage: string
): Promise< AiSessionSummary | undefined > {
const sessions = await listAiSessions( getAiSessionsRootDirectory() );
const sessions = await listAiSessions( getSessionsDirectory() );
if ( sessions.length === 0 ) {
console.log( noSessionsMessage );
return undefined;
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/commands/ai/sessions/list.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { listAiSessions } from '@studio/common/ai/sessions/store';
import { getSessionsDirectory } from '@studio/common/lib/well-known-paths';
import { __ } from '@wordpress/i18n';
import { getAiSessionsRootDirectory } from 'cli/ai/sessions/paths';
import { displaySessionsCompact } from 'cli/commands/ai/sessions/helpers';
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';
Expand All @@ -10,7 +10,7 @@ const logger = new Logger< string >();
type SessionOutputFormat = 'compact' | 'json';

export async function runCommand( format: SessionOutputFormat ): Promise< void > {
const sessions = await listAiSessions( getAiSessionsRootDirectory() );
const sessions = await listAiSessions( getSessionsDirectory() );

if ( format === 'json' ) {
console.log( JSON.stringify( sessions, null, 2 ) );
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/commands/ai/sessions/resume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { validateStudioChatFiles } from '@studio/common/ai/chat-files';
import { validateStudioChatImages } from '@studio/common/ai/chat-images';
import { resolveActiveSiteFromEntries } from '@studio/common/ai/sessions/active-site';
import { listAiSessions, loadAiSession } from '@studio/common/ai/sessions/store';
import { getSessionsDirectory } from '@studio/common/lib/well-known-paths';
import { __ } from '@wordpress/i18n';
import { JsonAdapter } from 'cli/ai/output-adapter';
import { getAiSessionsRootDirectory } from 'cli/ai/sessions/paths';
import { AiChatUI } from 'cli/ai/ui';
import { runCommand as runAiCommand } from 'cli/commands/ai';
import { chooseSessionForAction } from 'cli/commands/ai/sessions/helpers';
Expand Down Expand Up @@ -51,15 +51,15 @@ export async function runCommand(
}

if ( resolvedSessionIdOrPrefix.toLowerCase() === 'latest' ) {
const sessions = await listAiSessions( getAiSessionsRootDirectory() );
const sessions = await listAiSessions( getSessionsDirectory() );
if ( sessions.length === 0 ) {
throw new Error( __( 'No code sessions found' ) );
}

resolvedSessionIdOrPrefix = sessions[ 0 ].id;
}

const session = await loadAiSession( getAiSessionsRootDirectory(), resolvedSessionIdOrPrefix );
const session = await loadAiSession( getSessionsDirectory(), resolvedSessionIdOrPrefix );
const adapter: AiOutputAdapter = options.json ? new JsonAdapter() : new AiChatUI();
const inputPayload = options.inputPayloadPath
? await readInputPayload( options.inputPayloadPath )
Expand Down
5 changes: 4 additions & 1 deletion apps/cli/commands/ai/sessions/tests/resume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ vi.mock( '@studio/common/ai/sessions/store', () => ( {
loadAiSession: vi.fn(),
listAiSessions: vi.fn(),
} ) );
vi.mock( 'cli/ai/sessions/paths', () => ( { getAiSessionsRootDirectory: vi.fn() } ) );
vi.mock( '@studio/common/lib/well-known-paths', async ( importOriginal ) => ( {
...( await importOriginal< typeof import('@studio/common/lib/well-known-paths') >() ),
getSessionsDirectory: vi.fn(),
} ) );
vi.mock( 'cli/ai/ui', () => ( { AiChatUI: class AiChatUI {} } ) );
vi.mock( 'cli/commands/ai', () => ( { runCommand: vi.fn() } ) );
vi.mock( 'cli/commands/ai/sessions/helpers', () => ( { chooseSessionForAction: vi.fn() } ) );
Expand Down
5 changes: 4 additions & 1 deletion apps/cli/commands/ai/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ vi.mock( 'cli/ai/sessions/pi-session', () => ( {
listStudioSessionFiles: vi.fn(),
} ) );
vi.mock( 'cli/ai/sessions/replay', () => ( { replaySessionHistory: vi.fn() } ) );
vi.mock( 'cli/ai/sessions/paths', () => ( { getAiSessionsRootDirectory: vi.fn() } ) );
vi.mock( '@studio/common/lib/well-known-paths', async ( importOriginal ) => ( {
...( await importOriginal< typeof import('@studio/common/lib/well-known-paths') >() ),
getSessionsDirectory: vi.fn(),
} ) );
vi.mock( 'cli/ai/runtimes/pi', () => ( {
// A completed turn so the JSON single-turn path returns instead of looping.
runStudioAgentTurn: vi.fn( () => ( { result: Promise.resolve(), interrupt: vi.fn() } ) ),
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/commands/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ export const registerCommand = ( yargs: StudioArgv ) => {
// `@studio/local` is bundled into the CLI from source (see the vite
// alias); the dynamic import keeps Express off the startup path of
// every other command.
const [ { startLocalServer }, { getAiSessionsRootDirectory }, { STUDIO_SITES_ROOT } ] =
const [ { startLocalServer }, { getSessionsDirectory }, { STUDIO_SITES_ROOT } ] =
await Promise.all( [
import( '@studio/local' ),
import( 'cli/ai/sessions/paths' ),
import( '@studio/common/lib/well-known-paths' ),
import( 'cli/lib/site-paths' ),
] );

Expand All @@ -46,7 +46,7 @@ export const registerCommand = ( yargs: StudioArgv ) => {

const server = await startLocalServer( {
cliBinary,
sessionsRoot: getAiSessionsRootDirectory(),
sessionsRoot: getSessionsDirectory(),
sitesRoot: STUDIO_SITES_ROOT,
port: argv.port as number | undefined,
uiDist,
Expand Down
2 changes: 2 additions & 0 deletions apps/cli/migrations/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { moveAiSessionsToStudioDir } from '@studio/common/ai/sessions/root-migration';
import { checkStudioCompatibilityForInitialMigration } from './00-check-studio-compatibility';
import { hideStudioDirWindows } from './01-hide-studio-dir-windows';
import { renameProcessManagerHome } from './03-rename-pm-home';
Expand All @@ -13,4 +14,5 @@ export const migrations: Migration[] = [
cleanupObsoleteServerFiles,
migrateConnectedSitesToShared,
installBundledDefaultPhp,
moveAiSessionsToStudioDir,
];
31 changes: 9 additions & 22 deletions apps/hosted/src/lib/paths.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
import os from 'node:os';
import path from 'node:path';
import { getSessionsDirectory } from '@studio/common/lib/well-known-paths';

/**
* Where AI sessions live on disk.
*
* This is a **temporary stand-in**. The hosted backend ultimately won't read
* sessions from a local Studio appdata directory at all — sessions will be
* persisted server-side (git is the leading proposal). For now, so the
* experimental browser UI can talk to the same sessions the desktop app and CLI
* already write, we duplicate the CLI's appdata path resolution here rather than
* depend on CLI internals. `STUDIO_SESSIONS_APPDATA` overrides the base for
* tests and bespoke setups.
* sessions from a local Studio directory at all — sessions will be persisted
* server-side (git is the leading proposal). For now, so the experimental
* browser UI can talk to the same sessions the desktop app and CLI already
* write, we resolve the same `~/.studio/sessions` root they use.
* `STUDIO_SESSIONS_APPDATA` overrides the base for tests and bespoke setups.
*/
function getAppdataDirectory(): string {
export function getAiSessionsRootDirectory(): string {
if ( process.env.STUDIO_SESSIONS_APPDATA ) {
return process.env.STUDIO_SESSIONS_APPDATA;
}

if ( process.platform === 'win32' ) {
if ( ! process.env.APPDATA ) {
throw new Error( 'Studio config file path not found.' );
}
return path.join( process.env.APPDATA, 'Studio' );
return path.join( process.env.STUDIO_SESSIONS_APPDATA, 'sessions' );
}

return path.join( os.homedir(), 'Library', 'Application Support', 'Studio' );
}

export function getAiSessionsRootDirectory(): string {
return path.join( getAppdataDirectory(), 'sessions' );
return getSessionsDirectory();
}
Loading
Loading