Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions src-tauri/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,20 +261,24 @@ pub(crate) async fn send_user_message(
.map(remote_backend::normalize_path_for_remote)
.collect::<Vec<_>>()
});
let mut payload = Map::new();
payload.insert("workspaceId".to_string(), json!(workspace_id));
payload.insert("threadId".to_string(), json!(thread_id));
payload.insert("text".to_string(), json!(text));
payload.insert("model".to_string(), json!(model));
payload.insert("effort".to_string(), json!(effort));
payload.insert("accessMode".to_string(), json!(access_mode));
payload.insert("images".to_string(), json!(images));
if let Some(mode) = collaboration_mode {
if !mode.is_null() {
payload.insert("collaborationMode".to_string(), mode);
}
}
return remote_backend::call_remote(
&*state,
app,
"send_user_message",
json!({
"workspaceId": workspace_id,
"threadId": thread_id,
"text": text,
"model": model,
"effort": effort,
"accessMode": access_mode,
"images": images,
"collaborationMode": collaboration_mode,
}),
Value::Object(payload),
)
.await;
}
Expand Down Expand Up @@ -329,17 +333,22 @@ pub(crate) async fn send_user_message(
return Err("empty user message".to_string());
}

let params = json!({
"threadId": thread_id,
"input": input,
"cwd": session.entry.path,
"approvalPolicy": approval_policy,
"sandboxPolicy": sandbox_policy,
"model": model,
"effort": effort,
"collaborationMode": collaboration_mode,
});
session.send_request("turn/start", params).await
let mut params = Map::new();
params.insert("threadId".to_string(), json!(thread_id));
params.insert("input".to_string(), json!(input));
params.insert("cwd".to_string(), json!(session.entry.path));
params.insert("approvalPolicy".to_string(), json!(approval_policy));
params.insert("sandboxPolicy".to_string(), json!(sandbox_policy));
params.insert("model".to_string(), json!(model));
params.insert("effort".to_string(), json!(effort));
if let Some(mode) = collaboration_mode {
if !mode.is_null() {
params.insert("collaborationMode".to_string(), mode);
}
}
session
.send_request("turn/start", Value::Object(params))
.await
}

#[tauri::command]
Expand Down
16 changes: 14 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { useGitActions } from "./features/git/hooks/useGitActions";
import { useAutoExitEmptyDiff } from "./features/git/hooks/useAutoExitEmptyDiff";
import { useModels } from "./features/models/hooks/useModels";
import { useCollaborationModes } from "./features/collaboration/hooks/useCollaborationModes";
import { useCollaborationModeSelection } from "./features/collaboration/hooks/useCollaborationModeSelection";
import { useSkills } from "./features/skills/hooks/useSkills";
import { useCustomPrompts } from "./features/prompts/hooks/useCustomPrompts";
import { useWorkspaceFiles } from "./features/workspaces/hooks/useWorkspaceFiles";
Expand Down Expand Up @@ -549,6 +550,17 @@ function MainApp() {
setSelectedDiffPath,
});

const { collaborationModePayload } = useCollaborationModeSelection({
selectedCollaborationMode,
selectedCollaborationModeId,
models,
selectedModelId,
selectedEffort,
resolvedModel,
setSelectedModelId,
setSelectedEffort,
});

const {
setActiveThreadId,
activeThreadId,
Expand Down Expand Up @@ -582,14 +594,14 @@ function MainApp() {
startReview,
handleApprovalDecision,
handleApprovalRemember,
handleUserInputSubmit
handleUserInputSubmit,
} = useThreads({
activeWorkspace,
onWorkspaceConnected: markWorkspaceConnected,
onDebug: addDebugEntry,
model: resolvedModel,
effort: selectedEffort,
collaborationMode: selectedCollaborationMode?.value ?? null,
collaborationMode: collaborationModePayload,
accessMode,
steerEnabled: appSettings.experimentalSteerEnabled,
customPrompts: prompts,
Expand Down
123 changes: 123 additions & 0 deletions src/features/collaboration/hooks/useCollaborationModeSelection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { useEffect, useMemo, useRef } from "react";
import type { CollaborationModeOption, ModelOption } from "../../../types";

type UseCollaborationModeSelectionOptions = {
selectedCollaborationMode: CollaborationModeOption | null;
selectedCollaborationModeId: string | null;
models: ModelOption[];
selectedModelId: string | null;
selectedEffort: string | null;
resolvedModel: string | null;
setSelectedModelId: (id: string | null) => void;
setSelectedEffort: (effort: string | null) => void;
};

export function useCollaborationModeSelection({
selectedCollaborationMode,
selectedCollaborationModeId,
models,
selectedModelId,
selectedEffort,
resolvedModel,
setSelectedModelId,
setSelectedEffort,
}: UseCollaborationModeSelectionOptions) {
const lastAppliedCollaborationModeId = useRef<string | null>(null);
const lastUserModelId = useRef<string | null>(null);
const lastUserEffort = useRef<string | null>(null);
const wasCollaborationModeActive = useRef(false);

const collaborationModeModelOption = useMemo(() => {
const collaborationModeModel = selectedCollaborationMode?.model ?? null;
if (!collaborationModeModel) {
return null;
}
return (
models.find((model) => model.model === collaborationModeModel) ??
models.find((model) => model.id === collaborationModeModel) ??
null
);
}, [models, selectedCollaborationMode?.model]);

useEffect(() => {
if (!selectedCollaborationModeId) {
if (wasCollaborationModeActive.current) {
const restoreModelId = lastUserModelId.current;
if (
restoreModelId &&
restoreModelId !== selectedModelId &&
models.some((model) => model.id === restoreModelId)
) {
setSelectedModelId(restoreModelId);
}
if (lastUserEffort.current !== null && lastUserEffort.current !== selectedEffort) {
setSelectedEffort(lastUserEffort.current);
}
lastUserModelId.current = null;
lastUserEffort.current = null;
wasCollaborationModeActive.current = false;
}
lastAppliedCollaborationModeId.current = null;
return;
}
if (!wasCollaborationModeActive.current) {
lastUserModelId.current = selectedModelId;
lastUserEffort.current = selectedEffort;
wasCollaborationModeActive.current = true;
}
if (selectedCollaborationModeId === lastAppliedCollaborationModeId.current) {
return;
}
const collaborationModeModel = selectedCollaborationMode?.model ?? null;
const nextModelId = collaborationModeModelOption?.id ?? null;
if (nextModelId && nextModelId !== selectedModelId) {
setSelectedModelId(nextModelId);
}
const nextEffort = selectedCollaborationMode?.reasoningEffort ?? null;
if (nextEffort && nextEffort !== selectedEffort) {
setSelectedEffort(nextEffort);
}
if (!collaborationModeModel || nextModelId) {
lastAppliedCollaborationModeId.current = selectedCollaborationModeId;
}
}, [
collaborationModeModelOption?.id,
models,
selectedCollaborationMode?.model,
selectedCollaborationMode?.reasoningEffort,
selectedCollaborationModeId,
selectedEffort,
selectedModelId,
setSelectedEffort,
setSelectedModelId,
]);

const collaborationModePayload = useMemo(() => {
if (!selectedCollaborationModeId || !selectedCollaborationMode) {
return null;
}

const modeValue = selectedCollaborationMode.mode || selectedCollaborationMode.id;
if (!modeValue) {
return null;
}

return {
mode: modeValue,
settings: {
model: resolvedModel ?? selectedCollaborationMode.model ?? "",
reasoning_effort:
selectedEffort ?? selectedCollaborationMode.reasoningEffort ?? null,
developer_instructions:
selectedCollaborationMode.developerInstructions ?? null,
},
};
}, [
resolvedModel,
selectedCollaborationMode,
selectedCollaborationModeId,
selectedEffort,
]);

return { collaborationModePayload };
}
61 changes: 49 additions & 12 deletions src/features/collaboration/hooks/useCollaborationModes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,32 +59,69 @@ export function useCollaborationModes({
const rawData = response.result?.data ?? response.data ?? [];
const data: CollaborationModeOption[] = rawData
.map((item: any) => {
const mode = String(item.mode ?? "");
if (!item || typeof item !== "object") {
return null;
}
const mode = String(item.mode ?? item.name ?? "");
if (!mode) {
return null;
}
const model = String(item.model ?? "");
const reasoningEffort =
item.reasoningEffort ?? item.reasoning_effort ?? null;
const developerInstructions =
item.developerInstructions ?? item.developer_instructions ?? null;
const normalizedMode = mode.trim().toLowerCase();
if (normalizedMode && normalizedMode !== "plan" && normalizedMode !== "code") {
return null;
}

const settings =
item.settings && typeof item.settings === "object"
? item.settings
: {
model: item.model ?? null,
reasoning_effort:
item.reasoning_effort ?? item.reasoningEffort ?? null,
developer_instructions:
item.developer_instructions ??
item.developerInstructions ??
null,
};

const model = String(settings.model ?? "");
const reasoningEffort = settings.reasoning_effort ?? null;
const developerInstructions = settings.developer_instructions ?? null;

const labelSource = String(item.name ?? item.label ?? mode);

const normalizedValue = {
...(item as Record<string, unknown>),
mode: normalizedMode,
};

return {
id: mode,
label: formatCollaborationModeLabel(mode),
mode,
id: normalizedMode,
label: formatCollaborationModeLabel(labelSource),
mode: normalizedMode,
model,
reasoningEffort: reasoningEffort ? String(reasoningEffort) : null,
developerInstructions: developerInstructions
? String(developerInstructions)
: null,
value: item as Record<string, unknown>,
value: normalizedValue,
};
})
.filter(Boolean);
setModes(data);
lastFetchedWorkspaceId.current = workspaceId;
if (selectedModeId && !data.some((mode) => mode.id === selectedModeId)) {
setSelectedModeId(null);
const preferredModeId =
data.find((mode) => mode.mode === "code" || mode.id === "code")?.id ??
data[0]?.id ??
null;
if (!selectedModeId) {
if (preferredModeId) {
setSelectedModeId(preferredModeId);
}
return;
}
if (!data.some((mode) => mode.id === selectedModeId)) {
setSelectedModeId(preferredModeId);
}
} catch (error) {
onDebug?.({
Expand Down
6 changes: 2 additions & 4 deletions src/features/composer/components/ComposerMetaBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export function ComposerMetaBar({
onSelectAccessMode,
contextUsage = null,
}: ComposerMetaBarProps) {
const isCollaborationOverrideActive = Boolean(selectedCollaborationModeId);
const contextWindow = contextUsage?.modelContextWindow ?? null;
const lastTokens = contextUsage?.last.totalTokens ?? 0;
const totalTokens = contextUsage?.total.totalTokens ?? 0;
Expand Down Expand Up @@ -71,7 +70,6 @@ export function ComposerMetaBar({
}
disabled={disabled}
>
<option value="">Default</option>
{collaborationModes.map((mode) => (
<option key={mode.id} value={mode.id}>
{formatCollaborationModeLabel(mode.label || mode.id)}
Expand Down Expand Up @@ -113,7 +111,7 @@ export function ComposerMetaBar({
aria-label="Model"
value={selectedModelId ?? ""}
onChange={(event) => onSelectModel(event.target.value)}
disabled={disabled || isCollaborationOverrideActive}
disabled={disabled}
>
{models.length === 0 && <option value="">No models</option>}
{models.map((model) => (
Expand Down Expand Up @@ -157,7 +155,7 @@ export function ComposerMetaBar({
aria-label="Thinking mode"
value={selectedEffort ?? ""}
onChange={(event) => onSelectEffort(event.target.value)}
disabled={disabled || isCollaborationOverrideActive}
disabled={disabled}
>
{reasoningOptions.length === 0 && <option value="">Default</option>}
{reasoningOptions.map((effort) => (
Expand Down
2 changes: 1 addition & 1 deletion src/features/settings/components/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2250,7 +2250,7 @@ export function SettingsView({
<div>
<div className="settings-toggle-title">Collaboration modes</div>
<div className="settings-toggle-subtitle">
Enable collaboration mode presets (Default, Plan, Pair programming, Execute).
Enable collaboration mode presets (Code, Plan).
</div>
</div>
<button
Expand Down
Loading