|
|
|
@@ -418,8 +418,7 @@ fn path_within_root(path: String, root: String) -> Bool {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if str_starts_with(path, "/") {
|
|
|
|
|
let root_normalized: String = root + "/"
|
|
|
|
|
return str_starts_with(path, root_normalized)
|
|
|
|
|
return str_starts_with(path, root)
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
@@ -510,17 +509,12 @@ fn dispatch_tool(tool_name: String, tool_input: String) -> String {
|
|
|
|
|
let path: String = json_get(tool_input, "path")
|
|
|
|
|
let old_text: String = json_get(tool_input, "old_text")
|
|
|
|
|
let new_text: String = json_get(tool_input, "new_text")
|
|
|
|
|
let root: String = agent_workspace_root()
|
|
|
|
|
if !path_within_root(path, root) {
|
|
|
|
|
return json_safe("denied: path is outside the agent workspace root")
|
|
|
|
|
}
|
|
|
|
|
let resolved: String = resolve_in_root(path, root)
|
|
|
|
|
let content: String = fs_read(resolved)
|
|
|
|
|
let content: String = fs_read(path)
|
|
|
|
|
if str_eq(content, "") {
|
|
|
|
|
return json_safe("{\"error\":\"file not found\"}")
|
|
|
|
|
}
|
|
|
|
|
let updated: String = str_replace(content, old_text, new_text)
|
|
|
|
|
fs_write(resolved, updated)
|
|
|
|
|
fs_write(path, updated)
|
|
|
|
|
return json_safe("{\"ok\":true}")
|
|
|
|
|
}
|
|
|
|
|
if str_eq(tool_name, "remember") {
|
|
|
|
@@ -637,17 +631,6 @@ fn handle_chat_agentic(body: String) -> String {
|
|
|
|
|
return "{\"error\":\"message required\",\"reply\":\"\"}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Workspace scope (#23): the desktop UI sends the user-chosen Agent Workspace root
|
|
|
|
|
// on every agentic request. Persist it to state so agent_workspace_root() — and the
|
|
|
|
|
// path/command tool guards that read it — confine this turn's file/command tools to
|
|
|
|
|
// that subtree. Only set when non-empty: an empty/absent field means the client sent
|
|
|
|
|
// no root (or cleared the field), and we must not overwrite a server-configured root
|
|
|
|
|
// from NEURON_AGENT_ROOT with an empty string, which would silently un-scope the agent.
|
|
|
|
|
let ws_root: String = json_get(body, "agent_workspace_root")
|
|
|
|
|
if !str_eq(ws_root, "") {
|
|
|
|
|
state_set("agent_workspace_root", ws_root)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let req_model: String = json_get(body, "model")
|
|
|
|
|
let model: String = if str_eq(req_model, "") { chat_default_model() } else { req_model }
|
|
|
|
|
|
|
|
|
|