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
21 changes: 21 additions & 0 deletions apps/codex-claw/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Route as NewRouteImport } from './routes/new'
import { Route as ConnectRouteImport } from './routes/connect'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ChatSessionKeyRouteImport } from './routes/chat/$sessionKey'
import { Route as ApiWorkspacesRouteImport } from './routes/api/workspaces'
import { Route as ApiStreamRouteImport } from './routes/api/stream'
import { Route as ApiSessionsRouteImport } from './routes/api/sessions'
import { Route as ApiSendRouteImport } from './routes/api/send'
Expand Down Expand Up @@ -40,6 +41,11 @@ const ChatSessionKeyRoute = ChatSessionKeyRouteImport.update({
path: '/chat/$sessionKey',
getParentRoute: () => rootRouteImport,
} as any)
const ApiWorkspacesRoute = ApiWorkspacesRouteImport.update({
id: '/api/workspaces',
path: '/api/workspaces',
getParentRoute: () => rootRouteImport,
} as any)
const ApiStreamRoute = ApiStreamRouteImport.update({
id: '/api/stream',
path: '/api/stream',
Expand Down Expand Up @@ -81,6 +87,7 @@ export interface FileRoutesByFullPath {
'/api/send': typeof ApiSendRoute
'/api/sessions': typeof ApiSessionsRoute
'/api/stream': typeof ApiStreamRoute
'/api/workspaces': typeof ApiWorkspacesRoute
'/chat/$sessionKey': typeof ChatSessionKeyRoute
}
export interface FileRoutesByTo {
Expand All @@ -93,6 +100,7 @@ export interface FileRoutesByTo {
'/api/send': typeof ApiSendRoute
'/api/sessions': typeof ApiSessionsRoute
'/api/stream': typeof ApiStreamRoute
'/api/workspaces': typeof ApiWorkspacesRoute
'/chat/$sessionKey': typeof ChatSessionKeyRoute
}
export interface FileRoutesById {
Expand All @@ -106,6 +114,7 @@ export interface FileRoutesById {
'/api/send': typeof ApiSendRoute
'/api/sessions': typeof ApiSessionsRoute
'/api/stream': typeof ApiStreamRoute
'/api/workspaces': typeof ApiWorkspacesRoute
'/chat/$sessionKey': typeof ChatSessionKeyRoute
}
export interface FileRouteTypes {
Expand All @@ -120,6 +129,7 @@ export interface FileRouteTypes {
| '/api/send'
| '/api/sessions'
| '/api/stream'
| '/api/workspaces'
| '/chat/$sessionKey'
fileRoutesByTo: FileRoutesByTo
to:
Expand All @@ -132,6 +142,7 @@ export interface FileRouteTypes {
| '/api/send'
| '/api/sessions'
| '/api/stream'
| '/api/workspaces'
| '/chat/$sessionKey'
id:
| '__root__'
Expand All @@ -144,6 +155,7 @@ export interface FileRouteTypes {
| '/api/send'
| '/api/sessions'
| '/api/stream'
| '/api/workspaces'
| '/chat/$sessionKey'
fileRoutesById: FileRoutesById
}
Expand All @@ -157,6 +169,7 @@ export interface RootRouteChildren {
ApiSendRoute: typeof ApiSendRoute
ApiSessionsRoute: typeof ApiSessionsRoute
ApiStreamRoute: typeof ApiStreamRoute
ApiWorkspacesRoute: typeof ApiWorkspacesRoute
ChatSessionKeyRoute: typeof ChatSessionKeyRoute
}

Expand Down Expand Up @@ -190,6 +203,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ChatSessionKeyRouteImport
parentRoute: typeof rootRouteImport
}
'/api/workspaces': {
id: '/api/workspaces'
path: '/api/workspaces'
fullPath: '/api/workspaces'
preLoaderRoute: typeof ApiWorkspacesRouteImport
parentRoute: typeof rootRouteImport
}
'/api/stream': {
id: '/api/stream'
path: '/api/stream'
Expand Down Expand Up @@ -245,6 +265,7 @@ const rootRouteChildren: RootRouteChildren = {
ApiSendRoute: ApiSendRoute,
ApiSessionsRoute: ApiSessionsRoute,
ApiStreamRoute: ApiStreamRoute,
ApiWorkspacesRoute: ApiWorkspacesRoute,
ChatSessionKeyRoute: ChatSessionKeyRoute,
}
export const routeTree = rootRouteImport
Expand Down
5 changes: 2 additions & 3 deletions apps/codex-claw/src/routes/api/ping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import { codexCliCheck } from '../../server/codex-cli'
export const Route = createFileRoute('/api/ping')({
server: {
handlers: {
GET: async () => {
GET: () => {
try {
await codexCliCheck()
return json({ ok: true })
return json(codexCliCheck())
} catch (err) {
return json(
{
Expand Down
98 changes: 98 additions & 0 deletions apps/codex-claw/src/routes/api/workspaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'
import {
activateCodexWorkspace,
createCodexWorkspace,
deleteCodexWorkspace,
listCodexWorkspaces,
patchCodexWorkspace,
} from '../../server/codex-cli'

function workspaceInput(body: Record<string, unknown>) {
return {
id: typeof body.id === 'string' ? body.id.trim() : undefined,
name: typeof body.name === 'string' ? body.name.trim() : undefined,
codexCommand:
typeof body.codexCommand === 'string'
? body.codexCommand.trim()
: undefined,
codexSandbox:
typeof body.codexSandbox === 'string'
? body.codexSandbox.trim()
: undefined,
codexWorkdir:
typeof body.codexWorkdir === 'string'
? body.codexWorkdir.trim()
: undefined,
stateDir:
typeof body.stateDir === 'string' ? body.stateDir.trim() : undefined,
active: body.active === true,
}
}

export const Route = createFileRoute('/api/workspaces')({
server: {
handlers: {
GET: () => {
return json(listCodexWorkspaces())
},
POST: async ({ request }) => {
try {
const body = (await request.json().catch(() => ({}))) as Record<

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Do not ignore JSON parse errors in POST; malformed payloads currently create a default workspace instead of failing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/codex-claw/src/routes/api/workspaces.ts, line 41:

<comment>Do not ignore JSON parse errors in POST; malformed payloads currently create a default workspace instead of failing.</comment>

<file context>
@@ -0,0 +1,98 @@
+      },
+      POST: async ({ request }) => {
+        try {
+          const body = (await request.json().catch(() => ({}))) as Record<
+            string,
+            unknown
</file context>

string,
unknown
>
createCodexWorkspace(workspaceInput(body))
return json(listCodexWorkspaces())
} catch (err) {
return json(
{
ok: false,
error: err instanceof Error ? err.message : String(err),
},
{ status: 400 },
)
}
},
PATCH: async ({ request }) => {
try {
const body = (await request.json().catch(() => ({}))) as Record<
string,
unknown
>
const input = workspaceInput(body)
if (body.action === 'activate' && input.id) {
activateCodexWorkspace(input.id)
return json(listCodexWorkspaces())
}
patchCodexWorkspace(input)
return json(listCodexWorkspaces())
} catch (err) {
return json(
{
ok: false,
error: err instanceof Error ? err.message : String(err),
},
{ status: 400 },
)
}
},
DELETE: ({ request }) => {
try {
const url = new URL(request.url)
const id = url.searchParams.get('id') ?? ''
deleteCodexWorkspace(id)
return json(listCodexWorkspaces())
} catch (err) {
return json(
{
ok: false,
error: err instanceof Error ? err.message : String(err),
},
{ status: 400 },
)
}
},
},
},
})
56 changes: 56 additions & 0 deletions apps/codex-claw/src/screens/chat/chat-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type {
HistoryResponse,
SessionListResponse,
SessionMeta,
WorkspaceListResponse,
WorkspaceSummary,
} from './types'

type GatewayStatusResponse = {
Expand All @@ -14,6 +16,7 @@ type GatewayStatusResponse = {

export const chatQueryKeys = {
sessions: ['chat', 'sessions'] as const,
workspaces: ['chat', 'workspaces'] as const,
history: function history(friendlyId: string, sessionKey: string) {
return ['chat', 'history', friendlyId, sessionKey] as const
},
Expand Down Expand Up @@ -56,6 +59,59 @@ export async function fetchGatewayStatus(): Promise<GatewayStatusResponse> {
}
}

export async function fetchWorkspaces(): Promise<WorkspaceListResponse> {
const res = await fetch('/api/workspaces')
if (!res.ok) throw new Error(await readError(res))
return (await res.json()) as WorkspaceListResponse
}

export async function createWorkspace(
workspace: Partial<WorkspaceSummary> & { active?: boolean },
): Promise<WorkspaceListResponse> {
const res = await fetch('/api/workspaces', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(workspace),
})
if (!res.ok) throw new Error(await readError(res))
return (await res.json()) as WorkspaceListResponse
}

export async function updateWorkspace(
workspace: Partial<WorkspaceSummary> & { id: string; active?: boolean },
): Promise<WorkspaceListResponse> {
const res = await fetch('/api/workspaces', {
method: 'PATCH',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(workspace),
})
if (!res.ok) throw new Error(await readError(res))
return (await res.json()) as WorkspaceListResponse
}

export async function activateWorkspace(
id: string,
): Promise<WorkspaceListResponse> {
const res = await fetch('/api/workspaces', {
method: 'PATCH',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ id, action: 'activate' }),
})
if (!res.ok) throw new Error(await readError(res))
return (await res.json()) as WorkspaceListResponse
}

export async function deleteWorkspace(
id: string,
): Promise<WorkspaceListResponse> {
const query = new URLSearchParams({ id })
const res = await fetch(`/api/workspaces?${query.toString()}`, {
method: 'DELETE',
})
if (!res.ok) throw new Error(await readError(res))
return (await res.json()) as WorkspaceListResponse
}

export function updateHistoryMessages(
queryClient: QueryClient,
friendlyId: string,
Expand Down
20 changes: 20 additions & 0 deletions apps/codex-claw/src/screens/chat/components/chat-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,20 @@ function ChatSidebarComponent({
pathsLoading,
pathsError,
paths,
workspacesLoading,
workspacesError,
workspaceActionPending,
workspaceActionError,
workspaceData,
handleOpenSettings,
closeSettings,
copySessionsDir,
copyStorePath,
copyFixCommand,
createWorkspace,
updateWorkspace,
activateWorkspace,
deleteWorkspace,
} = useChatSettings()
const { deleteSession } = useDeleteSession()
const { renameSession } = useRenameSession()
Expand Down Expand Up @@ -352,9 +362,19 @@ function ChatSidebarComponent({
pathsLoading={pathsLoading}
pathsError={pathsError}
paths={paths}
workspacesLoading={workspacesLoading}
workspacesError={workspacesError}
workspaceActionPending={workspaceActionPending}
workspaceActionError={workspaceActionError}
workspaceData={workspaceData}
onClose={closeSettings}
onCopySessionsDir={copySessionsDir}
onCopyStorePath={copyStorePath}
onCopyFixCommand={copyFixCommand}
onCreateWorkspace={createWorkspace}
onUpdateWorkspace={updateWorkspace}
onActivateWorkspace={activateWorkspace}
onDeleteWorkspace={deleteWorkspace}
/>

<SessionRenameDialog
Expand Down
Loading
Loading