fix(pi): retain ownership for uncertain mutations
This commit is contained in:
@@ -764,7 +764,7 @@ describe('PI-100 coding core Host contract', () => {
|
||||
uncertainCalls = 0;
|
||||
|
||||
override async prompt(input: PromptConversationInput) {
|
||||
if (input.clientRequestId === 'request-uncertain') {
|
||||
if (input.clientRequestId.startsWith('request-uncertain')) {
|
||||
this.uncertainCalls += 1;
|
||||
throw new CodingRuntimeContractError(
|
||||
'CODING_REQUEST_UNCERTAIN',
|
||||
@@ -794,6 +794,25 @@ describe('PI-100 coding core Host contract', () => {
|
||||
await expect(result.conversations.acceptPrompt(input)).rejects.toMatchObject({
|
||||
code: 'CODING_REQUEST_UNCERTAIN',
|
||||
});
|
||||
const routeResponse = await dispatchHostApiRequest(context(result), {
|
||||
path: `/api/coding/conversations/${conversation.id}/prompt`,
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
clientRequestId: 'request-uncertain-route',
|
||||
mode: 'prompt',
|
||||
text: 'Still running',
|
||||
attachments: [],
|
||||
}),
|
||||
});
|
||||
expect(routeResponse).toMatchObject({
|
||||
status: 409,
|
||||
json: {
|
||||
code: 'CODING_REQUEST_UNCERTAIN',
|
||||
error: '请求确认延迟,可能仍在执行。请等待结果,或中止/恢复后再重试。',
|
||||
},
|
||||
});
|
||||
expect(JSON.stringify(routeResponse.json)).not.toContain('本地编程运行时暂时不可用');
|
||||
for (let index = 0; index < 512; index += 1) {
|
||||
await result.conversations.acceptPrompt({
|
||||
conversationId: conversation.id,
|
||||
@@ -806,9 +825,78 @@ describe('PI-100 coding core Host contract', () => {
|
||||
await expect(result.conversations.acceptPrompt(input)).rejects.toMatchObject({
|
||||
code: 'CODING_REQUEST_UNCERTAIN',
|
||||
});
|
||||
expect(runtime.uncertainCalls).toBe(1);
|
||||
expect(runtime.uncertainCalls).toBe(2);
|
||||
}, 15_000);
|
||||
|
||||
it('rejects model and fork mutations before persistence while confirmation is uncertain', async () => {
|
||||
class UncertainMutationRuntime extends InMemoryConversationRuntime {
|
||||
sourceConversationId = '';
|
||||
readonly forkInputs: Parameters<InMemoryConversationRuntime['fork']>[0][] = [];
|
||||
|
||||
override async getSnapshot(conversationId: string): Promise<ConversationSnapshot> {
|
||||
const snapshot = await super.getSnapshot(conversationId);
|
||||
if (conversationId !== this.sourceConversationId) return snapshot;
|
||||
return {
|
||||
...snapshot,
|
||||
nodes: [{
|
||||
kind: 'message',
|
||||
id: 'node-user-uncertain',
|
||||
sourceEntryId: 'entry-user-uncertain',
|
||||
role: 'user',
|
||||
status: 'complete',
|
||||
blocks: [{ kind: 'text', id: 'text-user-uncertain', text: 'Fork later', status: 'complete' }],
|
||||
}],
|
||||
run: {
|
||||
status: 'running',
|
||||
runId: 'run-uncertain',
|
||||
mode: 'prompt',
|
||||
startedAt: 1_000,
|
||||
error: {
|
||||
code: 'CODING_REQUEST_UNCERTAIN',
|
||||
message: '请求确认延迟,可能仍在执行。',
|
||||
recoverable: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
override async fork(input: Parameters<InMemoryConversationRuntime['fork']>[0]) {
|
||||
this.forkInputs.push(structuredClone(input));
|
||||
return await super.fork(input);
|
||||
}
|
||||
}
|
||||
|
||||
const runtime = new UncertainMutationRuntime();
|
||||
const result = await setup(runtime);
|
||||
const source = await createConversation(result.conversations);
|
||||
runtime.sourceConversationId = source.id;
|
||||
await result.conversations.getSnapshot(source.id);
|
||||
const store = result.projects.conversationStore(result.root);
|
||||
const create = vi.spyOn(store, 'create');
|
||||
const setModelState = vi.spyOn(store, 'setModelState');
|
||||
create.mockClear();
|
||||
setModelState.mockClear();
|
||||
|
||||
await expect(result.conversations.setModel(source.id, {
|
||||
...MODEL,
|
||||
modelId: 'model-next',
|
||||
})).rejects.toMatchObject({
|
||||
status: 409,
|
||||
code: 'CODING_REQUEST_UNCERTAIN',
|
||||
});
|
||||
await expect(result.conversations.fork(source.id, 'entry-user-uncertain')).rejects.toMatchObject({
|
||||
status: 409,
|
||||
code: 'CODING_REQUEST_UNCERTAIN',
|
||||
});
|
||||
|
||||
expect(setModelState).not.toHaveBeenCalled();
|
||||
expect(create).not.toHaveBeenCalled();
|
||||
expect(runtime.forkInputs).toEqual([]);
|
||||
await expect(store.read()).resolves.toMatchObject({
|
||||
conversations: [expect.objectContaining({ id: source.id, model: MODEL })],
|
||||
});
|
||||
});
|
||||
|
||||
it('disposes and archives a partially created fork before metadata rollback', async () => {
|
||||
let bindFork: ((conversationId: string) => Promise<void>) | undefined;
|
||||
let forkTargetId = '';
|
||||
|
||||
Reference in New Issue
Block a user