fix(pi): retain ownership for uncertain mutations

This commit is contained in:
2026-08-25 23:53:54 +08:00
parent 621ebb1781
commit 019cbb115a
21 changed files with 1113 additions and 67 deletions

View File

@@ -53,6 +53,25 @@ interface LocalComposerAttachment {
previewUrl: string;
}
interface LocalSubmissionError {
message: string;
backendCode?: string;
}
function localSubmissionError(error: unknown): LocalSubmissionError {
const message = error instanceof Error ? error.message : String(error);
const details = error && typeof error === 'object'
&& 'details' in error
&& error.details
&& typeof error.details === 'object'
? error.details as { backendCode?: unknown }
: null;
return {
message,
...(typeof details?.backendCode === 'string' ? { backendCode: details.backendCode } : {}),
};
}
function newestConversation(
conversations: CodingConversationMetadata[],
agentId: string,
@@ -124,7 +143,7 @@ export function CodingChatPanel({
const conversationSummaries = useCodingConversationStore((state) => state.summariesByConversationId);
const [provisionalDrafts, setProvisionalDrafts] = useState<Record<string, string>>({});
const [submissionErrors, setSubmissionErrors] = useState<Record<string, string>>({});
const [submissionErrors, setSubmissionErrors] = useState<Record<string, LocalSubmissionError>>({});
const [modesByDraftKey, setModesByDraftKey] = useState<Record<string, PromptMode>>({});
const [inspectorOpen, setInspectorOpen] = useState(false);
const [attachmentsByDraftKey, setAttachmentsByDraftKey] = useState<
@@ -387,7 +406,7 @@ export function CodingChatPanel({
}
setSubmissionErrors((current) => {
const next = { ...current };
if (validationError) next[draftKey] = validationError;
if (validationError) next[draftKey] = { message: validationError };
else delete next[draftKey];
return next;
});
@@ -488,7 +507,7 @@ export function CodingChatPanel({
}).catch((error) => {
setSubmissionErrors((current) => ({
...current,
[draftKey]: error instanceof Error ? error.message : String(error),
[draftKey]: localSubmissionError(error),
}));
}).finally(() => {
submissionFlightsRef.current.delete(conversationId);
@@ -506,6 +525,17 @@ export function CodingChatPanel({
const workerStatus = snapshot?.worker.status ?? 'stopped';
const runtimeError = snapshot?.run.error ?? snapshot?.worker.error ?? null;
const preparationError = entryError ?? runtimeError?.message ?? null;
useEffect(() => {
if (!draftKey
|| uncertainRequestCount > 0
|| runtimeError?.code === 'CODING_REQUEST_UNCERTAIN') return;
setSubmissionErrors((current) => {
if (current[draftKey]?.backendCode !== 'CODING_REQUEST_UNCERTAIN') return current;
const next = { ...current };
delete next[draftKey];
return next;
});
}, [draftKey, runtimeError?.code, uncertainRequestCount]);
const preparing = entryLoadState === 'loading'
|| workerStatus === 'starting'
|| workerStatus === 'recovering'
@@ -744,7 +774,7 @@ export function CodingChatPanel({
if (!draftKey) return;
setSubmissionErrors((current) => ({
...current,
[draftKey]: error instanceof Error ? error.message : String(error),
[draftKey]: localSubmissionError(error),
}));
});
}}
@@ -772,7 +802,7 @@ export function CodingChatPanel({
runStatus={runStatus}
mode={promptMode}
queue={snapshot?.queue ?? { items: [] }}
error={submissionError ?? preparationError}
error={submissionError?.message ?? preparationError}
recoverableError={!submissionError && Boolean(preparationError)}
acceptedCount={acceptedRequestCount}
attachments={localAttachments.map(({ id, name, previewUrl }) => ({ id, name, previewUrl }))}

View File

@@ -170,7 +170,7 @@ export function CodingConversationHeader({
value={modelValue}
aria-label="当前对话模型"
className="h-9 min-w-52 max-w-72 text-xs"
disabled={Boolean(busyAction)}
disabled={Boolean(busyAction) || running}
onChange={(event) => {
const selected = parseCodingModelKey(event.target.value);
if (!selected) return;
@@ -191,7 +191,7 @@ export function CodingConversationHeader({
value={thinkingLevel}
aria-label="当前对话思考级别"
className="h-9 w-36 shrink-0 text-xs"
disabled={Boolean(busyAction) || !model}
disabled={Boolean(busyAction) || !model || running}
onChange={(event) => perform('thinking', async () => {
await setCodingConversationThinking(
conversation.id,
@@ -215,7 +215,7 @@ export function CodingConversationHeader({
<RotateCcw className="mr-1.5 h-3.5 w-3.5" aria-hidden="true" />
</Button>
)}
<Button type="button" variant="ghost" className="min-h-9 shrink-0 rounded-xl px-3 text-xs" disabled={Boolean(busyAction)} onClick={() => perform('fork', onFork)}>
<Button type="button" variant="ghost" className="min-h-9 shrink-0 rounded-xl px-3 text-xs" disabled={Boolean(busyAction) || running} onClick={() => perform('fork', onFork)}>
<GitFork className="mr-1.5 h-3.5 w-3.5" aria-hidden="true" />
</Button>
<Button type="button" variant="ghost" className="min-h-9 shrink-0 rounded-xl px-3 text-xs" disabled={Boolean(busyAction)} onClick={() => perform('unread', onToggleUnread)}>