Files
yhh ca730caab2 feat(control): grant a workspace extra folders, vetted and treated as workspace
Extra roots are the project grant's folders in the user's home. They are a set
of their own: write roots and the OS jail read them, the guard on Reasonix's
own state does not treat them as exemptions, the turn tail names them, and the
scan, readiness and receipt of the executor and of every sub-agent count them
as workspace. One judgement (FolderGrantRefusal, shared with workspace trust
and POST /sandbox) refuses drive roots, home and its ancestors, storage roots,
key and credential directories and the forbid list, in either direction,
comparing file identity rather than spelling, plus folders the user does not
own, UNC and device-namespace paths, and overlaps with other write roots. Every
load vets each grant again. Withdrawing a root rebuilds every pane on the
workspace, after this pane's lock is released.
2026-10-11 05:40:15 -07:00

45 lines
1.6 KiB
Go

package agent
import (
"reasonix/internal/runtime/langpref"
"strconv"
"strings"
)
// WorkspaceBlock renders the transient block carrying this turn's per-project
// facts. They ride the turn rather than the system prompt because every byte
// after the first per-project one — the rest of the prompt and every tool
// schema — misses the provider's prefix cache on a project the machine has not
// warmed. Keep new per-project facts here, not in the prefix.
func WorkspaceBlock(root, vcs string, extra []string) string {
if root == "" {
return ""
}
if vcs == "" {
vcs = "none (not a repository)"
}
// Version control is stated either way. Left unsaid, a model reaches for
// `git diff` to review its own work and burns a round finding out.
block := "<workspace>\nCurrent workspace: " + strconv.Quote(root) +
". Shell commands start there and every tool resolves relative paths from it.\n" +
"Version control: " + vcs + ".\n"
if len(extra) > 0 {
quoted := make([]string, len(extra))
for i, dir := range extra {
quoted[i] = strconv.Quote(dir)
}
block += "Additional folders granted to this workspace, writable like it (use absolute paths): " + strings.Join(quoted, ", ") + ".\n"
}
return block + "</workspace>"
}
// WithWorkspace prefixes content with the transient workspace block unless the
// turn already starts with one.
func WithWorkspace(content, root, vcs string, extra []string) string {
block := WorkspaceBlock(root, vcs, extra)
if block == "" || langpref.HasLeadingInjectedBlock(content, "workspace") {
return content
}
return block + "\n\n" + content
}