The previous commit was made on a stale base and so reverted work that
landed on master before it. This commit puts every file back to the
state of its parent's parent. The permission enforcement of the
previous commit stays.
Assisted-by: Claude:claude-sonnet-5-5 go
Operators could not tell which client sends requests that are refused
with 401. Add --log-client (LOCALAI_LOG_CLIENT, off by default). When
set, the HTTP request log line also carries duration, remote, user_agent
and, when present, request_id.
The address is the TCP peer. It is never read from X-Forwarded-For,
X-Real-Ip or Forwarded, because LocalAI has no trusted-proxy setting and
any client can forge those headers. Behind a reverse proxy the value is
the proxy address. Authorization headers, cookies, query strings and
bodies are never logged. User agent and request id are cut to a fixed
length and have control characters replaced.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
The capabilities, role grants and pairing that a connector binding
stores now decide what a sender can make the agent do.
Authorize is one pure function: the capability must be in the owner's
ceiling for the binding and the sender must be on the allowlist. A
sensitive capability then needs a paired sender; roles never count for
it. Any other capability needs a pairing or a role that the owner
granted and that the kind lists. The default is no.
Each message gets a grant for its sender. A message that joins a
running turn narrows it to what both senders may do. The tool builder
offers a gated tool only for a held capability, and the tool checks
again when the model calls it, so a call that gets through is refused
with the reason. Today this covers propose_change (memory and profile
kinds), revert_change, the trigger tools, and files sent to the bot. A
chat worker gets the grant with the start request.
The capability gate that the trigger features ask is now answered by
Authorize, and the application sets it. Other features can add a
global switch with AddCapabilitySwitch.
Every use records an allow or deny row (time, binding, sender IDs,
roles, capability, result; no text or codes). GET
/api/chats/connectors/{id}/access returns the effective permissions
per kind of sender and the latest rows, for an add-on settings page.
A binding saved before this change has no capabilities, so its senders
lose these features until the owner enables them.
Assisted-by: Claude:claude-sonnet-5-5 go swag
The agent form offered a different set of choices than the chat: grants
as checkboxes for add-ons, connectors and skills, a free-text list of MCP
servers, and nothing for the built-in tool groups, so a person could not
tell which tools an agent can use.
The form now draws the lists of the Tools menu with the same components
(SwitchPanel, SkillsPanel) and the same rows (useToolGroups), fed by
useAgentTools: the draft lists of a new chat on the agent's model, with
the agent's grants and switches applied. A switch changes the form, and
the agent is saved with it:
- built-in groups: builtins_off and builtins_on (an opt-in group such
as saved agents starts on only when listed);
- add-ons, connectors, skills: the existing grant lists, so what is
switched on is what is granted. A granted entry this account cannot
see stays in the list, marked, so it can be removed.
The network choice and the folder and mode choice stay where they were.
The MCP servers of the model stay a free-text list in their own fold,
since a model that is not chosen yet has none to list.
Assisted-by: Claude:claude-sonnet-5-5 eslint playwright
The Tools menu built its rows inside the composer, drew only some of
them with a description, and showed a group it had no words for ("changes",
the memory and profile edits) by its raw ID.
The rows now come from useToolGroups, and every row has the same three
parts: a title, a description of what the model can do with the switch on
(and, once it is off, what it loses), and a state line (who serves the
tools, whether commands are isolated, why a switch cannot be used). The
words come from the server, which declares a title and a description for
each group and each add-on; a translation by group ID wins when the
locale has one, so a group added later needs no change here. An add-on
row shows the title its manifest declares instead of its ID.
SwitchMenu is split into the chip and SwitchPanel, and SkillsMenu into
the chip and SkillsPanel, so another page can draw the same lists inline.
New strings are in every locale.
Assisted-by: Claude:claude-sonnet-5-5 eslint playwright
The Tools menu of a chat listed a ninth built-in group, "changes", by its
raw ID with no text, and only some rows said what a switch did. The
registry now carries a title, a description of what the model can do when
the group is on, and what happens when it is off, for every group. Groups
that nobody described fall back to the label and description of their
first tool, so a new tool group needs no change in the interface.
An add-on row can show the title and description its manifest declares:
the broker may implement the optional AddonLister, and the lists carry
both fields next to the ID.
A saved agent gets builtins_off and builtins_on. builtins_off is a
ceiling: a chat of the agent shows the group off with a reason and cannot
switch it on, and a sub-agent follows it. builtins_on lists the opt-in
groups the agent's chats start with. Both are additive columns, a write
that does not name them keeps the stored ones, and groups the server does
not offer are dropped so an imported file still loads. An agent with
switches exports as version 3, so an older server refuses the file
instead of giving the agent every tool; a single-folder agent without
switches still exports as version 2.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt swag
The host reads the declared capabilities of the installed generation to
decide whether a tool call needs a credential. Copy the new field, and
teach two guard tests that GitHub the service is not the add-on.
Assisted-by: Claude:claude-sonnet-5-5 [go]
Add the GitHub connection page (fine-grained token, repository list,
read-only note, Test that checks each listed repository), let the shared
connector page hide messaging settings for a kind that carries no
messages, register the add-on in the package matrix and the catalog, and
document the credential slot and the GitHub add-on.
Assisted-by: Claude:claude-sonnet-5-5 [go node]
Add the github add-on: manifest with a credential slot, a read-only
tool set (code search, files, trees, issues, pull requests and diffs)
with size caps and untrusted-content framing, rate-limit mapping, a
connector route that checks the token, and a fake GitHub API for tests.
Assisted-by: Claude:claude-sonnet-5-5 [go]
Add a manifest credentials declaration, host-enforced per-binding
resource lists, Request.Credential and ResourceKey set by the host, and
a typed ErrRateLimited that reaches the model as rate_limited.
Assisted-by: Claude:claude-sonnet-5-5 [go]
Cover the folders field, the nested-folder mode rule, the new mount
names, the shared-folder budget that no longer refuses a chat, and agent
file version 3.
Assisted-by: Claude:claude-sonnet-5-5
The Files section of the agent editor is now a list of rows. Each row
has a folder picker, a read or write switch and a remove button. An
Add folder button stops at eight. A folder inside another one shows the
mode of the outer folder, because that is the mode it works at. The
agent chips, the Tools menu line and the Files page marks read the whole
list. The strings are in all nine locales.
Assisted-by: Claude:claude-sonnet-5-5 eslint playwright
POST and PUT on /api/chats/agents take folders, a list of path and mode
rows, up to eight. The old folder field still works and is read as a
list with one entry. Responses carry folders, the effective mode of each
row and a notice with the count of shared folders a chat does not mount.
A folder inside another folder of the same agent takes the mode of the
outer one, because the file store gives a node the highest mode among
the roots above it and cannot hold a read only folder inside a write
folder. The editor data shows that mode.
An agent with one folder still exports as version 2. An agent with
several exports as version 3 with folders. A server that reads version 2
refuses version 3. Import keeps a folder that is not found as a row
that is not chosen.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
The fence of a chat now holds every folder of the agent in one layer.
Where a folder is inside another one of the same agent, the outer mode
caps the inner one, and a shared folder is read only. A missing or
unchosen row stops the chat and the error names the row; it never
falls back to the whole home.
The fence budget no longer refuses a chat. Skill layers and the agent
folders are always kept, a shared folder inside a root already in the
layer is skipped, and the rest are kept by node ID until the layer is
full. Trigger tools, trigger runs, sub-agent checks and the agent list
take the whole list. Save and import take a list of folder rows.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
A view can now mount further folders next to the first one, at
folder-<name> with a numeric suffix when two names collide. The store
still caps each mount by the fence. A fenced view counts the shared
folders it leaves out, and the prompt says how many are not mounted.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
An agent may now have up to eight folders, each with its own mode. The
list is stored in a new table for GORM and in a folders field for the
JSON store. The old single-folder pair stays and mirrors the first entry
for one release, so a rollback keeps working. At start the GORM store
copies a single folder into the list; the copy is safe to repeat.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
A chat that does not exist yet has no execution status to read, so its
isolation is unknown by construction. Three places treated that as a
failure:
- Switching an add-on in a draft reset the commands state and never
refilled it, so the "isolation is unknown" notice stayed. The draft
now keeps the state its list gave it.
- The skills list of a draft hid the document skills of the selected
commands provider, because they need isolated commands. A draft list
now offers them; the turn of the real chat still gates them on the
status it reads. The draft add-on switch also reads the skills again.
- The shell row of the Tools menu printed the generic unknown text for
the draft state. It now says nothing about isolation there.
In a real chat, switching an add-on or a tool group now reads the
chat's network view again, so the network controls appear once the
provider serves commands.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt playwright
A connector chat can now propose a trigger, the person answers it in the
chat, and the run reports to the chat.
The trigger tools propose_trigger and list_triggers are offered in a
connector chat when a capability gate allows triggers.propose for the
binding. The gate is a small interface on the service with a deny-all
default, so nothing changes until the application sets one. Change and
delete stay in Studio.
A proposal from a chat stores the binding, chat and thread, a four
character code and a 24 hour expiry. The host writes the proposal and the
code in that chat. "confirm CODE" or "cancel CODE", as a whole message in
the same chat and thread, is handled before any model turn and needs
proposals.confirm. A binding may make 3 proposals an hour. Proposals that
lapse are marked expired, and the proposal card shows that state.
A trigger carries a delivery: the Inbox (default) or the chat of the
proposal, with the modes always, on_change and on_problem. The host writes
the last message of the run, cut and with broadcast mentions broken; a
message of exactly [SILENT] writes nothing; three failures in a row raise
one alert per 6 hours. A failed delivery falls back to the Inbox. Host
messages of a binding go through an outbox of 20 a minute that queues the
rest. While triggers.run is off, runs of such a trigger are skipped, not
replayed, and the record is unchanged. The audit records each step with a
hash and no text.
Assisted-by: Claude:claude-sonnet-5-5 go test ginkgo eslint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The add-on declares the three index files of the person's notes
(capabilities.contextFiles, manifest API 2), so the host loads them into
the prompt and serves their topics with context_read. It ships two skills:
memory, whose description asks the model to read the matching topic before
it answers almost anything and to save a note only as a proposal the person
confirms, and memory-check-own-files, which says what the model can and
cannot read. Its executable serves no tool, and its page explains the notes
and opens the Memory tab of the account.
The add-on joins the packaging matrix, the release workflow, the library
index and the case inventory. The declared capabilities the host hands to
an add-on now include the context files.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The tool that reads the details of a context file topic was called
memory_read. The loader is generic: any component or add-on that declares
a context file uses it, and nothing in it knows which one. Rename the tool
to context_read so the host does not name a feature of one add-on.
Behaviour and limits stay the same.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A turn now asks the add-on broker which running add-ons declare context
files, and loads their index files with the loader that already reads
registered sources. The same rules as for add-on skills apply: a stopped
add-on, one the chat switched off, one the chat's agent does not grant,
and one that cannot work for the user bring nothing. memory_read serves
the topics of those sources. The Memory page lists the files of any
installed add-on, running or not, because the files belong to the person.
The channel notes folder of a chat is now the one propose_change writes
for the same conversation: the binding and a hash of the chat. A note the
person confirmed is read back by the conversation that proposed it. Before,
the loader built the folder from the binding alone, so the two never met.
The prompt names the save tool only in a chat that has it, so a chat that
switched propose_change off is not told to call it.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
An add-on that keeps small notes for the person can now name the index
files that the host puts in the prompt of a chat. The declaration is
capabilities.contextFiles: at most one file for each scope (user, agent,
channel), each a path under Memory/ that ends in .md, with an optional
byte cap that can only lower the host's 2 KiB cap.
The field is new, so a manifest that uses it must accept manifest API 2.
The add-on only names paths. The host reads them as the person, so the
declaration gives the add-on no access to files.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A connector kind can now declare the roles it gives to a sender and the
capabilities it can grant to a role. The owner of a binding enables a
ceiling of capabilities and maps roles to them. The host stores these
values and returns them with the binding and the kind list, so an add-on
settings page can show them.
Capability names are a closed list owned by the host. Sensitive names
may be listed by a kind but are never granted to a role; only a paired
sender can use them.
Pairing proves that the owner controls a platform account. The owner
asks for a one-use code (valid ten minutes) and sends it as a direct
message to the bot from an allowlisted account. The host checks the code
before any model turn and never logs it. A binding holds three paired
senders, a pairing ends after 90 days, and five wrong codes in an hour
lock the sender out for an hour.
Contract: connector kinds gain optional roles and permissions, and a
message gains optional roles. They need manifest api 2, which the host
already accepts; protocol is unchanged and manifests that declare
nothing new behave as before. The host drops roles a kind does not
declare.
Enforcement of the capabilities by feature is not part of this change.
Assisted-by: Claude:claude-sonnet-5-5 go swag
The entry gets a badge with an accessible label when automated chats
wait for the person, which changes the link name. The exact name match
then found nothing whenever other specs left such chats on the shared
test server.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The gateway suite checks the specs of the session package against an
inventory. Eight specs were added without being listed, so the suite
failed on an inventory mismatch.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The resource mapping of an app is not part of the apps API, so the apps
list lost it when a frontend replica read the list from the owner. The
model listing tool of Tables then vanished on a replica. Send the
mapping beside the list on the carrier and put it back on arrival.
The fixture library also stamped every package with manifest API 1, so
the real Tables manifest, which declares a listing, was refused with
package_fetch_failed. Give a package API 2 when it declares one.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A connector chat kept the model its agent had when the chat was made, so
changing the agent's model did not reach senders with an existing chat.
Move the kept chat to the agent's current model when a message arrives.
A turn that failed because the tool definitions or the conversation did
not fit the context window showed only the generic error sentence. Name
the cause and the sizes, and give the same sentence in the web chat and
in the bots. A refused tool definition now has its own code.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The chat harness took the context window, agent options and input
modalities from the config of the name a chat asked for. An alias has
no model of its own and the loader gives it the generic default
window, so a chat on an alias budgeted against 4096 tokens and failed
on tool schemas even when the target ran with a far larger window.
Resolve the alias to its target first, as the capabilities endpoint
already does.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Remove the instructions to switch thread reading off and the
thread_context field from the API reference. The privacy statement
stays: the bot reads only the thread where it was mentioned.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The connector page no longer shows the switch or sends thread_context.
The host reads the thread of a mention whenever the kind can give it.
The Slack page spec checks that the switch is absent and not sent.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The thread_context binding setting is removed. When the kind declares
the history ability and the bot is mentioned in a thread, the host
reads the thread once for a new chat, as before. The other guards stay:
the ability check, the bound, the untrusted block, and a failure never
blocks the answer.
A binding stored by an earlier build still loads. The JSON store
ignores the unknown field. The GORM row no longer maps the
thread_context_off column; AutoMigrate does not drop it and it accepts
NULL, so reads and writes keep working. No data is migrated or removed.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The Settings page states the ceiling in effect from this field.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The owner policy key connector_files_ceiling_bytes gets a field on the
Settings page, next to the add-on upgrade setting. It states the ceiling
in effect and that larger files need the add-on to support streaming.
The host settings client accepts the new key as a number.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The section has a field for the largest file the bot reads and one for
the largest it sends. A value can only lower the limit of the add-on.
Each field shows the limit in effect and the layer that sets it, and the
page says that larger files need the add-on to support streaming while
the inline cap is the limit. The fixed 1 MB in the intro is gone.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The connector list shows, for each binding and each kind, the limit in
effect in both directions, the layer that sets it, the ability the kind
declares and the most an owner may set. A binding accepts
file_receive_max_bytes and file_send_max_bytes; a value above the limit
of the kind is a 400.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The key connector_files_ceiling_bytes sets the largest file a connector
moves. Zero means the default of 100 MB. The key is always written, and
a policy document stored before it reads as the default. The connector
manager reads the ceiling from the owner policy at each resolution.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The limit of a file is the smallest of the host ceiling, the ability the
connector kind declares, the owner's setting for the binding and the cap
of the inline path. One function decides it and names the layer that
won. The sentences to the sender and to the model are built from that
number and layer, in KiB or MiB, instead of a fixed 1 MB. A guard spec
fails when these files carry a size or a byte count of their own.
The owner can lower the limit of a binding; a value above the limit of
the kind is refused.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A binding keeps an optional limit for the files it reads and the files it
sends. Zero means the owner sets none. Both stores keep the values and the
validation bounds them.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A connector kind can declare files.receive and files.send, each with a
maximum size and optional media types. The field is additive and needs
manifest api 2. A kind that declares nothing keeps the inline path.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Chats that a trigger, a connector, a job or a page started no longer fill
the sidebar, Jump back in or the default All chats list. All chats gets a
second tab, From add-ons and automations, that groups them by origin kind
and by origin, using the name of the binding or the trigger (a kind this
release does not know falls under a generic group). The All chats entry
in the sidebar shows how many of them wait for the person. Ctrl+K still
finds every chat and marks these with a badge, and a transcript of such a
chat says what started it, with a link to its page. The browser remembers
the tab.
The lists ask the server for origin=person or origin=automated, so a
burst of bot chats cannot push a person's chats off a page.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
GET /api/chats takes an optional origin parameter: person (chats a
person started), automated (every other chat) or one origin kind such as
trigger or connector. The limit counts the chats kept, so a flood of
chats that a connector or a trigger started cannot push the chats of a
person off a page, and the other way round. Without the parameter the
list is unchanged.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A chat that is not created yet has no add-on bound to it, so its
execution status cannot be read. The host now says so with the reason
draft, and the Home composer shows no commands warning for it.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A harness may call a tool with a context that carries nothing of the
turn. Lend the values of the turn to the tool, so a status read in the
tool shares the turn's observation instead of starting without one.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
load_skill runs with the context the harness gives it, which has no
execution scope, so the status of the commands read as not selected and
the document skills were dropped. Make the descriptor and skill
resolution open a scope when the context has none, and report a scope-less
status read as a programming error: logged once in production, a failure
of the spec in tests.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>