@@ -6,6 +6,8 @@ import type { WebuiOperation, WebuiOperationValidation } from "./operation-contr
66import { invalidBody } from "./operation-contract.js" ;
77import type {
88 WebuiCanvasDocument ,
9+ WebuiWorkspaceArchiveListing ,
10+ WebuiWorkspaceArchiveExtractResult ,
911 WebuiWorkspaceDirectoryListing ,
1012 WebuiWorkspaceEnvironment ,
1113 WebuiWorkspaceFile ,
@@ -21,7 +23,7 @@ import {
2123 validateObjectBody ,
2224 validateOptionalObjectBody ,
2325} from "./common.js" ;
24- import { LIST_WORKSPACE_FILE_TREE_OPERATION_NAME , BROWSE_WORKSPACE_DIRS_OPERATION_NAME , READ_WORKSPACE_FILE_OPERATION_NAME , GET_WORKSPACE_ENVIRONMENT_OPERATION_NAME , MUTATE_WORKSPACE_GIT_OPERATION_NAME , GET_WORKSPACE_REVIEW_SUMMARY_OPERATION_NAME , LIST_WORKSPACE_REVIEW_FILE_DIFFS_OPERATION_NAME , GET_WORKSPACE_REVIEW_FILE_CONTENT_OPERATION_NAME , SEARCH_WORKSPACE_REVIEW_DIFFS_OPERATION_NAME , READ_CANVAS_OPERATION_NAME , APPLY_CANVAS_OPERATION_NAME , CREATE_TERMINAL_OPERATION_NAME , LIST_TERMINALS_OPERATION_NAME , WRITE_TERMINAL_OPERATION_NAME , RESIZE_TERMINAL_OPERATION_NAME , DISPOSE_TERMINAL_OPERATION_NAME , WATCH_TERMINAL_OPERATION_NAME } from "./names.js" ;
26+ import { LIST_WORKSPACE_FILE_TREE_OPERATION_NAME , BROWSE_WORKSPACE_DIRS_OPERATION_NAME , READ_WORKSPACE_FILE_OPERATION_NAME , GET_WORKSPACE_ENVIRONMENT_OPERATION_NAME , MUTATE_WORKSPACE_GIT_OPERATION_NAME , GET_WORKSPACE_REVIEW_SUMMARY_OPERATION_NAME , LIST_WORKSPACE_REVIEW_FILE_DIFFS_OPERATION_NAME , GET_WORKSPACE_REVIEW_FILE_CONTENT_OPERATION_NAME , SEARCH_WORKSPACE_REVIEW_DIFFS_OPERATION_NAME , READ_CANVAS_OPERATION_NAME , APPLY_CANVAS_OPERATION_NAME , READ_WORKSPACE_ARCHIVE_OPERATION_NAME , EXTRACT_WORKSPACE_ARCHIVE_OPERATION_NAME , CREATE_TERMINAL_OPERATION_NAME , LIST_TERMINALS_OPERATION_NAME , WRITE_TERMINAL_OPERATION_NAME , RESIZE_TERMINAL_OPERATION_NAME , DISPOSE_TERMINAL_OPERATION_NAME , WATCH_TERMINAL_OPERATION_NAME } from "./names.js" ;
2527
2628/** Upper bound on one directory listing. A home directory can hold
2729 * thousands of folders and the picker only needs a browsable page. */
@@ -49,6 +51,17 @@ interface ApplyCanvasBody {
4951 readonly sessionId : string ;
5052 readonly operation : Record < string , unknown > ;
5153}
54+ interface ReadWorkspaceArchiveBody {
55+ readonly workspaceDir : string ;
56+ readonly path : string ;
57+ readonly prefix ?: string ;
58+ }
59+ interface ExtractWorkspaceArchiveBody {
60+ readonly workspaceDir : string ;
61+ readonly path : string ;
62+ readonly destination : string ;
63+ readonly prefix ?: string ;
64+ }
5265
5366export const listWorkspaceFileTreeOperation : WebuiOperation < ListWorkspaceFileTreeBody , readonly WebuiWorkspaceFile [ ] > = {
5467 name : LIST_WORKSPACE_FILE_TREE_OPERATION_NAME ,
@@ -234,8 +247,56 @@ export const applyCanvasOperation: WebuiOperation<ApplyCanvasBody, { readonly op
234247 } ;
235248 } ,
236249} ;
237- export const createTerminalOperation : WebuiOperation < Record < string , unknown > > = { name : CREATE_TERMINAL_OPERATION_NAME , validate : ( body ) => validateObjectBody ( CREATE_TERMINAL_OPERATION_NAME , body ) } ;
238- export const listTerminalsOperation : WebuiOperation < Record < string , never > > = { name : LIST_TERMINALS_OPERATION_NAME , validate : ( body ) => body && typeof body === "object" && ! Array . isArray ( body ) && Object . keys ( body ) . length === 0 ? { ok : true , body : { } } : { ok : false , code : WebuiErrorCode . invalidBody , message : "listTerminals body must be an empty object" } } ;
250+ export const readWorkspaceArchiveOperation : WebuiOperation < ReadWorkspaceArchiveBody , WebuiWorkspaceArchiveListing > = {
251+ name : READ_WORKSPACE_ARCHIVE_OPERATION_NAME ,
252+ validate : ( body ) : WebuiOperationValidation < ReadWorkspaceArchiveBody > => {
253+ const result = validateObjectBody ( READ_WORKSPACE_ARCHIVE_OPERATION_NAME , body ) ;
254+ if ( ! result . ok ) return result ;
255+ if ( typeof result . body . workspaceDir !== "string" || ! result . body . workspaceDir . trim ( ) )
256+ return { ok : false , code : WebuiErrorCode . invalidBody , message : "workspaceDir is required" } ;
257+ if ( typeof result . body . path !== "string" || ! result . body . path . trim ( ) )
258+ return { ok : false , code : WebuiErrorCode . invalidBody , message : "path is required" } ;
259+ if ( result . body . prefix !== undefined && ( typeof result . body . prefix !== "string" || ! result . body . prefix . trim ( ) ) )
260+ return { ok : false , code : WebuiErrorCode . invalidBody , message : "prefix must be a non-empty string when present" } ;
261+ return {
262+ ok : true ,
263+ body : {
264+ workspaceDir : result . body . workspaceDir ,
265+ path : result . body . path ,
266+ ...( typeof result . body . prefix === "string" ? { prefix : result . body . prefix } : { } ) ,
267+ } ,
268+ } ;
269+ } ,
270+ } ;
271+ export const extractWorkspaceArchiveOperation : WebuiOperation < ExtractWorkspaceArchiveBody , WebuiWorkspaceArchiveExtractResult > = {
272+ name : EXTRACT_WORKSPACE_ARCHIVE_OPERATION_NAME ,
273+ validate : ( body ) : WebuiOperationValidation < ExtractWorkspaceArchiveBody > => {
274+ const result = validateObjectBody ( EXTRACT_WORKSPACE_ARCHIVE_OPERATION_NAME , body ) ;
275+ if ( ! result . ok ) return result ;
276+ if ( typeof result . body . workspaceDir !== "string" || ! result . body . workspaceDir . trim ( ) )
277+ return { ok : false , code : WebuiErrorCode . invalidBody , message : "workspaceDir is required" } ;
278+ if ( typeof result . body . path !== "string" || ! result . body . path . trim ( ) )
279+ return { ok : false , code : WebuiErrorCode . invalidBody , message : "path is required" } ;
280+ // Rejected here, before the runtime sees it: a browser cannot name an
281+ // absolute path, so a destination the client supplied verbatim would be
282+ // the one unvalidated path in the chain. The runtime re-validates
283+ // anyway — this is the first gate, not the only one.
284+ if ( typeof result . body . destination !== "string" || ! result . body . destination . trim ( ) )
285+ return { ok : false , code : WebuiErrorCode . invalidBody , message : "destination is required" } ;
286+ if ( result . body . prefix !== undefined && ( typeof result . body . prefix !== "string" || ! result . body . prefix . trim ( ) ) )
287+ return { ok : false , code : WebuiErrorCode . invalidBody , message : "prefix must be a non-empty string when present" } ;
288+ return {
289+ ok : true ,
290+ body : {
291+ workspaceDir : result . body . workspaceDir ,
292+ path : result . body . path ,
293+ destination : result . body . destination ,
294+ ...( typeof result . body . prefix === "string" ? { prefix : result . body . prefix } : { } ) ,
295+ } ,
296+ } ;
297+ } ,
298+ } ;
299+ export const createTerminalOperation : WebuiOperation < Record < string , unknown > > = { name : CREATE_TERMINAL_OPERATION_NAME , validate : ( body ) => validateObjectBody ( CREATE_TERMINAL_OPERATION_NAME , body ) } ; export const listTerminalsOperation : WebuiOperation < Record < string , never > > = { name : LIST_TERMINALS_OPERATION_NAME , validate : ( body ) => body && typeof body === "object" && ! Array . isArray ( body ) && Object . keys ( body ) . length === 0 ? { ok : true , body : { } } : { ok : false , code : WebuiErrorCode . invalidBody , message : "listTerminals body must be an empty object" } } ;
239300export const writeTerminalOperation : WebuiOperation < Record < string , unknown > > = { name : WRITE_TERMINAL_OPERATION_NAME , validate : ( body ) => validateObjectBody ( WRITE_TERMINAL_OPERATION_NAME , body ) } ;
240301export const resizeTerminalOperation : WebuiOperation < Record < string , unknown > > = { name : RESIZE_TERMINAL_OPERATION_NAME , validate : ( body ) => validateObjectBody ( RESIZE_TERMINAL_OPERATION_NAME , body ) } ;
241302export const disposeTerminalOperation : WebuiOperation < Record < string , unknown > > = { name : DISPOSE_TERMINAL_OPERATION_NAME , validate : ( body ) => validateObjectBody ( DISPOSE_TERMINAL_OPERATION_NAME , body ) } ;
0 commit comments