power-action and send-key are now approvalOverridable. The add-on page
has an "Ask before acting" section with Ask each time (default) or
Don't ask for each of them, with a plain warning that a chat can then
switch the machine off at once. type-text has no such choice and always
asks. The connection row lists which tools run without asking. The
page strings are translated to Italian and the shared connector pages
show the same list in the access view and the access preview.
Assisted-by: Claude:claude-sonnet-5-5
A write tool with approval "always" always waits for a person. A tool can
now also declare approvalOverridable, and the owner can then choose not to
be asked for that one tool, with a user setting approval-<tool> (ask or
skip, default ask). It is the only way to relax the ceiling.
The host honors the setting only for the owner's own chat and for a
messaging chat whose senders are all paired with the bot. Allowlisted
but unpaired senders, anyone-mode senders, roles, triggers, jobs, pages,
sub-agents and calls without a chat still wait for a person. The setting
is read again at the apply. Every run without a card is logged with the
tool, the connection and the outcome, never the arguments, and is added
to the latest decisions of the connection. The access view and the
access preview list the relaxable tools and who they run for.
Assisted-by: Claude:claude-sonnet-5-5
The GitHub add-on refused classic tokens in the page only, and its
repository list took exact owner/name entries.
A classic token (ghp_) is now an explicit choice of the connection. The
kind declares a token guard: the host refuses a token with that prefix
unless the connection sets the option classicToken to allow, and the
request that turns it on must carry confirm_wider_token. It is checked
when the token is saved and again each time it is used, so switching
the option off stops a stored token. The list and the write mode still
apply. The page shows a warning and two ticks.
The list takes owner/* and *. The host matches them before the add-on
runs, without regard to case and on whole names, so other-owner/x and
owner-evil/x are not covered by acme/*, and a key that holds a star is
never a name. A kind opts in with wildcardResources; only GitHub does,
so the other add-ons keep exact matching. Saving * needs
confirm_any_resource, and the write mode stays separate. A read tool
that declares enumerates (the new list-repos) runs only for a list of *
and asks GitHub which repositories the token reaches.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Creating a binding with no agent (home-assistant, jellyfin, pikvm,
github) failed with a 500. The token check for a new binding looked up
the agent even when the binding had none, so the host refused it. The
check now looks up an agent only when the binding has one. The token is
still required and a foreign agent is still refused.
A host denial (connectors.ErrForbidden) reached the client as a 500
server_error. The connector endpoints now answer 403 with a fixed text
that names no part of the check.
A person could have 10 bindings. Several bots plus one credential
binding per service need more, so the limit is now 50, like the limit
of resources and triggers. The docs and the API description say so.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
With the reply language on auto, the sentences the host writes in a chat
used the language detected from the sender, and the interface language
of the owner only when a server hook supplied it. No hook was wired, so
the interface language was never used.
The page of a connector add-on now sends its interface language with
each save (ui_language). The binding keeps it, so every frontend of a
cluster knows it. With auto, the host speaks that language when its
catalog has it, and reads the language of the sender only when the
owner's language is unknown or not in the catalog. A save without the
field keeps the stored value, and a value that is not a language tag is
ignored. A fixed reply language still wins.
The label of the Automatic option says what it does now, in all nine
languages of the page.
Assisted-by: Claude:claude-sonnet-5-5
The access view, the preview and the kinds call returned English text
for each capability and each refusal. They now add the same text in the
language of the request (lang query, then Accept-Language), in new
localized_* fields. Codes, English fields and the model-facing text stay
as they were.
The texts live in the new connectortext package, one JSON file per
language (it, de, es, fr, id, ko, pt-BR, zh-CN), keyed by capability
and by refusal code. A test fails when a capability or a code lacks a
text in any language, or when {action} is lost.
The shared connector page asks for the page language and shows the
localized fields, with English as the fallback for each field.
Assisted-by: Claude:claude-sonnet-5-5
The collections list reports max_chunk_size and chunk_overlap. The
Suggested changes card for a knowledge text now says how many passages
will be embedded and with which model, estimated from the text size and
the chunking of the server. Without a model name the card says "the
server's embedding model".
Assisted-by: Claude:claude-sonnet-5-5
The collections list now reports search_mode: semantic for the chromem
engine and hybrid for the postgres engine, which also matches words.
The Knowledge page shows it in plain words, so a person knows whether
an exact code or name can be found.
Assisted-by: Claude:claude-sonnet-5-5
The host now calls the roles of the notes slot itself, not only the tools
the model sees.
- At the start of a turn the context role is called with a 3 s limit and
a 6 KiB cap. The text goes in front of the message as data, in markers
that it cannot close, and not in the prompt, so it does not restart the
chat engine. The session passes the turn message to the hook and takes
an optional per-turn context block from it.
- A write through a provider follows the save level, the agent grant and
the messaging rules. Only Save and tell me applies a write at once, and
not for a remote provider that cannot undo. Other writes are held for a
confirmation.
- The export and the forget of a provider are routed by
GET and DELETE /api/chats/memory.
- A provider that fails three times in a row is left alone for a minute,
the slot shows a plain notice and the chat goes on without notes.
- Every call writes one audit line with the outcome and the size, and
never any text.
- The add-on that the built-in provider replaces is an alias: it is not
listed and a choice that names it means the built-in provider.
Assisted-by: Claude:claude-sonnet-5-5 go
Add the change kind `knowledge` and the sensitive grant and capability
`knowledge.write`. A text for a collection goes through the same
proposal flow as the other kinds, but it is never applied unasked:
the Ask, Auto and Always confirm levels all wait for the owner, and the
Never level takes the kind away. Confirm adds one entry to the
collection and Undo removes it. Text is capped at 64 KiB.
A paired sender can answer yes, no or undo in a messaging chat; a
sender who is only on the allowlist cannot. The connection page
explains a block with the shared reason codes, plus knowledge_none and
knowledge_no_embedding. The Suggested changes card names the
collection and previews the text. A kind can now declare a destination
other than the home folder, so core code stays generic.
Assisted-by: Claude:claude-sonnet-5-5
Memory is the first provider slot. The person picks one provider for the
notes: the built-in one, Off, or an add-on that declares the slot. The
choice is kept per person with the other host settings.
Core stays generic. providerslots.go lists the providers of a slot,
stores and resolves the choice, and gates an add-on that declares a slot:
its context files, skills and role tools count only while it is the
provider picked, and the chat can narrow a slot off for itself. The
notes wire into it in memory.go: an adapter gives the add-on's files and
skills behind the MemoryProvider interface, the built-in provider stops
reading while another serves, and the On or Off switch and the choice
stay one answer.
If the add-on that was picked stops, is disabled or is removed, memory is
off with the code memory_provider_unavailable and the add-on is named.
It never falls back to the built-in provider, so a note is not read or
saved where the person did not choose. The choice stays stored.
Add GET /api/chats/providers, GET and PUT /api/chats/providers/{slot}.
Settings > Memory lists the providers from that answer with a generic
picker, shows the failure with the shared Notice and offers to use the
built-in provider. Document the facility for add-on authors, and test it
with a fake add-on that provides the slot.
Assisted-by: Claude:claude-sonnet-5-5 go
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrQyk8ovFXvy5zrjHX1BD
A frontend kept the last apps list per person and answered 200 with a
stale mark when the owner timed out. Two frontends then disagreed: one
with a cached list answered 200, the other a typed 503. The acceptance
contract is that every frontend gives the same typed answer when the
owner cannot answer, so the server cache is gone.
The route now answers owner_busy (timeout) or owner_unavailable with a
503 from every frontend. An owner that is up and busy still answers
from its snapshot, with the operation of each add-on in the list. The
Add-ons page keeps the last list in the browser and shows it with the
busy notice, so it never goes empty. The specs, the docs and the
swagger spec follow the new contract.
Assisted-by: Claude:claude-sonnet-5-5
A person can now choose four save levels on the Memory tab: Save it and
tell me, Ask me first (the default), Always confirm and Never. Always
confirm is like ask, and nothing can loosen it: no agent or messaging
setting overrides it, and the model cannot undo or forget a note by
itself (revert_change is refused, and the person uses the Undo).
One function, effectiveSaveLevel, combines the rules. The strictest wins,
in the order auto, ask, always, never. The inputs are the level of the
person, the rule of the saved agent, the messaging row and the chat.
A table test covers every combination.
A saved agent has an optional memory_save field: always or never. It can
only be stricter than the person, so auto is not offered and the API
refuses it. A write without the field keeps the stored rule, and use_mine
clears it. It is stored by both stores and is part of an agent export and
import. The agent form shows three cards under When it wants to save a
note.
The new row In messaging apps (messaging_save on GET and PUT
/api/chats/memory) takes ask (the default), paired_private or never. With
paired_private a note is saved at once only when the sender is paired and
the chat is one-to-one, and it goes to the notes of that conversation,
with an Undo. The limits stay: five notes a reply, the size limit, and a
note that looks like a secret still waits. The row cannot loosen the level
of the person when that is always or never. A setting that cannot be read
reads as ask.
The connector turn now carries whether the chat is one-to-one and whether
the sender is paired, in the turn grant and in the worker JSON. Both read
as false when unknown. The Telegram, Slack and Discord transports already
set Direct; new specs pin it for private, group and supergroup chats,
direct messages, channels, group conversations and threads.
A note from a messaging chat goes to the conversation first. It reaches the
notes of the agent or of the person only from a paired sender, only when
the memory choice of the agent allows that scope, and only after that
sender confirms it. Under Always confirm only the paired sender who asked
can confirm. An agent whose memory is None still saves nothing.
The script of the confirm-in-chat specs now proposes a note for the
conversation, because a sender who is only listed cannot write to the
notes of the person.
Assisted-by: Claude:claude-sonnet-5-5
A saved agent now has a memory choice: Mine and its own, Its own only,
Mine, or None. The host enforces it. The notes in the prompt and the
scopes of context_read follow the choice, and a memory note may go only to
the scope the choice allows: the agent's own notes, or the person's. The
propose_change tool offers the model only those scopes, a note needs no
scope, and any other scope is refused. None offers no memory kind.
An agent saved before the field has no choice and behaves as before: it
reads notes when its add-ons name Memory and the model picks the scope.
A write without the field keeps the stored choice. The person's Off, a
private chat and the agent's grant still decide first. Sub-agents get no
notes and cannot save; triggers read per the choice and cannot save.
Messaging chats read as before.
The agent form has a Memory section with the four choices, an "As before"
note for older agents, a switch for the permission to save, and the line
that memory is off in the settings. New agents start with Mine and its own.
Deleting an agent that has notes asks to keep, export or delete them
(DELETE /api/chats/agents/{id}?memory=keep|export|delete). Kept notes show
on the Memory page under Deleted agents, and the notes list has a tab for
each agent. Copying an agent never copies its notes.
Assisted-by: Claude:claude-sonnet-5-5
A new opt-in tool group, Past chats (history), gives a chat two read-only
tools. search_chats finds messages in the earlier chats of the person
(query, scope this_chat, this_agent or all, limit, since) and read_chat
reads one chat from a message number. A call returns at most 10 hits with
300 character snippets, or 20 messages and about 6 KiB. Every result is
wrapped as untrusted text, a closing line in an old message cannot end
the wrapper, and the tool says plainly when nothing is found.
What the tools never reach: another user's chats, private chats (a chat
with the notes of memory switched off), sub-agent runs, and every chat
that no person started, which leaves out the messaging chats that hold
the words of other senders. A saved agent needs the new grant
history.read, which is off by default and turns the group on when it is
saved. An agent that works in a folder may search only its own chats. A
run that starts by itself, a messaging chat and a sub-agent do not get
the tools. The grant, the switch and the agent are checked again at each
call. Each call writes one log line with the tool, user, chat, scope, a
hash of the query and the chats returned, and never a query or a message.
The Tools menu and the agent form show the group and the grant through
the existing lists. The docs and the API notes describe both.
Assisted-by: Claude:claude-sonnet-5-5
The confirmations, refusals and notes that the host writes in a chat
without the model were fixed English: "Started a new chat", the question
about a proposal, the file notes, the pairing replies. They now come from
a catalog (core/services/studio/hostmsg) with a template for English,
Italian, German, Spanish, French and Portuguese (Brazil), and the same
{placeholders} in each. Text for the model (tool descriptions, the held
back note, the thread block) stays English.
A connection has a reply_language setting: auto (default), en, it, de,
es, fr or pt-BR. With auto the host scores the sender's message against
the common words of the six languages, with no model and no dependency.
A message that does not show a language ("ok", "yes", "thanks") changes
nothing, and a short message cannot move a conversation to another
language. The last clear language of a sender and of a chat is kept for
24 hours; a trigger that writes on its own uses the chat's. The setting
is stored in the JSON and the gorm store, is kept by an update that
leaves it out, and is on the connection page under "How it answers".
Every sentence goes through msg or a hostmsg.Voice. A guard test fails
when a fixed text reaches say, Post or Outgoing, when a key is not in the
catalog, or when a key is never used. The reply parser now reads the
yes, no and undo words of the six languages, which the new texts ask for.
Assisted-by: Claude:claude-sonnet-5-5
Memory now has one switch, On or Off, in the Memory tab of the account
settings. Off means nothing from the notes is put in the prompt, the
model has no memory skill, tool or note kind, nothing is proposed or
saved (also by triggers and other runs that start by themselves), and
the model is told in one line that memory is off. The notes stay and can
still be read, exported and deleted.
A chat can be private: the Memory row of its Tools menu is the memory
switch group, stored with the chat. The narrowest level wins: chat,
agent, person, server. A saved agent cannot widen the choice of the
person. The tools list says which level is off (memory.code, blocked_by,
fix); the chat shows a chip and one line, and the agent form shows the
person's switch as a read-only line.
New API: GET and PUT /api/chats/memory for the switch and the save level,
and GET /api/chats/memory/export for a zip of the notes. The Memory tab
gains the switch, the three save levels, Export and Forget everything.
Assisted-by: Claude:claude-sonnet-5-5
The manifest said its text in English only. An add-on can now translate
what the host shows to people, and the library list shows it before the
add-on is installed.
The title and the description use the language maps of ui.title and
ui.description, which every host already reads. A new optional field,
i18n, translates the connector kinds by language tag. It stays under api
2 and its set of fields is closed: the title and the description of a
declared kind, and only where the English kind has them. Tool
descriptions, schemas and skills are for the model and stay English.
Tags follow the language map rule (at most 16, no English), each string
has the limit of its English field, all translated text has at most
12 000 characters, and markup, control characters and direction
overrides are refused. A host that does not know the field refuses the
manifest, as it refuses any unknown field. The field is part of the
manifest, so the package digest and the signature cover it.
The catalog generator copies the translated title and description of
the manifest into each library entry (ui.titles, ui.descriptions), the
host validates them like the manifest values, and a library search
matches the English and the translated text. The apps list and the
connector kinds return the text of every language and, when the request
names a language (lang, else Accept-Language), the resolved text beside
the English fields: language, localizedTitle and localizedDescription.
No existing field changes.
Every add-on on master now has its title and description in Italian,
German, Spanish, French, Brazilian Portuguese, Simplified Chinese,
Korean and Indonesian, and the chat connectors carry their kind text in
the same languages. The packaging tests require that, and that a
translation has the fields of the English text it translates. The names
of the add-ons that provide a role are matched in their English title
only: a translation is often an ordinary word of its language.
Assisted-by: Claude:claude-sonnet-5-5 go
Search read the newest 500 chats and scanned them in memory, so older
chats dropped out silently. The chat store now keeps a search index of
the text of user and assistant messages: FTS5 with the trigram
tokenizer on SQLite, a pg_trgm GIN index on PostgreSQL, and a scan of
the same table when neither is there. The file store scans its
in-memory transcripts for up to two seconds, newest chat first, and
says when it stopped early.
The index row is written in the transaction that writes the entry and
removed in the one that deletes the chat or truncates the transcript.
A query joins the chat row for the owner, so one user never sees the
chats of another, and sub-agent chats stay out. Messages written before
the index existed are added in the background with a resumable cursor
and progress in the log; startup does not wait for it.
GET /api/chats/search keeps its fields and adds the role, time, match
count and highlight offsets of the hit, and filters for agent, origin,
role and date range. A phrase in double quotes is one word. Hits rank
by match quality and recency. group=message lists every matching line.
Assisted-by: Claude:claude-sonnet-5-5
An install or upgrade holds the owner's state lock through the backup,
the commit and the start of the new generation. The apps list, the host
access overview and the installed list took the same lock, so they
waited for the whole operation and the frontends timed out after 65 s.
The Add-ons page then had no list to show.
Reads now work on a snapshot of the journal that every journal write
publishes, and never wait for an operation. The apps list reports the
running operation of each add-on (kind, phase, since), and the owner
records the stage of an upgrade through the start of the candidate. A
second upgrade of an add-on that is changing is refused with a conflict.
A frontend waits eight seconds for a quick read of the owner. When the
owner does not answer, the apps list returns the last list it got for
the same person, marked stale for ten minutes. Without such a list the
route answers a typed owner_busy or owner_unavailable 503, and the pins
route no longer drops pins it could not check.
Assisted-by: Claude:claude-sonnet-5-5
The access view said only that a capability was refused, in free text.
It now reports, for every capability of every kind of sender, the first
link of the permission chain that fails: a closed code, who holds the
link (server, tools, agent, connection, sender), one plain sentence, one
fix as a kind and a stable target, and the links not yet checked. The
chain order is server setting, Tools switch, agent permission, the
permission of the connection, then sender trust. The code is produced
where Authorize and the capability switches decide, so the English
reason and the decision cannot drift apart. The Tools switch of the
agent and the server triggers setting are now switches in that chain.
POST /api/chats/connectors/{id}/access/preview runs the same chain on a
form that is not saved (id "new" works with a kind), optionally for one
kind of sender. It writes nothing, records nothing and never takes or
returns the token.
The held-back note for the model, the tool errors and the trigger tool
reason take their sentence from the same table of codes, in plain words
with no setting names.
Assisted-by: Claude:claude-sonnet-5-5
Pin the field on the chat and the draft endpoint for off, ask, on, no
setting and an unknown value. Regenerate the swagger files for the new
field.
Assisted-by: Claude:claude-sonnet-5-5
A chat with no add-on that runs commands could not tell a person
whether the server's built-in bash was off or simply not selected.
The add-on list now carries commands_host_exec (off, ask or on), a
read-only copy of the chats_host_exec setting as it applies now.
Assisted-by: Claude:claude-sonnet-5-5
A trigger proposed from Slack or Telegram was always read only, and
the person had no way to let its runs act except to edit the trigger
on the Triggers page afterwards.
The approval of a binding now has an acts list, the act.* permissions
that a proposal from its chats may carry. It is empty by default, so
nothing changes until the owner sets it. It is stored in the same
approval JSON (no migration), checked against the closed list in the
API, and always limited to what the agent holds.
The acts of a proposal are what the model asks for, within the list of
the binding and the grants of the agent. The rest is dropped and
named in the proposal text, and the model is told which acts it may
ask for and to ask the person first when the job changes things. A
read only proposal now says what the person can do next.
For a proposal with acts, auto_readonly still asks. auto starts it at
once only when every act is allowed for the binding, nothing was
dropped and every sender of the turn is paired; otherwise it asks. A
trigger made without asking also keeps to the acts of its binding at
run time.
Assisted-by: Claude:claude-sonnet-5-5
A binding with its capabilities ticked still gave nothing to the
owner's own allowlisted account: Authorize wanted a pairing or a role
even for capabilities that are not sensitive, so the agent had no
reminder, memory or talk-back tool.
A sender whose ID or @name is an entry of the allowlist now holds every
capability that is not sensitive, within the ceiling of the binding and
while no switch is off. The decision says Via "allowlist". A sensitive
capability still needs a pairing, and a role never grants it.
The entry of a channel or group lets its members talk to the bot but
does not trust them: the trust is matched against the sender only, and
a binding that accepts anyone lists nobody. Such a sender still gets
capabilities from a pairing or a role. The capability gate takes the
handle of the sender for the same match, and a sensitive request
ignores the allowlist as it ignores roles.
When a tool is held back for a missing right, the turn now carries a
short note with the reason in plain words and tells the model to relay
it, instead of saying that it has no such tool. The reasons are kept
in the grant of the turn, so a worker reports the same words. The
access view lists a "listed" sender apart from one let in through a
channel.
Assisted-by: Claude:claude-sonnet-5-5
Checking several add-ons of one library in a row reached the registry
only for the first, because the library interval also throttled the
per-add-on tag resolution. The others answered unknown at once, so a
check-all reported one update and the next pass the next one.
The library interval now spares only the library fetch; each add-on is
throttled by its own interval.
Turning on an installed package whose manifest this host refuses now
fails with the typed installed_incompatible cause and a message that
says to upgrade, instead of the generic owner-unavailable 503. The
upgrade of such a package stays allowed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The plain typed install and the owner's automatic upgrade never install
a package from a file. Only the host access install, which needs the
administrator's own browser session, sets an approval, so a key or a
tool cannot install a staged file. Add auth tests for the file routes.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
POST /api/addons/files streams the zip to the owner in chunks over the
host access call, so a frontend of a split deployment keeps no copy and
the owner enforces the size limit while it receives. GET and DELETE read
and remove a staged file. The install is the existing host access
preview and install with sourceId local-file, so it keeps the human
surface rule, the nonce and the digest binding. The install body gains a
downgrade confirmation.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
Staging the grants of an install now goes through Store.UpdateAudited, so
an install fails and the document stays as it was when the audit line
cannot be written.
A write from an API key, an MCP session or a tool call was refused in the
HTTP layer before it reached the owner, so the refusal left no audit line.
The route now hands the refused write to the owner without the human
mark; the owner refuses it and records the credential kind, never the
credential.
Manager.HostGrantsChangedReport names the add-ons the re-sync started or
stopped, and the owner writes a restart-for-grant line for each. The
event constant was never used before.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
An add-on can declare userSettings in its manifest: a key, the allowed
values and a default. The host keeps each person's answer in the account
database every frontend shares (one file when accounts are off), checks
every write against the declaration, and never asks the add-on.
GET and PUT /api/addons/{id}/user-settings read and write them. A write
needs the person's own browser session, so no API key, model or tool can
change one. A setting may carry a role that the host reads for its own
rules; the host fixes the values and the default of a role so a manifest
cannot loosen it. Pages reach their settings through sdk.userSettings,
which is bound to the add-on that calls it.
Assisted-by: Claude:claude-sonnet-5-5 go node
A chat now enables a set of connections for an add-on whose tools run with
a credential, and the model names one of them in each call. This applies to
every add-on that declares a credential in its manifest.
- The Tools menu row of the add-on lists the connections with a checkbox
each. The saved agent names a set too (an empty set means each chat
chooses). A chat that a connector binding started keeps that binding
alone. With one connection, it is the set. With none enabled, the tools
are not offered.
- With one connection enabled the tools are unchanged. With two or more,
the host adds a required `connection` argument to every tool of the
add-on in the definitions it gives the model: an enum of the connection
names (the display name folded to lower case, digits and hyphens; the
description keeps the display name). The add-on never sees the argument.
- The name is checked against the set the chat has now, not against all the
connections of the person. A missing or unknown name returns a sentence
that lists the allowed names, and nothing is sent. A write tool never runs
without the name. Text in a tool result cannot widen the set, and the
owner checks the chosen binding against the set again.
- The resource list of the named connection is the only one that applies.
Results, the approval card and the text for a pending change name the
connection; the audit line has the binding ID.
- A connection name must be unique per person and add-on among the
credential-only connections, compared by name fold. Older duplicates get
a numeric suffix in the model's list.
- The stored chat, draft and agent values move from one binding ID per
add-on to a list of IDs. A single string is read as a set of one in the
stores and in every request, so older data and clients keep working.
PUT /api/chats/{id}/connections takes `connections`; the older
`connection` field still works.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo node eslint playwright swag
A chat never uses a connection that the host picked for it. For an add-on
whose tools run with the token of a connection (such as GitHub):
- With one connection, it is selected. With several, the Tools menu row
of the add-on has a Connection selector, and until the person picks one
the tools are not offered and the row says so. With none, the row says
so and links to the Connectors page. The choice is stored with the chat
and its draft, like the other switches, and follows a fork, a
sub-agent and the runs of a trigger the chat proposed.
- A saved agent can name its connection per add-on (agent form field, API
field connections). Its chats use it, show it read only and cannot
change it. An agent with no choice follows the same rule as a chat. A
chat that a connector binding started keeps using that binding.
- CredentialFor takes the selected binding and uses only it, so the
resource list of that binding is the only one that applies. A call with
no selected binding is refused with the code select_connection and a
note the model can relay. The first-match code (connectors.Pick) is
removed.
- Tool results and proposal cards say which connection acted, for example
"as Work, repository acme/widgets". A proposal is applied with the
connection it was previewed with. The credential use is logged with the
binding ID.
Contract: the credential slot takes an optional resourceLabel (the word
for the resource in that line). The agent export file does not carry the
connections, since a binding ID belongs to one server.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo node playwright protoc swag
The confirm header names what the administrator acted on: addonId@digest
for an install, the add-on ID for an edit or a revoke, "blanket" and "deny"
for the two switches. The operation ID is optional on every write but the
install; the receiving host makes one when the caller sent none.
Preview and page statuses are allowed, not_allowed, blocked or unsupported
in both. A preview names what an upgrade adds, using the identity of the
installed package. The page carries the library, the new optional access and
the required operations that keep an add-on from running. "keep" relabels
named blanket grants as the administrator's own. The blanket switch ignores
scope options when it is off.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo swag
Add the host access routes to the /api/instructions text of the add-ons
group. Add a spec that no MCP tool maps to them and that nothing under
pkg/mcp names them, so an agent cannot reach them through a tool.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo
Seven routes: read the host access state, preview an install, install or
upgrade with an approval, edit or revoke the grants of an add-on, change the
blanket approval and change the deny rules (with a dry run).
Reads take an administrator session or API key. Every write takes only the
administrator's own browser session: same-origin fetch and the confirm
header equal to the operation ID. An API key, bearer token, MCP session or
tool call gets 403 not_human_surface. The dry run of a deny change is a read.
The first path segment host-grants is reserved as an add-on ID, and the
routes are listed in the feature registry.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo
A connector kind can declare agentOptional. A binding of such a kind may
have no agent: it holds the token, the resource list and the write mode
for the tools of a chat, and never receives or answers a message. The
GitHub kind declares it, so a person no longer makes a throwaway agent
only to store a token.
Without an agent the host refuses every setting that needs one:
listening, accepting senders, an allowlist, a notification chat,
talk-back channels, permissions, pairing and trigger approval. The
places that read the agent of a binding handle the empty value:
inbound routing, the polling loop, notifications, pairing, the capability
check (deny by default) and the grant switch. A kind that does not
declare the key still needs an agent on every binding.
The binding page makes the agent field optional for such a kind with
help text, and the list shows "Credentials only".
Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo node protoc swag
A trigger run with nobody present only reads unless its trigger holds
act.* grants, and until now only the REST API could set them. An agent
that proposes a trigger can now ask for acts too:
- propose_trigger takes an optional acts list. The names must be
act.* grants the agent of the chat holds; anything else is refused
and nothing is stored. A read_only proposal cannot carry acts.
- The proposal stores the list. Confirming it, in Studio, by a plain
yes or by confirm CODE, grants exactly that list. No tool changes
the acts of a trigger afterwards. Two proposals that differ only in
their acts are kept apart.
- The text of a proposal in a messaging app now has a Permissions
line, in plain words with sensitive classes marked: read only, or
what a run may do. The confirmation repeats it. A proposal that asks
for acts is never set up without a yes, whatever the approval mode.
- The trigger REST object gains acts_held, read_only and
read_only_why, computed with the same rules as the run, so a client
can show why a run only reads.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo
A new chat with the sandbox switched off in the composer showed no
"switched off" notice. GET /api/chats/draft/addons took only the model
and the agent, so it always reported the default commands state.
Accept the draft's switches as comma-separated query parameters
(addons_off, builtins_off, builtins_on), check them as the create request
does, and apply them to the draft record before the commands state is
worked out. The composer sends its choices each time an add-on or a
built-in group is switched, so the notice follows the switch.
Assisted-by: Claude:claude-sonnet-5-5 go-test playwright
The speech-to-speech path labels its transcription WAV with the session
rate for client commits, so resampling first handed it 16 kHz audio under
a 24 kHz header. Resample only in the chained pipeline, and say so in the
docs.
Assisted-by: Claude:claude-sonnet-5-5 go-test
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
(cherry picked from commit 82cc411af54cf884e4a44a3e96292a323ba5611c)
The committed-utterance WAV is written with a fixed 16 kHz header. The VAD
commit path stores audio that is already resampled to 16 kHz (vadCommit),
but the client-commit path (input_audio_buffer.commit) passed the raw
buffer at the session input rate. For a 24 kHz OpenAI Realtime session the
backend decoded 24 kHz audio as 16 kHz (1.5x speed/pitch): garbage or empty
transcripts, then input_not_committed on the follow-up response.
commitUtterance now resamples from session.InputSampleRate to
localSampleRate, so both paths hand 16 kHz audio to the 16 kHz header.
This replaces the earlier approach of this PR (header at the session rate),
which mislabelled the already-resampled VAD audio (review feedback).
Tests go through the real paths with a 24 kHz session: commitUtterance and
a server_vad commit via vadTick; both must produce a 16 kHz WAV of the
resampled length. The client-commit spec fails without the fix.
Fixes#12747
Signed-off-by: nexxtmobile.de <git@nexxtmobile.de>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 05ba891da6)
(cherry picked from commit 56e2b6d0cc5cbf1944ef50735f7b499d998eb25c)
qwen3's chat template wraps <think> in {% if enable_thinking %}, but the
prefill detectors scan the raw template without evaluating the condition.
With streaming and no tools, the synthetic prefill made the Go-side extractor
classify the whole answer as reasoning whenever the client sent
metadata.enable_thinking=false.
Skip prefill detection when the request disables thinking. Real
<think>...</think> blocks that a model emits anyway are still extracted from
the content itself.
Fixes#12939
Assisted-by: ZCode:deepseek-flash
Signed-off-by: xianshujun <xianshupang@gmail.com>
(cherry picked from commit cee7ed4b32)
(cherry picked from commit 29618866d07a05624934bf9f595c95a68c938461)
A model can cite a plausible deep link it never opened, written from
memory. The client cannot tell it from a link a search tool returned,
because server-side MCP tool results never reach it.
evidence_check.urls compares every http(s) link of the final answer,
bare or inside a Markdown link, with the URLs of the request (system
prompt, system, user and tool messages) and of the request's successful
MCP tool results. Links are compared without scheme, fragment, "www."
and trailing slash; a link counts as retrieved when it equals an evidence
URL or is a shorter form of one at a path or query boundary. Unbacked
links (at most five) are listed in an appended note; urls_note replaces
the default note, with {urls} as the placeholder for the list.
Assistant messages are not evidence, so an invented link is marked again
when the client sends it back on the next turn.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 523e8cd3c4)
ChatEndpoint gained a compressor parameter after the spec was written, so
the openai test package no longer compiled.
Assisted-by: Claude:claude-sonnet-5-5 go golangci-lint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The comment in sourceURLs named ExecuteMCPToolCall, which the tool
consumer matrix test (core/services/addons/tools) reads as a marker of a
file that discovers or executes tools. evidence.go only reads tool
output, so reword the comment instead of classifying the file.
Docs-Not-Needed: comment-only change, no behaviour change
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 1043d3cbc4)
Keep balanced parentheses in source URLs and decode MCP text arrays before
extracting links. This preserves Wikipedia paths and escaped query
separators instead of appending truncated citations.
Assisted-by: Codex:gpt-6
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 9b81cb20ab)
A system prompt that tells a model to look things up first does not stop
it from answering from its own knowledge, for example when the question
comes with a picture.
profile.require_tool makes the first model call of a chat request that
offers the profile's server-side MCP tools use tool_choice "required".
Every follow-up call of the server-side tool loop is made with the
client's tool_choice again: a "required" that stayed in place would make
the model call a tool after every tool result and never answer until the
iteration limit.
With a tokenizer template the backend enforces "required" with a grammar
built from the chat template's tool-call format. Where LocalAI builds the
tool-call grammar itself, the first call's grammar leaves out the
no-action function instead.
The first call is not forced when no server-side tool was offered, when
the client set tool_choice or function_call itself, or when the request
does not end with a user message, so a request returning a client tool's
result cannot bounce tool calls between client and server.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 2801938aac)
A research profile asks the model in its system prompt to cite its
sources, but a model can answer from a search result without naming a
single link, and the client never sees the server-side tool results that
held them.
evidence_check.sources appends one line with the distinct http(s) URLs
returned by the request's successful MCP tool calls (at most five, in
order of appearance) when the final answer names no URL itself. An answer
that already links something is left unchanged, and nothing is added when
no tool result holds a URL, so a link is never made up. The label is
configurable through evidence_check.sources_label (default "Sources") for
profiles that answer in another language.
It runs in the existing evidence gate, on the same streaming and
non-streaming paths as the number note.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 4ee6ff1395)
A trigger run or an agent job has nobody to approve a step, and so far
the approval mode auto_readonly and the act.* grants were only recorded.
The prompt was the only thing that kept such a run from acting.
Every tool call of an unattended run now goes through one check at the
call. A tool that only reads runs. Any other tool needs the grant of its
class, and the check fails closed:
- act.files for write_file, edit_file, move_file, delete_file and the
built-in bash;
- act.addons, or act.addons:<id>, for add-on tools whose declared kind is
not read, plus act.spend when the add-on gates the tool with a
permission;
- act.spend for MCP tools.
A trigger holds its new acts list within the grants of its agent. A
trigger that declared read only runs, or that a binding in auto_readonly
mode made without asking, is refused every non-read tool whatever it
holds. A binding that cannot be read counts as auto_readonly. A job
holds the act grants of its agent. Sub-agents follow their parent, and a
saved agent cannot be started from an unattended run.
A refused call answers with a plain sentence the model can pass on, not
an error, and writes one audit log line. Triggers set acts through the
REST API; a model never can. A trigger whose plan names a file it writes
keeps act.files.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo swag
The Skills page asks for the skills of the saved agents with
agents=true and shows them with the agent's name. A skill of the
agent layer says who proposed it, can be removed (to the Trash) and
is not offered to other agents. The list of a plain chat or of the
agent form never carries them.
Assisted-by: Claude:claude-sonnet-5-5 go node