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 @@ -17,6 +17,7 @@ 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'
import { Route as ApiRepoContextRouteImport } from './routes/api/repo-context'
import { Route as ApiPingRouteImport } from './routes/api/ping'
import { Route as ApiPathsRouteImport } from './routes/api/paths'
import { Route as ApiHistoryRouteImport } from './routes/api/history'
Expand Down Expand Up @@ -61,6 +62,11 @@ const ApiSendRoute = ApiSendRouteImport.update({
path: '/api/send',
getParentRoute: () => rootRouteImport,
} as any)
const ApiRepoContextRoute = ApiRepoContextRouteImport.update({
id: '/api/repo-context',
path: '/api/repo-context',
getParentRoute: () => rootRouteImport,
} as any)
const ApiPingRoute = ApiPingRouteImport.update({
id: '/api/ping',
path: '/api/ping',
Expand All @@ -84,6 +90,7 @@ export interface FileRoutesByFullPath {
'/api/history': typeof ApiHistoryRoute
'/api/paths': typeof ApiPathsRoute
'/api/ping': typeof ApiPingRoute
'/api/repo-context': typeof ApiRepoContextRoute
'/api/send': typeof ApiSendRoute
'/api/sessions': typeof ApiSessionsRoute
'/api/stream': typeof ApiStreamRoute
Expand All @@ -97,6 +104,7 @@ export interface FileRoutesByTo {
'/api/history': typeof ApiHistoryRoute
'/api/paths': typeof ApiPathsRoute
'/api/ping': typeof ApiPingRoute
'/api/repo-context': typeof ApiRepoContextRoute
'/api/send': typeof ApiSendRoute
'/api/sessions': typeof ApiSessionsRoute
'/api/stream': typeof ApiStreamRoute
Expand All @@ -111,6 +119,7 @@ export interface FileRoutesById {
'/api/history': typeof ApiHistoryRoute
'/api/paths': typeof ApiPathsRoute
'/api/ping': typeof ApiPingRoute
'/api/repo-context': typeof ApiRepoContextRoute
'/api/send': typeof ApiSendRoute
'/api/sessions': typeof ApiSessionsRoute
'/api/stream': typeof ApiStreamRoute
Expand All @@ -126,6 +135,7 @@ export interface FileRouteTypes {
| '/api/history'
| '/api/paths'
| '/api/ping'
| '/api/repo-context'
| '/api/send'
| '/api/sessions'
| '/api/stream'
Expand All @@ -139,6 +149,7 @@ export interface FileRouteTypes {
| '/api/history'
| '/api/paths'
| '/api/ping'
| '/api/repo-context'
| '/api/send'
| '/api/sessions'
| '/api/stream'
Expand All @@ -152,6 +163,7 @@ export interface FileRouteTypes {
| '/api/history'
| '/api/paths'
| '/api/ping'
| '/api/repo-context'
| '/api/send'
| '/api/sessions'
| '/api/stream'
Expand All @@ -166,6 +178,7 @@ export interface RootRouteChildren {
ApiHistoryRoute: typeof ApiHistoryRoute
ApiPathsRoute: typeof ApiPathsRoute
ApiPingRoute: typeof ApiPingRoute
ApiRepoContextRoute: typeof ApiRepoContextRoute
ApiSendRoute: typeof ApiSendRoute
ApiSessionsRoute: typeof ApiSessionsRoute
ApiStreamRoute: typeof ApiStreamRoute
Expand Down Expand Up @@ -231,6 +244,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiSendRouteImport
parentRoute: typeof rootRouteImport
}
'/api/repo-context': {
id: '/api/repo-context'
path: '/api/repo-context'
fullPath: '/api/repo-context'
preLoaderRoute: typeof ApiRepoContextRouteImport
parentRoute: typeof rootRouteImport
}
'/api/ping': {
id: '/api/ping'
path: '/api/ping'
Expand Down Expand Up @@ -262,6 +282,7 @@ const rootRouteChildren: RootRouteChildren = {
ApiHistoryRoute: ApiHistoryRoute,
ApiPathsRoute: ApiPathsRoute,
ApiPingRoute: ApiPingRoute,
ApiRepoContextRoute: ApiRepoContextRoute,
ApiSendRoute: ApiSendRoute,
ApiSessionsRoute: ApiSessionsRoute,
ApiStreamRoute: ApiStreamRoute,
Expand Down
33 changes: 33 additions & 0 deletions apps/codex-claw/src/routes/api/repo-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'
import { getRepoContextPayload } from '../../server/repo-context'
import type { RepoContextSelection } from '../../server/repo-context'

function parseSelections(url: URL): Array<RepoContextSelection> {
return url.searchParams
.getAll('selected')
.map((path) => path.trim())
.filter(Boolean)
.slice(0, 50)
.map((path) => ({ path }))
}

export const Route = createFileRoute('/api/repo-context')({
server: {
handlers: {
GET: ({ request }) => {
try {
const url = new URL(request.url)
return json(getRepoContextPayload(parseSelections(url)))
} catch (err) {
return json(
{
error: err instanceof Error ? err.message : String(err),
},
{ status: 500 },
)
}
},
},
},
})
40 changes: 38 additions & 2 deletions apps/codex-claw/src/routes/api/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
resolveCodexSession,
sendCodexPrompt,
} from '../../server/codex-cli'
import { buildRepositoryContextPrompt } from '../../server/repo-context'
import type { RepoContextSelection } from '../../server/repo-context'

type ParsedAttachment = {
mimeType: string
Expand All @@ -24,6 +26,28 @@ type AttachmentParseResult =
error: string
}

function parseContextSelections(rawSelections: unknown) {
if (typeof rawSelections === 'undefined') return []
if (!Array.isArray(rawSelections)) return []

const selections: Array<RepoContextSelection> = []
for (const rawSelection of rawSelections.slice(0, 50)) {
if (!rawSelection || typeof rawSelection !== 'object') continue
const selection = rawSelection as Record<string, unknown>
const selectedPath =
typeof selection.path === 'string' ? selection.path.trim() : ''
if (!selectedPath) continue
selections.push({
path: selectedPath,
type:
selection.type === 'directory' || selection.type === 'file'
? selection.type
: undefined,
})
}
return selections
}

function parseAttachments(rawAttachments: unknown): AttachmentParseResult {
if (typeof rawAttachments === 'undefined') {
return { ok: true, attachments: undefined }
Expand Down Expand Up @@ -111,8 +135,19 @@ export const Route = createFileRoute('/api/send')({
)
}
const attachments = parsedAttachments.attachments

if (!message.trim() && (!attachments || attachments.length === 0)) {
const contextSelections = parseContextSelections(
body.contextSelections,
)
const contextBlock =
contextSelections.length > 0
? buildRepositoryContextPrompt(contextSelections)
: undefined

if (
!message.trim() &&
(!attachments || attachments.length === 0) &&
!contextBlock
) {
return json(
{ ok: false, error: 'message required' },
{ status: 400 },
Expand Down Expand Up @@ -143,6 +178,7 @@ export const Route = createFileRoute('/api/send')({
message,
thinking,
attachments,
contextBlock,
idempotencyKey:
typeof body.idempotencyKey === 'string'
? body.idempotencyKey
Expand Down
15 changes: 15 additions & 0 deletions apps/codex-claw/src/screens/chat/chat-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { QueryClient } from '@tanstack/react-query'
import type {
GatewayMessage,
HistoryResponse,
RepoContextPayload,
RepoContextSelection,
SessionListResponse,
SessionMeta,
WorkspaceListResponse,
Expand Down Expand Up @@ -112,6 +114,19 @@ export async function deleteWorkspace(
return (await res.json()) as WorkspaceListResponse
}

export async function fetchRepoContext(
selections: Array<RepoContextSelection>,
): Promise<RepoContextPayload> {
const query = new URLSearchParams()
for (const selection of selections) {
if (selection.path.trim()) query.append('selected', selection.path.trim())
}
const suffix = query.toString() ? '?' + query.toString() : ''
const res = await fetch('/api/repo-context' + suffix)
if (!res.ok) throw new Error(await readError(res))
return (await res.json()) as RepoContextPayload
}

export function updateHistoryMessages(
queryClient: QueryClient,
friendlyId: string,
Expand Down
10 changes: 9 additions & 1 deletion apps/codex-claw/src/screens/chat/chat-screen-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { GatewayMessage } from './types'
import type { GatewayMessage, RepoContextSelection } from './types'
import type { AttachmentFile } from '@/components/attachment-button'
import { randomUUID } from '@/lib/utils'

Expand All @@ -11,6 +11,7 @@ type OptimisticMessagePayload = {
export function createOptimisticMessage(
body: string,
attachments?: Array<AttachmentFile>,
contextSelections?: Array<RepoContextSelection>,
): OptimisticMessagePayload {
const clientId = randomUUID()
const optimisticId = `opt-${clientId}`
Expand Down Expand Up @@ -39,6 +40,13 @@ export function createOptimisticMessage(

if (body.trim()) {
content.push({ type: 'text', text: body })
} else if (contextSelections && contextSelections.length > 0) {
content.push({
type: 'text',
text:
'Repository context: ' +
contextSelections.map((selection) => selection.path).join(', '),
})
} else if (attachments && attachments.length > 0) {
content.push({ type: 'text', text: '' })
}
Expand Down
26 changes: 22 additions & 4 deletions apps/codex-claw/src/screens/chat/chat-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { shouldRedirectToConnect } from './hooks/use-chat-error-state'
import { useChatRedirect } from './hooks/use-chat-redirect'
import type { AttachmentFile } from '@/components/attachment-button'
import type { ChatComposerHelpers } from './components/chat-composer'
import type { RepoContextSelection } from './types'
import { useExport } from '@/hooks/use-export'
import { useChatSettings } from '@/hooks/use-chat-settings'
import { cn, randomUUID } from '@/lib/utils'
Expand Down Expand Up @@ -158,7 +159,8 @@ export function ChatScreen({
!isRecentSession(activeFriendlyId) &&
sessionsQuery.isSuccess &&
!sessions.some((session) => session.friendlyId === activeFriendlyId) &&
(missingSessionError || (!historyQuery.isFetching && !historyQuery.isSuccess))
(missingSessionError ||
(!historyQuery.isFetching && !historyQuery.isSuccess))

const refreshHistory = useCallback(() => {
void historyQuery.refetch()
Expand Down Expand Up @@ -229,12 +231,14 @@ export function ChatScreen({
body: string,
skipOptimistic = false,
attachments?: Array<AttachmentFile>,
contextSelections?: Array<RepoContextSelection>,
) {
let optimisticClientId = ''
if (!skipOptimistic) {
const { clientId, optimisticMessage } = createOptimisticMessage(
body,
attachments,
contextSelections,
)
optimisticClientId = clientId
appendHistoryMessage(
Expand Down Expand Up @@ -272,6 +276,7 @@ export function ChatScreen({
thinking: settings.thinkingLevel,
idempotencyKey: randomUUID(),
attachments: attachmentsPayload,
contextSelections,
}),
})
.then(async (res) => {
Expand Down Expand Up @@ -349,13 +354,18 @@ export function ChatScreen({
const send = useCallback(
(body: string, helpers: ChatComposerHelpers) => {
const attachments = helpers.attachments
if (body.length === 0 && (!attachments || attachments.length === 0))
const contextSelections = helpers.contextSelections
if (
body.length === 0 &&
(!attachments || attachments.length === 0) &&
(!contextSelections || contextSelections.length === 0)
)
return
helpers.reset()

if (isNewChat) {
const { clientId, optimisticId, optimisticMessage } =
createOptimisticMessage(body, attachments)
createOptimisticMessage(body, attachments, contextSelections)
appendHistoryMessage(queryClient, 'new', 'new', optimisticMessage)
setPendingGeneration(true)
setSending(true)
Expand All @@ -371,6 +381,7 @@ export function ChatScreen({
message: body,
optimisticMessage,
attachments,
contextSelections,
})
if (onSessionResolved) {
onSessionResolved({ sessionKey, friendlyId })
Expand Down Expand Up @@ -404,7 +415,14 @@ export function ChatScreen({
resolvedSessionKey ||
activeSessionKey ||
activeFriendlyId
sendMessage(sessionKeyForSend, activeFriendlyId, body, false, attachments)
sendMessage(
sessionKeyForSend,
activeFriendlyId,
body,
false,
attachments,
contextSelections,
)
},
[
activeFriendlyId,
Expand Down
Loading
Loading