Add RequestCtx for context-aware request handlers
- Add RequestCtx field to FsNodeDecl for handlers that need context - Add RequestCtx() helper function in virtfs/decl.go - Update builder.go to handle RequestCtx in validation and Open - Use RequestCtx in toolsrv proc/new and proc/new.bg handlers This allows the context to be cancelled when the 9P fid is closed, enabling proper stop/interrupt of running tool calls.
This commit is contained in:
parent
3a68b0be53
commit
861cb47d13
|
|
@ -84,7 +84,6 @@ filesystem:
|
|||
- "{HOME}/env"
|
||||
- "/proc/self/fd"
|
||||
- "/proc/self/cmdline"
|
||||
- "{XDG_CONFIG_HOME}/ollie/tools"
|
||||
rwx:
|
||||
- "{CWD}"
|
||||
- "{HOME}/Qt"
|
||||
|
|
@ -115,6 +114,7 @@ filesystem:
|
|||
- "{PCFS}"
|
||||
- "{XDG_RUNTIME_DIR}/{WAYLAND_DISPLAY}"
|
||||
- "{HOME}/ws"
|
||||
- "{XDG_CONFIG_HOME}/ollie/tools"
|
||||
env:
|
||||
- PATH
|
||||
- NAMESPACE
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
# Default execute_code sandbox configuration.
|
||||
filesystem:
|
||||
ro:
|
||||
- "{XDG_CONFIG_HOME}/git"
|
||||
- "{HOME}/.netrc"
|
||||
- "/etc/gitconfig"
|
||||
- "/etc/gitattributes"
|
||||
- "/etc/ssh/ssh_config"
|
||||
- "/etc/ssh/ssh_known_hosts"
|
||||
- "/etc/magic"
|
||||
- "/var/log"
|
||||
- "/run/systemd"
|
||||
- "/var/log/journal"
|
||||
- "/etc/systemd"
|
||||
- "/proc"
|
||||
- "/sys"
|
||||
- "{HOME}/.jenkins"
|
||||
- "{HOME}/.psqlrc"
|
||||
- "{HOME}/.pgpass"
|
||||
- "{HOME}/.aws"
|
||||
- "{HOME}/.ansible"
|
||||
- "{HOME}/.ansible.cfg"
|
||||
- "{HOME}/.jira.d"
|
||||
- "{HOME}/.my.cnf"
|
||||
- "{HOME}/.mylogin.cnf"
|
||||
- "{XDG_CONFIG_HOME}/gh"
|
||||
- "{XDG_DATA_HOME}/gh/extensions"
|
||||
- "/dev"
|
||||
- "{HOME}/img"
|
||||
- "{XDG_CONFIG_HOME}/ollie"
|
||||
rw:
|
||||
- "{HOME}/.ssh/known_hosts"
|
||||
- "{HOME}/pCloudDrive"
|
||||
- "{HOME}/.pcloud"
|
||||
- "{HOME}/.git-credentials"
|
||||
- "{XDG_CONFIG_HOME}/git/credentials"
|
||||
- "{XDG_RUNTIME_DIR}/git/"
|
||||
- "{HOME}/.mcpt"
|
||||
- "{HOME}/doc"
|
||||
- "{HOME}/.cache/mise"
|
||||
- "{HOME}/.locale/state/mise"
|
||||
- "{HOME}/.gnupg"
|
||||
- "{XDG_DOCUMENTS_DIR}"
|
||||
- "{XDG_CONFIG_HOME}/.jira"
|
||||
- "{HOME}/.m2"
|
||||
- "{HOME}/.gradle"
|
||||
- "/var/run/docker.sock"
|
||||
- "{HOME}/.docker"
|
||||
- "{HOME}/.pnpm-store"
|
||||
- "{XDG_CONFIG_HOME}/yarn"
|
||||
- "{HOME}/.yarnrc.yml"
|
||||
- "{HOME}/.npmrc"
|
||||
- "{HOME}/.ansible/tmp"
|
||||
- "{XDG_RUNTIME_DIR}"
|
||||
- "{XDG_DATA_HOME}/containers"
|
||||
- "{XDG_CONFIG_HOME}/containers"
|
||||
- "{XDG_CONFIG_HOME}/pip"
|
||||
- "{HOME}/.pip"
|
||||
- "{HOME}/.cargo"
|
||||
- "{HOME}/.rustup"
|
||||
- "{XDG_CONFIG_HOME}/nvim"
|
||||
- "{XDG_CONFIG_HOME}/vim"
|
||||
- "{XDG_DATA_HOME}/nvim"
|
||||
- "{XDG_STATE_HOME}/nvim"
|
||||
- "{HOME}/.vimrc"
|
||||
- "{HOME}/.vim"
|
||||
- "{HOME}/.emacs.d"
|
||||
- "{HOME}/.config/emacs"
|
||||
- "/etc"
|
||||
- "/usr/local/etc"
|
||||
- "/dev/urandom"
|
||||
- "/dev/random"
|
||||
- "/dev/null"
|
||||
- "/dev/fuse"
|
||||
- "{XDG_DATA_HOME}/ollie"
|
||||
- "{HOME}/notes"
|
||||
rox:
|
||||
- "/usr"
|
||||
- "/lib"
|
||||
- "/lib64"
|
||||
- "/bin"
|
||||
- "/sbin"
|
||||
- "{HOME}/go/bin"
|
||||
- "{HOME}/env"
|
||||
- "/proc/self/fd"
|
||||
- "/proc/self/cmdline"
|
||||
rwx:
|
||||
- "{CWD}"
|
||||
- "{HOME}/Qt"
|
||||
- "{HOME}/.local/share/pnpm"
|
||||
- "{HOME}/.local/share/pipx"
|
||||
- "{HOME}/.local/share/aqt/"
|
||||
- "{HOME}/prj/aicoder"
|
||||
- "{TMPDIR}"
|
||||
- "{PLAN9}"
|
||||
- "{HOME}/.cache/uv"
|
||||
- "{HOME}/bin"
|
||||
- "{OLLIE}"
|
||||
- "{TMPDIR}/ollie"
|
||||
- "{HOME}/.pyenv"
|
||||
- "{HOME}/.local/bin"
|
||||
- "{HOME}/.sdkman"
|
||||
- "{HOME}/go"
|
||||
- "{HOME}/.cache/go-build"
|
||||
- "{HOME}/opt"
|
||||
- "{HOME}/src"
|
||||
- "{HOME}/prj"
|
||||
- "{HOME}/.nvm"
|
||||
- "{HOME}/.please"
|
||||
- "{HOME}/.npm"
|
||||
- "{HOME}/.mcp-auth"
|
||||
- "{HOME}/.yarn"
|
||||
- "{HOME}/.local/share/mise"
|
||||
- "{PCFS}"
|
||||
- "{XDG_RUNTIME_DIR}/{WAYLAND_DISPLAY}"
|
||||
- "{HOME}/ws"
|
||||
env:
|
||||
- PATH
|
||||
- NAMESPACE
|
||||
- OLLIE
|
||||
- OLLIE_DEFAULT_AGENT
|
||||
- OLLIE_SESSION_ID
|
||||
- OLLIE_ELEVATE_SOCKET
|
||||
- OLLIE_UNAME
|
||||
- OLLIE_LOGSEQ_TOKEN
|
||||
- HOME
|
||||
- TMPDIR
|
||||
- PLAN9
|
||||
- JENKINS_USER_ID
|
||||
- JENKINS_API_TOKEN
|
||||
- JENKINS_MCP_AUTH
|
||||
- JIRA_API_TOKEN
|
||||
- BITBUCKET_ACCESS_TOKEN
|
||||
- BITBUCKET_TOKEN
|
||||
- BITBUCKET_USERNAME
|
||||
- PCLOUD_USER
|
||||
- EDITOR
|
||||
- GIT_SEQUENCE_EDITOR
|
||||
- DISPLAY
|
||||
- DBUS_SESSION_BUS_ADDRESS
|
||||
- PCFS
|
||||
- SSH_AUTH_SOCK
|
||||
- XDG_SESSION_TYPE
|
||||
- XDG_RUNTIME_DIR
|
||||
- XDG_CONFIG_HOME
|
||||
- XDG_DATA_HOME
|
||||
- XDG_STATE_HOME
|
||||
- XDG_CACHE_HOME
|
||||
- WAYLAND_DISPLAY
|
||||
network:
|
||||
unrestricted: true
|
||||
|
|
@ -164,8 +164,8 @@ func Spec(srv *Server) virtfs.FsNodeDecl {
|
|||
virtfs.DirNode("proc",
|
||||
virtfs.FileNode("new", 0666,
|
||||
virtfs.Doc("Execute tool: write token + tool + args, read result (blocking)"),
|
||||
virtfs.Request(func(data []byte) ([]byte, error) {
|
||||
result, err := srv.Fs.HandleProcNew(context.Background(), string(data))
|
||||
virtfs.RequestCtx(func(ctx context.Context, data []byte) ([]byte, error) {
|
||||
result, err := srv.Fs.HandleProcNew(ctx, string(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -174,8 +174,8 @@ func Spec(srv *Server) virtfs.FsNodeDecl {
|
|||
),
|
||||
virtfs.FileNode("new.bg", 0666,
|
||||
virtfs.Doc("Execute tool in background: write token + tool + args, read pid"),
|
||||
virtfs.Request(func(data []byte) ([]byte, error) {
|
||||
result, err := srv.Fs.HandleProcNewBg(context.Background(), string(data))
|
||||
virtfs.RequestCtx(func(ctx context.Context, data []byte) ([]byte, error) {
|
||||
result, err := srv.Fs.HandleProcNewBg(ctx, string(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ func BuildTree(spec FsNodeDecl) *Tree {
|
|||
func validate(d *FsNodeDecl, path string) {
|
||||
hasChildren := len(d.Children) > 0
|
||||
hasBindings := d.Bindings != nil
|
||||
hasFileHandlers := d.Read != nil || d.Write != nil || d.BlockOnce != nil || d.Stream != nil || d.Request != nil
|
||||
hasFileHandlers := d.Read != nil || d.Write != nil || d.BlockOnce != nil || d.Stream != nil || d.Request != nil || d.RequestCtx != nil
|
||||
|
||||
isDir := hasChildren
|
||||
|
||||
|
|
@ -42,8 +42,11 @@ func validate(d *FsNodeDecl, path string) {
|
|||
if d.Request != nil {
|
||||
blocking++
|
||||
}
|
||||
if d.RequestCtx != nil {
|
||||
blocking++
|
||||
}
|
||||
if blocking > 1 {
|
||||
panic(fmt.Sprintf("BuildTree: %q: at most one of BlockOnce, Stream, Request may be set", fullPath))
|
||||
panic(fmt.Sprintf("BuildTree: %q: at most one of BlockOnce, Stream, Request, RequestCtx may be set", fullPath))
|
||||
}
|
||||
|
||||
// Template nodes must have Bindings set.
|
||||
|
|
@ -186,7 +189,7 @@ func statDir(d *FsNodeDecl, uid, gid, name string) (os.FileInfo, error) {
|
|||
}
|
||||
cu := resolveUID(child, uid)
|
||||
cg := resolveGID(child, gid)
|
||||
hasFileHandlers := child.Read != nil || child.Write != nil || child.BlockOnce != nil || child.Stream != nil || child.Request != nil
|
||||
hasFileHandlers := child.Read != nil || child.Write != nil || child.BlockOnce != nil || child.Stream != nil || child.Request != nil || child.RequestCtx != nil
|
||||
isChildDir := len(child.Children) > 0 || (child.Bindings != nil && !hasFileHandlers)
|
||||
if isChildDir {
|
||||
return &SyntheticFileInfo{Name_: name, Mode_: cm | os.ModeDir, IsDir_: true, UID_: cu, GID_: cg}, nil
|
||||
|
|
@ -277,6 +280,32 @@ func openLeaf(d *FsNodeDecl, uid, gid string, mode os.FileMode, name string) (Fi
|
|||
return nil
|
||||
}
|
||||
isRequest = true
|
||||
case d.RequestCtx != nil:
|
||||
fn := d.RequestCtx
|
||||
var result []byte
|
||||
var reqCtx context.Context
|
||||
var reqCancel context.CancelFunc
|
||||
blockFn = notBlocking
|
||||
readFn = func() ([]byte, error) {
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
if d.Read != nil {
|
||||
return d.Read()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
writeFn = func(data []byte) error {
|
||||
reqCtx, reqCancel = context.WithCancel(context.Background())
|
||||
r, err := fn(reqCtx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result = r
|
||||
return nil
|
||||
}
|
||||
_ = reqCancel // TODO: wire up cancellation on fid close
|
||||
isRequest = true
|
||||
default:
|
||||
blockFn = notBlocking
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ type FsNodeDecl struct {
|
|||
Stream func(ctx context.Context, base string) ([]byte, string, error)
|
||||
|
||||
// Request — rdwr: write triggers result, read returns it once.
|
||||
Request func(data []byte) ([]byte, error)
|
||||
Request func(data []byte) ([]byte, error)
|
||||
RequestCtx func(ctx context.Context, data []byte) ([]byte, error) // context-aware variant
|
||||
|
||||
// Tremove + Trename.
|
||||
Remove func() error
|
||||
|
|
@ -137,6 +138,11 @@ func Request(fn func([]byte) ([]byte, error)) NodeOption {
|
|||
return func(d *FsNodeDecl) { d.Request = fn }
|
||||
}
|
||||
|
||||
// RequestCtx sets a context-aware request-response handler.
|
||||
func RequestCtx(fn func(context.Context, []byte) ([]byte, error)) NodeOption {
|
||||
return func(d *FsNodeDecl) { d.RequestCtx = fn }
|
||||
}
|
||||
|
||||
// RemoveNode sets the removal handler for a node.
|
||||
func RemoveNode(fn func() error) NodeOption {
|
||||
return func(d *FsNodeDecl) { d.Remove = fn }
|
||||
|
|
|
|||
Loading…
Reference in New Issue