fix(git): materialize draft session for generate (#1761)
* fix(git): materialize draft session for generate * fix(sync): remove redundant draft session side effects --------- Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
This commit is contained in:
committed by
GitHub
co-authored by
Leonid Skorobogatyy
parent
b0e476ab7c
commit
03e6f789a4
@@ -2,8 +2,8 @@
|
|||||||
import * as gitHttp from './gitApiHttp';
|
import * as gitHttp from './gitApiHttp';
|
||||||
import { opencodeClient } from './opencode/client';
|
import { opencodeClient } from './opencode/client';
|
||||||
import { renderMagicPrompt } from './magicPrompts';
|
import { renderMagicPrompt } from './magicPrompts';
|
||||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
import { materializeOpenDraftSession, useSessionUIStore } from '@/sync/session-ui-store';
|
||||||
import { useContextStore } from '@/stores/contextStore';
|
import { useSelectionStore } from '@/sync/selection-store';
|
||||||
import { useConfigStore } from '@/stores/useConfigStore';
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
||||||
|
|
||||||
@@ -217,11 +217,7 @@ export async function generateCommitMessage(
|
|||||||
): Promise<{ message: import('./api/types').GeneratedCommitMessage }> {
|
): Promise<{ message: import('./api/types').GeneratedCommitMessage }> {
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
void options;
|
void options;
|
||||||
const generationSession = resolveSessionGenerationContext();
|
const generationSession = await resolveGenerationSessionContext();
|
||||||
|
|
||||||
if (!generationSession) {
|
|
||||||
throw new Error('Select existing session for generation');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info('[git-generation][browser] request', {
|
console.info('[git-generation][browser] request', {
|
||||||
transport: 'session',
|
transport: 'session',
|
||||||
@@ -283,10 +279,7 @@ export async function generatePullRequestDescription(
|
|||||||
payload: { base: string; head: string; context?: string; zenModel?: string; providerId?: string; modelId?: string }
|
payload: { base: string; head: string; context?: string; zenModel?: string; providerId?: string; modelId?: string }
|
||||||
): Promise<import('./api/types').GeneratedPullRequestDescription> {
|
): Promise<import('./api/types').GeneratedPullRequestDescription> {
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
const generationSession = resolveSessionGenerationContext();
|
const generationSession = await resolveGenerationSessionContext();
|
||||||
if (!generationSession) {
|
|
||||||
throw new Error('Select existing session for generation');
|
|
||||||
}
|
|
||||||
|
|
||||||
const commitLog = await getGitLog(directory, {
|
const commitLog = await getGitLog(directory, {
|
||||||
from: payload.base,
|
from: payload.base,
|
||||||
@@ -387,19 +380,65 @@ type SessionGenerationContext = {
|
|||||||
variant?: string;
|
variant?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GENERATION_CONFIG_ERROR = 'No default provider or model configured. Please select a provider and model in settings first.';
|
||||||
|
|
||||||
|
async function resolveGenerationSessionContext(): Promise<SessionGenerationContext> {
|
||||||
|
const activeSession = resolveSessionGenerationContext();
|
||||||
|
if (activeSession) {
|
||||||
|
return activeSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
const draft = useSessionUIStore.getState().newSessionDraft;
|
||||||
|
if (!draft?.open) {
|
||||||
|
throw new Error('Select existing session for generation');
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = useConfigStore.getState();
|
||||||
|
if (!config.currentProviderId || !config.currentModelId) {
|
||||||
|
throw new Error(GENERATION_CONFIG_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdDraftSession = await materializeOpenDraftSession({
|
||||||
|
providerID: config.currentProviderId,
|
||||||
|
modelID: config.currentModelId,
|
||||||
|
agent: config.currentAgentName || undefined,
|
||||||
|
variant: config.currentVariant || undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!createdDraftSession) {
|
||||||
|
const retry = resolveSessionGenerationContext();
|
||||||
|
if (retry) {
|
||||||
|
return retry;
|
||||||
|
}
|
||||||
|
throw new Error('Failed to create session for generation');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessionId: createdDraftSession.sessionId,
|
||||||
|
providerID: config.currentProviderId,
|
||||||
|
modelID: config.currentModelId,
|
||||||
|
agent: createdDraftSession.agent,
|
||||||
|
variant: config.currentVariant || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const resolveSessionGenerationContext = (): SessionGenerationContext | null => {
|
const resolveSessionGenerationContext = (): SessionGenerationContext | null => {
|
||||||
const sessionId = useSessionUIStore.getState().currentSessionId;
|
const sessionId = useSessionUIStore.getState().currentSessionId;
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = useContextStore.getState();
|
const selection = useSelectionStore.getState();
|
||||||
const config = useConfigStore.getState();
|
const config = useConfigStore.getState();
|
||||||
|
const lastChoice = useSessionUIStore.getState().getLastUserChoice(sessionId);
|
||||||
|
|
||||||
const agent = context.getSessionAgentSelection(sessionId) || config.currentAgentName || undefined;
|
const agent = selection.getSessionAgentSelection(sessionId) || lastChoice?.agent || config.currentAgentName || undefined;
|
||||||
const sessionModel = context.getSessionModelSelection(sessionId);
|
const sessionModel = selection.getSessionModelSelection(sessionId);
|
||||||
const agentModel = agent ? context.getAgentModelForSession(sessionId, agent) : null;
|
const agentModel = agent ? selection.getAgentModelForSession(sessionId, agent) : null;
|
||||||
const selectedModel = agentModel || sessionModel || (config.currentProviderId && config.currentModelId
|
const lastChoiceModel = lastChoice?.providerID && lastChoice.modelID
|
||||||
|
? { providerId: lastChoice.providerID, modelId: lastChoice.modelID }
|
||||||
|
: null;
|
||||||
|
const selectedModel = agentModel || sessionModel || lastChoiceModel || (config.currentProviderId && config.currentModelId
|
||||||
? { providerId: config.currentProviderId, modelId: config.currentModelId }
|
? { providerId: config.currentProviderId, modelId: config.currentModelId }
|
||||||
: null);
|
: null);
|
||||||
|
|
||||||
@@ -407,10 +446,18 @@ const resolveSessionGenerationContext = (): SessionGenerationContext | null => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const agentVariant = agent
|
const selectionVariant = agent
|
||||||
? context.getAgentModelVariantForSession(sessionId, agent, selectedModel.providerId, selectedModel.modelId)
|
? selection.getAgentModelVariantForSession(sessionId, agent, selectedModel.providerId, selectedModel.modelId)
|
||||||
: undefined;
|
: undefined;
|
||||||
const variant = agentVariant || config.currentVariant || undefined;
|
const lastChoiceVariant = lastChoiceModel
|
||||||
|
&& lastChoiceModel.providerId === selectedModel.providerId
|
||||||
|
&& lastChoiceModel.modelId === selectedModel.modelId
|
||||||
|
? lastChoice?.variant
|
||||||
|
: undefined;
|
||||||
|
const configVariant = config.currentProviderId === selectedModel.providerId && config.currentModelId === selectedModel.modelId
|
||||||
|
? config.currentVariant
|
||||||
|
: undefined;
|
||||||
|
const variant = selectionVariant || lastChoiceVariant || configVariant || undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionId,
|
sessionId,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { markPendingUserSendAnimation } from "@/lib/userSendAnimation"
|
|||||||
import { flattenAssistantTextParts } from "@/lib/messages/messageText"
|
import { flattenAssistantTextParts } from "@/lib/messages/messageText"
|
||||||
import { composeForkSessionMessage } from "@/lib/messages/executionMeta"
|
import { composeForkSessionMessage } from "@/lib/messages/executionMeta"
|
||||||
import { waitForPendingDraftWorktreeRequest } from "@/lib/worktrees/pendingDraftWorktree"
|
import { waitForPendingDraftWorktreeRequest } from "@/lib/worktrees/pendingDraftWorktree"
|
||||||
|
import { waitForWorktreeBootstrap } from "@/lib/worktrees/worktreeBootstrap"
|
||||||
import { resolveProjectForSessionDirectory } from "@/lib/projectResolution"
|
import { resolveProjectForSessionDirectory } from "@/lib/projectResolution"
|
||||||
import {
|
import {
|
||||||
getSyncSessions,
|
getSyncSessions,
|
||||||
@@ -408,6 +409,75 @@ const writeRuntimeSessionMemory = (key: string, patch: Partial<RuntimeSessionMem
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MaterializedDraftSession = {
|
||||||
|
sessionId: string
|
||||||
|
directory: string | null
|
||||||
|
agent?: string
|
||||||
|
syntheticParts?: SyntheticContextPart[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function materializeOpenDraftSession(selection: {
|
||||||
|
providerID: string
|
||||||
|
modelID: string
|
||||||
|
agent?: string
|
||||||
|
variant?: string
|
||||||
|
}): Promise<MaterializedDraftSession | null> {
|
||||||
|
const store = useSessionUIStore.getState()
|
||||||
|
const draft = store.newSessionDraft
|
||||||
|
if (!draft?.open) return null
|
||||||
|
|
||||||
|
const trimmedAgent = typeof selection.agent === "string" && selection.agent.trim().length > 0
|
||||||
|
? selection.agent.trim()
|
||||||
|
: undefined
|
||||||
|
let draftDirectoryOverride = draft.bootstrapPendingDirectory ?? draft.directoryOverride ?? null
|
||||||
|
const draftProjectId = draft.selectedProjectId ?? null
|
||||||
|
|
||||||
|
if (draft.pendingWorktreeRequestId) {
|
||||||
|
draftDirectoryOverride = await waitForPendingDraftWorktreeRequest(draft.pendingWorktreeRequestId)
|
||||||
|
store.resolvePendingDraftWorktreeTarget(draft.pendingWorktreeRequestId, draftDirectoryOverride)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draftDirectoryOverride) {
|
||||||
|
await waitForWorktreeBootstrap(draftDirectoryOverride)
|
||||||
|
}
|
||||||
|
|
||||||
|
const created = await store.createSession(draft.title, draftDirectoryOverride, draft.parentID ?? null)
|
||||||
|
if (!created?.id) throw new Error("Failed to create session")
|
||||||
|
|
||||||
|
persistDraftTarget({
|
||||||
|
projectId: draftProjectId,
|
||||||
|
directory: normalizePath(draftDirectoryOverride ?? created.directory ?? null),
|
||||||
|
})
|
||||||
|
|
||||||
|
const draftSyntheticParts = draft.syntheticParts
|
||||||
|
const createdDirectory = normalizePath(draftDirectoryOverride ?? created.directory ?? null)
|
||||||
|
const configState = useConfigStore.getState()
|
||||||
|
void activateConfigForDirectory(createdDirectory).catch((error) => {
|
||||||
|
console.warn("Failed to activate directory after creating session:", error)
|
||||||
|
})
|
||||||
|
|
||||||
|
const effectiveDraftAgent = trimmedAgent ?? configState.currentAgentName
|
||||||
|
|
||||||
|
useSelectionStore.getState().saveSessionModelSelection(created.id, selection.providerID, selection.modelID)
|
||||||
|
|
||||||
|
if (effectiveDraftAgent) {
|
||||||
|
useSelectionStore.getState().saveSessionAgentSelection(created.id, effectiveDraftAgent)
|
||||||
|
useSelectionStore.getState().saveAgentModelForSession(created.id, effectiveDraftAgent, selection.providerID, selection.modelID)
|
||||||
|
useSelectionStore.getState().saveAgentModelVariantForSession(created.id, effectiveDraftAgent, selection.providerID, selection.modelID, selection.variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
store.initializeNewOpenChamberSession(created.id, configState.agents ?? [])
|
||||||
|
|
||||||
|
store.setCurrentSession(created.id, createdDirectory)
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessionId: created.id,
|
||||||
|
directory: createdDirectory,
|
||||||
|
agent: effectiveDraftAgent,
|
||||||
|
syntheticParts: draftSyntheticParts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Store
|
// Store
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -919,59 +989,21 @@ export const useSessionUIStore = create<SessionUIState>()((set, get) => ({
|
|||||||
|
|
||||||
// ---- New session from draft ----
|
// ---- New session from draft ----
|
||||||
if (!options?.sessionId && draft?.open) {
|
if (!options?.sessionId && draft?.open) {
|
||||||
const draftTargetFolderId = draft.targetFolderId
|
const createdDraftSession = await materializeOpenDraftSession({
|
||||||
let draftDirectoryOverride = draft.bootstrapPendingDirectory ?? draft.directoryOverride ?? null
|
providerID,
|
||||||
const draftProjectId = draft.selectedProjectId ?? null
|
modelID,
|
||||||
|
agent: trimmedAgent,
|
||||||
if (draft.pendingWorktreeRequestId) {
|
variant,
|
||||||
draftDirectoryOverride = await waitForPendingDraftWorktreeRequest(draft.pendingWorktreeRequestId)
|
|
||||||
get().resolvePendingDraftWorktreeTarget(draft.pendingWorktreeRequestId, draftDirectoryOverride)
|
|
||||||
}
|
|
||||||
|
|
||||||
const created = await get().createSession(draft.title, draftDirectoryOverride, draft.parentID ?? null)
|
|
||||||
if (!created?.id) throw new Error("Failed to create session")
|
|
||||||
|
|
||||||
persistDraftTarget({
|
|
||||||
projectId: draftProjectId,
|
|
||||||
directory: normalizePath(draftDirectoryOverride ?? created.directory ?? null),
|
|
||||||
})
|
})
|
||||||
|
if (!createdDraftSession) throw new Error("Failed to create session")
|
||||||
|
|
||||||
const draftSyntheticParts = draft.syntheticParts
|
const mergedAdditionalParts = createdDraftSession.syntheticParts?.length
|
||||||
const createdDirectory = normalizePath(draftDirectoryOverride ?? created.directory ?? null)
|
? [...(additionalParts || []), ...createdDraftSession.syntheticParts]
|
||||||
const configState = useConfigStore.getState()
|
|
||||||
void activateConfigForDirectory(createdDirectory).catch((error) => {
|
|
||||||
console.warn("Failed to activate directory after creating session:", error)
|
|
||||||
})
|
|
||||||
|
|
||||||
const effectiveDraftAgent = trimmedAgent ?? configState.currentAgentName
|
|
||||||
|
|
||||||
useSelectionStore.getState().saveSessionModelSelection(created.id, providerID, modelID)
|
|
||||||
|
|
||||||
if (effectiveDraftAgent) {
|
|
||||||
useSelectionStore.getState().saveSessionAgentSelection(created.id, effectiveDraftAgent)
|
|
||||||
useSelectionStore.getState().saveAgentModelForSession(created.id, effectiveDraftAgent, providerID, modelID)
|
|
||||||
useSelectionStore.getState().saveAgentModelVariantForSession(created.id, effectiveDraftAgent, providerID, modelID, variant)
|
|
||||||
}
|
|
||||||
|
|
||||||
get().initializeNewOpenChamberSession(created.id, configState.agents ?? [])
|
|
||||||
|
|
||||||
get().closeNewSessionDraft()
|
|
||||||
get().setCurrentSession(created.id, createdDirectory)
|
|
||||||
|
|
||||||
if (draftTargetFolderId) {
|
|
||||||
const scopeKey = draftDirectoryOverride || created.directory || null
|
|
||||||
if (scopeKey) {
|
|
||||||
useSessionFoldersStore.getState().addSessionToFolder(scopeKey, draftTargetFolderId, created.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedAdditionalParts = draftSyntheticParts?.length
|
|
||||||
? [...(additionalParts || []), ...draftSyntheticParts]
|
|
||||||
: additionalParts
|
: additionalParts
|
||||||
|
|
||||||
notifyMessageSent(created.id)
|
notifyMessageSent(createdDraftSession.sessionId)
|
||||||
|
|
||||||
markPendingUserSendAnimation(created.id)
|
markPendingUserSendAnimation(createdDraftSession.sessionId)
|
||||||
|
|
||||||
const files = attachments?.map((a) => ({
|
const files = attachments?.map((a) => ({
|
||||||
type: "file" as const,
|
type: "file" as const,
|
||||||
@@ -981,12 +1013,12 @@ export const useSessionUIStore = create<SessionUIState>()((set, get) => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
await routeMessage({
|
await routeMessage({
|
||||||
sessionId: created.id,
|
sessionId: createdDraftSession.sessionId,
|
||||||
directory: createdDirectory,
|
directory: createdDraftSession.directory,
|
||||||
content,
|
content,
|
||||||
providerID,
|
providerID,
|
||||||
modelID,
|
modelID,
|
||||||
agent: effectiveDraftAgent,
|
agent: createdDraftSession.agent,
|
||||||
agentMentionName,
|
agentMentionName,
|
||||||
variant,
|
variant,
|
||||||
inputMode,
|
inputMode,
|
||||||
|
|||||||
Reference in New Issue
Block a user