Skip to content

Commit 6e83a87

Browse files
committed
Release v0.0.51
## What's New ### Features - **Open Automations in Browser** — Automations now open in browser with external link icon ### Improvements & Fixes - **Send Now Fix** — Fixed queued messages being lost when clicking "Send Now" during streaming — thanks @jjjrmy! (#126) - **Symlinks in Skills** — Fixed symlink handling in skills - **Compact Commands** — Improved compact command display - **Plan Text Fix** — Fixed build plan text rendering
1 parent daeb1cb commit 6e83a87

9 files changed

Lines changed: 165 additions & 140 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "21st-desktop",
3-
"version": "0.0.50",
3+
"version": "0.0.51",
44
"private": true,
55
"description": "1Code - UI for parallel work with AI agents",
66
"author": {

src/main/lib/claude/transform.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ export function createTransformer(options?: { emitSdkMessageUuid?: boolean; isUs
344344
}
345345

346346
// ===== USER MESSAGE (tool results) =====
347-
if (msg.type === "user" && msg.message?.content) {
347+
if (msg.type === "user" && msg.message?.content && Array.isArray(msg.message.content)) {
348348
// DEBUG: Log the message structure to understand tool_use_result
349349
console.log("[Transform DEBUG] User message:", {
350350
tool_use_result: msg.tool_use_result,

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { observable } from "@trpc/server/observable"
22
import { eq } from "drizzle-orm"
33
import { app, BrowserWindow, safeStorage } from "electron"
4-
import { readFileSync } from "fs"
54
import * as fs from "fs/promises"
65
import * as os from "os"
7-
import path, { join } from "path"
6+
import path from "path"
87
import { z } from "zod"
8+
import { setConnectionMethod } from "../../analytics"
99
import {
1010
buildClaudeEnv,
1111
checkOfflineFallback,
@@ -20,7 +20,6 @@ import { chats, claudeCodeCredentials, getDatabase, subChats } from "../../db"
2020
import { createRollbackStash } from "../../git/stash"
2121
import { ensureMcpTokensFresh, fetchMcpTools, fetchMcpToolsStdio, getMcpAuthStatus, startMcpOAuth } from "../../mcp-auth"
2222
import { fetchOAuthMetadata, getMcpBaseUrl } from "../../oauth"
23-
import { setConnectionMethod } from "../../analytics"
2423
import { publicProcedure, router } from "../index"
2524
import { buildAgentsOption } from "./agent-utils"
2625

@@ -1504,7 +1503,7 @@ ${prompt}
15041503

15051504
// When result arrives, assign the last assistant UUID to metadata
15061505
// It will be emitted as part of the merged message-metadata chunk below
1507-
if (msgAny.type === "result" && historyEnabled && lastAssistantUuid) {
1506+
if (msgAny.type === "result" && historyEnabled && lastAssistantUuid && !abortController.signal.aborted) {
15081507
metadata.sdkMessageUuid = lastAssistantUuid
15091508
}
15101509

@@ -1832,6 +1831,8 @@ ${prompt}
18321831
parts.push({ type: "text", text: currentText })
18331832
}
18341833

1834+
const savedSessionId = metadata.sessionId
1835+
18351836
if (parts.length > 0) {
18361837
const assistantMessage = {
18371838
id: crypto.randomUUID(),
@@ -1845,7 +1846,7 @@ ${prompt}
18451846
db.update(subChats)
18461847
.set({
18471848
messages: JSON.stringify(finalMessages),
1848-
sessionId: metadata.sessionId,
1849+
sessionId: savedSessionId,
18491850
streamId: null,
18501851
updatedAt: new Date(),
18511852
})
@@ -1855,7 +1856,7 @@ ${prompt}
18551856
// No assistant response - just clear streamId
18561857
db.update(subChats)
18571858
.set({
1858-
sessionId: metadata.sessionId,
1859+
sessionId: savedSessionId,
18591860
streamId: null,
18601861
updatedAt: new Date(),
18611862
})
@@ -1896,14 +1897,13 @@ ${prompt}
18961897
activeSessions.delete(input.subChatId)
18971898
clearPendingApprovals("Session ended.", input.subChatId)
18981899

1899-
// Save sessionId on abort so conversation can be resumed
1900-
// Clear streamId since we're no longer streaming
1900+
// Clear streamId since we're no longer streaming.
1901+
// sessionId is NOT saved here — the save block in the async function
1902+
// handles it (saves on normal completion, clears on abort). This avoids
1903+
// a redundant DB write that the cancel mutation would then overwrite.
19011904
const db = getDatabase()
19021905
db.update(subChats)
1903-
.set({
1904-
streamId: null,
1905-
...(currentSessionId && { sessionId: currentSessionId })
1906-
})
1906+
.set({ streamId: null })
19071907
.where(eq(subChats.id, input.subChatId))
19081908
.run()
19091909
}
@@ -1964,9 +1964,10 @@ ${prompt}
19641964
controller.abort()
19651965
activeSessions.delete(input.subChatId)
19661966
clearPendingApprovals("Session cancelled.", input.subChatId)
1967-
return { cancelled: true }
19681967
}
1969-
return { cancelled: false }
1968+
1969+
1970+
return { cancelled: !!controller }
19701971
}),
19711972

19721973
/**

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,19 @@ async function scanSkillsDirectory(
4949
const entries = await fs.readdir(dir, { withFileTypes: true })
5050

5151
for (const entry of entries) {
52-
if (!entry.isDirectory()) continue
52+
// Check if entry is a directory or a symlink pointing to a directory
53+
let isDir = entry.isDirectory()
54+
if (!isDir && entry.isSymbolicLink()) {
55+
try {
56+
const targetPath = path.join(dir, entry.name)
57+
const stat = await fs.stat(targetPath) // stat() follows symlinks
58+
isDir = stat.isDirectory()
59+
} catch {
60+
// Symlink target doesn't exist or is inaccessible - skip it
61+
continue
62+
}
63+
}
64+
if (!isDir) continue
5365

5466
// Validate entry name for security (prevent path traversal)
5567
if (entry.name.includes("..") || entry.name.includes("/") || entry.name.includes("\\")) {

src/renderer/features/agents/lib/ipc-chat-transport.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import type { ChatTransport, UIMessage } from "ai"
33
import { toast } from "sonner"
44
import {
55
agentsLoginModalOpenAtom,
6+
autoOfflineModeAtom,
7+
type CustomClaudeConfig,
68
customClaudeConfigAtom,
79
enableTasksAtom,
810
extendedThinkingEnabledAtom,
911
historyEnabledAtom,
10-
sessionInfoAtom,
12+
normalizeCustomClaudeConfig,
1113
selectedOllamaModelAtom,
14+
sessionInfoAtom,
1215
showOfflineModeFeaturesAtom,
13-
autoOfflineModeAtom,
14-
type CustomClaudeConfig,
15-
normalizeCustomClaudeConfig,
1616
} from "../../../lib/atoms"
1717
import { appStore } from "../../../lib/jotai-store"
1818
import { trpcClient } from "../../../lib/trpc"
@@ -26,6 +26,7 @@ import {
2626
pendingUserQuestionsAtom,
2727
} from "../atoms"
2828
import { useAgentSubChatStore } from "../stores/sub-chat-store"
29+
import type { AgentMessageMetadata } from "../ui/agent-message-usage"
2930

3031
// Error categories and their user-friendly messages
3132
const ERROR_TOAST_CONFIG: Record<
@@ -151,11 +152,13 @@ export class IPCChatTransport implements ChatTransport<UIMessage> {
151152
const prompt = this.extractText(lastUser)
152153
const images = this.extractImages(lastUser)
153154

154-
// Get sessionId for resume
155+
// Get sessionId for resume (server preserves sessionId on abort so
156+
// the next message can resume with full conversation context)
155157
const lastAssistant = [...options.messages]
156158
.reverse()
157159
.find((m) => m.role === "assistant")
158-
const sessionId = (lastAssistant as any)?.metadata?.sessionId
160+
const metadata = lastAssistant?.metadata as AgentMessageMetadata | undefined
161+
const sessionId = metadata?.sessionId
159162

160163
// Read extended thinking setting dynamically (so toggle applies to existing chats)
161164
const thinkingEnabled = appStore.get(extendedThinkingEnabledAtom)
@@ -464,7 +467,7 @@ export class IPCChatTransport implements ChatTransport<UIMessage> {
464467
options.abortSignal?.addEventListener("abort", () => {
465468
console.log(`[SD] R:ABORT sub=${subId} n=${chunkCount} last=${lastChunkType}`)
466469
sub.unsubscribe()
467-
trpcClient.claude.cancel.mutate({ subChatId: this.config.subChatId })
470+
// trpcClient.claude.cancel.mutate({ subChatId: this.config.subChatId })
468471
try {
469472
controller.close()
470473
} catch {

0 commit comments

Comments
 (0)