Merge remote-tracking branch 'origin/master' into codex/migrate-to-oxlint

This commit is contained in:
Turtle
2026-07-29 14:32:21 +08:00
308 changed files with 10890 additions and 1735 deletions
+8 -1
View File
@@ -14,7 +14,8 @@ import type { Wire } from '../api/rpc.schema.ts'
import { rpcReceiptSchema, serverRequestSchema, serverResponseSchema } from '../api/rpc.schema.ts'
import { hostFrameSchema, muxFrameSchema } from '../api/events.schema.ts'
import {
hostDescribeValueSchema, hostOpenPathValueSchema, hostPickDirectoryValueSchema,
hostCreateDirectoryValueSchema, hostDescribeValueSchema,
hostListDirectoryValueSchema, hostOpenPathValueSchema, hostPickDirectoryValueSchema,
} from '../api/host.schema.ts'
import {
sessionCancelValueSchema,
@@ -71,6 +72,8 @@ export interface IApiClient {
host: {
describe(payload: RequestPayload<'host.describe'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'host.describe'>>>
pickDirectory(payload: RequestPayload<'host.pickDirectory'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'host.pickDirectory'>>>
listDirectory(payload: RequestPayload<'host.listDirectory'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'host.listDirectory'>>>
createDirectory(payload: RequestPayload<'host.createDirectory'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'host.createDirectory'>>>
openPath(payload: RequestPayload<'host.openPath'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'host.openPath'>>>
}
workspace: {
@@ -117,6 +120,8 @@ const UNARY_VALUE_SCHEMAS: { [K in keyof RpcMethodMap]: z.ZodType<Wire<ResponseV
'session.cancel': sessionCancelValueSchema,
'host.describe': hostDescribeValueSchema,
'host.pickDirectory': hostPickDirectoryValueSchema,
'host.listDirectory': hostListDirectoryValueSchema,
'host.createDirectory': hostCreateDirectoryValueSchema,
'host.openPath': hostOpenPathValueSchema,
'workspace.list': workspaceListValueSchema,
'workspace.create': workspaceCreateValueSchema,
@@ -331,6 +336,8 @@ export abstract class AbstractApiClient implements IApiClient {
// A native system dialog is user-paced and may legitimately stay open
// longer than the normal unary deadline. Caller/connection aborts remain.
pickDirectory: (payload, signal) => this.callUnary('host.pickDirectory', payload, signal, false),
listDirectory: (payload, signal) => this.callUnary('host.listDirectory', payload, signal),
createDirectory: (payload, signal) => this.callUnary('host.createDirectory', payload, signal),
openPath: (payload, signal) => this.callUnary('host.openPath', payload, signal),
}
+18 -3
View File
@@ -2,8 +2,8 @@
* Server side of the fetch carrier: maps an ApiProxy onto a pure
* WHATWG Request->Response function. Two-level parse: full form (type/rpcId/method +
* path==method) -> payload dispatched per method. HTTP status expresses only the carrier
* (404 unknown path / 400 non-JSON body / 500 handler crash); business errors are always
* 200 + ServerResponse.
* (404 unknown path / 415 non-JSON media type / 400 non-JSON body / 500 handler crash);
* business errors are always 200 + ServerResponse.
*/
import { randomUUID } from 'node:crypto'
@@ -24,7 +24,9 @@ import {
sessionSelectModelRequestSchema,
} from '../api/sessions.schema.ts'
import {
hostDescribeRequestSchema, hostOpenPathRequestSchema, hostPickDirectoryRequestSchema,
hostCreateDirectoryRequestSchema, hostDescribeRequestSchema,
hostListDirectoryRequestSchema, hostOpenPathRequestSchema,
hostPickDirectoryRequestSchema,
} from '../api/host.schema.ts'
import {
workspaceCreateRequestSchema,
@@ -70,6 +72,8 @@ const UNARY_ROUTES: UnaryRoutes = {
'session.cancel': { schema: sessionCancelRequestSchema, invoke: (api, r) => api.sessions.cancel(r) },
'host.describe': { schema: hostDescribeRequestSchema, invoke: (api, r) => api.host.describe(r) },
'host.pickDirectory': { schema: hostPickDirectoryRequestSchema, invoke: (api, r, signal) => api.host.pickDirectory(r, signal) },
'host.listDirectory': { schema: hostListDirectoryRequestSchema, invoke: (api, r, signal) => api.host.listDirectory(r, signal) },
'host.createDirectory': { schema: hostCreateDirectoryRequestSchema, invoke: (api, r) => api.host.createDirectory(r) },
'host.openPath': { schema: hostOpenPathRequestSchema, invoke: (api, r, signal) => api.host.openPath(r, signal) },
'workspace.list': { schema: workspaceListRequestSchema, invoke: (api, r) => api.workspace.list(r) },
'workspace.create': { schema: workspaceCreateRequestSchema, invoke: (api, r) => api.workspace.create(r) },
@@ -205,6 +209,17 @@ export function toFetchHandler(api: ApiProxy): { fetch: typeof fetch } {
return new Response('not found', { status: 404 })
}
// Cross-site write fence: browsers send "simple" POSTs (text/plain,
// form encodings) without a CORS preflight, so a malicious page could
// otherwise execute side-effectful RPCs blind — the response stays
// unreadable cross-origin, but session.prompt would still run. Only the
// JSON media type is accepted; anything else is forced into a preflight
// this server never answers. 415 = carrier layer, like the 400 below.
const mediaType = req.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase()
if (mediaType !== 'application/json') {
return new Response('content type must be application/json', { status: 415 })
}
let body: unknown
try {
body = await req.json()