-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feature: Allow model and harness selection before submitting implementation in a new thread #1877
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -629,6 +629,7 @@ export default function ChatView(props: ChatViewProps) { | |
| const setLogicalProjectDraftThreadId = useComposerDraftStore( | ||
| (store) => store.setLogicalProjectDraftThreadId, | ||
| ); | ||
| const applyComposerDraftStickyState = useComposerDraftStore((store) => store.applyStickyState); | ||
| const draftThread = useComposerDraftStore((store) => | ||
| routeKind === "server" | ||
| ? store.getDraftSessionByRef(routeThreadRef) | ||
|
|
@@ -3031,131 +3032,41 @@ export default function ChatView(props: ChatViewProps) { | |
| ); | ||
|
|
||
| const onImplementPlanInNewThread = useCallback(async () => { | ||
| const api = readEnvironmentApi(environmentId); | ||
| if ( | ||
| !api || | ||
| !activeThread || | ||
| !activeProject || | ||
| !activeProposedPlan || | ||
| !isServerThread || | ||
| isSendBusy || | ||
| isConnecting || | ||
| sendInFlightRef.current | ||
| ) { | ||
| return; | ||
| } | ||
|
|
||
| const sendCtx = composerRef.current?.getSendContext(); | ||
| if (!sendCtx) { | ||
| if (!activeThread || !activeProject || !activeProposedPlan) { | ||
| return; | ||
| } | ||
| const { | ||
| selectedProvider: ctxSelectedProvider, | ||
| selectedModel: ctxSelectedModel, | ||
| selectedProviderModels: ctxSelectedProviderModels, | ||
| selectedPromptEffort: ctxSelectedPromptEffort, | ||
| selectedModelSelection: ctxSelectedModelSelection, | ||
| } = sendCtx; | ||
|
|
||
| const createdAt = new Date().toISOString(); | ||
| const nextDraftId = newDraftId(); | ||
| const nextThreadId = newThreadId(); | ||
| const planMarkdown = activeProposedPlan.planMarkdown; | ||
| const implementationPrompt = buildPlanImplementationPrompt(planMarkdown); | ||
| const outgoingImplementationPrompt = formatOutgoingPrompt({ | ||
| provider: ctxSelectedProvider, | ||
| model: ctxSelectedModel, | ||
| models: ctxSelectedProviderModels, | ||
| effort: ctxSelectedPromptEffort, | ||
| text: implementationPrompt, | ||
| }); | ||
| const nextThreadTitle = truncate(buildPlanImplementationThreadTitle(planMarkdown)); | ||
| const nextThreadModelSelection: ModelSelection = ctxSelectedModelSelection; | ||
| const logicalProjectKey = deriveLogicalProjectKey(activeProject); | ||
| const activeProjectRef = scopeProjectRef(activeProject.environmentId, activeProject.id); | ||
|
|
||
| sendInFlightRef.current = true; | ||
| beginLocalDispatch({ preparingWorktree: false }); | ||
| const finish = () => { | ||
| sendInFlightRef.current = false; | ||
| resetLocalDispatch(); | ||
| }; | ||
| setLogicalProjectDraftThreadId(logicalProjectKey, activeProjectRef, nextDraftId, { | ||
| threadId: nextThreadId, | ||
| createdAt: new Date().toISOString(), | ||
| branch: activeThread.branch ?? null, | ||
| worktreePath: activeThread.worktreePath ?? null, | ||
| runtimeMode, | ||
| interactionMode: DEFAULT_INTERACTION_MODE, | ||
| }); | ||
| applyComposerDraftStickyState(nextDraftId); | ||
| setComposerDraftPrompt(nextDraftId, implementationPrompt); | ||
|
|
||
| await api.orchestration | ||
| .dispatchCommand({ | ||
| type: "thread.create", | ||
| commandId: newCommandId(), | ||
| threadId: nextThreadId, | ||
| projectId: activeProject.id, | ||
| title: nextThreadTitle, | ||
| modelSelection: nextThreadModelSelection, | ||
| runtimeMode, | ||
| interactionMode: "default", | ||
| branch: activeThread.branch, | ||
| worktreePath: activeThread.worktreePath, | ||
| createdAt, | ||
| }) | ||
| .then(() => { | ||
| return api.orchestration.dispatchCommand({ | ||
| type: "thread.turn.start", | ||
| commandId: newCommandId(), | ||
| threadId: nextThreadId, | ||
| message: { | ||
| messageId: newMessageId(), | ||
| role: "user", | ||
| text: outgoingImplementationPrompt, | ||
| attachments: [], | ||
| }, | ||
| modelSelection: ctxSelectedModelSelection, | ||
| titleSeed: nextThreadTitle, | ||
| runtimeMode, | ||
| interactionMode: "default", | ||
| sourceProposedPlan: { | ||
| threadId: activeThread.id, | ||
| planId: activeProposedPlan.id, | ||
| }, | ||
| createdAt, | ||
| }); | ||
| }) | ||
| .then(() => { | ||
| return waitForStartedServerThread(scopeThreadRef(activeThread.environmentId, nextThreadId)); | ||
| }) | ||
| .then(() => { | ||
| // Signal that the plan sidebar should open on the new thread. | ||
| planSidebarOpenOnNextThreadRef.current = true; | ||
| return navigate({ | ||
| to: "/$environmentId/$threadId", | ||
| params: { | ||
| environmentId: activeThread.environmentId, | ||
| threadId: nextThreadId, | ||
| }, | ||
| }); | ||
| }) | ||
| .catch(async (err: unknown) => { | ||
| await api.orchestration | ||
| .dispatchCommand({ | ||
| type: "thread.delete", | ||
| commandId: newCommandId(), | ||
| threadId: nextThreadId, | ||
| }) | ||
| .catch(() => undefined); | ||
| toastManager.add({ | ||
| type: "error", | ||
| title: "Could not start implementation thread", | ||
| description: | ||
| err instanceof Error ? err.message : "An error occurred while creating the new thread.", | ||
| }); | ||
| }) | ||
| .then(finish, finish); | ||
| await navigate({ | ||
| to: "/draft/$draftId", | ||
| params: buildDraftThreadRouteParams(nextDraftId), | ||
| }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implementation thread loses plan source linkageHigh Severity The old Additional Locations (1)Reviewed by Cursor Bugbot for commit ef214d1. Configure here.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good point but will increase PR scope, let me know if must be done in this scope. |
||
| }, [ | ||
| activeProject, | ||
| activeProposedPlan, | ||
| activeThread, | ||
| beginLocalDispatch, | ||
| isConnecting, | ||
| isSendBusy, | ||
| isServerThread, | ||
| applyComposerDraftStickyState, | ||
| navigate, | ||
| resetLocalDispatch, | ||
| runtimeMode, | ||
| environmentId, | ||
| setComposerDraftPrompt, | ||
| setLogicalProjectDraftThreadId, | ||
| ]); | ||
|
|
||
| const onProviderModelSelect = useCallback( | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Existing project draft silently discarded on implement
Medium Severity
onImplementPlanInNewThreadalways creates a freshnextDraftIdand callssetLogicalProjectDraftThreadIdwithout first checking whether a draft already exists for the logical project.setLogicalProjectDraftThreadIdreplaces the logical-project-to-draft mapping and deletes the previous draft's thread state and composer content if it's no longer referenced. If the user had an in-progress draft with unsaved content for the same project, that content is silently destroyed. The existingopenOrReuseProjectDraftThreadanduseHandleNewThreadboth check for and reuse existing drafts to avoid this.Reviewed by Cursor Bugbot for commit ef214d1. Configure here.