Skip to content

Commit 9f1bc76

Browse files
Release v0.0.72
Manual sync from private repo desktop source.
1 parent 9178ae5 commit 9f1bc76

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2896
-421
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,17 @@ Learn more at [1code.dev/agents/api](https://1code.dev/agents/api)
142142
### Option 1: Build from source (free)
143143

144144
```bash
145-
# Prerequisites: Bun, Python, Xcode Command Line Tools (macOS)
145+
# Prerequisites: Bun, Python 3.11, setuptools, Xcode Command Line Tools (macOS)
146146
bun install
147147
bun run claude:download # Download Claude binary (required!)
148+
bun run codex:download # Download Codex binary (required!)
148149
bun run build
149150
bun run package:mac # or package:win, package:linux
150151
```
151152

152-
> **Important:** The `claude:download` step downloads the Claude CLI binary which is required for the agent chat to work. If you skip this step, the app will build but agent functionality won't work.
153+
> **Important:** The `claude:download` and `codex:download` steps download required agent binaries. If you skip them, the app may build but agent functionality will not work correctly.
154+
>
155+
> **Python note:** Python 3.11 is recommended for native module rebuilds. On Python 3.12+, make sure `setuptools` is installed (`pip install setuptools`).
153156
154157
### Option 2: Subscribe to 1code.dev (recommended)
155158

@@ -162,6 +165,7 @@ Your subscription helps us maintain and improve 1Code.
162165
```bash
163166
bun install
164167
bun run claude:download # First time only
168+
bun run codex:download # First time only
165169
bun run dev
166170
```
167171

electron-builder.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ mac:
55
# Code signing identity - set via APPLE_IDENTITY env var
66
# Example: "Developer ID Application: Your Name (TEAM_ID)"
77
identity: ${env.APPLE_IDENTITY}
8-
notarize:
9-
teamId: ${env.APPLE_TEAM_ID}
8+
# Notarization is handled explicitly in the CI workflow's "Notarize" step
9+
# Disable built-in notarization to avoid double-notarization hangs
10+
notarize: false

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"name": "21st-desktop",
3-
"version": "0.0.66",
3+
"version": "0.0.72",
44
"private": true,
55
"description": "1Code - UI for parallel work with AI agents",
6+
"homepage": "https://21st.dev",
67
"author": {
78
"name": "21st.dev",
89
"email": "support@21st.dev"
@@ -241,7 +242,7 @@
241242
"AppImage",
242243
"deb"
243244
],
244-
"icon": "build/icons",
245+
"icon": "build/icon.png",
245246
"category": "Development"
246247
},
247248
"nsis": {

scripts/download-claude-binary.mjs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
* Downloads Claude Code native binaries for bundling with the Electron app.
44
*
55
* Usage:
6-
* node scripts/download-claude-binary.mjs # Download for current platform
7-
* node scripts/download-claude-binary.mjs --all # Download all platforms
8-
* node scripts/download-claude-binary.mjs --version 2.1.5 # Specific version
6+
* node scripts/download-claude-binary.mjs # Download for current platform
7+
* node scripts/download-claude-binary.mjs --all # Download all platforms
8+
* node scripts/download-claude-binary.mjs --platform darwin-x64 # Download for specific platform
9+
* node scripts/download-claude-binary.mjs --version=2.1.5 # Specific version
910
*/
1011

1112
import fs from "node:fs"
@@ -28,6 +29,7 @@ const PLATFORMS = {
2829
"darwin-x64": { dir: "darwin-x64", binary: "claude" },
2930
"linux-arm64": { dir: "linux-arm64", binary: "claude" },
3031
"linux-x64": { dir: "linux-x64", binary: "claude" },
32+
"win32-arm64": { dir: "win32-arm64", binary: "claude.exe" },
3133
"win32-x64": { dir: "win32-x64", binary: "claude.exe" },
3234
}
3335

@@ -218,6 +220,13 @@ async function main() {
218220
const downloadAll = args.includes("--all")
219221
const versionArg = args.find((a) => a.startsWith("--version="))
220222
const specifiedVersion = versionArg?.split("=")[1]
223+
const platformArgIdx = args.indexOf("--platform")
224+
const platformArgEq = args.find((a) => a.startsWith("--platform="))
225+
const specifiedPlatform = platformArgEq
226+
? platformArgEq.split("=")[1]
227+
: platformArgIdx >= 0
228+
? args[platformArgIdx + 1]
229+
: null
221230

222231
console.log("Claude Code Binary Downloader")
223232
console.log("=============================\n")
@@ -242,6 +251,14 @@ async function main() {
242251
let platformsToDownload
243252
if (downloadAll) {
244253
platformsToDownload = Object.keys(PLATFORMS)
254+
} else if (specifiedPlatform) {
255+
// Specific platform requested via --platform
256+
if (!PLATFORMS[specifiedPlatform]) {
257+
console.error(`Unsupported platform: ${specifiedPlatform}`)
258+
console.log(`Supported platforms: ${Object.keys(PLATFORMS).join(", ")}`)
259+
process.exit(1)
260+
}
261+
platformsToDownload = [specifiedPlatform]
245262
} else {
246263
// Current platform only
247264
const currentPlatform = `${process.platform}-${process.arch}`

scripts/download-codex-binary.mjs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,16 @@ const PLATFORMS = {
5555
}
5656

5757
function getRequestHeaders() {
58-
return {
58+
const headers = {
5959
"User-Agent": USER_AGENT,
6060
Accept: "application/vnd.github+json",
6161
}
62+
// Use GITHUB_TOKEN if available (avoids API rate limits in CI)
63+
const token = process.env.GITHUB_TOKEN
64+
if (token) {
65+
headers.Authorization = `Bearer ${token}`
66+
}
67+
return headers
6268
}
6369

6470
function fetchJson(url) {
@@ -335,6 +341,13 @@ async function main() {
335341
const args = process.argv.slice(2)
336342
const downloadAll = args.includes("--all")
337343
const specifiedVersion = getVersionArg(args)
344+
const platformArgIdx = args.indexOf("--platform")
345+
const platformArgEq = args.find((a) => a.startsWith("--platform="))
346+
const specifiedPlatform = platformArgEq
347+
? platformArgEq.split("=")[1]
348+
: platformArgIdx >= 0
349+
? args[platformArgIdx + 1]
350+
: null
338351

339352
console.log("Codex Binary Downloader")
340353
console.log("=======================\n")
@@ -347,6 +360,13 @@ async function main() {
347360
let platformsToDownload
348361
if (downloadAll) {
349362
platformsToDownload = Object.keys(PLATFORMS)
363+
} else if (specifiedPlatform) {
364+
if (!PLATFORMS[specifiedPlatform]) {
365+
console.error(`Unsupported platform: ${specifiedPlatform}`)
366+
console.log(`Supported platforms: ${Object.keys(PLATFORMS).join(", ")}`)
367+
process.exit(1)
368+
}
369+
platformsToDownload = [specifiedPlatform]
350370
} else {
351371
const currentPlatform = `${process.platform}-${process.arch}`
352372
if (!PLATFORMS[currentPlatform]) {

scripts/generate-update-manifest.mjs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,16 @@ function getFileSize(filePath) {
5959
}
6060

6161
/**
62-
* Find ZIP file matching pattern in release directory
62+
* Find file matching pattern and extension in release directory
6363
*/
64-
function findZipFile(pattern) {
64+
function findReleaseFile(pattern, ext = ".zip") {
6565
if (!existsSync(releaseDir)) {
6666
console.error(`Release directory not found: ${releaseDir}`)
6767
process.exit(1)
6868
}
6969

7070
const files = readdirSync(releaseDir)
71-
const match = files.find((f) => f.includes(pattern) && f.endsWith(".zip"))
71+
const match = files.find((f) => f.includes(pattern) && f.endsWith(ext))
7272
return match ? join(releaseDir, match) : null
7373
}
7474

@@ -80,7 +80,7 @@ function generateManifest(arch) {
8080
// arm64: Agents-{version}-arm64-mac.zip
8181
// x64: Agents-{version}-mac.zip
8282
const pattern = arch === "arm64" ? `${version}-arm64-mac` : `${version}-mac`
83-
const zipPath = findZipFile(pattern)
83+
const zipPath = findReleaseFile(pattern, ".zip")
8484

8585
if (!zipPath) {
8686
console.warn(`Warning: ZIP file not found for pattern: ${pattern}`)
@@ -174,6 +174,53 @@ function formatBytes(bytes) {
174174
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]
175175
}
176176

177+
/**
178+
* Generate manifest for Linux AppImage
179+
*/
180+
function generateLinuxManifest() {
181+
const appImagePath = findReleaseFile(`${version}`, ".AppImage")
182+
183+
if (!appImagePath) {
184+
console.warn(`Warning: AppImage file not found for version: ${version}`)
185+
console.warn(`Skipping Linux manifest generation`)
186+
return null
187+
}
188+
189+
const appImageName = appImagePath.split("/").pop()
190+
const sha512 = calculateSha512(appImagePath)
191+
const size = getFileSize(appImagePath)
192+
193+
const manifest = {
194+
version,
195+
files: [
196+
{
197+
url: appImageName,
198+
sha512,
199+
size,
200+
},
201+
],
202+
path: appImageName,
203+
sha512,
204+
releaseDate: new Date().toISOString(),
205+
}
206+
207+
const prefix = channel === "beta" ? "beta" : "latest"
208+
const manifestFileName = `${prefix}-linux.yml`
209+
const manifestPath = join(releaseDir, manifestFileName)
210+
211+
const yaml = objectToYaml(manifest)
212+
writeFileSync(manifestPath, yaml)
213+
214+
console.log(`Generated ${manifestFileName}:`)
215+
console.log(` Version: ${version}`)
216+
console.log(` File: ${appImageName}`)
217+
console.log(` Size: ${formatBytes(size)}`)
218+
console.log(` SHA512: ${sha512.substring(0, 20)}...`)
219+
console.log()
220+
221+
return manifestPath
222+
}
223+
177224
// Main execution
178225
console.log("=".repeat(50))
179226
console.log("Generating electron-updater manifests")
@@ -185,8 +232,9 @@ console.log()
185232

186233
const arm64Manifest = generateManifest("arm64")
187234
const x64Manifest = generateManifest("x64")
235+
const linuxManifest = generateLinuxManifest()
188236

189-
if (!arm64Manifest && !x64Manifest) {
237+
if (!arm64Manifest && !x64Manifest && !linuxManifest) {
190238
console.error("No manifest files were generated!")
191239
console.error("Make sure you have built the app with: npm run dist")
192240
process.exit(1)

src/main/index.ts

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Sentry from "@sentry/electron/main"
2-
import { app, BrowserWindow, Menu, nativeImage, session } from "electron"
2+
import { app, BrowserWindow, dialog, Menu, nativeImage, session } from "electron"
33
import { existsSync, readFileSync, readlinkSync, unlinkSync } from "fs"
44
import { createServer } from "http"
55
import { join } from "path"
@@ -28,13 +28,14 @@ import {
2828
} from "./lib/cli"
2929
import { cleanupGitWatchers } from "./lib/git/watcher"
3030
import { cancelAllPendingOAuth, handleMcpOAuthCallback } from "./lib/mcp-auth"
31-
import { getAllMcpConfigHandler } from "./lib/trpc/routers/claude"
32-
import { getAllCodexMcpConfigHandler } from "./lib/trpc/routers/codex"
31+
import { getAllMcpConfigHandler, hasActiveClaudeSessions, abortAllClaudeSessions } from "./lib/trpc/routers/claude"
32+
import { getAllCodexMcpConfigHandler, hasActiveCodexStreams, abortAllCodexStreams } from "./lib/trpc/routers/codex"
3333
import {
3434
createMainWindow,
3535
createWindow,
3636
getWindow,
3737
getAllWindows,
38+
setIsQuitting,
3839
} from "./windows/main"
3940
import { windowManager } from "./windows/window-manager"
4041

@@ -53,6 +54,10 @@ if (IS_DEV) {
5354
console.log("[Dev] Using separate userData path:", devUserData)
5455
}
5556

57+
// Increase V8 old-space limit for renderer/main processes to reduce OOM frequency
58+
// under heavy multi-chat workloads. Must be set before app readiness/window creation.
59+
app.commandLine.appendSwitch("js-flags", "--max-old-space-size=8192")
60+
5661
// Initialize Sentry before app is ready (production only)
5762
if (app.isPackaged && !IS_DEV) {
5863
const sentryDsn = import.meta.env.MAIN_VITE_SENTRY_DSN
@@ -699,7 +704,32 @@ if (gotTheLock) {
699704
{ role: "hideOthers" },
700705
{ role: "unhide" },
701706
{ type: "separator" },
702-
{ role: "quit" },
707+
{
708+
label: "Quit",
709+
accelerator: "CmdOrCtrl+Q",
710+
click: async () => {
711+
if (hasActiveClaudeSessions() || hasActiveCodexStreams()) {
712+
const { dialog } = await import("electron")
713+
const { response } = await dialog.showMessageBox({
714+
type: "warning",
715+
buttons: ["Cancel", "Quit Anyway"],
716+
defaultId: 0,
717+
cancelId: 0,
718+
title: "Active Sessions",
719+
message: "There are active agent sessions running.",
720+
detail: "Quitting now will interrupt them. Are you sure you want to quit?",
721+
})
722+
if (response === 1) {
723+
abortAllClaudeSessions()
724+
abortAllCodexStreams()
725+
setIsQuitting(true)
726+
app.quit()
727+
}
728+
} else {
729+
app.quit()
730+
}
731+
},
732+
},
703733
],
704734
},
705735
{
@@ -756,8 +786,37 @@ if (gotTheLock) {
756786
label: "View",
757787
submenu: [
758788
// Cmd+R is disabled to prevent accidental page refresh
759-
// Use Cmd+Shift+R (Force Reload) for intentional reloads
760-
{ role: "forceReload" },
789+
// Cmd+Shift+R reloads but warns if there are active streams
790+
{
791+
label: "Force Reload",
792+
accelerator: "CmdOrCtrl+Shift+R",
793+
click: () => {
794+
const win = BrowserWindow.getFocusedWindow()
795+
if (!win) return
796+
if (hasActiveClaudeSessions() || hasActiveCodexStreams()) {
797+
dialog
798+
.showMessageBox(win, {
799+
type: "warning",
800+
buttons: ["Cancel", "Reload Anyway"],
801+
defaultId: 0,
802+
cancelId: 0,
803+
title: "Active Sessions",
804+
message: "There are active agent sessions running.",
805+
detail:
806+
"Reloading will interrupt them. The current progress will be saved. Are you sure you want to reload?",
807+
})
808+
.then(({ response }) => {
809+
if (response === 1) {
810+
abortAllClaudeSessions()
811+
abortAllCodexStreams()
812+
win.webContents.reloadIgnoringCache()
813+
}
814+
})
815+
} else {
816+
win.webContents.reloadIgnoringCache()
817+
}
818+
},
819+
},
761820
// Only show DevTools in dev mode or when unlocked via hidden feature
762821
...(showDevTools ? [{ role: "toggleDevTools" as const }] : []),
763822
{ type: "separator" },

src/main/lib/trpc/routers/chats.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,7 @@ export const chatsRouter = router({
745745
z.object({
746746
subChatId: z.string(),
747747
messageId: z.string(),
748+
messageIndex: z.number().int().nonnegative().optional(),
748749
name: z.string().optional(),
749750
}),
750751
)
@@ -761,9 +762,15 @@ export const chatsRouter = router({
761762

762763
// 2. Parse messages and find the cutoff point
763764
const allMessages = JSON.parse(sourceSubChat.messages || "[]")
764-
const cutoffIndex = allMessages.findIndex(
765+
let cutoffIndex = allMessages.findIndex(
765766
(m: any) => m.id === input.messageId,
766767
)
768+
// Fallback: AI SDK generates its own message IDs on the client which differ
769+
// from the server-generated UUIDs stored in the DB. Use the message index
770+
// (passed from the client) as a fallback when the ID doesn't match.
771+
if (cutoffIndex === -1 && input.messageIndex !== undefined && input.messageIndex < allMessages.length) {
772+
cutoffIndex = input.messageIndex
773+
}
767774
if (cutoffIndex === -1) throw new Error("Message not found")
768775

769776
// 3. Slice messages up to and including the target

src/main/lib/trpc/routers/claude-code.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@ export const claudeCodeRouter = router({
114114
*/
115115
hasExistingCliConfig: publicProcedure.query(() => {
116116
const shellEnv = getClaudeShellEnvironment()
117-
const hasConfig = !!(shellEnv.ANTHROPIC_API_KEY || shellEnv.ANTHROPIC_BASE_URL)
117+
const hasConfig = !!(shellEnv.ANTHROPIC_API_KEY || shellEnv.ANTHROPIC_AUTH_TOKEN || shellEnv.ANTHROPIC_BASE_URL)
118118
return {
119119
hasConfig,
120-
hasApiKey: !!shellEnv.ANTHROPIC_API_KEY,
120+
hasApiKey: !!(shellEnv.ANTHROPIC_API_KEY || shellEnv.ANTHROPIC_AUTH_TOKEN),
121121
baseUrl: shellEnv.ANTHROPIC_BASE_URL || null,
122122
}
123123
}),

0 commit comments

Comments
 (0)