Fix: Restore views properly, model selection for commit and pr and speed up some cli models with session resume (#801)

* Changes from fix/restoring-view

* feat: Add resume query safety checks and optimize store selectors

* feat: Improve session management and model normalization

* refactor: Extract prompt building logic and handle file path parsing for renames
This commit is contained in:
gsxdsm
2026-02-22 10:45:45 -08:00
committed by GitHub
parent 2f071a1ba3
commit 9305ecc242
26 changed files with 761 additions and 203 deletions

View File

@@ -170,6 +170,30 @@ describe('codex-provider.ts', () => {
expect(call.args).toContain('--json');
});
it('uses exec resume when sdkSessionId is provided', async () => {
vi.mocked(spawnJSONLProcess).mockReturnValue((async function* () {})());
await collectAsyncGenerator(
provider.executeQuery({
prompt: 'Continue',
model: 'gpt-5.2',
cwd: '/tmp',
sdkSessionId: 'codex-session-123',
outputFormat: { type: 'json_schema', schema: { type: 'object', properties: {} } },
codexSettings: { additionalDirs: ['/extra/dir'] },
})
);
const call = vi.mocked(spawnJSONLProcess).mock.calls[0][0];
expect(call.args[0]).toBe('exec');
expect(call.args[1]).toBe('resume');
expect(call.args).toContain('codex-session-123');
expect(call.args).toContain('--json');
// Resume queries must not include --output-schema or --add-dir
expect(call.args).not.toContain('--output-schema');
expect(call.args).not.toContain('--add-dir');
});
it('overrides approval policy when MCP auto-approval is enabled', async () => {
// Note: With full-permissions always on (--dangerously-bypass-approvals-and-sandbox),
// approval policy is bypassed, not configured via --config

View File

@@ -1,17 +1,35 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { CopilotProvider, CopilotErrorCode } from '@/providers/copilot-provider.js';
import { collectAsyncGenerator } from '../../utils/helpers.js';
import { CopilotClient } from '@github/copilot-sdk';
const createSessionMock = vi.fn();
const resumeSessionMock = vi.fn();
function createMockSession(sessionId = 'test-session') {
let eventHandler: ((event: any) => void) | null = null;
return {
sessionId,
send: vi.fn().mockImplementation(async () => {
if (eventHandler) {
eventHandler({ type: 'assistant.message', data: { content: 'hello' } });
eventHandler({ type: 'session.idle' });
}
}),
destroy: vi.fn().mockResolvedValue(undefined),
on: vi.fn().mockImplementation((handler: (event: any) => void) => {
eventHandler = handler;
}),
};
}
// Mock the Copilot SDK
vi.mock('@github/copilot-sdk', () => ({
CopilotClient: vi.fn().mockImplementation(() => ({
start: vi.fn().mockResolvedValue(undefined),
stop: vi.fn().mockResolvedValue(undefined),
createSession: vi.fn().mockResolvedValue({
sessionId: 'test-session',
send: vi.fn().mockResolvedValue(undefined),
destroy: vi.fn().mockResolvedValue(undefined),
on: vi.fn(),
}),
createSession: createSessionMock,
resumeSession: resumeSessionMock,
})),
}));
@@ -49,6 +67,16 @@ describe('copilot-provider.ts', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(CopilotClient).mockImplementation(function () {
return {
start: vi.fn().mockResolvedValue(undefined),
stop: vi.fn().mockResolvedValue(undefined),
createSession: createSessionMock,
resumeSession: resumeSessionMock,
} as any;
});
createSessionMock.mockResolvedValue(createMockSession());
resumeSessionMock.mockResolvedValue(createMockSession('resumed-session'));
// Mock fs.existsSync for CLI path validation
vi.mocked(fs.existsSync).mockReturnValue(true);
@@ -514,4 +542,45 @@ describe('copilot-provider.ts', () => {
expect(todoInput.todos[0].status).toBe('completed');
});
});
describe('executeQuery resume behavior', () => {
it('uses resumeSession when sdkSessionId is provided', async () => {
const results = await collectAsyncGenerator(
provider.executeQuery({
prompt: 'Hello',
model: 'claude-sonnet-4.6',
cwd: '/tmp/project',
sdkSessionId: 'session-123',
})
);
expect(resumeSessionMock).toHaveBeenCalledWith(
'session-123',
expect.objectContaining({ model: 'claude-sonnet-4.6', streaming: true })
);
expect(createSessionMock).not.toHaveBeenCalled();
expect(results.some((msg) => msg.session_id === 'resumed-session')).toBe(true);
});
it('falls back to createSession when resumeSession fails', async () => {
resumeSessionMock.mockRejectedValueOnce(new Error('session not found'));
createSessionMock.mockResolvedValueOnce(createMockSession('fresh-session'));
const results = await collectAsyncGenerator(
provider.executeQuery({
prompt: 'Hello',
model: 'claude-sonnet-4.6',
cwd: '/tmp/project',
sdkSessionId: 'stale-session',
})
);
expect(resumeSessionMock).toHaveBeenCalledWith(
'stale-session',
expect.objectContaining({ model: 'claude-sonnet-4.6', streaming: true })
);
expect(createSessionMock).toHaveBeenCalledTimes(1);
expect(results.some((msg) => msg.session_id === 'fresh-session')).toBe(true);
});
});
});

View File

@@ -0,0 +1,39 @@
import { describe, it, expect } from 'vitest';
import { CursorProvider } from '@/providers/cursor-provider.js';
describe('cursor-provider.ts', () => {
describe('buildCliArgs', () => {
it('adds --resume when sdkSessionId is provided', () => {
const provider = Object.create(CursorProvider.prototype) as CursorProvider & {
cliPath?: string;
};
provider.cliPath = '/usr/local/bin/cursor-agent';
const args = provider.buildCliArgs({
prompt: 'Continue the task',
model: 'gpt-5',
cwd: '/tmp/project',
sdkSessionId: 'cursor-session-123',
});
const resumeIndex = args.indexOf('--resume');
expect(resumeIndex).toBeGreaterThan(-1);
expect(args[resumeIndex + 1]).toBe('cursor-session-123');
});
it('does not add --resume when sdkSessionId is omitted', () => {
const provider = Object.create(CursorProvider.prototype) as CursorProvider & {
cliPath?: string;
};
provider.cliPath = '/usr/local/bin/cursor-agent';
const args = provider.buildCliArgs({
prompt: 'Start a new task',
model: 'gpt-5',
cwd: '/tmp/project',
});
expect(args).not.toContain('--resume');
});
});
});

View File

@@ -0,0 +1,35 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { GeminiProvider } from '@/providers/gemini-provider.js';
describe('gemini-provider.ts', () => {
let provider: GeminiProvider;
beforeEach(() => {
provider = new GeminiProvider();
});
describe('buildCliArgs', () => {
it('should include --resume when sdkSessionId is provided', () => {
const args = provider.buildCliArgs({
prompt: 'Hello',
model: '2.5-flash',
cwd: '/tmp/project',
sdkSessionId: 'gemini-session-123',
});
const resumeIndex = args.indexOf('--resume');
expect(resumeIndex).toBeGreaterThan(-1);
expect(args[resumeIndex + 1]).toBe('gemini-session-123');
});
it('should not include --resume when sdkSessionId is missing', () => {
const args = provider.buildCliArgs({
prompt: 'Hello',
model: '2.5-flash',
cwd: '/tmp/project',
});
expect(args).not.toContain('--resume');
});
});
});

View File

@@ -303,6 +303,36 @@ describe('agent-service.ts', () => {
expect(fs.writeFile).toHaveBeenCalled();
});
it('should include context/history preparation for Gemini requests', async () => {
let capturedOptions: any;
const mockProvider = {
getName: () => 'gemini',
executeQuery: async function* (options: any) {
capturedOptions = options;
yield {
type: 'result',
subtype: 'success',
};
},
};
vi.mocked(ProviderFactory.getProviderForModelName).mockReturnValue('gemini');
vi.mocked(ProviderFactory.getProviderForModel).mockReturnValue(mockProvider as any);
vi.mocked(promptBuilder.buildPromptWithImages).mockResolvedValue({
content: 'Hello',
hasImages: false,
});
await service.sendMessage({
sessionId: 'session-1',
message: 'Hello',
model: 'gemini-2.5-flash',
});
expect(contextLoader.loadContextFiles).toHaveBeenCalled();
expect(capturedOptions).toBeDefined();
});
});
describe('stopExecution', () => {