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 @@ -14,6 +14,7 @@ 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 ApiTasksRouteImport } from './routes/api/tasks'
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 @@ -49,6 +50,11 @@ const ApiWorkspacesRoute = ApiWorkspacesRouteImport.update({
path: '/api/workspaces',
getParentRoute: () => rootRouteImport,
} as any)
const ApiTasksRoute = ApiTasksRouteImport.update({
id: '/api/tasks',
path: '/api/tasks',
getParentRoute: () => rootRouteImport,
} as any)
const ApiStreamRoute = ApiStreamRouteImport.update({
id: '/api/stream',
path: '/api/stream',
Expand Down Expand Up @@ -108,6 +114,7 @@ export interface FileRoutesByFullPath {
'/api/send': typeof ApiSendRoute
'/api/sessions': typeof ApiSessionsRoute
'/api/stream': typeof ApiStreamRoute
'/api/tasks': typeof ApiTasksRoute
'/api/workspaces': typeof ApiWorkspacesRoute
'/chat/$sessionKey': typeof ChatSessionKeyRoute
}
Expand All @@ -124,6 +131,7 @@ export interface FileRoutesByTo {
'/api/send': typeof ApiSendRoute
'/api/sessions': typeof ApiSessionsRoute
'/api/stream': typeof ApiStreamRoute
'/api/tasks': typeof ApiTasksRoute
'/api/workspaces': typeof ApiWorkspacesRoute
'/chat/$sessionKey': typeof ChatSessionKeyRoute
}
Expand All @@ -141,6 +149,7 @@ export interface FileRoutesById {
'/api/send': typeof ApiSendRoute
'/api/sessions': typeof ApiSessionsRoute
'/api/stream': typeof ApiStreamRoute
'/api/tasks': typeof ApiTasksRoute
'/api/workspaces': typeof ApiWorkspacesRoute
'/chat/$sessionKey': typeof ChatSessionKeyRoute
}
Expand All @@ -159,6 +168,7 @@ export interface FileRouteTypes {
| '/api/send'
| '/api/sessions'
| '/api/stream'
| '/api/tasks'
| '/api/workspaces'
| '/chat/$sessionKey'
fileRoutesByTo: FileRoutesByTo
Expand All @@ -175,6 +185,7 @@ export interface FileRouteTypes {
| '/api/send'
| '/api/sessions'
| '/api/stream'
| '/api/tasks'
| '/api/workspaces'
| '/chat/$sessionKey'
id:
Expand All @@ -191,6 +202,7 @@ export interface FileRouteTypes {
| '/api/send'
| '/api/sessions'
| '/api/stream'
| '/api/tasks'
| '/api/workspaces'
| '/chat/$sessionKey'
fileRoutesById: FileRoutesById
Expand All @@ -208,6 +220,7 @@ export interface RootRouteChildren {
ApiSendRoute: typeof ApiSendRoute
ApiSessionsRoute: typeof ApiSessionsRoute
ApiStreamRoute: typeof ApiStreamRoute
ApiTasksRoute: typeof ApiTasksRoute
ApiWorkspacesRoute: typeof ApiWorkspacesRoute
ChatSessionKeyRoute: typeof ChatSessionKeyRoute
}
Expand Down Expand Up @@ -249,6 +262,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiWorkspacesRouteImport
parentRoute: typeof rootRouteImport
}
'/api/tasks': {
id: '/api/tasks'
path: '/api/tasks'
fullPath: '/api/tasks'
preLoaderRoute: typeof ApiTasksRouteImport
parentRoute: typeof rootRouteImport
}
'/api/stream': {
id: '/api/stream'
path: '/api/stream'
Expand Down Expand Up @@ -328,6 +348,7 @@ const rootRouteChildren: RootRouteChildren = {
ApiSendRoute: ApiSendRoute,
ApiSessionsRoute: ApiSessionsRoute,
ApiStreamRoute: ApiStreamRoute,
ApiTasksRoute: ApiTasksRoute,
ApiWorkspacesRoute: ApiWorkspacesRoute,
ChatSessionKeyRoute: ChatSessionKeyRoute,
}
Expand Down
52 changes: 52 additions & 0 deletions apps/codex-claw/src/routes/api/tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'

import {
cancelCodexTask,
listCodexTasks,
retryCodexTask,
} from '../../server/codex-cli'

export const Route = createFileRoute('/api/tasks')({
server: {
handlers: {
GET: () => {
try {
return json(listCodexTasks())
} catch (err) {
return json(
{
ok: false,
error: err instanceof Error ? err.message : String(err),
},
{ status: 500 },
)
}
},
POST: async ({ request }) => {
try {
const body = (await request.json().catch(() => ({}))) as Record<
string,
unknown
>
const action = typeof body.action === 'string' ? body.action : ''
const id = typeof body.id === 'string' ? body.id.trim() : ''
if (action === 'cancel') return json(cancelCodexTask(id))
if (action === 'retry') return json(retryCodexTask(id))
return json(
{ ok: false, error: 'unsupported action' },
{ status: 400 },
)
} catch (err) {
return json(
{
ok: false,
error: err instanceof Error ? err.message : String(err),
},
{ status: 400 },
)
}
},
},
},
})
28 changes: 28 additions & 0 deletions apps/codex-claw/src/screens/chat/chat-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
RepoContextSelection,
SessionListResponse,
SessionMeta,
TaskListResponse,
WorkspaceListResponse,
WorkspaceSummary,
} from './types'
Expand All @@ -21,6 +22,7 @@ type GatewayStatusResponse = {
export const chatQueryKeys = {
sessions: ['chat', 'sessions'] as const,
workspaces: ['chat', 'workspaces'] as const,
tasks: ['chat', 'tasks'] as const,
history: function history(friendlyId: string, sessionKey: string) {
return ['chat', 'history', friendlyId, sessionKey] as const
},
Expand Down Expand Up @@ -141,6 +143,32 @@ export async function fetchMcpHealth(): Promise<McpHealthPayload> {
return (await res.json()) as McpHealthPayload
}

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

export async function cancelCodexTask(id: string): Promise<TaskListResponse> {
const res = await fetch('/api/tasks', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ action: 'cancel', id }),
})
if (!res.ok) throw new Error(await readError(res))
return fetchCodexTasks()
}

export async function retryCodexTask(id: string): Promise<TaskListResponse> {
const res = await fetch('/api/tasks', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ action: 'retry', id }),
})
if (!res.ok) throw new Error(await readError(res))
return fetchCodexTasks()
}

export async function stageGitReviewFiles(
paths: Array<string>,
): Promise<GitReviewPayload> {
Expand Down
8 changes: 8 additions & 0 deletions apps/codex-claw/src/screens/chat/chat-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ChatMessageList } from './components/chat-message-list'
import { ChatComposer } from './components/chat-composer'
import { GitReviewPanel } from './components/git-review-panel'
import { McpHealthPanel } from './components/mcp-health-panel'
import { TaskQueuePanel } from './components/task-queue-panel'
import { GatewayStatusMessage } from './components/gateway-status-message'
import {
hasPendingGeneration,
Expand Down Expand Up @@ -73,6 +74,7 @@ export function ChatScreen({
const [isRedirecting, setIsRedirecting] = useState(false)
const [gitReviewOpen, setGitReviewOpen] = useState(false)
const [mcpHealthOpen, setMcpHealthOpen] = useState(false)
const [taskQueueOpen, setTaskQueueOpen] = useState(false)
const { headerRef, composerRef, mainRef, pinGroupMinHeight, headerHeight } =
useChatMeasurements()
const [waitingForResponse, setWaitingForResponse] = useState(
Expand Down Expand Up @@ -633,6 +635,12 @@ export function ChatScreen({
onToggleGitReview={() => setGitReviewOpen((current) => !current)}
mcpHealthOpen={mcpHealthOpen}
onToggleMcpHealth={() => setMcpHealthOpen((current) => !current)}
taskQueueOpen={taskQueueOpen}
onToggleTaskQueue={() => setTaskQueueOpen((current) => !current)}
/>
<TaskQueuePanel
open={taskQueueOpen}
onClose={() => setTaskQueueOpen(false)}
/>
<McpHealthPanel
open={mcpHealthOpen}
Expand Down
20 changes: 20 additions & 0 deletions apps/codex-claw/src/screens/chat/components/chat-header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { memo } from 'react'
import { HugeiconsIcon } from '@hugeicons/react'
import {
CheckmarkCircle01Icon,
GitBranchIcon,
Menu01Icon,
Settings01Icon,
Expand All @@ -25,6 +26,8 @@ type ChatHeaderProps = {
onToggleGitReview?: () => void
mcpHealthOpen?: boolean
onToggleMcpHealth?: () => void
taskQueueOpen?: boolean
onToggleTaskQueue?: () => void
}

function ChatHeaderComponent({
Expand All @@ -41,6 +44,8 @@ function ChatHeaderComponent({
onToggleGitReview,
mcpHealthOpen = false,
onToggleMcpHealth,
taskQueueOpen = false,
onToggleTaskQueue,
}: ChatHeaderProps) {
return (
<div
Expand Down Expand Up @@ -84,6 +89,21 @@ function ChatHeaderComponent({
<HugeiconsIcon icon={Settings01Icon} size={20} strokeWidth={1.5} />
</Button>
) : null}
{onToggleTaskQueue ? (
<Button
size="icon-sm"
variant="ghost"
onClick={onToggleTaskQueue}
className={taskQueueOpen ? 'bg-primary-100' : undefined}
aria-label="Open task queue"
>
<HugeiconsIcon
icon={CheckmarkCircle01Icon}
size={20}
strokeWidth={1.5}
/>
</Button>
) : null}
{showExport ? (
<ExportMenu onExport={onExport} disabled={exportDisabled} />
) : null}
Expand Down
Loading
Loading