mirror of
https://github.com/stablyai/orca.git
synced 2026-10-06 16:02:25 +00:00
refactor(page): parse the file list with its schema instead of a sanitizer
The desktop wrapper already types and redacts the search result; the page strips unknown fields with the contract schema like readDir does, and the last shell-era sanitizer module goes. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
@@ -165,7 +165,7 @@ describe('page-safe file listing and text', () => {
|
||||
}
|
||||
})
|
||||
respond({
|
||||
files: [{ relativePath: 'docs/report.md', kind: 'text' }],
|
||||
files: [{ relativePath: 'docs/report.md', basename: 'report.md', kind: 'text' }],
|
||||
totalCount: 1,
|
||||
truncated: false,
|
||||
futureField: true
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import { sanitizeListResult } from '../../shared/mobile-web/file-list-presentation'
|
||||
import { projectMobileWebHostFileContent } from './mobile-web-host-file-content'
|
||||
import {
|
||||
MOBILE_WEB_FILE_CHUNK_MAX_BYTES,
|
||||
MobileWebFileListPayloadSchema,
|
||||
MobileWebFileListResultSchema,
|
||||
MobileWebFileOpenPayloadSchema,
|
||||
MobileWebFileReadPayloadSchema,
|
||||
MobileWebFileSearchPayloadSchema,
|
||||
@@ -47,6 +47,19 @@ const WriteResultSchema = z.union([
|
||||
z.object({ outcome: z.enum(['conflict', 'too_large']) })
|
||||
])
|
||||
|
||||
// The desktop already types and redacts the list; the page only strips fields it does not know.
|
||||
function projectListResult(
|
||||
result: unknown,
|
||||
workspaceId: string,
|
||||
limit: number
|
||||
): MobileWebFileListResult {
|
||||
const parsed = MobileWebFileListResultSchema.omit({ workspaceId: true }).strip().safeParse(result)
|
||||
if (!parsed.success || parsed.data.files.length > limit) {
|
||||
throw new MobileWebBridgeClientError('invalid_message', false)
|
||||
}
|
||||
return { ...parsed.data, workspaceId }
|
||||
}
|
||||
|
||||
export class MobileWebFileRequestClient extends MobileWebFileReadClient {
|
||||
list(
|
||||
payload: MobileWebFileListPayload,
|
||||
@@ -59,7 +72,7 @@ export class MobileWebFileRequestClient extends MobileWebFileReadClient {
|
||||
'mobileWeb.files.searchPaths',
|
||||
payload.workspaceId,
|
||||
{ query: '', limit: payload.limit },
|
||||
(result) => sanitizeListResult(result, payload.workspaceId, undefined, payload.limit),
|
||||
(result) => projectListResult(result, payload.workspaceId, payload.limit),
|
||||
options
|
||||
)
|
||||
}
|
||||
@@ -75,7 +88,7 @@ export class MobileWebFileRequestClient extends MobileWebFileReadClient {
|
||||
'mobileWeb.files.searchPaths',
|
||||
payload.workspaceId,
|
||||
{ query: payload.query, limit: payload.limit },
|
||||
(result) => sanitizeListResult(result, payload.workspaceId, undefined, payload.limit),
|
||||
(result) => projectListResult(result, payload.workspaceId, payload.limit),
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import {
|
||||
MobileWebFileEntrySchema,
|
||||
MobileWebFileListResultSchema,
|
||||
type MobileWebFileEntry,
|
||||
type MobileWebFileListResult
|
||||
} from './file-operation-contract'
|
||||
import { MobileWebBrokerError } from './bridge-operation-error'
|
||||
|
||||
export function sanitizeListResult(
|
||||
result: unknown,
|
||||
pageWorkspaceId: string,
|
||||
hostWorkspaceId: string | undefined,
|
||||
limit: number
|
||||
): MobileWebFileListResult {
|
||||
if (!isRecord(result) || result.worktree !== hostWorkspaceId || !Array.isArray(result.files)) {
|
||||
throw new MobileWebBrokerError('host_error')
|
||||
}
|
||||
const files = result.files.slice(0, limit).flatMap((value): MobileWebFileEntry[] => {
|
||||
if (!isRecord(value) || typeof value.relativePath !== 'string') {
|
||||
return []
|
||||
}
|
||||
const parsed = MobileWebFileEntrySchema.safeParse({
|
||||
relativePath: value.relativePath,
|
||||
basename: value.relativePath.split('/').at(-1)?.slice(0, 255),
|
||||
kind: value.kind === 'binary' ? 'binary' : 'text'
|
||||
})
|
||||
return parsed.success ? [parsed.data] : []
|
||||
})
|
||||
const totalCount =
|
||||
typeof result.totalCount === 'number' &&
|
||||
Number.isSafeInteger(result.totalCount) &&
|
||||
result.totalCount >= 0
|
||||
? result.totalCount
|
||||
: result.files.length
|
||||
return MobileWebFileListResultSchema.parse({
|
||||
workspaceId: pageWorkspaceId,
|
||||
files,
|
||||
totalCount,
|
||||
truncated:
|
||||
result.truncated === true || result.files.length > files.length || totalCount > files.length
|
||||
})
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
Reference in New Issue
Block a user