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
1 change: 1 addition & 0 deletions .claude/worktrees/fervent-volhard-90de58
Submodule fervent-volhard-90de58 added at 9f1bc7
1 change: 1 addition & 0 deletions .claude/worktrees/vibrant-gates-8bad30
Submodule vibrant-gates-8bad30 added at 36f047
54 changes: 36 additions & 18 deletions bun.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "21st-desktop",
"version": "0.0.72",
"version": "1.0.0-aletc1",
"private": true,
"description": "1Code - UI for parallel work with AI agents",
"homepage": "https://21st.dev",
Expand All @@ -20,8 +20,8 @@
"dist": "electron-builder",
"dist:manifest": "node scripts/generate-update-manifest.mjs",
"dist:upload": "node scripts/upload-release.mjs",
"claude:download": "node scripts/download-claude-binary.mjs --version=2.1.45",
"claude:download:all": "node scripts/download-claude-binary.mjs --version=2.1.45 --all",
"claude:download": "node scripts/download-claude-binary.mjs --version=2.1.112",
"claude:download:all": "node scripts/download-claude-binary.mjs --version=2.1.112 --all",
"codex:download": "node scripts/download-codex-binary.mjs --version=0.98.0",
"codex:download:all": "node scripts/download-codex-binary.mjs --version=0.98.0 --all",
"release": "rm -rf release && bun i && bun run claude:download && bun run codex:download && bun run build && bun run package:mac && bun run dist:manifest && ./scripts/upload-release-wrangler.sh",
Expand All @@ -36,7 +36,7 @@
},
"dependencies": {
"@ai-sdk/react": "^3.0.14",
"@anthropic-ai/claude-agent-sdk": "0.2.45",
"@anthropic-ai/claude-agent-sdk": "0.2.112",
"@git-diff-view/react": "^0.0.35",
"@git-diff-view/shiki": "^0.0.36",
"@mcpc-tech/acp-ai-provider": "^0.2.4",
Expand Down
3 changes: 1 addition & 2 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,7 @@ if (gotTheLock) {

// Function to build and set application menu
const buildMenu = () => {
// Show devtools menu item only in dev mode or when unlocked
const showDevTools = !app.isPackaged || devToolsUnlocked
const showDevTools = true
const template: Electron.MenuItemConstructorOptions[] = [
{
label: app.name,
Expand Down
26 changes: 17 additions & 9 deletions src/main/lib/git/utils/parse-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@ function mapGitStatus(gitIndex: string, gitWorking: string): FileStatus {
return "modified";
}

// Normalize to POSIX separators so the renderer can safely split on "/"
// regardless of platform. Git itself stores paths with "/" on every OS, and
// Node fs / path.join accept "/" on Windows, so functionality is preserved.
function toPosix(p: string): string {
return p.replace(/\\/g, "/");
}

function toChangedFile(
path: string,
gitIndex: string,
gitWorking: string,
): ChangedFile {
return {
path,
path: toPosix(path),
status: mapGitStatus(gitIndex, gitWorking),
additions: 0,
deletions: 0,
Expand All @@ -47,8 +54,9 @@ export function parseGitStatus(

if (index && index !== " " && index !== "?") {
staged.push({
path,
oldPath: file.path !== file.from ? file.from : undefined,
path: toPosix(path),
oldPath:
file.path !== file.from && file.from ? toPosix(file.from) : undefined,
status: mapGitStatus(index, " "),
additions: 0,
deletions: 0,
Expand All @@ -57,7 +65,7 @@ export function parseGitStatus(

if (working && working !== " " && working !== "?") {
unstaged.push({
path,
path: toPosix(path),
status: mapGitStatus(" ", working),
additions: 0,
deletions: 0,
Expand Down Expand Up @@ -139,12 +147,12 @@ export function parseDiffNumstat(

const renameMatch = rawPath.match(/^(.+) => (.+)$/);
if (renameMatch) {
const oldPath = renameMatch[1];
const newPath = renameMatch[2];
const oldPath = toPosix(renameMatch[1]);
const newPath = toPosix(renameMatch[2]);
stats.set(newPath, statEntry);
stats.set(oldPath, statEntry);
} else {
stats.set(rawPath, statEntry);
stats.set(toPosix(rawPath), statEntry);
}
}

Expand Down Expand Up @@ -188,8 +196,8 @@ export function parseNameStatus(nameStatusOutput: string): ChangedFile[] {
}

files.push({
path,
oldPath,
path: toPosix(path),
oldPath: oldPath ? toPosix(oldPath) : undefined,
status,
additions: 0,
deletions: 0,
Expand Down
8 changes: 4 additions & 4 deletions src/main/lib/trpc/routers/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,9 @@ export const claudeRouter = router({
baseUrl: z.string().min(1),
})
.optional(),
maxThinkingTokens: z.number().optional(), // Enable extended thinking
effort: z
.enum(["low", "medium", "high", "xhigh", "max"])
.optional(), // Thinking/reasoning effort level
images: z.array(imageAttachmentSchema).optional(), // Image attachments
historyEnabled: z.boolean().optional(),
offlineModeEnabled: z.boolean().optional(), // Whether offline mode (Ollama) is enabled in settings
Expand Down Expand Up @@ -1994,9 +1996,7 @@ ${prompt}
...(!resumeSessionId && { continue: true }),
...(resolvedModel && { model: resolvedModel }),
// fallbackModel: "claude-opus-4-5-20251101",
...(input.maxThinkingTokens && {
maxThinkingTokens: input.maxThinkingTokens,
}),
...(input.effort && { effort: input.effort }),
},
}

Expand Down
5 changes: 1 addition & 4 deletions src/main/windows/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,9 @@ function registerIpcHandlers(): void {
}
})

// DevTools - only allowed in dev mode or when unlocked
ipcMain.handle("window:toggle-devtools", (event) => {
const win = getWindowFromEvent(event)
// Check if devtools are unlocked (or in dev mode)
const isUnlocked = !app.isPackaged || (global as any).__devToolsUnlocked
if (win && isUnlocked) {
if (win) {
win.webContents.toggleDevTools()
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import {
ctrlTabTargetAtom,
defaultAgentModeAtom,
desktopNotificationsEnabledAtom,
extendedThinkingEnabledAtom,
notifyWhenFocusedAtom,
soundNotificationsEnabledAtom,
preferredEditorAtom,
type AgentMode,
type AutoAdvanceTarget,
type CtrlTabTarget,
} from "../../../lib/atoms"
import { lastSelectedClaudeThinkingAtom } from "../../../features/agents/atoms"
import {
formatClaudeThinkingLabel,
type ClaudeThinkingLevel,
} from "../../../features/agents/lib/models"
import { APP_META, type ExternalApp } from "../../../../shared/external-apps"

// Editor icon imports
Expand Down Expand Up @@ -142,8 +146,8 @@ function useIsNarrowScreen(): boolean {
}

export function AgentsPreferencesTab() {
const [thinkingEnabled, setThinkingEnabled] = useAtom(
extendedThinkingEnabledAtom,
const [claudeThinking, setClaudeThinking] = useAtom(
lastSelectedClaudeThinkingAtom,
)
const [soundEnabled, setSoundEnabled] = useAtom(soundNotificationsEnabledAtom)
const [desktopNotificationsEnabled, setDesktopNotificationsEnabled] = useAtom(desktopNotificationsEnabledAtom)
Expand Down Expand Up @@ -197,18 +201,34 @@ export function AgentsPreferencesTab() {
<div className="flex items-center justify-between p-4">
<div className="flex flex-col space-y-1">
<span className="text-sm font-medium text-foreground">
Extended Thinking
Thinking Effort
</span>
<span className="text-xs text-muted-foreground">
Enable deeper reasoning with more thinking tokens (uses more
credits).{" "}
<span className="text-foreground/70">Disables response streaming.</span>
Default effort level for Claude's reasoning. Higher levels think
longer and use more credits.
</span>
</div>
<Switch
checked={thinkingEnabled}
onCheckedChange={setThinkingEnabled}
/>
<Select
value={claudeThinking}
onValueChange={(value: ClaudeThinkingLevel) =>
setClaudeThinking(value)
}
>
<SelectTrigger className="w-auto px-2">
<span className="text-xs">
{formatClaudeThinkingLabel(claudeThinking)}
</span>
</SelectTrigger>
<SelectContent>
{(
["off", "low", "medium", "high", "xhigh", "max"] as const
).map((level) => (
<SelectItem key={level} value={level}>
{formatClaudeThinkingLabel(level)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between p-4 border-t border-border">
<div className="flex flex-col space-y-1">
Expand Down
61 changes: 61 additions & 0 deletions src/renderer/features/agents/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,34 @@ export const lastSelectedCodexThinkingAtom = atomWithStorage<CodexThinkingPrefer
{ getOnInit: true },
)

export type ClaudeThinkingPreference =
| "off"
| "low"
| "medium"
| "high"
| "xhigh"
| "max"

// One-time migration from the legacy boolean toggle: true → "high", false → "off".
// Only consulted the first time the new key is read (atomWithStorage keeps the user's
// choice thereafter).
function readInitialClaudeThinking(): ClaudeThinkingPreference {
try {
const raw = localStorage.getItem("preferences:extended-thinking-enabled")
if (raw === null) return "high"
return JSON.parse(raw) === false ? "off" : "high"
} catch {
return "high"
}
}

export const lastSelectedClaudeThinkingAtom = atomWithStorage<ClaudeThinkingPreference>(
"agents:lastSelectedClaudeThinking",
readInitialClaudeThinking(),
undefined,
{ getOnInit: true },
)

// Storage for per-subChat Claude model selection.
// Falls back to lastSelectedModelIdAtom when sub-chat has no explicit selection yet.
const subChatModelIdsStorageAtom = atomWithStorage<Record<string, string>>(
Expand Down Expand Up @@ -323,6 +351,38 @@ export const subChatCodexThinkingAtomFamily = atomFamily((subChatId: string) =>
),
)

// Storage for per-subChat Claude thinking level.
// Falls back to lastSelectedClaudeThinkingAtom when sub-chat has no explicit selection yet.
const subChatClaudeThinkingStorageAtom = atomWithStorage<
Record<string, ClaudeThinkingPreference>
>(
"agents:subChatClaudeThinking",
{},
undefined,
{ getOnInit: true },
)

export const subChatClaudeThinkingAtomFamily = atomFamily((subChatId: string) =>
atom(
(get) => {
if (!subChatId) return get(lastSelectedClaudeThinkingAtom)
return (
get(subChatClaudeThinkingStorageAtom)[subChatId] ??
get(lastSelectedClaudeThinkingAtom)
)
},
(get, set, newThinking: ClaudeThinkingPreference) => {
if (!subChatId) {
set(lastSelectedClaudeThinkingAtom, newThinking)
return
}
const current = get(subChatClaudeThinkingStorageAtom)
if (current[subChatId] === newThinking) return
set(subChatClaudeThinkingStorageAtom, { ...current, [subChatId]: newThinking })
},
),
)

// Storage for all sub-chat modes (persisted per subChatId)
const subChatModesStorageAtom = atomWithStorage<Record<string, AgentMode>>(
"agents:subChatModes",
Expand All @@ -345,6 +405,7 @@ export const subChatModeAtomFamily = atomFamily((subChatId: string) =>
// Model ID to full Claude model string mapping
export const MODEL_ID_MAP: Record<string, string> = {
opus: "opus",
"opus[1m]": "opus[1m]",
sonnet: "sonnet",
haiku: "haiku",
}
Expand Down
Loading