Repository navigation
feat: Add just-bash support for WebContainer editing - #489
Conversation
📝 WalkthroughWalkthroughReworks WebContainer internals and AI tooling: adds FS adapter, sandbox, and a WebContainer-backed AI toolkit (bash/readFile/writeFile) with renderers; updates store wiring to inject the toolkit; changes persistence handling and Vite aliases; removes redundant example tools and adjusts example UI/config. Changes
Sequence DiagramsequenceDiagram
participant Store as Store Init
participant Toolkit as createWebContainerToolkit
participant Sandbox as WebContainerSandbox
participant FsAdapter as WebContainerFsAdapter
participant AiSlice as createAiSlice
participant WebContainer as WebContainer Instance
Store->>Toolkit: createWebContainerToolkit(store)
Toolkit->>WebContainer: read webContainer from store.getState()
Toolkit->>FsAdapter: new WebContainerFsAdapter(webContainer)
Toolkit->>Sandbox: createWebContainerSandbox(getState)
Toolkit-->>Store: return { tools, toolRenderers }
Store->>AiSlice: createAiSlice({ tools: default + toolkit.tools, toolRenderers: ... })
AiSlice->>Toolkit: invoke toolkit tools (bash/readFile/writeFile)
Toolkit->>Sandbox: bash/readFile/writeFile -> sandbox methods
Sandbox->>FsAdapter: sync/read/write operations
Sandbox->>WebContainer: execute/persist/open/update/saveAllOpenFiles
WebContainer-->>Store: sync slice config / update persisted state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
packages/webcontainer/src/WebContainerFsAdapter.ts (1)
100-102: Type assertion bypasses compile-time interface verification.Using
as unknown as IFileSystemcircumvents TypeScript's structural typing. IfIFileSystemadds new required methods injust-bash, this class won't fail to compile even though it's incomplete.Consider adding
implements IFileSystemto the class declaration for compile-time guarantees:-export class WebContainerFsAdapter { +export class WebContainerFsAdapter implements IFileSystem {This would require importing
IFileSystemas a type and ensuring all methods match the expected signatures.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/WebContainerFsAdapter.ts` around lines 100 - 102, The current asFileSystem() uses a double type assertion that bypasses TypeScript checks; update the WebContainerFsAdapter class to explicitly implement the IFileSystem interface (add "implements IFileSystem" to the class declaration and import IFileSystem as a type), then adjust or remove asFileSystem() to return this as a properly-typed IFileSystem (or simply return this) and ensure all methods on WebContainerFsAdapter match the IFileSystem method signatures so the compiler can verify completeness.packages/webcontainer/src/shims/node-zlib.ts (1)
7-11: Consider clarifying the error message.The message "just-bash gzip commands remain unsupported in this environment" could be confusing since it implies just-bash's gzip support is limited, when actually the issue is that Node's synchronous zlib API isn't available in the browser. The actual just-bash gzip commands may work fine through the WebContainer.
💡 Suggested clarification
function unsupported(methodName: string): never { throw new Error( - `${methodName} is not available in browser builds. just-bash gzip commands remain unsupported in this environment.`, + `${methodName} is not available in browser builds. Use the WebContainer's native filesystem or just-bash commands instead.`, ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/shims/node-zlib.ts` around lines 7 - 11, The error thrown by the unsupported function currently implies that "just-bash gzip commands" are unsupported; update the message in unsupported(methodName: string) to clearly state that Node's synchronous zlib APIs are not available in browser builds and that the browser environment (WebContainer) may still support gzip via other means, while including the methodName and the phrase "not available in browser builds (synchronous zlib APIs unsupported)". Keep the throw in unsupported(...) and only change the error text to this clarified wording.packages/webcontainer/src/createWebContainerBashTool.tsx (2)
162-167: Props types are missingtoolCallIdexpected byToolRendererProps.Per the
ToolRendererPropsinterface inpackages/ai-core/src/types.ts, tool renderers receive atoolCallIdprop. While the current implementation works (unused props are simply ignored), adding it to the type definitions would ensure consistency with the framework's expectations.♻️ Suggested props type alignment
type WebContainerBashToolResultProps = { input: WebContainerBashToolParameters; output: WebContainerBashToolOutput | undefined; state: ToolResultState; + toolCallId: string; errorText?: string; };Similarly for the other result props types.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 162 - 167, The WebContainerBashToolResultProps type is missing the toolCallId prop expected by the ToolRendererProps interface; update the type definition for WebContainerBashToolResultProps (and any other result props types) to include toolCallId: string (or the correct id type) so the component signatures match the framework's expectations, ensuring you add the field alongside input, output, state, and errorText in the type declaration.
43-50: Consider improving type safety forWebContainerToolkitResult.The
anytypes here lose the benefit of TypeScript's type checking. While the eslint-disable comment indicates this is intentional, using more specific types would improve DX for consumers.💡 Suggested type improvement
-// eslint-disable-next-line `@typescript-eslint/no-explicit-any` -export type WebContainerToolkitResult = { - bash: any; - readFile: any; - writeFile: any; - tools: Record<string, any>; - toolRenderers: Record<string, React.ComponentType<any>>; -}; +export type WebContainerToolkitResult = { + bash: ReturnType<typeof tool<typeof WebContainerBashToolParameters, WebContainerBashToolOutput>>; + readFile: ReturnType<typeof tool>; + writeFile: ReturnType<typeof tool>; + tools: { + bash: WebContainerToolkitResult['bash']; + readFile: WebContainerToolkitResult['readFile']; + writeFile: WebContainerToolkitResult['writeFile']; + }; + toolRenderers: Record<string, React.ComponentType<ToolRendererProps<unknown, unknown>>>; +};Alternatively, if complex inference is problematic, at minimum the
toolsandtoolRendererskeys could be typed as string literals:tools: Record<'bash' | 'readFile' | 'writeFile', Tool>;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 43 - 50, The WebContainerToolkitResult type uses broad any types (bash, readFile, writeFile, tools, toolRenderers) which reduces TypeScript safety; define explicit interfaces (e.g., a Tool type for tool functions/objects and a ToolRenderer type for React components) and replace the anys with those types—assign bash a callable signature (promise-returning), readFile/writeFile appropriate param/return types, tools as Record<string, Tool> (or a union of literal keys like 'bash'|'readFile'|'writeFile' -> Tool), and toolRenderers as Record<string, ToolRenderer>; remove the eslint-disable comment after typing so WebContainerToolkitResult, bash, readFile, writeFile, tools, and toolRenderers are strongly typed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/ai-core/src/AiSlice.ts`:
- Around line 694-712: The setSessionUiMessages setter swallows all errors and
logs a misleading "payload too large" message which causes callers (e.g.,
useSessionChat and chatTransport.ts) to be unaware of persistence failures;
update setSessionUiMessages to surface failures by changing its signature to
return a boolean (or accept an optional onError callback) so callers can react:
inside setSessionUiMessages (the function currently doing
JSON.parse(JSON.stringify(uiMessages))) catch the error, log the full error
object/message (not a fixed payload message), invoke the onError callback if
provided (or return false) and only return true on success; update callers
(useSessionChat and chatTransport.ts) to check the returned success value (or
pass onError) and handle retry/notification/local state accordingly.
In `@packages/webcontainer/src/WebContainerFsAdapter.ts`:
- Around line 159-172: The mirror is mutated before the WebContainer, so if
webContainer.fs operations (e.g., webContainer.fs.writeFile) fail the mirror
becomes inconsistent; update the adapter to perform the WebContainer operation
first and only update the in-memory mirror on success (or, alternatively, catch
WebContainer errors and roll back the mirror change or re-sync from
WebContainer). Apply this change for writeFile and the same pattern to
appendFile, mkdir, rm, cp, and mv; use resolveWithinRoot and
ensureParentDirectory as needed but ensure mirror.write* calls occur only after
webContainer.fs.* completes successfully (or are reverted on error).
In `@packages/webcontainer/src/WebContainerSandbox.ts`:
- Around line 44-54: writeFiles currently calls webContainer.updateFileContent
which only mutates filesTree for new files and then saveAllOpenFiles only
persists openedFiles, so newly created files never get written to instance.fs.
Fix writeFiles in WebContainerSandbox by detecting when a file path is not
already opened (use webContainer.openedFiles or a helper on
getState().webContainer) and either call webContainer.openFile(path) before
updateFileContent or bypass updateFileContent for new files and write directly
to webContainer.instance.fs.writeFile(path, content) (then still call
saveAllOpenFiles for existing opened files). Ensure you reference the existing
methods updateFileContent, saveAllOpenFiles, openFile and the instance.fs write
API on the webContainer object so new files persist.
---
Nitpick comments:
In `@packages/webcontainer/src/createWebContainerBashTool.tsx`:
- Around line 162-167: The WebContainerBashToolResultProps type is missing the
toolCallId prop expected by the ToolRendererProps interface; update the type
definition for WebContainerBashToolResultProps (and any other result props
types) to include toolCallId: string (or the correct id type) so the component
signatures match the framework's expectations, ensuring you add the field
alongside input, output, state, and errorText in the type declaration.
- Around line 43-50: The WebContainerToolkitResult type uses broad any types
(bash, readFile, writeFile, tools, toolRenderers) which reduces TypeScript
safety; define explicit interfaces (e.g., a Tool type for tool functions/objects
and a ToolRenderer type for React components) and replace the anys with those
types—assign bash a callable signature (promise-returning), readFile/writeFile
appropriate param/return types, tools as Record<string, Tool> (or a union of
literal keys like 'bash'|'readFile'|'writeFile' -> Tool), and toolRenderers as
Record<string, ToolRenderer>; remove the eslint-disable comment after typing so
WebContainerToolkitResult, bash, readFile, writeFile, tools, and toolRenderers
are strongly typed.
In `@packages/webcontainer/src/shims/node-zlib.ts`:
- Around line 7-11: The error thrown by the unsupported function currently
implies that "just-bash gzip commands" are unsupported; update the message in
unsupported(methodName: string) to clearly state that Node's synchronous zlib
APIs are not available in browser builds and that the browser environment
(WebContainer) may still support gzip via other means, while including the
methodName and the phrase "not available in browser builds (synchronous zlib
APIs unsupported)". Keep the throw in unsupported(...) and only change the error
text to this clarified wording.
In `@packages/webcontainer/src/WebContainerFsAdapter.ts`:
- Around line 100-102: The current asFileSystem() uses a double type assertion
that bypasses TypeScript checks; update the WebContainerFsAdapter class to
explicitly implement the IFileSystem interface (add "implements IFileSystem" to
the class declaration and import IFileSystem as a type), then adjust or remove
asFileSystem() to return this as a properly-typed IFileSystem (or simply return
this) and ensure all methods on WebContainerFsAdapter match the IFileSystem
method signatures so the compiler can verify completeness.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e5b07864-8c79-4f88-b221-13442f17ef85
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (23)
apps/sqlrooms-cli-ui/src/store.tsapps/sqlrooms-cli-ui/vite.config.tsexamples/ai-nextjs/next-env.d.tsexamples/app-builder/package.jsonexamples/app-builder/src/components/AssistantView.tsxexamples/app-builder/src/components/Room.tsxexamples/app-builder/src/store/store.tsexamples/app-builder/src/tools/getFileContent/GetFileContentToolResult.tsxexamples/app-builder/src/tools/getFileContent/getFileContentTool.tsexamples/app-builder/src/tools/listFiles/ListFilesToolResult.tsxexamples/app-builder/src/tools/listFiles/createListFilesTool.tsexamples/app-builder/src/tools/updateFileContent/UpdateFileContentToolResult.tsxexamples/app-builder/src/tools/updateFileContent/updateFileContentTool.tsexamples/app-builder/vite.config.tspackages/ai-core/src/AiSlice.tspackages/room-store/src/createPersistHelpers.tspackages/webcontainer/package.jsonpackages/webcontainer/src/WebContainerFsAdapter.tspackages/webcontainer/src/WebContainerSandbox.tspackages/webcontainer/src/WebContainerSlice.tspackages/webcontainer/src/createWebContainerBashTool.tsxpackages/webcontainer/src/index.tspackages/webcontainer/src/shims/node-zlib.ts
💤 Files with no reviewable changes (6)
- examples/app-builder/src/tools/listFiles/ListFilesToolResult.tsx
- examples/app-builder/src/tools/updateFileContent/UpdateFileContentToolResult.tsx
- examples/app-builder/src/tools/getFileContent/GetFileContentToolResult.tsx
- examples/app-builder/src/tools/listFiles/createListFilesTool.ts
- examples/app-builder/src/tools/updateFileContent/updateFileContentTool.ts
- examples/app-builder/src/tools/getFileContent/getFileContentTool.ts
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/webcontainer/src/WebContainerFsAdapter.ts (1)
174-193:⚠️ Potential issue | 🟠 MajorBroaden the append rollback to cover parent creation.
The
tryblock only starts after Line 185. IfwebContainer.fs.mkdir()fails there, the mirror keeps the appended bytes while the WebContainer stays unchanged. The existing resync path should start beforeensureParentDirectory()as well.🛠️ Suggested fix
async appendFile( path: string, content: FileContent, options?: {encoding?: BufferEncoding} | BufferEncoding, ): Promise<void> { const resolvedPath = this.resolveWithinRoot('/', path); await this.mirror.appendFile( resolvedPath, content, options as BufferEncoding, ); - await this.ensureParentDirectory(resolvedPath); - const updatedContents = await this.mirror.readFileBuffer(resolvedPath); try { + await this.ensureParentDirectory(resolvedPath); + const updatedContents = await this.mirror.readFileBuffer(resolvedPath); await this.webContainer.fs.writeFile(resolvedPath, updatedContents); } catch (error) { await this.syncFromWebContainer(); throw error; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/WebContainerFsAdapter.ts` around lines 174 - 193, In appendFile, broaden the try/catch to start before calling ensureParentDirectory so any failure during parent-directory creation or the subsequent write is rolled back; specifically, wrap the mirror.appendFile and ensureParentDirectory and the webContainer.fs.writeFile calls inside the same try block (referencing appendFile, mirror.appendFile, ensureParentDirectory, webContainer.fs.writeFile) and retain the catch behavior to call this.syncFromWebContainer() and rethrow the error.
🧹 Nitpick comments (4)
packages/webcontainer/src/createWebContainerBashTool.tsx (4)
52-61: Consider extracting Zod schemas forreadFileandwriteFileparameters for type consistency.The
bashtool has its parameter schema exported (WebContainerBashToolParameters) with an inferred type, butreadFileandwriteFileuse inline object types inWebContainerToolkitResultwhile the actual tool definitions use inline Zod schemas (lines 116-118, 128-131). Extracting these schemas would provide consistent typing and allow consumers to reuse the parameter types.♻️ Suggested refactor for consistent schema exports
+export const WebContainerReadFileToolParameters = z.object({ + path: z.string().describe('The path to the file to read'), +}); +export type WebContainerReadFileToolParameters = z.infer< + typeof WebContainerReadFileToolParameters +>; + +export const WebContainerWriteFileToolParameters = z.object({ + path: z.string().describe('The path where the file should be written'), + content: z.string().describe('The content to write to the file'), +}); +export type WebContainerWriteFileToolParameters = z.infer< + typeof WebContainerWriteFileToolParameters +>; + export type WebContainerToolkitResult = { bash: Tool<WebContainerBashToolParameters, WebContainerBashToolOutput>; - readFile: Tool<{path: string}, WebContainerReadFileToolOutput>; - writeFile: Tool< - {path: string; content: string}, - WebContainerWriteFileToolOutput - >; + readFile: Tool<WebContainerReadFileToolParameters, WebContainerReadFileToolOutput>; + writeFile: Tool<WebContainerWriteFileToolParameters, WebContainerWriteFileToolOutput>; tools: Record<string, Tool>; toolRenderers: Record<string, ComponentType<ToolResultRendererProps>>; };Then reference these schemas in the tool definitions at lines 116-118 and 128-131.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 52 - 61, Extract and export Zod schemas and inferred TS types for the readFile and writeFile tool parameters (e.g., create WebContainerReadFileParamsSchema and WebContainerWriteFileParamsSchema and export types like WebContainerReadFileParameters and WebContainerWriteFileParameters), replace the inline object types used in the WebContainerToolkitResult type and the readFile/writeFile tool definitions with these exported parameter types, and update any references to use the new schema variables where the tools are defined (matching how WebContainerBashToolParameters is used) to ensure consistent typing and reuse.
218-226: Unused destructured props may trigger lint warnings.The
outputandstateprops are declared in the type but not used. ThereadFilerenderer doesn't display success/error states unlike thebashrenderer (which shows exit code and error text). Consider either:
- Prefix unused props with underscore to satisfy linting
- Enhance the renderer to show loading/success/error states for consistency
♻️ Option 1: Prefix unused props
export function WebContainerReadFileToolResult({ input, + state: _state, + output: _output, }: WebContainerReadFileToolResultProps) {♻️ Option 2: Show state feedback
export function WebContainerReadFileToolResult({ input, + state, + output, }: WebContainerReadFileToolResultProps) { + const isFinished = state === 'output-available' || state === 'output-error'; + const suffix = isFinished + ? output + ? ` (${output.content.length} bytes)` + : ' (failed)' + : ''; + return ( <div className="text-foreground/50 text-xs"> - Reading file <span className="font-mono">{input.path}</span> + Reading file <span className="font-mono">{input.path}</span>{suffix} </div> ); }As per coding guidelines: "Maintain lint baseline with warnings only; zero errors is the expected state."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 218 - 226, The component WebContainerReadFileToolResult currently destructures props declared on WebContainerReadFileToolResultProps but does not use output and state, causing lint warnings; either (A) rename the unused props to _output and _state in the WebContainerReadFileToolResult signature to satisfy linting, or (B) enhance WebContainerReadFileToolResult to render state feedback consistent with the bash renderer: use state to show loading indicator, display output (file contents) on success, and render error text/exit code on failure (reference the output and state properties on WebContainerReadFileToolResultProps when implementing this).
235-242: Same unused props concern applies toWebContainerWriteFileToolResult.Similar to
WebContainerReadFileToolResult, theoutputandstateprops are unused and the renderer doesn't show operation status. The same two options (prefix unused or enhance renderer) apply here.♻️ Suggested enhancement for write file renderer
export function WebContainerWriteFileToolResult({ input, + state, }: WebContainerWriteFileToolResultProps) { + const isFinished = state === 'output-available' || state === 'output-error'; + const suffix = state === 'output-error' + ? ' (failed)' + : isFinished + ? ' (done)' + : ''; + return ( <div className="text-foreground/50 text-xs"> - Writing file <span className="font-mono">{input.path}</span> + Writing file <span className="font-mono">{input.path}</span>{suffix} </div> ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 235 - 242, The WebContainerWriteFileToolResult component declares props (output, state, input) but only uses input.path; either remove the unused props from WebContainerWriteFileToolResultProps and the component signature to avoid lint warnings, or enhance the renderer to display operation status by using the state and output props (e.g., show "Writing...", "Success" / "Error" and any message from output) — update the WebContainerWriteFileToolResult function signature and JSX accordingly to either drop output/state references or render their values alongside input.path.
114-139: Inconsistent error handling between tools—consider returning structured errors instead of throwing.The
bashtool returns errors within a structured response ({success, details, errorMessage?}), butreadFileandwriteFilethrow exceptions. While the Vercel AI SDK does handle thrown errors as 'tool-error' parts in tool execution, the inconsistency makes the API harder to use. AlignreadFileandwriteFilewith the bash tool pattern by returning structured error responses instead of throwing, which also better matches SDK best practices of returning meaningful error objects.♻️ Suggested error handling for file tools
const readFile = tool({ description: 'Read the contents of a file from the project', inputSchema: z.object({ path: z.string().describe('The path to the file to read'), }), execute: async ({path}) => { + try { const content = await getSandbox().readFile(path); return {content} satisfies WebContainerReadFileToolOutput; + } catch (error) { + throw new Error( + `Failed to read file "${path}": ${error instanceof Error ? error.message : String(error)}` + ); + } }, }); const writeFile = tool({ description: 'Write content to a file in the project. Creates the file if it does not exist.', inputSchema: z.object({ path: z.string().describe('The path where the file should be written'), content: z.string().describe('The content to write to the file'), }), execute: async ({ path, content, }): Promise<WebContainerWriteFileToolOutput> => { + try { await getSandbox().writeFiles([{path, content}]); return {success: true}; + } catch (error) { + throw new Error( + `Failed to write file "${path}": ${error instanceof Error ? error.message : String(error)}` + ); + } }, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 114 - 139, The readFile and writeFile tool execute handlers currently throw on failure; change them to mirror the bash tool's structured response pattern by catching errors from getSandbox().readFile and getSandbox().writeFiles and returning typed objects (e.g., WebContainerReadFileToolOutput and WebContainerWriteFileToolOutput) with success: false plus errorMessage and optional details instead of throwing; when successful return success: true and content (for readFile) or success: true (for writeFile), and preserve the existing inputSchema and types so callers receive consistent structured error responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/webcontainer/src/WebContainerFsAdapter.ts`:
- Around line 159-172: The adapter's writeFile method drops the encoding when
calling this.webContainer.fs.writeFile, causing divergence with
this.mirror.writeFile when non-UTF8 encodings are used; update writeFile to
forward the encoding from the options parameter (handle both options object and
BufferEncoding overload) into the call to this.webContainer.fs.writeFile so both
the webContainer and mirror receive the same encoding; locate the writeFile
function and pass the resolved encoding (derived from options or cast as
BufferEncoding) into webContainer.fs.writeFile as the third argument.
- Around line 257-260: The chmod and utimes implementations in
WebContainerFsAdapter currently only mutate this.mirror (resolveWithinRoot +
this.mirror.chmod/utimes) which loses metadata on sync; change both chmod(path:
string, mode: number) and utimes(path: string, atime: number, mtime: number) to
throw the same unsupported-operation error used by symlink() and link() (include
a clear message like "chmod/utimes not supported by WebContainer adapter")
instead of mutating the mirror, so they fail fast and avoid silent metadata
loss.
---
Duplicate comments:
In `@packages/webcontainer/src/WebContainerFsAdapter.ts`:
- Around line 174-193: In appendFile, broaden the try/catch to start before
calling ensureParentDirectory so any failure during parent-directory creation or
the subsequent write is rolled back; specifically, wrap the mirror.appendFile
and ensureParentDirectory and the webContainer.fs.writeFile calls inside the
same try block (referencing appendFile, mirror.appendFile,
ensureParentDirectory, webContainer.fs.writeFile) and retain the catch behavior
to call this.syncFromWebContainer() and rethrow the error.
---
Nitpick comments:
In `@packages/webcontainer/src/createWebContainerBashTool.tsx`:
- Around line 52-61: Extract and export Zod schemas and inferred TS types for
the readFile and writeFile tool parameters (e.g., create
WebContainerReadFileParamsSchema and WebContainerWriteFileParamsSchema and
export types like WebContainerReadFileParameters and
WebContainerWriteFileParameters), replace the inline object types used in the
WebContainerToolkitResult type and the readFile/writeFile tool definitions with
these exported parameter types, and update any references to use the new schema
variables where the tools are defined (matching how
WebContainerBashToolParameters is used) to ensure consistent typing and reuse.
- Around line 218-226: The component WebContainerReadFileToolResult currently
destructures props declared on WebContainerReadFileToolResultProps but does not
use output and state, causing lint warnings; either (A) rename the unused props
to _output and _state in the WebContainerReadFileToolResult signature to satisfy
linting, or (B) enhance WebContainerReadFileToolResult to render state feedback
consistent with the bash renderer: use state to show loading indicator, display
output (file contents) on success, and render error text/exit code on failure
(reference the output and state properties on
WebContainerReadFileToolResultProps when implementing this).
- Around line 235-242: The WebContainerWriteFileToolResult component declares
props (output, state, input) but only uses input.path; either remove the unused
props from WebContainerWriteFileToolResultProps and the component signature to
avoid lint warnings, or enhance the renderer to display operation status by
using the state and output props (e.g., show "Writing...", "Success" / "Error"
and any message from output) — update the WebContainerWriteFileToolResult
function signature and JSX accordingly to either drop output/state references or
render their values alongside input.path.
- Around line 114-139: The readFile and writeFile tool execute handlers
currently throw on failure; change them to mirror the bash tool's structured
response pattern by catching errors from getSandbox().readFile and
getSandbox().writeFiles and returning typed objects (e.g.,
WebContainerReadFileToolOutput and WebContainerWriteFileToolOutput) with
success: false plus errorMessage and optional details instead of throwing; when
successful return success: true and content (for readFile) or success: true (for
writeFile), and preserve the existing inputSchema and types so callers receive
consistent structured error responses.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 50456dcf-241f-4cba-b8de-342ca111dd1b
📒 Files selected for processing (5)
packages/ai-core/src/AiSlice.tspackages/webcontainer/src/WebContainerFsAdapter.tspackages/webcontainer/src/WebContainerSandbox.tspackages/webcontainer/src/createWebContainerBashTool.tsxpackages/webcontainer/src/shims/node-zlib.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/webcontainer/src/shims/node-zlib.ts
- packages/ai-core/src/AiSlice.ts
- packages/webcontainer/src/WebContainerSandbox.ts
| async chmod(path: string, mode: number): Promise<void> { | ||
| const resolvedPath = this.resolveWithinRoot('/', path); | ||
| await this.mirror.chmod(resolvedPath, mode); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the WebContainerFsAdapter file
find . -name "WebContainerFsAdapter.ts" -type fRepository: sqlrooms/sqlrooms
Length of output: 113
🏁 Script executed:
# Read the WebContainerFsAdapter.ts file
wc -l ./packages/webcontainer/src/WebContainerFsAdapter.tsRepository: sqlrooms/sqlrooms
Length of output: 117
🏁 Script executed:
# Read the entire WebContainerFsAdapter.ts file
cat -n ./packages/webcontainer/src/WebContainerFsAdapter.tsRepository: sqlrooms/sqlrooms
Length of output: 10953
🏁 Script executed:
# Check if there are type definitions or API docs mentioning chmod/utimes in WebContainer
rg "chmod|utimes" --type ts --type tsx -iRepository: sqlrooms/sqlrooms
Length of output: 88
🏁 Script executed:
# Check for chmod/utimes references in TypeScript files
rg "chmod|utimes" --type ts -iRepository: sqlrooms/sqlrooms
Length of output: 502
🏁 Script executed:
# Check the WebContainer type definition to see what fs methods are available
rg "@webcontainer/api" --type ts -A 3 -B 3Repository: sqlrooms/sqlrooms
Length of output: 3520
🏁 Script executed:
# Look for package.json to see `@webcontainer/api` version
find . -name "package.json" -type f | head -5 | xargs grep -l "@webcontainer/api"Repository: sqlrooms/sqlrooms
Length of output: 96
🏁 Script executed:
# Check the WebContainer API package.json
cat ./apps/sqlrooms-cli-ui/package.json | grep -A 2 -B 2 "@webcontainer"Repository: sqlrooms/sqlrooms
Length of output: 233
🏁 Script executed:
# Check just-bash import to understand the InMemoryFs interface
rg "InMemoryFs" --type ts -B 5 -A 10Repository: sqlrooms/sqlrooms
Length of output: 4292
🏁 Script executed:
# Search for any tests or usage of chmod/utimes in the codebase
rg "chmod|utimes" -i --type tsRepository: sqlrooms/sqlrooms
Length of output: 502
🏁 Script executed:
# Check what methods are called on webContainer.fs throughout the codebase
rg "this\.webContainer\.fs\." --type tsRepository: sqlrooms/sqlrooms
Length of output: 1098
🏁 Script executed:
# Check the WebContainer type to understand what methods are available
rg "type WebContainerLike" --type ts -A 5Repository: sqlrooms/sqlrooms
Length of output: 526
🏁 Script executed:
# Check package.json files to see all dependencies and their versions
find . -name "package.json" -path "*/webcontainer/*" -exec cat {} \;Repository: sqlrooms/sqlrooms
Length of output: 1039
🏁 Script executed:
# Search for any usage of the WebContainerFsAdapter class
rg "WebContainerFsAdapter" --type tsRepository: sqlrooms/sqlrooms
Length of output: 655
🏁 Script executed:
# Quick check to see if there are any tests for WebContainerFsAdapter
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs rg -l "WebContainerFsAdapter|chmod|utimes" 2>/dev/nullRepository: sqlrooms/sqlrooms
Length of output: 45
🏁 Script executed:
# Look for test files
find . -name "*.test.ts" -o -name "*.spec.ts" | head -10Repository: sqlrooms/sqlrooms
Length of output: 641
Throw on chmod/utimes to prevent silent metadata loss.
Lines 257-260 and 289-292 only update this.mirror without persisting to WebContainer. Since syncFromWebContainer() replaces the entire mirror with a fresh export, these metadata changes disappear silently on the next sync. Meanwhile, symlink() and link() explicitly throw for unsupported operations, and other methods like writeFile(), mkdir(), and rm() persist to both WebContainer and mirror. This inconsistency creates a data loss hazard.
Either implement real persistence for chmod/utimes or throw, matching the pattern of symlink() and link().
🛠️ Suggested fix
async chmod(_path: string, _mode: number): Promise<void> {
- const resolvedPath = this.resolveWithinRoot('/', path);
- await this.mirror.chmod(resolvedPath, mode);
+ throw new Error(
+ 'WebContainer filesystem adapter does not support persisting chmod changes.',
+ );
}
@@
async utimes(path: string, atime: Date, mtime: Date): Promise<void> {
- const resolvedPath = this.resolveWithinRoot('/', path);
- await this.mirror.utimes(resolvedPath, atime, mtime);
+ throw new Error(
+ 'WebContainer filesystem adapter does not support persisting timestamp changes.',
+ );
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/webcontainer/src/WebContainerFsAdapter.ts` around lines 257 - 260,
The chmod and utimes implementations in WebContainerFsAdapter currently only
mutate this.mirror (resolveWithinRoot + this.mirror.chmod/utimes) which loses
metadata on sync; change both chmod(path: string, mode: number) and utimes(path:
string, atime: number, mtime: number) to throw the same unsupported-operation
error used by symlink() and link() (include a clear message like "chmod/utimes
not supported by WebContainer adapter") instead of mutating the mirror, so they
fail fast and avoid silent metadata loss.
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/webcontainer/src/createWebContainerBashTool.tsx (2)
62-68: Minor:ToolResultStateis referenced before its declaration.The type
ToolResultStateused on line 66 is defined later at line 204. While TypeScript hoists type declarations so this compiles correctly, consider movingToolResultStatedefinition earlier (near line 62) for improved readability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 62 - 68, ToolResultRendererProps references the ToolResultState type before its declaration; move the ToolResultState type definition so it appears above (or immediately before) the ToolResultRendererProps definition, or alternatively inline/export it near the top of this file so ToolResultRendererProps references a previously declared symbol — update the declaration order around ToolResultState and ToolResultRendererProps to improve readability.
115-132: Inconsistent error handling:bashtool lacks try-catch wrapper.The
readFileandwriteFiletools wrap their execute handlers in try-catch to return structured error responses. However,bashdoes not, so ifgetSandbox().executeCommand(command)throws (e.g., sandbox initialization failure), the exception propagates instead of returning{ success: false, errorMessage: ... }.♻️ Proposed fix for consistent error handling
execute: async ({command}) => { + try { const result = await getSandbox().executeCommand(command); return { success: result.exitCode === 0, details: { command, stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode, }, ...(result.exitCode === 0 ? {} : { errorMessage: result.stderr || `Command exited with code ${result.exitCode}.`, }), } satisfies WebContainerBashToolOutput; + } catch (error) { + return { + success: false, + details: { + command, + stdout: '', + stderr: '', + exitCode: -1, + }, + errorMessage: + error instanceof Error ? error.message : 'Failed to execute command', + } satisfies WebContainerBashToolOutput; + } },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 115 - 132, The bash tool's execute handler (the async execute in createWebContainerBashTool) doesn't catch exceptions from getSandbox().executeCommand(command), so thrown errors propagate instead of returning a structured WebContainerBashToolOutput; wrap the body of that execute function in a try-catch, call getSandbox().executeCommand(command) inside try, construct the success/details/errorMessage shape as currently done on nonzero exitCode, and in catch return { success: false, errorMessage: error.message || String(error), details: { command } } to match the other tools' error-response pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/webcontainer/src/createWebContainerBashTool.tsx`:
- Around line 62-68: ToolResultRendererProps references the ToolResultState type
before its declaration; move the ToolResultState type definition so it appears
above (or immediately before) the ToolResultRendererProps definition, or
alternatively inline/export it near the top of this file so
ToolResultRendererProps references a previously declared symbol — update the
declaration order around ToolResultState and ToolResultRendererProps to improve
readability.
- Around line 115-132: The bash tool's execute handler (the async execute in
createWebContainerBashTool) doesn't catch exceptions from
getSandbox().executeCommand(command), so thrown errors propagate instead of
returning a structured WebContainerBashToolOutput; wrap the body of that execute
function in a try-catch, call getSandbox().executeCommand(command) inside try,
construct the success/details/errorMessage shape as currently done on nonzero
exitCode, and in catch return { success: false, errorMessage: error.message ||
String(error), details: { command } } to match the other tools' error-response
pattern.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c873fb43-db70-4f37-8525-97965ab3faf1
📒 Files selected for processing (3)
packages/webcontainer/src/WebContainerFsAdapter.tspackages/webcontainer/src/createWebContainerBashTool.tsxpackages/webcontainer/src/index.ts
✅ Files skipped from review due to trivial changes (1)
- packages/webcontainer/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/webcontainer/src/WebContainerFsAdapter.ts
Summary
WebContainerFsAdapterandexecuteBashCommand()path in@sqlrooms/webcontainerusingjust-bashcreateWebContainerBashTooland renderer, then switch the app-builder example and CLI app to the new bash toolnode:zlibshim and Vite aliases so the app-builder example can bundlejust-bashTesting
pnpm --filter @sqlrooms/webcontainer typecheckpnpm --filter @sqlrooms/webcontainer buildpnpm --filter app-builder-example buildjust-bashthroughWebContainerFsAdapterand verified read/edit/copy/remove operations persist back to the mocked WebContainer filesystempnpm buildfails in@sqlrooms/keplerpnpm --filter sqlrooms-cli-app build/pnpm --filter sqlrooms-cli-app exec vite buildfail on existing@sqlrooms/cells/@sqlrooms/notebookresolution and type issuesSummary by CodeRabbit
New Features
Bug Fixes
Refactor
Chores