mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-18 22:33:08 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
39
apps/server/tests/unit/providers/cursor-provider.test.ts
Normal file
39
apps/server/tests/unit/providers/cursor-provider.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
35
apps/server/tests/unit/providers/gemini-provider.test.ts
Normal file
35
apps/server/tests/unit/providers/gemini-provider.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user