fix(coding): preserve conflict and unicode search results

This commit is contained in:
2026-08-23 18:31:53 +08:00
parent 9520872d2f
commit 365c2b0f76
6 changed files with 152 additions and 19 deletions

View File

@@ -1,8 +1,10 @@
// @vitest-environment node
import { execFile } from 'node:child_process';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { promisify } from 'node:util';
import { afterEach, describe, expect, it } from 'vitest';
import type { AgentBrowserModule } from '../../electron/agent-browser';
import {
@@ -19,6 +21,7 @@ import { PiProductTools } from '../../electron/coding-runtime/pi/product-tools';
const roots: string[] = [];
const conversationId = '11111111-1111-4111-8111-111111111111';
const exec = promisify(execFile);
afterEach(async () => {
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
@@ -62,6 +65,14 @@ function productTools(root: string): PiProductTools {
});
}
async function git(root: string, ...args: string[]): Promise<void> {
await exec('git', ['-C', root, ...args], { windowsHide: true });
}
async function gitOutput(root: string, ...args: string[]): Promise<string> {
return (await exec('git', ['-C', root, ...args], { windowsHide: true })).stdout.trim();
}
describe('PI-105 product Host composition', () => {
it('projects managed skills and a stable command catalog without raw Pi fields', async () => {
const root = await configuredProject();
@@ -116,6 +127,40 @@ describe('PI-105 product Host composition', () => {
expect(JSON.stringify(await host.getChanges(conversationId))).not.toContain(root);
});
it('keeps a merge conflict created during the target run in its changes snapshot', async () => {
const root = await configuredProject();
await git(root, 'init');
await git(root, 'config', 'user.email', 'pi-products@example.invalid');
await git(root, 'config', 'user.name', 'PI Products');
await writeFile(path.join(root, 'conflict.txt'), 'baseline\n', 'utf8');
await git(root, 'add', '.');
await git(root, 'commit', '-m', 'baseline');
const baseBranch = await gitOutput(root, 'branch', '--show-current');
await git(root, 'switch', '-c', 'conflicting-change');
await writeFile(path.join(root, 'conflict.txt'), 'branch change\n', 'utf8');
await git(root, 'commit', '-am', 'branch change');
await git(root, 'switch', baseBranch);
await writeFile(path.join(root, 'conflict.txt'), 'base change\n', 'utf8');
await git(root, 'commit', '-am', 'base change');
const tools = productTools(root);
const host = createCodingProductHost({
getActiveProject: async () => ({ id: 'project-a', path: root }),
productTools: tools,
});
await tools.beginRun({ conversationId, runId: 'run-conflict', projectPath: root });
await expect(exec('git', ['-C', root, 'merge', 'conflicting-change'], { windowsHide: true }))
.rejects.toThrow();
await tools.markBash(conversationId, 'run-conflict');
await tools.settleRun(conversationId, 'run-conflict');
expect(await host.getChanges(conversationId)).toMatchObject({
conversationId,
runId: 'run-conflict',
files: [expect.objectContaining({ path: 'conflict.txt', status: 'conflicted' })],
});
});
it('returns typed project, Agent, and Conversation errors', async () => {
const root = await configuredProject();
const tools = productTools(root);