mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 04:23:15 -05:00
refactor(studio): word the command provider text without naming an add-on
The prompt, the Tools menu reasons, the network error texts, the CLI help,
the API instructions and the assistant tool descriptions said "Sandbox" for
the add-on that runs commands. Core must not know that name.
The reason a chat has no commands now uses the title the provider declares
("the Zeta add-on is off for this chat") or, when none is installed, says
"the add-on that runs commands". Text that has no provider in hand speaks of
the network of commands or of an add-on that isolates them.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
22 files changed
+64
-50
No files matched your search
@@ -48,7 +48,7 @@ func (l *lastGood[T]) get(raw string) T {
|
||||
}
|
||||
if l.logged != raw {
|
||||
l.logged = raw
|
||||
xlog.Warn("Chats: a Sandbox network setting is not valid; the last valid value stays in force", "setting", l.name, "kept_last_valid", l.ok, "error", err)
|
||||
xlog.Warn("Chats: a command network setting is not valid; the last valid value stays in force", "setting", l.name, "kept_last_valid", l.ok, "error", err)
|
||||
}
|
||||
return keep
|
||||
}
|
||||
@@ -499,7 +499,7 @@ func (a *Application) initStudioEgressLists() {
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// studioEgressAudit opens the audit of the Sandbox network requests, beside
|
||||
// studioEgressAudit opens the audit of the command network requests, beside
|
||||
// the audit of the commands run on this machine.
|
||||
func (a *Application) studioEgressAudit() audit.Store {
|
||||
var (
|
||||
@@ -515,7 +515,7 @@ func (a *Application) studioEgressAudit() audit.Store {
|
||||
st, err = audit.NewJSON("")
|
||||
}
|
||||
if err != nil {
|
||||
xlog.Warn("Chats: the audit of the Sandbox network is kept in memory", "error", err)
|
||||
xlog.Warn("Chats: the audit of the command network is kept in memory", "error", err)
|
||||
st, _ = audit.NewJSON("")
|
||||
}
|
||||
return st
|
||||
@@ -532,9 +532,9 @@ func (a *Application) startStudioEgressPrune(svc *studio.Service) {
|
||||
prune := func() {
|
||||
n, err := svc.PruneEgressAudit(ctx)
|
||||
if err != nil {
|
||||
xlog.Warn("Chats: could not prune the Sandbox network audit", "error", err)
|
||||
xlog.Warn("Chats: could not prune the command network audit", "error", err)
|
||||
} else if n > 0 {
|
||||
xlog.Info("Chats: pruned the Sandbox network audit", "lines", n)
|
||||
xlog.Info("Chats: pruned the command network audit", "lines", n)
|
||||
}
|
||||
n, err = svc.PruneHostExecAudit(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
// webSettingsHint is where an administrator fixes the search. It is part of
|
||||
// the error a user reads when no provider works, after what each provider did.
|
||||
const webSettingsHint = "an administrator can set a SearXNG instance in Settings > Tools, or install the Web add-on for search providers with a key from Add-ons"
|
||||
const webSettingsHint = "an administrator can set a SearXNG instance in Settings > Tools, or install an add-on that provides search providers with a key from Add-ons"
|
||||
|
||||
// studioWeb serves the built-in web tools of the chats. It reads the runtime
|
||||
// settings at each call and builds the fetch client and the searcher again
|
||||
|
||||
@@ -115,7 +115,7 @@ var _ = Describe("the chats' built-in web tools", func() {
|
||||
cfg.ChatsWebSearchProviders = "duckduckgo,bing_html"
|
||||
_, err := w.Search(context.Background(), "alice", json.RawMessage(`{"query":"localai"}`))
|
||||
Expect(err).To(MatchError("no search provider works. Tried duckduckgo: challenged by a bot check; bing_html: blocked this server (429 Too Many Requests). " +
|
||||
"An administrator can set a SearXNG instance in Settings > Tools, or install the Web add-on for search providers with a key from Add-ons."))
|
||||
"An administrator can set a SearXNG instance in Settings > Tools, or install an add-on that provides search providers with a key from Add-ons."))
|
||||
_, _, _, attempts, err := w.TestSearch(context.Background(), "root", "localai")
|
||||
Expect(err).To(MatchError(ContainSubstring("no search provider works. Tried duckduckgo: challenged")))
|
||||
Expect(attempts).To(HaveLen(2))
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ type RunCMD struct {
|
||||
// LocalAI Assistant chat modality (in-process admin MCP server)
|
||||
DisableLocalAIAssistant bool `env:"LOCALAI_DISABLE_ASSISTANT" default:"false" help:"Disable the LocalAI Assistant chat modality (in-process admin MCP server)" group:"assistant"`
|
||||
DisableSharedPages bool `env:"LOCALAI_DISABLE_SHARED_PAGES" default:"false" help:"Turn off the pages of shared folders (they can be switched on again in the runtime settings)" group:"assistant"`
|
||||
ChatsHostExec string `env:"LOCALAI_CHATS_HOST_EXEC" default:"" enum:",off,ask,on" help:"Who gets the chats' built-in bash, which runs commands on this machine with no isolation and with its network: off, ask (administrators, who allow each command) or on. Empty: off with accounts, ask without. The Sandbox add-on replaces it with isolated commands" group:"assistant"`
|
||||
ChatsHostExec string `env:"LOCALAI_CHATS_HOST_EXEC" default:"" enum:",off,ask,on" help:"Who gets the chats' built-in bash, which runs commands on this machine with no isolation and with its network: off, ask (administrators, who allow each command) or on. Empty: off with accounts, ask without. An add-on that runs commands in isolation replaces it" group:"assistant"`
|
||||
|
||||
// Removed agent, job and skill options: accepted and ignored.
|
||||
RemovedAgentOptions `embed:""`
|
||||
|
||||
File diff suppressed because one or more lines are too long.
@@ -144,7 +144,7 @@ var chatRules = []errRule{
|
||||
fixed(http.StatusNotFound, typeNotFound, "no such network card", studiosvc.ErrEgressNoCard),
|
||||
fixed(http.StatusBadRequest, typeInvalid, "the answer must be once, chat or deny", studiosvc.ErrEgressBadDecision),
|
||||
fixed(http.StatusConflict, "already_answered", "the network card was answered already with another answer", studiosvc.ErrEgressAnswered),
|
||||
fixed(http.StatusBadRequest, "egress_above_ceiling", "the limit an administrator set for the Sandbox network does not admit this host; an administrator can raise it on the Settings tab of the add-on that runs the commands", studiosvc.ErrEgressHostAboveCeiling),
|
||||
fixed(http.StatusBadRequest, "egress_above_ceiling", "the limit an administrator set for the network of commands does not admit this host; an administrator can raise it on the Settings tab of the add-on that runs the commands", studiosvc.ErrEgressHostAboveCeiling),
|
||||
fixed(http.StatusForbidden, typeAuthz, "only an administrator can allow a host inside your network; anyone can deny it", studiosvc.ErrEgressDecision),
|
||||
verbatim(http.StatusConflict, typeConflict, "", studiosvc.ErrGoalNotAllowed, studiosvc.ErrGoalReached),
|
||||
verbatim(http.StatusNotFound, typeNotFound, "", studiosvc.ErrNoGoal),
|
||||
|
||||
@@ -321,6 +321,16 @@ func commandsTitle(c Commands) string {
|
||||
return "the add-on"
|
||||
}
|
||||
|
||||
// commandsPhrase names the add-on that runs commands in a sentence: the
|
||||
// declared title when there is one, else a generic description. The title comes
|
||||
// from the add-on's declaration, never from this file.
|
||||
func commandsPhrase(c Commands) string {
|
||||
if c.Title != "" {
|
||||
return "the " + c.Title + " add-on"
|
||||
}
|
||||
return "the add-on that runs commands"
|
||||
}
|
||||
|
||||
// commandsBlock is the block of the system prompt that states whether the chat
|
||||
// can run commands. writeFile says whether the chat has write_file,
|
||||
// fileTools whether it has the file tools to read them, and skillsOff
|
||||
@@ -365,9 +375,9 @@ func commandsBlock(c Commands, writeFile, fileTools, skillsOff bool, place sessi
|
||||
case CommandsSwitchedOff:
|
||||
b.WriteString("they switched running commands off for this chat and can switch Run commands on in its Tools menu.")
|
||||
case CommandsOff:
|
||||
b.WriteString("they can switch the " + commandsTitle(c) + " add-on on in the Tools menu of this chat.")
|
||||
b.WriteString("they can switch " + commandsPhrase(c) + " on in the Tools menu of this chat.")
|
||||
case CommandsUnavailable:
|
||||
b.WriteString("the " + commandsTitle(c) + " add-on cannot run commands on this server; an administrator can fix that.")
|
||||
b.WriteString(commandsPhrase(c) + " cannot run commands on this server; an administrator can fix that.")
|
||||
default:
|
||||
b.WriteString("an administrator can enable running code by installing an add-on that runs commands.")
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ var _ = Describe("commands of a scoped chat", func() {
|
||||
g := shellGroup(r.svc, alice, rec.ID)
|
||||
Expect(g.Usable).To(BeFalse())
|
||||
Expect(g.Reason).To(ContainSubstring("works in one folder"))
|
||||
Expect(g.Reason).To(ContainSubstring("Sandbox"))
|
||||
Expect(g.Reason).To(ContainSubstring("only in an add-on that isolates them"))
|
||||
cmds, err := r.svc.CommandRunner(r.ctx, alice, rec.ID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds.State).NotTo(Equal(studio.CommandsAvailable))
|
||||
|
||||
@@ -504,7 +504,7 @@ func Describe(p Preset, reg *Registries, asks bool) string {
|
||||
case PresetRegistries:
|
||||
return "Network for commands: package registries only (" + strings.Join(reg.Titles(), ", ") + ")." + tail
|
||||
case PresetAll:
|
||||
return "Network for commands: all public sites, through the proxy the sandbox sets." + tail
|
||||
return "Network for commands: all public sites, through the proxy of the environment that runs them." + tail
|
||||
}
|
||||
return "Network for commands: off." + tail
|
||||
}
|
||||
@@ -517,10 +517,10 @@ func Describe(p Preset, reg *Registries, asks bool) string {
|
||||
func DescribeLimit(eff, ceiling Preset, reg *Registries, asks bool) string {
|
||||
const where = "An administrator can change it on the Settings tab of the add-on that runs the commands, on the Add-ons page."
|
||||
if normal(ceiling) == PresetOff {
|
||||
return "Network for commands: off. The server's setting for the Sandbox network does not allow any access, and no user approval changes that, so do not wait for one or retry a blocked request. " +
|
||||
return "Network for commands: off. The server's setting for the network of commands does not allow any access, and no user approval changes that, so do not wait for one or retry a blocked request. " +
|
||||
"Tell the user the network is off by the server's setting. " + where
|
||||
}
|
||||
return Describe(eff, reg, asks) + " The server's setting for the Sandbox network limits it; a host outside the limit is blocked by that setting, not by a missing approval, so do not retry it. Tell the user. " + where
|
||||
return Describe(eff, reg, asks) + " The server's setting for the network of commands limits it; a host outside the limit is blocked by that setting, not by a missing approval, so do not retry it. Tell the user. " + where
|
||||
}
|
||||
|
||||
// Hint is the sentence after Describe in the turn's note: which tool to use
|
||||
@@ -533,7 +533,7 @@ func Hint(p Preset, web bool) string {
|
||||
return "curl, pip and git cannot reach anything now; ask the user to turn the network on if a task needs it."
|
||||
}
|
||||
if web {
|
||||
return "curl, pip and git work in the sandbox through the proxy. To read or search web pages use web_fetch or web_search; use curl for APIs, downloads and installs."
|
||||
return "curl, pip and git work in the isolated environment through the proxy. To read or search web pages use web_fetch or web_search; use curl for APIs, downloads and installs."
|
||||
}
|
||||
return "curl, pip and git work in the sandbox through the proxy."
|
||||
return "curl, pip and git work in the isolated environment through the proxy."
|
||||
}
|
||||
@@ -283,7 +283,7 @@ var _ = Describe("the registries list", func() {
|
||||
h := egress.Hint(egress.PresetOff, web)
|
||||
Expect(h).To(ContainSubstring("cannot reach anything"))
|
||||
Expect(h).NotTo(ContainSubstring("web_"))
|
||||
Expect(h).NotTo(ContainSubstring("work in the sandbox"))
|
||||
Expect(h).NotTo(ContainSubstring("work in the isolated environment"))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -46,7 +46,7 @@ var (
|
||||
// ErrEgressHostAboveCeiling means the administrator's limit does not admit
|
||||
// the host of the card, so only an administrator can make it work, by
|
||||
// raising the limit. The card is refused and stays so.
|
||||
ErrEgressHostAboveCeiling = errors.New("studio: the limit an administrator set for the Sandbox network does not admit this host")
|
||||
ErrEgressHostAboveCeiling = errors.New("studio: the limit an administrator set for the network of commands does not admit this host")
|
||||
)
|
||||
|
||||
// EgressDecision is the answer of a card, sent on the chat bus to the check
|
||||
|
||||
@@ -224,7 +224,7 @@ func (s *Service) EgressAudit(ctx context.Context, user, rid string, evs []Egres
|
||||
|
||||
func (s *Service) auditEgress(ctx context.Context, e audit.Event) {
|
||||
if err := s.egressAudit().Add(context.WithoutCancel(ctx), e); err != nil {
|
||||
s.log.Warn("Chats: could not audit a Sandbox network request", "error", err)
|
||||
s.log.Warn("Chats: could not audit a command network request", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,11 +101,10 @@ func (s *Service) isAdmin(ctx context.Context, user string) bool {
|
||||
func (s *Service) registerHostBash() error {
|
||||
host := s.d.HostExec != nil
|
||||
return s.builtins.Register(builtins.Tool{
|
||||
ID: commandTool,
|
||||
Group: builtins.GroupShell,
|
||||
Label: "Commands (bash)",
|
||||
Description: "Runs commands and code for the chats directly on this machine, with no isolation: commands can read what LocalAI can read " +
|
||||
"and reach the internet and your network. The Sandbox add-on replaces it with isolated commands.",
|
||||
ID: commandTool,
|
||||
Group: builtins.GroupShell,
|
||||
Label: "Commands (bash)",
|
||||
Description: hostBashDescription,
|
||||
Setting: builtins.SettingHostExec,
|
||||
Unlisted: !host,
|
||||
Replaceable: true,
|
||||
@@ -115,10 +114,15 @@ func (s *Service) registerHostBash() error {
|
||||
})
|
||||
}
|
||||
|
||||
// hostBashDescription is the line of Settings > Tools for the built-in bash.
|
||||
// It says that an add-on can replace it and names none.
|
||||
const hostBashDescription = "Runs commands and code for the chats directly on this machine, with no isolation: commands can read what LocalAI can read " +
|
||||
"and reach the internet and your network. An add-on that runs commands in isolation can replace it."
|
||||
|
||||
// scopedHostBashReason is why a chat whose agent works in one folder has no
|
||||
// built-in bash (D10 of the agent folder design): a command on this machine
|
||||
// reads what the server can read, and no workspace rule confines it.
|
||||
const scopedHostBashReason = "this agent works in one folder, and commands on this machine could read files outside it; it runs commands only in the Sandbox add-on"
|
||||
const scopedHostBashReason = "this agent works in one folder, and commands on this machine could read files outside it; it runs commands only in an add-on that isolates them"
|
||||
|
||||
// buildHostBash gives a chat the built-in bash when the setting allows it.
|
||||
// A chat whose actor is fenced (its agent, or its parent's, has a folder)
|
||||
|
||||
@@ -177,7 +177,7 @@ var ErrScoped = errors.New("hostexec: this agent works in one folder, and a comm
|
||||
|
||||
// ScopedText is the result of a call for a chat whose agent works in one
|
||||
// folder.
|
||||
const ScopedText = "Not run: this agent works in one folder, and a command on this machine could read files outside it. Commands for this agent run only in the Sandbox add-on."
|
||||
const ScopedText = "Not run: this agent works in one folder, and a command on this machine could read files outside it. Commands for this agent run only in an add-on that isolates them."
|
||||
|
||||
// Run runs one call of bash for a in its chat's scratch folder.
|
||||
func (r *Runner) Run(ctx context.Context, a workspace.Actor, args json.RawMessage) (string, error) {
|
||||
|
||||
@@ -154,17 +154,17 @@ func shellSwitched(c Commands, hidden map[string]bool) Commands {
|
||||
func shellReason(c Commands, buildReason string) string {
|
||||
switch c.State {
|
||||
case CommandsOff:
|
||||
return "the Sandbox add-on is off for this chat"
|
||||
return commandsPhrase(c) + " is off for this chat"
|
||||
case CommandsUnavailable:
|
||||
if c.Reason != "" {
|
||||
return "the Sandbox add-on cannot run commands here: " + c.Reason
|
||||
return commandsPhrase(c) + " cannot run commands here: " + c.Reason
|
||||
}
|
||||
return "the Sandbox add-on cannot run commands here"
|
||||
return commandsPhrase(c) + " cannot run commands here"
|
||||
}
|
||||
if buildReason != "" && buildReason != builtins.ReasonOff {
|
||||
return buildReason
|
||||
}
|
||||
return "nothing here runs commands: no Sandbox add-on is installed and commands on this machine are off"
|
||||
return "nothing here runs commands: no add-on that runs commands is installed and commands on this machine are off"
|
||||
}
|
||||
|
||||
// filesNote is the line under the chat's file list when the chat has no
|
||||
|
||||
@@ -963,7 +963,7 @@ shown when the Sandbox is already installed, or when add-ons are off.
|
||||
server log has one line per command with the prefix `[host-exec]`.
|
||||
Administrators read the audit with `GET /api/chats/admin/host-exec/events`
|
||||
or the assistant tool `list_host_exec_events`. Lines older than
|
||||
`chats_egress_audit_days` (default 30, the retention of the Sandbox network
|
||||
`chats_egress_audit_days` (default 30, the retention of the command network
|
||||
audit) are removed daily; in a cluster one frontend does it, under an
|
||||
advisory lock. The file store also keeps at most 10000 lines.
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ The groups reorganise the fields the page used to show under fifteen sections. T
|
||||
|
||||
Search at the top of the page matches a setting's name, description and key, the group, and the old section name, so a search for "Watchdog" still finds the watchdog settings and each result says where it used to be.
|
||||
|
||||
`/app/settings?group=<id>` opens a group (`memory`, `speed`, `backends`, `access`, `debug`, `agents`, `tools`, `swarm`, `look` or `addons`), and a hash after it brings a block of the group into view: `#settings-builtin-tools` (the built-in chat tools) on the Tools group. The old address of the Sandbox network, `/app/settings?group=tools#settings-egress`, opens the Settings tab of the add-on that holds it now (see below), or the Tools group when no add-on does.
|
||||
`/app/settings?group=<id>` opens a group (`memory`, `speed`, `backends`, `access`, `debug`, `agents`, `tools`, `swarm`, `look` or `addons`), and a hash after it brings a block of the group into view: `#settings-builtin-tools` (the built-in chat tools) on the Tools group. The old address of the command network, `/app/settings?group=tools#settings-egress`, opens the Settings tab of the add-on that holds it now (see below), or the Tools group when no add-on does.
|
||||
|
||||
The settings of an add-on are not on this page: each add-on keeps them on the **Settings** tab of its details on the Add-ons page. When at least one add-on has settings, an **Add-ons** group at the end of the list names those add-ons, each with a link to its Settings tab. See [Host slots]({{%relref "features/addons#host-slots" %}}). An add-on can also ask LocalAI to draw some of LocalAI's own settings on that tab, with a [host control]({{%relref "features/addons#host-controls" %}}). The rule: this page keeps the settings of the features that exist without any add-on (the commands on this machine, the built-in web tools), and points to the add-on where an add-on serves them; the settings that exist only for an add-on are on its tab. The Sandbox network below is one: with the Sandbox installed, the Tools group shows a row that links to its Settings tab, and neither the group nor the search lists those settings. A built-in tool row that an add-on with the `tool-providers` control serves links to that add-on's tab.
|
||||
|
||||
@@ -151,7 +151,7 @@ The **Tools** tab lists the built-in chat tools, from `GET /api/chats/admin/tool
|
||||
- **Search providers** (`chats_web_search_providers`, default empty): the providers of `web_search` in the order they are tried, comma separated: `searxng`, `duckduckgo`, `brave_html`, `bing_html`. Leave one out to never use it. Empty: `searxng` when an instance is set, else `duckduckgo,brave_html,bing_html`. An unknown name is refused. **Test search** on the `web_search` row says what each provider did (ok, challenged by a bot check, blocked, no results). See [Search providers]({{%relref "features/web#search-providers" %}}).
|
||||
- **Web pages may be on private networks** (`chats_web_allow_private`, default off): let `web_fetch` reach addresses inside your network. The cloud metadata addresses stay refused. Turn it on only if you trust every user of chats.
|
||||
- **Ports for web pages** (`chats_web_ports`, default `80,443`): the ports `web_fetch` may use, comma separated. A value that is not a list of ports is refused.
|
||||
- **Sandbox network** (applies at the next connection of a command; an allow the proxy cached lives at most 30 seconds). The commands a chat runs in the Sandbox reach the network only through the proxy of the Sandbox add-on, and these settings bound it. They are on the Settings tab of the add-on that runs the commands (its `network-policy` host control), not on this page; `chats_egress_audit_days` is on both, since it also keeps the audit of the commands on this machine. They do not apply to commands on this machine (`chats_host_exec`), which have the network of the server. See [Network access]({{%relref "features/addons#network-access" %}}).
|
||||
- **Command network** (applies at the next connection of a command; an allow the proxy cached lives at most 30 seconds). The commands a chat runs in isolation reach the network only through the proxy of the add-on that runs them, and these settings bound it. They are on the Settings tab of the add-on that runs the commands (its `network-policy` host control), not on this page; `chats_egress_audit_days` is on both, since it also keeps the audit of the commands on this machine. They do not apply to commands on this machine (`chats_host_exec`), which have the network of the server. See [Network access]({{%relref "features/addons#network-access" %}}).
|
||||
- `chats_egress_ceiling` (`off`, `registries` or `all`, default `off`): the widest preset any chat may use. `registries` admits the package registries only; `all` admits every public host. A chat chooses its own preset up to this.
|
||||
- `chats_egress_default` (`off`, `registries` or `all`, default `off`): the preset a new chat starts with, capped by the ceiling.
|
||||
- `chats_egress_allow_private` (default off): let a host that an administrator allowed, by a rule or a card answer, reach addresses inside your network. A preset alone never does, and the cloud metadata addresses and the server's own addresses stay refused. Turning it on needs `chats_egress_private_confirm` in the same request, with the phrase *I understand that commands can reach my private network*; the phrase is not stored. The add-on's Settings tab asks for it. With add-ons enabled the add-on owner checks the same phrase, so a direct `host-settings-update` call needs `chats_egress_private_confirm` in its patch too.
|
||||
|
||||
@@ -45,7 +45,7 @@ func ParseEgressPorts(s string) ([]int, error) {
|
||||
}
|
||||
for _, p := range ports {
|
||||
if p == 22 || p == 25 {
|
||||
return nil, fmt.Errorf("port %d is not allowed for the Sandbox network", p)
|
||||
return nil, fmt.Errorf("port %d is not allowed for the network of commands", p)
|
||||
}
|
||||
}
|
||||
return ports, nil
|
||||
@@ -126,7 +126,7 @@ func splitAllowedHost(f string) (host string, port int, err error) {
|
||||
return "", 0, fmt.Errorf("%q is not a port", p)
|
||||
}
|
||||
if n == 22 || n == 25 {
|
||||
return "", 0, fmt.Errorf("port %d is not allowed for the Sandbox network", n)
|
||||
return "", 0, fmt.Errorf("port %d is not allowed for the network of commands", n)
|
||||
}
|
||||
return h, n, nil
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ The MCP `tools/list` endpoint also exposes the full input schema for each of the
|
||||
- `list_shared_folder_events` — Read the audit of the shared folders, newest first.
|
||||
- `list_chat_tools` — List the built-in chat tools (bash on this machine, the web tools): on or off, who serves each, and the host-commands mode.
|
||||
- `list_host_exec_events` — Read the audit of the commands the chats' built-in bash ran directly on this machine, newest first.
|
||||
- `get_chat_egress` — Read the network settings of the chats' Sandbox commands (ceiling, default, ports, registries, always-allowed hosts) and the requests of the last 24 hours.
|
||||
- `list_egress_events` — Read the audit of the Sandbox network requests, newest first, with filters.
|
||||
- `get_chat_egress` — Read the network settings of the chats' isolated commands (ceiling, default, ports, registries, always-allowed hosts) and the requests of the last 24 hours.
|
||||
- `list_egress_events` — Read the audit of the isolated commands' network requests, newest first, with filters.
|
||||
- `get_branding` — Read the resolved instance name, tagline, and branding asset URLs.
|
||||
- `get_usage_stats` — Read token/request usage aggregates when usage tracking is enabled.
|
||||
- `get_pii_events` — Inspect recent PII-filter events without returning redacted request bodies.
|
||||
@@ -48,7 +48,7 @@ The MCP `tools/list` endpoint also exposes the full input schema for each of the
|
||||
- `toggle_model_state` — Enable or disable a model (`action`: `enable` or `disable`).
|
||||
- `toggle_model_pinned` — Pin or unpin a model (`action`: `pin` or `unpin`).
|
||||
- `set_branding` — Change the instance name or tagline.
|
||||
- `set_chat_egress_ceiling` — Set the widest network preset any chat's Sandbox commands may use: off, registries or all.
|
||||
- `set_chat_egress_ceiling` — Set the widest network preset any chat's isolated commands may use: off, registries or all.
|
||||
- `set_alias` — Create, update, or remove a model alias.
|
||||
- `seed_router_corpus` — Add validated labelled exemplars to a KNN router corpus.
|
||||
- `clear_router_corpus` — Permanently clear a KNN router corpus and its live index entries.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Skill: Manage the network of the Sandbox commands
|
||||
# Skill: Manage the network of the isolated commands
|
||||
|
||||
Use this when the admin asks whether chats can install packages, what the commands of the chats reached on the network, or wants to open or close the network of the Sandbox.
|
||||
Use this when the admin asks whether chats can install packages, what the commands of the chats reached on the network, or wants to open or close the network of the isolated commands.
|
||||
|
||||
The commands a chat runs in the Sandbox reach the network only through a proxy. Each chat has a preset: `off` (nothing), `registries` (the package registries, such as PyPI, npm, Go modules, crates.io, RubyGems, Maven Central and GitHub releases) or `all` (every public host). The ceiling is the widest preset any chat may use. Commands that run directly on this machine (the built-in bash) are not covered: they have the network of the server.
|
||||
|
||||
1. Call `get_chat_egress` first. Report the ceiling, the default of a new chat, whether a Sandbox add-on runs commands, and the allowed and blocked requests of the last 24 hours.
|
||||
1. Call `get_chat_egress` first. Report the ceiling, the default of a new chat, whether an add-on runs commands, and the allowed and blocked requests of the last 24 hours.
|
||||
2. To see what commands reached or tried to reach, call `list_egress_events`. Filter by `verdict` `block` to explain why a package install failed, or by `host` or `user`. Summarise; do not paste every line.
|
||||
3. To change the ceiling, say what changes and wait for confirmation (safety rule 1). For example: "I'll set the network ceiling of the chats to **registries**, so commands can reach the package registries only — confirm?" Then call `set_chat_egress_ceiling`. Say that `all` lets commands reach any public host, which can carry the user's files out.
|
||||
4. `get_chat_egress` also lists `allowed_hosts`: exact hosts (or `host:port`) that every chat with the network on may always reach, with the reason `allowed_host` in the audit. With the ceiling off they do nothing. You cannot change the list: tell the admin to edit "Always allowed hosts" in Settings > Tools > Sandbox network.
|
||||
5. You cannot let commands reach private network addresses. That switch needs a typed confirmation: tell the admin to use Settings > Tools > Sandbox network.
|
||||
4. `get_chat_egress` also lists `allowed_hosts`: exact hosts (or `host:port`) that every chat with the network on may always reach, with the reason `allowed_host` in the audit. With the ceiling off they do nothing. You cannot change the list: tell the admin to edit "Always allowed hosts" in Settings > Tools > Command network.
|
||||
5. You cannot let commands reach private network addresses. That switch needs a typed confirmation: tell the admin to use Settings > Tools > Command network.
|
||||
6. A user changes the preset of one chat in the chat's Tools menu, up to the ceiling. You do not change a chat's preset.
|
||||
7. On an error, quote it verbatim (safety rule 3) and stop.
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
// The built-in chat tools are the tools of the chats that an add-on may
|
||||
// replace: bash, which runs commands directly on this machine with no isolation
|
||||
// when no Sandbox add-on serves it, and the web tools. Both tools here only
|
||||
// when no add-on serves it, and the web tools. Both tools here only
|
||||
// read; an administrator changes the settings (chats_host_exec,
|
||||
// chats_tool_providers) in Settings > Tools. The same list carries the
|
||||
// built-in skills that installed add-ons replace, pinned in the same map.
|
||||
@@ -44,14 +44,14 @@ func registerChatToolTools(s *mcp.Server, client LocalAIClient, _ Options) {
|
||||
}
|
||||
|
||||
// registerChatEgressTools exposes the settings and the audit of the network of
|
||||
// the chats' Sandbox commands. Only the ceiling can be changed here; the
|
||||
// the chats' isolated commands. Only the ceiling can be changed here; the
|
||||
// private-network switch needs a typed confirmation in Settings > Tools, and
|
||||
// the lists of hosts (extra registries, always-allowed hosts) are edited there
|
||||
// too: get_chat_egress reads them, no tool writes them.
|
||||
func registerChatEgressTools(s *mcp.Server, client LocalAIClient, opts Options) {
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: ToolGetChatEgress,
|
||||
Description: "Read the network settings of the chats' Sandbox commands: the ceiling (off, registries, all), the default of a new chat, " +
|
||||
Description: "Read the network settings of the chats' isolated commands: the ceiling (off, registries, all), the default of a new chat, " +
|
||||
"whether private addresses may be reached, the ports, the extra registries, the always-allowed hosts (allowed_hosts), the audit retention, the package registries list, " +
|
||||
"which add-on runs commands and how, and the allowed and blocked requests of the last 24 hours.",
|
||||
}, func(ctx context.Context, _ *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
|
||||
@@ -64,7 +64,7 @@ func registerChatEgressTools(s *mcp.Server, client LocalAIClient, opts Options)
|
||||
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: ToolListEgressEvents,
|
||||
Description: "Read the audit of the network requests of the chats' Sandbox commands, newest first: user, chat, host, port, " +
|
||||
Description: "Read the audit of the network requests of the chats' isolated commands, newest first: user, chat, host, port, " +
|
||||
"verdict (allow, block, or done with the bytes moved) and reason. Filters by user, chat, verdict or host.",
|
||||
}, func(ctx context.Context, _ *mcp.CallToolRequest, args EgressEventsQuery) (*mcp.CallToolResult, any, error) {
|
||||
if args.Limit <= 0 {
|
||||
@@ -83,7 +83,7 @@ func registerChatEgressTools(s *mcp.Server, client LocalAIClient, opts Options)
|
||||
}
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: ToolSetChatEgressCeiling,
|
||||
Description: "Set the widest network preset any chat's Sandbox commands may use (chats_egress_ceiling): off, registries (the package registries only) " +
|
||||
Description: "Set the widest network preset any chat's isolated commands may use (chats_egress_ceiling): off, registries (the package registries only) " +
|
||||
"or all (every public host). Lowering it applies within 30 seconds. Confirm with the user first (safety rule 1).",
|
||||
}, func(ctx context.Context, _ *mcp.CallToolRequest, args SetChatEgressCeilingRequest) (*mcp.CallToolResult, any, error) {
|
||||
out, err := client.SetChatEgressCeiling(ctx, args.Ceiling)
|
||||
|
||||
Reference in new issue
Block a user