mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-15 21:11:08 +02:00
Refactor builtin file tools beyond workspace scope
Replace workspace-scoped builtin file tools with general-purpose file-* tools that accept relative, absolute, and ~/ paths. Relative paths still resolve against the configured workdir. File operations within the workdir are auto-approved. File operations outside the workdir now emit file permission metadata and require user approval, with support for once, session, and persistent grants. Add a shared filesystem layer for text-focused read/write/edit/list/search operations, including binary-file safeguards for text reads. parseFile and LLMParse continue to read file buffers for document/image parsing. Update copilot prompts, background/live-note agents, knowledge workflows, and renderer labels/UI to use the new file-* tool surface and permission details. Add package-local Vitest setup for @x/core with colocated filesystem unit tests covering path resolution, canonical permission paths, binary detection, read/write/edit behavior, glob, and grep.
This commit is contained in:
parent
f1d3b7b825
commit
31e35e00b8
41 changed files with 1777 additions and 615 deletions
|
|
@ -5677,6 +5677,7 @@ function App() {
|
|||
{rendered}
|
||||
<PermissionRequest
|
||||
toolCall={permRequest.toolCall}
|
||||
permission={permRequest.permission}
|
||||
onApprove={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve')}
|
||||
onApproveSession={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'session')}
|
||||
onApproveAlways={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'always')}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { cn } from "@/lib/utils";
|
|||
import { AlertTriangleIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, XCircleIcon, XIcon } from "lucide-react";
|
||||
import type { ComponentProps } from "react";
|
||||
import { ToolCallPart } from "@x/shared/dist/message.js";
|
||||
import { ToolPermissionMetadata } from "@x/shared/dist/runs.js";
|
||||
import z from "zod";
|
||||
|
||||
export type PermissionRequestProps = ComponentProps<"div"> & {
|
||||
|
|
@ -22,6 +23,15 @@ export type PermissionRequestProps = ComponentProps<"div"> & {
|
|||
onDeny?: () => void;
|
||||
isProcessing?: boolean;
|
||||
response?: 'approve' | 'deny' | null;
|
||||
permission?: z.infer<typeof ToolPermissionMetadata>;
|
||||
};
|
||||
|
||||
const fileActionLabels: Record<string, string> = {
|
||||
read: "Read file",
|
||||
list: "List folder",
|
||||
search: "Search files",
|
||||
write: "Write files",
|
||||
delete: "Delete path",
|
||||
};
|
||||
|
||||
export const PermissionRequest = ({
|
||||
|
|
@ -33,14 +43,16 @@ export const PermissionRequest = ({
|
|||
onDeny,
|
||||
isProcessing = false,
|
||||
response = null,
|
||||
permission,
|
||||
...props
|
||||
}: PermissionRequestProps) => {
|
||||
// Extract command from arguments if it's executeCommand
|
||||
const command = toolCall.toolName === "executeCommand"
|
||||
const command = permission?.kind === "command" || toolCall.toolName === "executeCommand"
|
||||
? (typeof toolCall.arguments === "object" && toolCall.arguments !== null && "command" in toolCall.arguments
|
||||
? String(toolCall.arguments.command)
|
||||
: JSON.stringify(toolCall.arguments))
|
||||
: null;
|
||||
const filePermission = permission?.kind === "file" ? permission : null;
|
||||
|
||||
const isResponded = response !== null;
|
||||
const isApproved = response === 'approve';
|
||||
|
|
@ -113,7 +125,35 @@ export const PermissionRequest = ({
|
|||
</pre>
|
||||
</div>
|
||||
)}
|
||||
{!command && toolCall.arguments && (
|
||||
{filePermission && (
|
||||
<div className="rounded-md border bg-background/50 p-3 mt-3 space-y-3">
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1.5 uppercase tracking-wide">
|
||||
Action
|
||||
</p>
|
||||
<p className="text-xs font-medium text-foreground">
|
||||
{fileActionLabels[filePermission.operation] ?? filePermission.operation}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1.5 uppercase tracking-wide">
|
||||
Path{filePermission.paths.length === 1 ? "" : "s"}
|
||||
</p>
|
||||
<pre className="whitespace-pre-wrap text-xs font-mono text-foreground break-all">
|
||||
{filePermission.paths.join("\n")}
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1.5 uppercase tracking-wide">
|
||||
Approval Scope
|
||||
</p>
|
||||
<pre className="whitespace-pre-wrap text-xs font-mono text-foreground break-all">
|
||||
{filePermission.pathPrefix}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!command && !filePermission && toolCall.arguments && (
|
||||
<div className="rounded-md border bg-background/50 p-3 mt-3">
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1.5 uppercase tracking-wide">
|
||||
Arguments
|
||||
|
|
@ -133,12 +173,12 @@ export const PermissionRequest = ({
|
|||
size="sm"
|
||||
onClick={onApprove}
|
||||
disabled={isProcessing}
|
||||
className={cn("flex-1", command && "rounded-r-none")}
|
||||
className={cn("flex-1", (command || filePermission) && "rounded-r-none")}
|
||||
>
|
||||
<CheckIcon className="size-4" />
|
||||
Approve
|
||||
</Button>
|
||||
{command && (
|
||||
{(command || filePermission) && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -479,19 +479,19 @@ export const getComposioConnectCardData = (tool: ToolCall): ComposioConnectCardD
|
|||
|
||||
// Human-friendly display names for builtin tools
|
||||
const TOOL_DISPLAY_NAMES: Record<string, string> = {
|
||||
'workspace-readFile': 'Reading file',
|
||||
'workspace-writeFile': 'Writing file',
|
||||
'workspace-edit': 'Editing file',
|
||||
'workspace-readdir': 'Reading directory',
|
||||
'workspace-exists': 'Checking path',
|
||||
'workspace-stat': 'Getting file info',
|
||||
'workspace-glob': 'Finding files',
|
||||
'workspace-grep': 'Searching files',
|
||||
'workspace-mkdir': 'Creating directory',
|
||||
'workspace-rename': 'Renaming',
|
||||
'workspace-copy': 'Copying file',
|
||||
'workspace-remove': 'Removing',
|
||||
'workspace-getRoot': 'Getting workspace root',
|
||||
'file-readText': 'Reading file',
|
||||
'file-writeText': 'Writing file',
|
||||
'file-editText': 'Editing file',
|
||||
'file-list': 'Reading directory',
|
||||
'file-exists': 'Checking path',
|
||||
'file-stat': 'Getting file info',
|
||||
'file-glob': 'Finding files',
|
||||
'file-grep': 'Searching files',
|
||||
'file-mkdir': 'Creating directory',
|
||||
'file-rename': 'Renaming',
|
||||
'file-copy': 'Copying file',
|
||||
'file-remove': 'Removing',
|
||||
'file-getRoot': 'Getting file root',
|
||||
'loadSkill': 'Loading skill',
|
||||
'parseFile': 'Parsing file',
|
||||
'LLMParse': 'Extracting content',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue