move t/ into s/{sid}/t/; add allowTools filtering at 9P layer
This commit is contained in:
parent
393460f767
commit
c075964a1f
|
|
@ -63,11 +63,11 @@ ollie/
|
|||
systemprompt read: fully rendered system prompt for this session
|
||||
tail exec: exec tail -f chat
|
||||
usage read: token counts (input, output, requests; [estimated] if not reported by backend)
|
||||
t/ dir: tool scripts (r/w, backed by ~/.config/ollie/tools/)
|
||||
idx read: tool index (name, description, args — synthesized at read time)
|
||||
<script> r/w: tool script content
|
||||
sk/ dir: skills (r/w, from OLLIE_SKILLS_PATH or ~/.config/ollie/skills/)
|
||||
<name>.md r/w: skill SKILL.md content
|
||||
t/ dir: tool scripts (r/w, backed by ~/.config/ollie/tools/)
|
||||
idx read: tool index (name, description, args — synthesized at read time)
|
||||
<script> r/w: tool script content
|
||||
tr/ dir: saved transcripts (read, backed by OLLIE_TRANSCRIPT_PATH or ~/.config/ollie/transcript/)
|
||||
<timestamp>-chat.md read: transcript saved at session end
|
||||
tmp/ dir: temporary files (r/w, backed by OLLIE_TMP_PATH or $XDG_DATA_HOME/ollie/tmp/)
|
||||
|
|
@ -90,7 +90,6 @@ Each 9P directory endpoint is backed by a named store implementing the `Store` i
|
|||
| `/m` | `FlatDirStore` | `Store` |
|
||||
| `/p` | `FlatDirStore` | `Store` |
|
||||
| `/sk` | `SkillStore` | `Store` |
|
||||
| `/t` | `ToolStore` | `Store` |
|
||||
| `/tr` | `FlatDirStore` | `Store` |
|
||||
| `/tmp`| `FlatDirStore` | `Store` |
|
||||
| `/u` | `UtilStore` | `Store` |
|
||||
|
|
|
|||
314
server.go
314
server.go
|
|
@ -327,6 +327,35 @@ func (s *Server) sessionFileStore(sessID string) (store.RunnableStore, bool) {
|
|||
return st, true
|
||||
}
|
||||
|
||||
// sessionAllowTools returns the allowTools set for a session, or nil if unrestricted.
|
||||
func (s *Server) sessionAllowTools(sessID string) map[string]bool {
|
||||
sess := s.sessionStore.Session(sessID)
|
||||
if sess == nil || len(sess.AllowTools) == 0 {
|
||||
return nil
|
||||
}
|
||||
m := make(map[string]bool, len(sess.AllowTools))
|
||||
for _, t := range sess.AllowTools {
|
||||
m[t] = true
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// filterToolIndex filters a tool index (## name\n...\n\n sections) to only include allowed tools.
|
||||
func filterToolIndex(data []byte, allowed map[string]bool) []byte {
|
||||
var out []byte
|
||||
for _, section := range strings.Split(string(data), "## ") {
|
||||
if section == "" {
|
||||
continue
|
||||
}
|
||||
name, _, _ := strings.Cut(section, "\n")
|
||||
if allowed[name] {
|
||||
out = append(out, "## "...)
|
||||
out = append(out, section...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// storeRead opens an entry in a store and reads it.
|
||||
func storeRead(s store.Store, name string) ([]byte, error) {
|
||||
e, err := s.Open(name)
|
||||
|
|
@ -541,8 +570,6 @@ func (s *Server) pathType(path string) string {
|
|||
return "dir"
|
||||
case len(parts) == 1 && parts[0] == "sk":
|
||||
return "dir"
|
||||
case len(parts) == 1 && parts[0] == "t":
|
||||
return "dir"
|
||||
case len(parts) == 1 && parts[0] == "u":
|
||||
return "dir"
|
||||
case len(parts) == 2 && parts[0] == "u":
|
||||
|
|
@ -588,22 +615,22 @@ func (s *Server) pathType(path string) string {
|
|||
if _, err := s.skillStore.Stat(parts[1]); err == nil {
|
||||
return "file"
|
||||
}
|
||||
case len(parts) == 2 && parts[0] == "t":
|
||||
if info, err := s.toolStore.Stat(parts[1]); err == nil {
|
||||
if info.IsDir() {
|
||||
return "dir"
|
||||
}
|
||||
return "file"
|
||||
}
|
||||
case len(parts) == 3 && parts[0] == "t":
|
||||
if info, err := s.toolStore.Stat(parts[1] + "/" + parts[2]); err == nil {
|
||||
if info.IsDir() {
|
||||
return "dir"
|
||||
}
|
||||
return "file"
|
||||
}
|
||||
case len(parts) == 3 && parts[0] == "s":
|
||||
if sfs, ok := s.sessionFileStore(parts[1]); ok {
|
||||
if parts[2] == "t" {
|
||||
// /s/{sid}/t is the tools directory
|
||||
if s.sessionStore.Session(parts[1]) != nil {
|
||||
return "dir"
|
||||
}
|
||||
} else if strings.HasPrefix(parts[2], "t/") {
|
||||
// /s/{sid}/t/{rest} — tool file or subdir
|
||||
rel := strings.TrimPrefix(parts[2], "t/")
|
||||
if info, err := s.toolStore.Stat(rel); err == nil {
|
||||
if info.IsDir() {
|
||||
return "dir"
|
||||
}
|
||||
return "file"
|
||||
}
|
||||
} else if sfs, ok := s.sessionFileStore(parts[1]); ok {
|
||||
if _, err := sfs.Stat(parts[2]); err == nil {
|
||||
return "file"
|
||||
}
|
||||
|
|
@ -731,23 +758,27 @@ func (s *Server) create(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
return errFcall(fc, "bad fid")
|
||||
}
|
||||
if fc.Perm&plan9.DMDIR != 0 {
|
||||
if f.path == "/t" || strings.HasPrefix(f.path, "/t/") {
|
||||
rel := strings.TrimPrefix(f.path, "/t")
|
||||
if rel == "" {
|
||||
rel = fc.Name
|
||||
} else {
|
||||
rel = rel[1:] + "/" + fc.Name // strip leading /
|
||||
// mkdir under session tool directory: /s/{sid}/t or /s/{sid}/t/...
|
||||
if strings.HasPrefix(f.path, "/s/") {
|
||||
parts := strings.SplitN(strings.TrimPrefix(f.path, "/s/"), "/", 3)
|
||||
if (len(parts) == 2 && parts[1] == "t") || (len(parts) == 3 && parts[1] == "t") {
|
||||
var rel string
|
||||
if len(parts) == 2 {
|
||||
rel = fc.Name
|
||||
} else {
|
||||
rel = parts[2] + "/" + fc.Name
|
||||
}
|
||||
dir := filepath.Join(s.toolStore.dir, rel)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
newPath := pathJoin(f.path, fc.Name)
|
||||
qid := plan9.Qid{Type: QTDir, Path: qidPath(newPath)}
|
||||
f.path = newPath
|
||||
f.qid = qid
|
||||
f.mode = fc.Mode
|
||||
return &plan9.Fcall{Type: plan9.Rcreate, Tag: fc.Tag, Qid: qid}
|
||||
}
|
||||
dir := filepath.Join(s.toolStore.dir, rel)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
newPath := pathJoin(f.path, fc.Name)
|
||||
qid := plan9.Qid{Type: QTDir, Path: qidPath(newPath)}
|
||||
f.path = newPath
|
||||
f.qid = qid
|
||||
f.mode = fc.Mode
|
||||
return &plan9.Fcall{Type: plan9.Rcreate, Tag: fc.Tag, Qid: qid}
|
||||
}
|
||||
return errFcall(fc, "mkdir not supported")
|
||||
}
|
||||
|
|
@ -764,10 +795,6 @@ func (s *Server) create(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
if err := s.memStore.Create(fc.Name); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
case "/t":
|
||||
if err := s.toolStore.Create(fc.Name); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
case "/tmp":
|
||||
if err := s.tmpStore.Create(fc.Name); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
|
|
@ -777,10 +804,19 @@ func (s *Server) create(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
return errFcall(fc, err.Error())
|
||||
}
|
||||
default:
|
||||
if strings.HasPrefix(f.path, "/t/") {
|
||||
rel := strings.TrimPrefix(f.path, "/t/") + "/" + fc.Name
|
||||
if err := s.toolStore.Create(rel); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
// Tool creates under session: /s/{sid}/t or /s/{sid}/t/...
|
||||
if strings.HasPrefix(f.path, "/s/") {
|
||||
parts := strings.SplitN(strings.TrimPrefix(f.path, "/s/"), "/", 3)
|
||||
if (len(parts) == 2 && parts[1] == "t") || (len(parts) == 3 && parts[1] == "t") {
|
||||
var rel string
|
||||
if len(parts) == 2 {
|
||||
rel = fc.Name
|
||||
} else {
|
||||
rel = parts[2] + "/" + fc.Name
|
||||
}
|
||||
if err := s.toolStore.Create(rel); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -880,16 +916,6 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
|
|||
return s.readSlice(fc, content)
|
||||
}
|
||||
|
||||
// Tool files are served from the tool store.
|
||||
if strings.HasPrefix(path, "/t/") {
|
||||
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
|
||||
content, err := storeReadCtx(ctx, s.toolStore, strings.TrimPrefix(path, "/t/"))
|
||||
if err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
return s.readSlice(fc, content)
|
||||
}
|
||||
|
||||
// Util files are served from the util store.
|
||||
if strings.HasPrefix(path, "/u/") {
|
||||
s.log.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
|
||||
|
|
@ -934,6 +960,25 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall, ctx context.Context) *plan
|
|||
if strings.HasPrefix(path, "/s/") {
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/"), "/", 3)
|
||||
if len(parts) == 3 {
|
||||
// Tool files under session: /s/{id}/t/{tool}
|
||||
if parts[2] == "t" || strings.HasPrefix(parts[2], "t/") {
|
||||
rel := strings.TrimPrefix(parts[2], "t/")
|
||||
if rel == "" {
|
||||
// reading the directory itself as a file — shouldn't happen
|
||||
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Count: 0}
|
||||
}
|
||||
s.log.Debug("Tread tool path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
|
||||
content, err := storeReadCtx(ctx, s.toolStore, rel)
|
||||
if err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
if rel == "idx" {
|
||||
if allowed := s.sessionAllowTools(parts[1]); allowed != nil {
|
||||
content = filterToolIndex(content, allowed)
|
||||
}
|
||||
}
|
||||
return s.readSlice(fc, content)
|
||||
}
|
||||
s.log.Debug("Tread session file path=%q offset=%d count=%d", path, fc.Offset, fc.Count)
|
||||
sfs, ok := s.sessionFileStore(parts[1])
|
||||
if !ok {
|
||||
|
|
@ -1093,26 +1138,25 @@ func (s *Server) wstat(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
f.qid.Path = qidPath(f.path)
|
||||
cs.mu.Unlock()
|
||||
|
||||
case strings.HasPrefix(f.path, "/t/"):
|
||||
oldRel := strings.TrimPrefix(f.path, "/t/")
|
||||
parentRel := oldRel[:len(oldRel)-len(oldName)]
|
||||
newRel := parentRel + newDir.Name
|
||||
if err := s.toolStore.Rename(oldRel, newRel); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
cs.mu.Lock()
|
||||
f.path = "/t/" + newRel
|
||||
f.qid.Path = qidPath(f.path)
|
||||
cs.mu.Unlock()
|
||||
|
||||
case strings.HasPrefix(f.path, "/s/") && f.path != "/s/new":
|
||||
// Session directory rename: /s/{old} -> /s/{new}
|
||||
parts := strings.SplitN(strings.TrimPrefix(f.path, "/"), "/", 3)
|
||||
if len(parts) != 2 || parts[0] != "s" {
|
||||
return errFcall(fc, "rename not supported")
|
||||
}
|
||||
if err := s.sessionStore.Rename(parts[1], newDir.Name); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
case strings.HasPrefix(f.path, "/s/"):
|
||||
parts := strings.SplitN(strings.TrimPrefix(f.path, "/s/"), "/", 3)
|
||||
if len(parts) == 3 && parts[1] == "t" {
|
||||
// Tool rename under session: /s/{sid}/t/{rel}
|
||||
oldRel := parts[2]
|
||||
parentRel := oldRel[:len(oldRel)-len(oldName)]
|
||||
newRel := parentRel + newDir.Name
|
||||
if err := s.toolStore.Rename(oldRel, newRel); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
cs.mu.Lock()
|
||||
f.path = "/s/" + parts[0] + "/t/" + newRel
|
||||
f.qid.Path = qidPath(f.path)
|
||||
cs.mu.Unlock()
|
||||
} else if len(parts) == 1 && f.path != "/s/new" {
|
||||
// Session directory rename: /s/{old} -> /s/{new}
|
||||
if err := s.sessionStore.Rename(parts[0], newDir.Name); err != nil {
|
||||
return errFcall(fc, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
@ -1187,18 +1231,28 @@ func (s *Server) remove(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
err = s.memStore.Delete(pathBase(path))
|
||||
case strings.HasPrefix(path, "/sk/"):
|
||||
err = s.skillStore.Delete(pathBase(path))
|
||||
case strings.HasPrefix(path, "/t/"):
|
||||
err = s.toolStore.Delete(strings.TrimPrefix(path, "/t/"))
|
||||
case strings.HasPrefix(path, "/tmp/"):
|
||||
err = s.tmpStore.Delete(pathBase(path))
|
||||
case strings.HasPrefix(path, "/tr/"):
|
||||
err = s.transcriptStore.Delete(pathBase(path))
|
||||
case strings.HasPrefix(path, "/s/") && path != "/s/new":
|
||||
// If path is /s/{id}/{file}, it's a synthetic session file — no-op so
|
||||
// that "rm -r s/{id}" can proceed to remove the directory itself, which
|
||||
// triggers the actual session kill via Delete({id}).
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 2)
|
||||
if len(parts) == 2 {
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 3)
|
||||
if len(parts) >= 2 && (parts[1] == "t" || strings.HasPrefix(parts[1], "t/")) {
|
||||
// Tool remove: /s/{sid}/t/{tool}
|
||||
// Reconstruct: parts[0]=sid, rest starts with t/...
|
||||
// Actually with SplitN(..., 3): parts = [sid, "t", rest] or [sid, "t"]
|
||||
// But we split on /s/ prefix first, so path="/s/sid/t/file" -> "sid/t/file" split into ["sid","t","file"] or ["sid","t/file"]
|
||||
// Wait — SplitN("sid/t/file", "/", 3) = ["sid", "t", "file"]
|
||||
// SplitN("sid/t/sub/file", "/", 3) = ["sid", "t", "sub/file"]
|
||||
if len(parts) == 2 && parts[1] == "t" {
|
||||
err = nil // can't remove the t/ dir itself
|
||||
} else if len(parts) == 3 && parts[1] == "t" {
|
||||
err = s.toolStore.Delete(parts[2])
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
} else if len(parts) >= 2 {
|
||||
// Session file remove
|
||||
err = nil // synthetic file; let rm -r continue
|
||||
} else {
|
||||
err = s.sessionStore.Delete(parts[0])
|
||||
|
|
@ -1240,11 +1294,6 @@ func (s *Server) handleWrite(path, input string) error {
|
|||
return storeWrite(s.skillStore, pathBase(path), []byte(input))
|
||||
}
|
||||
|
||||
// Tool file writes go to the tool store.
|
||||
if strings.HasPrefix(path, "/t/") {
|
||||
return storeWrite(s.toolStore, strings.TrimPrefix(path, "/t/"), []byte(input))
|
||||
}
|
||||
|
||||
// Tmp file writes go to the tmp store.
|
||||
if strings.HasPrefix(path, "/tmp/") {
|
||||
return storeWrite(s.tmpStore, pathBase(path), []byte(input))
|
||||
|
|
@ -1254,6 +1303,11 @@ func (s *Server) handleWrite(path, input string) error {
|
|||
if strings.HasPrefix(path, "/s/") {
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/"), "/", 3)
|
||||
if len(parts) == 3 {
|
||||
// Tool file writes: /s/{id}/t/{tool}
|
||||
if strings.HasPrefix(parts[2], "t/") {
|
||||
rel := strings.TrimPrefix(parts[2], "t/")
|
||||
return storeWrite(s.toolStore, rel, []byte(input))
|
||||
}
|
||||
sfs, ok := s.sessionFileStore(parts[1])
|
||||
if !ok {
|
||||
return fmt.Errorf("session not found: %s", parts[1])
|
||||
|
|
@ -1295,7 +1349,6 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte {
|
|||
dirs = append(dirs, makeDir("p", "/p", true, plan9.DMDIR|0555))
|
||||
dirs = append(dirs, makeDir("s", "/s", true, plan9.DMDIR|0555))
|
||||
dirs = append(dirs, makeDir("sk", "/sk", true, plan9.DMDIR|0555))
|
||||
dirs = append(dirs, makeDir("t", "/t", true, plan9.DMDIR|0777))
|
||||
dirs = append(dirs, makeDir("tmp", "/tmp", true, plan9.DMDIR|0755))
|
||||
dirs = append(dirs, makeDir("u", "/u", true, plan9.DMDIR|0755))
|
||||
dirs = append(dirs, makeDir("x", "/x", true, plan9.DMDIR|0555))
|
||||
|
|
@ -1335,21 +1388,6 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte {
|
|||
}
|
||||
dirs = append(dirs, makeDir(e.Name(), "/sk/"+e.Name(), false, mode))
|
||||
}
|
||||
} else if path == "/t" {
|
||||
entries, _ := s.toolStore.List()
|
||||
for _, e := range entries {
|
||||
mode := plan9.Perm(0777)
|
||||
if e.Name() == "idx" {
|
||||
mode = 0444
|
||||
}
|
||||
dirs = append(dirs, makeDir(e.Name(), "/t/"+e.Name(), e.IsDir(), mode|plan9.Perm(boolToDir(e.IsDir()))))
|
||||
}
|
||||
} else if strings.HasPrefix(path, "/t/") {
|
||||
rel := strings.TrimPrefix(path, "/t/")
|
||||
entries, _ := s.toolStore.ListDir(rel)
|
||||
for _, e := range entries {
|
||||
dirs = append(dirs, makeDir(e.Name(), path+"/"+e.Name(), e.IsDir(), 0777|plan9.Perm(boolToDir(e.IsDir()))))
|
||||
}
|
||||
} else if path == "/tmp" {
|
||||
entries, _ := s.tmpStore.List()
|
||||
for _, e := range entries {
|
||||
|
|
@ -1395,15 +1433,46 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte {
|
|||
}
|
||||
dirs = append(dirs, makeDir(e.Name(), "/s/"+e.Name(), e.IsDir(), perm))
|
||||
}
|
||||
} else {
|
||||
// Session subdirectory: /s/{sessid}
|
||||
sessID := pathBase(path)
|
||||
if sfs, ok := s.sessionFileStore(sessID); ok {
|
||||
entries, _ := sfs.List()
|
||||
for _, e := range entries {
|
||||
info, _ := e.Info()
|
||||
dirs = append(dirs, makeDir(e.Name(), path+"/"+e.Name(), false, plan9.Perm(info.Mode())))
|
||||
} else if strings.HasPrefix(path, "/s/") {
|
||||
// /s/{sid}/t — list tools
|
||||
// /s/{sid}/t/{subdir} — list tool subdirectory
|
||||
// /s/{sid} — list session files + t/
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 3)
|
||||
if len(parts) >= 2 && parts[1] == "t" {
|
||||
if len(parts) == 2 {
|
||||
// /s/{sid}/t
|
||||
entries, _ := s.toolStore.List()
|
||||
allowed := s.sessionAllowTools(parts[0])
|
||||
for _, e := range entries {
|
||||
if e.Name() == "idx" {
|
||||
dirs = append(dirs, makeDir(e.Name(), path+"/"+e.Name(), false, 0444))
|
||||
continue
|
||||
}
|
||||
if allowed != nil && !allowed[e.Name()] {
|
||||
continue
|
||||
}
|
||||
mode := plan9.Perm(0777)
|
||||
dirs = append(dirs, makeDir(e.Name(), path+"/"+e.Name(), e.IsDir(), mode|plan9.Perm(boolToDir(e.IsDir()))))
|
||||
}
|
||||
} else {
|
||||
// /s/{sid}/t/{subdir...}
|
||||
rel := parts[2]
|
||||
entries, _ := s.toolStore.ListDir(rel)
|
||||
for _, e := range entries {
|
||||
dirs = append(dirs, makeDir(e.Name(), path+"/"+e.Name(), e.IsDir(), 0777|plan9.Perm(boolToDir(e.IsDir()))))
|
||||
}
|
||||
}
|
||||
} else if len(parts) == 1 {
|
||||
// /s/{sid} — session files + t/
|
||||
sessID := parts[0]
|
||||
if sfs, ok := s.sessionFileStore(sessID); ok {
|
||||
entries, _ := sfs.List()
|
||||
for _, e := range entries {
|
||||
info, _ := e.Info()
|
||||
dirs = append(dirs, makeDir(e.Name(), path+"/"+e.Name(), false, plan9.Perm(info.Mode())))
|
||||
}
|
||||
}
|
||||
dirs = append(dirs, makeDir("t", path+"/t", true, plan9.DMDIR|0777))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1454,8 +1523,13 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
var mode plan9.Perm
|
||||
if isDir {
|
||||
qid.Type = QTDir
|
||||
if path == "/t" || strings.HasPrefix(path, "/t/") {
|
||||
mode = plan9.DMDIR | 0777
|
||||
if strings.HasPrefix(path, "/s/") {
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 3)
|
||||
if (len(parts) == 2 && parts[1] == "t") || (len(parts) == 3 && parts[1] == "t") {
|
||||
mode = plan9.DMDIR | 0777
|
||||
} else {
|
||||
mode = plan9.DMDIR | 0555
|
||||
}
|
||||
} else if path == "/a" || path == "/m" || path == "/tmp" || path == "/u" {
|
||||
mode = plan9.DMDIR | 0755
|
||||
} else {
|
||||
|
|
@ -1487,10 +1561,15 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
mode = 0444
|
||||
} else if strings.HasPrefix(path, "/sk/") {
|
||||
mode = 0666
|
||||
} else if path == "/t/idx" {
|
||||
mode = 0444
|
||||
} else if strings.HasPrefix(path, "/t/") {
|
||||
mode = 0777
|
||||
} else if strings.HasPrefix(path, "/s/") {
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 3)
|
||||
if len(parts) == 3 && parts[1] == "t" && parts[2] == "idx" {
|
||||
mode = 0444
|
||||
} else if len(parts) == 3 && parts[1] == "t" {
|
||||
mode = 0777
|
||||
} else {
|
||||
mode = 0444
|
||||
}
|
||||
} else if strings.HasPrefix(path, "/u/") {
|
||||
mode = 0555
|
||||
} else if strings.HasPrefix(path, "/x/") {
|
||||
|
|
@ -1561,9 +1640,12 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
if content, err := storeRead(s.skillStore, base); err == nil {
|
||||
dir.Length = uint64(len(content))
|
||||
}
|
||||
case strings.HasPrefix(path, "/t/"):
|
||||
if content, err := storeRead(s.toolStore, strings.TrimPrefix(path, "/t/")); err == nil {
|
||||
dir.Length = uint64(len(content))
|
||||
case strings.HasPrefix(path, "/s/") && strings.Contains(path, "/t/"):
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 3)
|
||||
if len(parts) == 3 && parts[1] == "t" {
|
||||
if content, err := storeRead(s.toolStore, parts[2]); err == nil {
|
||||
dir.Length = uint64(len(content))
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(path, "/u/"):
|
||||
if content, err := storeRead(s.utilStore, base); err == nil {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ type Session struct {
|
|||
ChatOffset int
|
||||
plan []byte
|
||||
prevPrompt []byte // last submitted prompt; overwritten on each new submission
|
||||
AllowTools []string // if non-empty, only these tools are visible/executable
|
||||
}
|
||||
|
||||
func NewSession(id string, core agent.Core, ctx context.Context, cancel context.CancelFunc) *Session {
|
||||
|
|
@ -356,6 +357,7 @@ func (s *SessionStore) createSession(args []string) error {
|
|||
}
|
||||
|
||||
var core agent.Core
|
||||
var allowTools []string
|
||||
if s.cfg.NewCore != nil {
|
||||
var err error
|
||||
core, err = s.cfg.NewCore(sessID, agentName, cwd)
|
||||
|
|
@ -380,6 +382,9 @@ func (s *SessionStore) createSession(args []string) error {
|
|||
}
|
||||
|
||||
cfg := LoadAgentConfig(s.cfg.AgentsDir, agentName, nil)
|
||||
if cfg != nil {
|
||||
allowTools = cfg.AllowTools
|
||||
}
|
||||
|
||||
var execOpts []execute.Option
|
||||
if s.cfg.Strict {
|
||||
|
|
@ -388,6 +393,12 @@ func (s *SessionStore) createSession(args []string) error {
|
|||
if s.cfg.Yolo {
|
||||
execOpts = append(execOpts, execute.WithYolo())
|
||||
}
|
||||
if cfg != nil && len(cfg.AllowExecutors) > 0 {
|
||||
execOpts = append(execOpts, execute.WithAllowExecutors(cfg.AllowExecutors))
|
||||
}
|
||||
if cfg != nil && len(cfg.AllowTools) > 0 {
|
||||
execOpts = append(execOpts, execute.WithAllowTools(cfg.AllowTools))
|
||||
}
|
||||
newDisp := tools.NewDispatcherFunc(map[string]func() tools.Server{
|
||||
"execute": execute.Decl(cwd, execOpts...),
|
||||
})
|
||||
|
|
@ -409,6 +420,7 @@ func (s *SessionStore) createSession(args []string) error {
|
|||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
sess := NewSession(sessID, core, ctx, cancel)
|
||||
sess.AllowTools = allowTools
|
||||
|
||||
s.mu.Lock()
|
||||
s.sessions[sessID] = sess
|
||||
|
|
|
|||
Reference in New Issue