Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions packages/cli/src/test-utils/mockConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
getListSessions: vi.fn(() => false),
getDeleteSession: vi.fn(() => undefined),
setSessionId: vi.fn(),
resetNewSessionState: vi.fn(),
getSessionId: vi.fn().mockReturnValue('mock-session-id'),
getWorktreeSettings: vi.fn(() => undefined),
getContentGeneratorConfig: vi.fn(() => ({ authType: 'google' })),
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/ui/commands/clearCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('clearCommand', () => {
agentContext: {
config: {
getEnableHooks: vi.fn().mockReturnValue(false),
setSessionId: vi.fn(),
resetNewSessionState: vi.fn(),
getMessageBus: vi.fn().mockReturnValue(undefined),
getHookSystem: vi.fn().mockReturnValue({
fireSessionEndEvent: vi.fn().mockResolvedValue(undefined),
Expand Down Expand Up @@ -74,6 +74,9 @@ describe('clearCommand', () => {

expect(mockResetChat).toHaveBeenCalledTimes(1);
expect(mockHintClear).toHaveBeenCalledTimes(1);
expect(
mockContext.services.agentContext?.config.resetNewSessionState,
).toHaveBeenCalledTimes(1);
expect(uiTelemetryService.clear).toHaveBeenCalled();
expect(uiTelemetryService.clear).toHaveBeenCalledTimes(1);
expect(mockContext.ui.clear).toHaveBeenCalledTimes(1);
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/ui/commands/clearCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const clearCommand: SlashCommand = {
let newSessionId: string | undefined;
if (config) {
newSessionId = randomUUID();
config.setSessionId(newSessionId);
config.resetNewSessionState(newSessionId);
}

if (geminiClient) {
Expand Down
44 changes: 44 additions & 0 deletions packages/core/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1774,6 +1774,50 @@ describe('Server Config (config.ts)', () => {
expect(config1.topicState.getTopic()).toBe('Topic 1');
expect(config2.topicState.getTopic()).toBe('Topic 2');
});

it('updates storage session-scoped directories when the sessionId changes', async () => {
const config = new Config({
...baseParams,
sessionId: 'session-one',
plan: true,
});

await config.initialize();
const tempDir = config.storage.getProjectTempDir();
const oldPlansDir = path.join(tempDir, 'session-one', 'plans');
const oldTrackerService = config.getTrackerService();

config.setSessionId('session-two');

expect(config.getSessionId()).toBe('session-two');
expect(config.storage.getProjectTempPlansDir()).toBe(
path.join(tempDir, 'session-two', 'plans'),
);
expect(config.storage.getProjectTempTrackerDir()).toBe(
path.join(tempDir, 'session-two', 'tracker'),
);
expect(config.getTrackerService()).not.toBe(oldTrackerService);
expect(config.getTrackerService().trackerDir).toBe(
path.join(tempDir, 'session-two', 'tracker'),
);
expect(config.getWorkspaceContext().getDirectories()).not.toContain(
oldPlansDir,
);
});

it('clears the approved plan when starting a new session', () => {
const config = new Config({
...baseParams,
sessionId: 'session-one',
});

config.setApprovedPlanPath('/tmp/session-one/plans/approved.md');

expect(() => config.resetNewSessionState('session-two')).not.toThrow();

expect(config.getSessionId()).toBe('session-two');
expect(config.getApprovedPlanPath()).toBeUndefined();
});
});

describe('GemmaModelRouterSettings', () => {
Expand Down
39 changes: 39 additions & 0 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1760,7 +1760,22 @@ export class Config implements McpContext, AgentLoopContext {
}

setSessionId(sessionId: string): void {
const previousPlansDir = this.storage.isInitialized()
? this.storage.getPlansDir()
: undefined;

this._sessionId = sessionId;
this.storage.setSessionId(sessionId);
this.trackerService = undefined;

if (previousPlansDir) {
this.refreshSessionScopedPlansDirectory(previousPlansDir);
}
}

resetNewSessionState(sessionId: string): void {
this.setSessionId(sessionId);
this.approvedPlanPath = undefined;
}

setTerminalBackground(terminalBackground: string | undefined): void {
Expand Down Expand Up @@ -2049,6 +2064,30 @@ export class Config implements McpContext, AgentLoopContext {
return getWorkspaceContextOverride() ?? this.workspaceContext;
}

private refreshSessionScopedPlansDirectory(previousPlansDir: string): void {
const nextPlansDir = this.storage.getPlansDir();
if (previousPlansDir === nextPlansDir) {
return;
}

const previousPlansDirRealPath = resolveToRealPath(previousPlansDir);
const currentDirectories = this.workspaceContext
.getDirectories()
.filter((dir) => dir !== previousPlansDirRealPath);
Comment thread
jasonsuhari marked this conversation as resolved.
Outdated

this.workspaceContext.setDirectories(currentDirectories);

try {
if (fs.existsSync(nextPlansDir)) {
this.workspaceContext.addDirectory(nextPlansDir);
}
} catch {
// Ignore invalid or unreadable plans directories here. This mirrors
// initialization behavior, which only adds the plans directory when it
// already exists and is readable.
}
}

getAgentRegistry(): AgentRegistry {
return this.agentRegistry;
}
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/config/storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,27 @@ describe('Storage – additional helpers', () => {
expect(storageWithSession.getProjectTempTrackerDir()).toBe(expected);
});

it('updates session-scoped directories when the sessionId changes', async () => {
const storageWithSession = new Storage(projectRoot, 'session-one');
ProjectRegistry.prototype.getShortId = vi
.fn()
.mockReturnValue(PROJECT_SLUG);
await storageWithSession.initialize();
const tempDir = storageWithSession.getProjectTempDir();

storageWithSession.setSessionId('session-two');

expect(storageWithSession.getProjectTempPlansDir()).toBe(
path.join(tempDir, 'session-two', 'plans'),
);
expect(storageWithSession.getProjectTempTrackerDir()).toBe(
path.join(tempDir, 'session-two', 'tracker'),
);
expect(storageWithSession.getProjectTempTasksDir()).toBe(
path.join(tempDir, 'session-two', 'tasks'),
);
});

describe('Session and JSON Loading', () => {
beforeEach(async () => {
await storage.initialize();
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/config/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const AUTO_SAVED_POLICY_FILENAME = 'auto-saved.toml';

export class Storage {
private readonly targetDir: string;
private readonly sessionId: string | undefined;
private sessionId: string | undefined;
private projectIdentifier: string | undefined;
private initPromise: Promise<void> | undefined;
private customPlansDir: string | undefined;
Expand All @@ -42,6 +42,14 @@ export class Storage {
this.customPlansDir = dir;
}

setSessionId(sessionId: string | undefined): void {
this.sessionId = sessionId;
}

isInitialized(): boolean {
return !!this.projectIdentifier;
}

static getGlobalGeminiDir(): string {
const homeDir = homedir();
if (!homeDir) {
Expand Down