Repository navigation
add: a project's work folders, granted to every turn up front - #516
Merged
Merged
Conversation
A project's settings page lists the folders outside the workspaces that every turn in it may write to, added through the platform's folder picker and removed per row. They are the project's existing writableDirs in project.json, the list an add_directory "always for this project" answer extends, so the sandbox (Seatbelt and Landlock), the approval gate and the classifier already read them; what is new is the UI, the refusals, a folder gone from the machine granting nothing, and the turn's context naming them. Shown only for a local project where the picked driver enforces the sandbox: not on Windows, phones, a guest, a built-in with rootfs isolation, or a paired device's project.
refuseFolder in lib/domain/work_folders.dart is now the predicate both the work folders and add_directory take, with the stricter set: not a directory, the root, the home or anything holding it, the app's data tree, and anything holding the workspaces. add_directory now refuses ~ and /, which it used to accept. The agent filters the persisted grants once per turn rather than the repository on every call, the repository merges the tiers without duplicates, the prompt asks the gate whether it confines a command, the rows are EditableRow and read their reason from the refusal, and the tilde check is one predicate.
# Conflicts: # lib/data/builtin/approval_gate.dart # test/data/builtin/permission_gate_test.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A long chat that worked in a repository outside Entoli's workspace left the sandbox 105 times, needed 72 approval prompts, and had 12 commands refused while the user was away. A project can now declare its work folders up front. Every turn in the project can then write to them inside the sandbox, with nothing to approve and no one needing to be there.
What it is
Most of the setting already existed. The project tier's
writableDirsinproject.jsonis where anadd_directory"always for this project" answer lands, and it already reached the sandbox ruleset (Seatbelt and Landlock), the gate's "leaves the sandbox" decision, the file tools and the classifier'salsoWritable. This PR builds the UI over that same path rather than a parallel one. A folder granted on a banner shows in the list, and one removed from the list is gone at the next call.Settings UI. The project settings page has a "Work folders" section (
work_folders_section.dart, replacing the typed-textwritable_dirs_section.dartthat sat on the Permissions sub-page). "Add folder" opens the platform folder picker (a newFolderPickerport overfile_selector'sgetDirectoryPath), and each row is the sharedEditableRow(itsonTapandsubtitleare now optional) with a remove control.One check for what a grant may name.
refuseFolderinlib/domain/work_folders.dartreturns aFolderRefusal, and both doors use it: the settings page andAddDirectoryTool.run. It refuses:The settings page shows the refusal's
messagein a snack bar, and a row shows itsrowwording.Behaviour change in
add_directory: it now refuses~,/and any folder holding the home or the workspaces, all of which it used to accept. Its tool description is unchanged; a sibling PR owns it.Missing folders. The repository merges the tiers' lists once, without duplicates. The agent hands its gate that policy minus what
grantsNowdrops (anythingrefuseFolderrefuses), checking each path once per turn rather than per tool call. A folder gone from this machine therefore grants nothing, and a command cannot recreate it under the grant. Its row says "Missing on this device". A~path passes through for the gate to resolve;isTildePathinshell.dartis now the one check for that, shared with the gate.Telling the model. The turn's second system message gets a "## Work folders" section (
turnPrompt'sworkFolders) naming the granted folders as writable, telling the model not to useunsandboxedor ask for access there. It reads the same filtered policy as the gate, and appears only where the newApprovalGate.confinessays commands run confined (the condition_sandboxForuses).Platforms. The section shows only for a local project whose picked driver's
Confinement.enforcesSandboxis true, which means macOS and Linux on the host. It is hidden on Windows (following "What is absent, and honestly so"), on phones, under a Linux guest, for a built-in with rootfs isolation, and for a paired device's project, whose paths a picker here cannot name.Storage: the folders stay in
project.jsonI did not follow the
instruction_files.jsonprecedent, and the user chose this. The field already lives inproject.jsonand already reaches the sandbox and the gate. Moving it would cost a migration, and the only unreleased migration entry (7.5.0) has already run on real data directories, so the move would need a second entry that breaks the rule of naming entries by release version. A path that reaches another machine through a backup or sync names a folder that is not there, and that grants nothing. No migration is needed and no file is added.Not touched: the
bash_tool.dartanddirectory_tools.darttool descriptions. OnlyAddDirectoryTool.run's checks changed.Tests
sidebar_test: a picked folder is added as a row and its cross removes it; the data directory is refused; a vanished folder's row says it is missing; no section where the host confines nothing (none,appSandbox).directory_tools_test:add_directoryrefuses~, a folder holding the home,/, and a folder holding the workspaces, all before any banner.permission_gate_test: the turn's writable folders reach the sandbox while they are folders on disk, and a missing folder or a file does not. The model is told about the work folders under the sandbox, and not with the sandbox off. Tests that granted made-up paths now use real temp folders.chat_repository_test: a folder granted at two tiers reaches the turn once.templates_test: the section is named only when there are folders.Notes updated:
lib/ui/settings/AGENTS.md(new "Work folders"),lib/data/builtin/AGENTS.md(sandbox, approvals,add_directory), and the rootAGENTS.md(project.jsoncontents and the index).