Commit Graph
667 Commits
Author SHA1 Message Date
Ettore Di Giacinto 39b3e9bb7d test: fix the mock chat window, the MCP route scan and the add-on index
A chat now reports the window of its model, so the mock model with no
context_size got the 4096 default and the tool schemas of a chat no
longer fit: six Chats specs got no answer. Give the mock model a
window that fits.

The MCP route scan read every file under the package, including the
test binaries that a macOS run builds there, and found the route names
in them. It reads only Go source now.

Regenerate the add-on library index for the new Memory description.

Assisted-by: Claude:claude-sonnet-5-5 go ginkgo sh
2026-11-04 02:23:47 +00:00
Ettore Di Giacinto cbec432ed6 feat(addons): upload a zip to the owner and install it from the Add-ons API
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
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto d537695895 feat(addons): write one add-on package as a zip
Add the zip format of a package (a digest manifest plus the files of the
OCI package for one platform) and an addonpkg zip subcommand that writes
it from a verified layout, with a sha256 file. The zip comes from the
same layout the registry package comes from, so there is one build path.

Version comparison moves to contract.CompareVersions so the installer
can use it for upgrades and downgrades.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto 23beb6dc80 feat(chats): let a person choose how memory notes are saved
A memory note is still a proposal that the person confirms by default.
An add-on can declare the user setting with the role memory.save-mode,
and the person then picks: ask first, save it and tell me, or never.

With auto, a note from the person's own Studio chat is applied at once
through the whole proposal path (grant, gate, checks, rate limit, audit)
and confirmed as the person, so it is one change set that revert_change
and the Undo on the chat restore. The chat shows "Saved to memory" with
Undo and no confirmation card. The record carries auto: true and the
audit says the write was automatic.

Safety rules live in the host. A connector chat always asks, a trigger or
job has no tool, a saved agent cannot widen the choice, and a failure to
read the setting asks. At most 5 notes are saved unasked in a turn; a note
over 4 KiB or one that looks like it holds a secret becomes a normal
proposal. With never the memory kind is hidden from propose_change and a
stray call is refused. Only memory reads the setting.

Assisted-by: Claude:claude-sonnet-5-5 go node playwright
2026-11-03 22:46:22 +00:00
Ettore Di Giacinto 7ce4507da1 feat(addons): keep per-person settings that an add-on declares
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
2026-11-03 22:46:22 +00:00
Ettore Di Giacinto c56c1ac343 feat(addons): choose the connection of a credentialed add-on explicitly
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
2026-11-03 22:02:30 +00:00
Ettore Di Giacinto 48b32619b5 docs(api): describe the host access routes and keep them out of the MCP tools
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
2026-11-03 21:55:38 +00:00
Ettore Di Giacinto c5400edd3f feat(addons): add the /api/addons/host-grants routes
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
2026-11-03 21:55:38 +00:00
Ettore Di Giacinto 43f4a2845b feat(addons): declare the connector file operations in Slack and Telegram
The two connectors move files through connector.file.get and
connector.file.put but their manifests asked for nothing, so the install
dialog and blanket approval could not cover them. Declare both as
optional: a connector works without files. Delivery is pull, so neither
calls connector.inbound and it is not declared.

The audit of official packages now reads the operation constants of the
shared connector code. A deployment whose policy file already grants the
connector prefix sees no change.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 21:50:00 +00:00
Ettore Di Giacinto 8cf6153ea6 feat(connectors): let a credentials-only kind have bindings without an agent
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
2026-11-03 21:29:29 +00:00
Ettore Di Giacinto 3607c08c17 feat(connectors): grant the usual permissions once to old bindings
Bindings saved before permission enforcement have an empty capability
list, so file upload, triggers, memory and talk-back stay denied until
the owner sets them. Give those bindings the set that "Allow the usual"
grants, one time.

An empty list is stored the same whether the owner never chose or
cleared every switch, so the store keeps a migration record instead of
a flag on each binding. The record is claimed atomically with a lease,
so several frontends starting together run it once, and a finished
record is never claimed again: later empty lists are the owner's choice.
Each binding is written only while it still has no capabilities, so a
save made at the same moment is not overwritten.

Only bindings whose kind is running and lists permissions are changed;
sensitive capabilities are never granted. One log line per binding and
one summary. LOCALAI_SKIP_BINDING_CAPS_MIGRATION skips it.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt protoc
2026-11-03 21:17:39 +00:00
Ettore Di Giacinto 6cbee5181c fix(http): believe forwarded headers only from a trusted proxy
BaseURL and BasePathPrefix read X-Forwarded-Proto, X-Forwarded-Host,
X-Forwarded-Prefix and Forwarded from any caller when LOCALAI_BASE_URL
is not set. A client could pick the scheme and host that end up in the
SPA <base href>, generated links and redirects.

Gate them on the trusted-proxy resolver (--trusted-proxies): the headers
count only when the TCP peer is trusted. An empty list trusts nobody, so
the request's own scheme and host are used. LOCALAI_BASE_URL still wins
over everything for the origin.

This changes behavior for setups behind a proxy that rely on these
headers without setting LOCALAI_BASE_URL: they must now list the proxy
in --trusted-proxies. The docs say so.

Assisted-by: Claude:claude-sonnet-5-5 go-test
2026-11-03 19:38:36 +00:00
Ettore Di Giacinto ca8b398650 chore(mcp): drop stale welcome-JSON comments
Assisted-by: Claude:claude-sonnet-5-5 go-test
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
(cherry picked from commit 92283865d720f7cee08391ed2ea4816578538174)
2026-11-03 19:24:13 +00:00
L0ria 281b43a693 fix(mcp): point httpapi list_installed_models & system_info at live routes
The standalone 'local-ai mcp-server' HTTP client (httpapi.Client) called
GET / for both list_installed_models and system_info, expecting the legacy
welcome JSON (ModelsConfig / Version / InstalledBackends). That endpoint was
unregistered when the React UI moved to /app, so GET / now 301-redirects to
the SPA. The client uses LocalAI's hardened httpclient, which refuses all
redirects by default, so both tools failed with:

    Get "/app": httpclient: redirect blocked: refusing to follow redirect ...

These two tools have never returned data over HTTP (the client was added
after / became a redirect). Repoint them at live, non-redirecting routes:

  - list_installed_models -> GET /api/models/capabilities
    (returns id/backend/capabilities/disabled/pinned; also makes the
    capability filter, previously a no-op over HTTP, actually work)
  - system_info           -> GET /version + GET /backends + GET /system

The dead routeWelcome constant is removed and the three new routes are
added as route* constants per the package convention. Test fakes and the
Tool<->REST drift map in coverage_test.go are updated to the new routes,
and new specs cover list_installed_models (all / filtered / empty) and
system_info (version + backends + loaded models).

Assisted-by: LocalAI-Agent:qwen3.8-27b-heretic-abliterated-uncensored
Signed-off-by: L0ria <loria@hno3.eu>
(cherry picked from commit d123083bc0)
(cherry picked from commit f352b4278f764ad25902882d26afb761fa1b953d)
2026-11-03 19:24:13 +00:00
Ettore Di Giacinto afb06fd42f docs(addons): describe manifest api 2 only and update the test inventory
The add-on guide names one manifest api, one way to move a file and no
migration path, and drops the section on arguments of older tool
versions. The Swagger files are generated again, which also brings in
fields that the last generation missed, and the test of the apps view
now emits every field it documents. The inventory of the add-on suites
lists the specs by their new names.

Assisted-by: Claude:claude-sonnet-5-5 go swag
2026-11-03 16:57:13 +00:00
Ettore Di Giacinto c5f636d1b9 refactor(addons): move connector file bytes by handle only
A connector add-on no longer carries file bytes in the connector
envelope. The ops media and send_file need a transfer handle, and the
gateway refuses a call to them without one, or for a kind that declares
no files.

- The manifest field files.transfer, the inline file of a request and
  of an answer (ConnectorFile) and the 1 MiB cap
  (MaxConnectorFileBytes) are removed. The 2 MiB limit of a connector
  call between hosts drops to 1 MiB, because it holds text and no file.
- A kind that declares no files moves no file: the host answers a sender
  who attaches one with a plain sentence and does not offer attach_file.
  A direction left out of a declared files block still gets the host
  ceiling.
- The inline layer is gone from the file limit, from the API
  (inline_bytes) and from the Files section of the connector page.
- The Telegram and Slack add-ons keep only the streaming paths. The
  tests moved to the handle path, and the studio test rig gives every
  connector add-on a table of handles.

Assisted-by: Claude:claude-sonnet-5-5 go test node
2026-11-03 16:57:04 +00:00
Ettore Di Giacinto 2a491cb2cf refactor(addons): remove the shims for older add-on data
No release carried add-ons, so nothing reads older add-on data or
manifests. Delete the code that did.

- Connectors: the kind and the add-on of the Telegram connectors that
  core once carried are gone (LegacyAddon, KindTelegram, the stored
  binding normalization, the platform titles). A binding needs a kind,
  and its add-on is the one that serves the kind. The conversion of the
  old agent pool lists every connector as dropped instead of making a
  Telegram binding, and its notes say so.
- The library index reads only the id field of an entry, not addonId.
- A host control that has a capability block (the network policy) is
  attached by that block alone, not by a tool that replaces bash.
- The Office create tools no longer answer for the argument names of an
  earlier schema, and the identity file note about old addresses is
  gone.
- The known debt of the provider-name guard drops the entries of the
  conversion and of the resource listing shim, and its ceiling goes
  from 13 to 5.

Assisted-by: Claude:claude-sonnet-5-5 go test
2026-11-03 16:56:49 +00:00
Ettore Di Giacinto 1834d4fd72 refactor(addons): make manifest api 2 the only version
No release ever shipped add-ons, so there is no installed base on api 1.
The manifest must now declare api {"min": 2, "max": 2}; a manifest with
api 1, no api or a wider range is refused with a message that names the
range.

Every field that was gated on api 2 is now part of the contract, so the
Min*API constants and their gates are gone, and so are HostAPIMin and
the apiMin host field. Three older-manifest shims go with them: the
resource listing the host implied for one add-on, the legacyArgs hint
for arguments an older version of a tool took, and the tests that
checked the gates. All shipped manifests and the test fixtures declare
api 2.

Assisted-by: Claude:claude-sonnet-5-5 go test
2026-11-03 16:15:07 +00:00
Ettore Di Giacinto ab0848293e feat(slack,telegram): stream files by handle
Both add-ons implement the streaming side of the connector route: they
read a file from the platform and put it to the host chunk by chunk, and
they pull the file to send from the host and stream it into the
platform upload. Neither holds more than one chunk in memory.

Slack uploads with the external upload flow and tells Slack the length
first. Telegram sends a multipart body of known length, and a picture
over 10 MB goes as a document, because sendPhoto refuses more. The
client of a streamed file has no total timeout; the context carries the
bound.

The manifests declare the limits that the platforms state, and
files.transfer. Telegram moves to manifest API 2 for it. The limits are:

  Slack: 1 GB per file, as in the help center article
    https://slack.com/help/articles/201330736-Add-files-to-Slack
  The upload API names no per-file cap
    https://docs.slack.dev/reference/methods/files.getUploadURLExternal
  Telegram, public Bot API: bots download up to 20 MB with getFile and
    send up to 50 MB (10 MB for photos)
    https://core.telegram.org/bots/faq
  A local Bot API server lifts both (no download limit, 2000 MB upload)
    https://core.telegram.org/bots/api#using-a-local-bot-api-server
    The add-on has no setting for another server, so it declares the
    limits of the public one.

Slack gets the host operation prefix connector. in the official host
policy, as Telegram has, so it can call the two file operations.

Assisted-by: Claude:claude-sonnet-5-5 go test
2026-11-03 15:54:45 +00:00
Ettore Di Giacinto 4ba1b7b2bd feat(addons): move connector file bytes by handle
A connector add-on that declares files.transfer no longer carries file
bytes in the connector envelope. The host puts a random handle in the
request of the op media (write) or send_file (read), and the add-on
streams the file with two new host operations, connector.file.put and
connector.file.get, in chunks of at most 256 KiB.

The process that runs the add-on registers the handle when the call
arrives and ends it when the call returns. A handle is bound to the
add-on, so another add-on gets the same answer as for an unknown
handle. An unfinished inbound file is dropped with its partial bytes,
and a handle with no call for two minutes expires. Inbound bytes go
to the blob store of the workspace, outbound bytes come from it, so a
cluster needs the shared object store and no shared disk. The file is
never held in memory. The host overwrites the size and hash of an
inbound file with its own record.

The change is additive: the field files.transfer needs manifest API 2,
a kind without it keeps the inline path and its 1 MiB cap, and the host
never sends it a handle. A call that carries a handle may run up to 15
minutes. The ceiling of a file also stays under the largest file of a
workspace.

Assisted-by: Claude:claude-sonnet-5-5 go test
2026-11-03 15:54:45 +00:00
Ettore Di Giacinto f5f4b7ea58 feat(http): add --trusted-proxies to resolve the real client address
Echo's RealIP trusted X-Forwarded-For and X-Real-Ip from any peer
when no IPExtractor is set. That fed the login and registration rate
limits and the API trace client address, so a client could dodge a
limit by rotating a forged header.

Add pkg/clientip, one resolver for the whole server. With
--trusted-proxies / LOCALAI_TRUSTED_PROXIES (IPs and CIDRs, empty by
default), the forwarded chain is read only when the TCP peer is
listed. The client is the right-most X-Forwarded-For entry that is
not a trusted proxy; a malformed entry stops the walk. With the flag
unset every request resolves to its TCP peer.

The resolver is set as Echo's IPExtractor and used by the access log
(--log-client), so all consumers agree.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 15:48:38 +00:00
Ettore Di Giacinto 2b588a7350 fix(connectors): hold add-on write tools to tools.write in connector chats
A write tool of an add-on that holds a credential was protected only by
the write mode of the connection and the confirmation of the proposal.
In a connector chat it now also needs the capability tools.write for the
sender of the turn, through the same Authorize path as the other
capabilities: the tool is not offered without it, a call is checked again
and every decision is audited. The default is no.

The rule is by shape, not by name. The manifest says an add-on holds a
credential, and contract.ToolPermission maps its non-read tools to
tools.write. The chat reads the permission from the tool definition, so
the core names no add-on.

Also close a hole in local-owner mode, where a chat turn and the browser
share one credential: a proposal that waits for a person could be
confirmed from a chat turn. The call of a chat tool now carries a mark,
and the owner refuses to confirm such a proposal under it. A remote owner
does not see the mark; local-owner mode has no remote owner.

Assisted-by: Claude:claude-sonnet-5-5 go test
2026-11-03 15:41:45 +00:00
Ettore Di Giacinto 15e2321d50 feat(connectors): edit the permissions of a binding on the shared connector page
Bindings saved before permissions were enforced have no capabilities, so
they deny files from senders, triggers, memory writes and talk-back, and
the owner could only fix that through the REST API.

The shared connector page now has a section for it, so every connector
add-on gets it: a switch and a description for each capability with the
sensitive ones marked, the role matrix (never a sensitive capability, never
above the ceiling), pairing with a code shown once, the approved channels
for talk-back, and a read-only view of the access check with the latest
decisions. A binding with no capability shows a notice that says why the
features are off, with an "Allow the usual" button that turns on the
non-sensitive everyday set.

The kinds endpoint returns the closed list of capabilities with a title,
a description and the sensitive and usual flags. The field is additive.

Assisted-by: Claude:claude-sonnet-5-5 go node playwright swag
2026-11-03 15:36:21 +00:00
Ettore Di Giacinto 24c837e453 feat(studio): grant built-in capabilities to a saved agent
A saved agent now has a Builtins list: the capabilities its owner gave
it (memory.write, profile.write, skills.write, pages.write,
dashboard.write, triggers.propose, triggers.run, talkback.channels,
talkback.new_channel, files.upload, act.files, act.addons, act.send,
act.spend and the opt-in group saved_agents). The list is empty by
default, and nothing turns a grant on for a template, an import or a
write that omits the field.

A chat of the agent holds the grants the user may use at the turn
(agent scope). A tool exists when the grant is held, the chat did not
switch its group off and the run can hold rights: a sub-agent or a run
that started by itself never does. propose_change and revert_change
now exist only for a chat that holds the grant of at least one kind,
and the schema lists only those kinds.

propose_change gains the kinds skill, page and dashboard. A skill is a
SKILL.md for the personal or the agent layer, and a skill with
locked: true is refused, also as a shadow of a locked personal skill.
A page is a set of files checked against the Pages rules and written
as one change set. The dashboard is a home.json checked against its
schema. All three are re-checked when the owner confirms or edits.

GET /api/chats/agents/builtin-grants lists each grant with its title,
description and sensitive flag. The agent record, the write body and
the export file carry builtins; an old file imports as before and an
unknown name is dropped in an import and refused in a write.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt swag
2026-11-03 14:56:42 +00:00
Ettore Di Giacinto e790271c63 feat(addons): let the GitHub add-on write, under a per-connection write mode
A connection now has a write mode, chosen on the add-on page: off (the
default), propose (each change waits for the person to confirm) or auto
(a chat of the person makes it at once). The host enforces it for every
write tool of an add-on that holds a credential, before the add-on is
called, and holds the call to the repository list of the connection.

Host:
- Binding.WriteMode, stored in both stores and in the connector API
  (write_mode). Absent means off, so existing bindings stay read only.
- A credentialed add-on may declare write tools. They name the resource
  with the credential slot argument, never resourceId. A tool can set
  approval "always", a ceiling that keeps a proposal even in auto.
- The proposal broker fetches the credential for the preview and again
  for the apply, so it is never stored. A mode lowered between the
  proposal and the confirmation refuses the apply. A proposal that
  awaits a person cannot be confirmed by a chat turn.
- New capability name tools.write for the roles a connector kind
  declares.

Add-on, tools (kind write): comment-issue, comment-pr, create-issue,
edit-issue (title, close or reopen), add-labels, review-comment-pr.
They preview plain facts, refuse a stale target, cap and screen the
text for secrets, and keep a receipt journal plus a hidden marker in the
text, so a change whose answer was lost is not sent twice.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo node
2026-11-03 14:53:20 +00:00
Ettore Di Giacinto 337a034601 feat(addons): declare document slots and legacy arguments in the manifest
The four document skill names were code in the contract, the hint for
binary files kept its own list of formats and libraries, and the chat
boundary rewrote the old arguments of one add-on by its ID.

A skill that shadows a name can now declare the slot it serves: kind,
file extensions, name words and a library hint. A tool can declare
legacyArgs, the arguments it refuses now, each with a hint. Both need
manifest api 2. The four default slots move to a data file that the
contract reads, so a manifest that declares nothing keeps working.

The host takes the replaceable names, the files a skill covers and the
hint for a script from the skills the add-ons list. It compares no
format or add-on ID. A guard spec fails when it does again. The Office
manifest declares its slots and legacy arguments and accepts api 2.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 14:45:43 +00:00
Ettore Di Giacinto b15905e90f fix: restore the files that the previous commit removed by mistake
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
2026-11-03 14:33:05 +00:00
Ettore Di Giacinto 9e6d6520bd feat(connectors): enforce add-on permissions on each connector turn
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
2026-11-03 14:32:01 +00:00
Ettore Di Giacinto 3bbb40b6ff feat(addons): GitHub settings page, registration and docs
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]
2026-11-03 14:20:22 +00:00
Ettore Di Giacinto e0da582988 feat(addons): credential slot, resource allowlist and rate-limit error
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]
2026-11-03 14:20:12 +00:00
Ettore Di Giacinto df14cf43e2 feat(addons): add the Short memory add-on
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>
2026-11-03 13:57:28 +00:00
Ettore Di Giacinto ad7e9b3261 feat(addons): let a manifest declare the context files the host loads
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>
2026-11-03 13:57:28 +00:00
Ettore Di Giacinto 1f481b0747 feat(connectors): add-on roles and permissions, and sender pairing
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
2026-11-03 13:52:56 +00:00
Ettore Di Giacinto 6cda05c526 test(addons): name the files API spec apart from the network one
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:47:51 +00:00
Ettore Di Giacinto 6d3aec30b3 feat(studio): build connector file limits and sentences from one resolver
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>
2026-11-03 11:47:50 +00:00
Ettore Di Giacinto 198d03125b feat(addons): let a connector kind declare its file abilities
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>
2026-11-03 11:47:50 +00:00
Ettore Di Giacinto 0e50c3f9ca feat(addons): let a connector kind declare per-binding options
A kind can declare options with a few values and a default (manifest
api 2). The host checks the owner's choice against the declaration,
stores it on the binding, shows it in the REST API and sends the value in
effect with every poll. The add-on decides what the option means.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:30:11 +00:00
Ettore Di Giacinto ca5c8e79e3 feat(addons): add the history op to the connector contract
A connector kind can declare history.thread in its manifest (api 2) and
then serves the op history: the earlier messages of one thread, oldest
first, bounded by a limit. A kind that declares nothing is never asked.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:30:11 +00:00
Ettore Di Giacinto 3273f1772b test(addons): inventory the network capability specs
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:58:46 +00:00
Ettore Di Giacinto bcd81fbc2e docs(addons): document the network capability
Describe capabilities.network for add-on authors: its fields, how the
host control attaches to it, that the chats_egress_* settings keep their
names and values, and how a manifest without the field keeps working.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:58:46 +00:00
Ettore Di Giacinto cf3de8886f feat(sandbox): declare the network capability in the manifest
The manifest keeps asking for the network-policy control and the
egress.check operation, and now also declares capabilities.network with
the title the host uses in text. A test keeps the control working for a
manifest of the same shape that declares no network.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:58:46 +00:00
Ettore Di Giacinto fc6abfca8c refactor(studio): name the network from the add-on's declaration
The chat's network view, the administrator view and the line the model
reads about the network of its commands now take their name from the
capabilities.network an add-on declares, or from the title of its
commands tool when it declares none. The sentences no longer say
"Sandbox" or "the sandbox". The API gains a network object with the
title and the preset titles next to the existing addon field.

The text of the built-in bash and of the host-exec setting names an
add-on that provides bash instead of one add-on.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:58:46 +00:00
Ettore Di Giacinto f7a4c68fad feat(addons): add a network capability to the manifest
The network-policy host control was tied to a tool that shadows bash, so
it could only belong to an add-on that replaces that one tool. An add-on
now declares capabilities.network (kind egress-check, an optional title
and optional preset titles) and the host attaches the control to the
declaration. A manifest that declares nothing keeps the old attachment.

The field is additive and needs manifest api 2 and the egress.check host
operation.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:58:46 +00:00
Ettore Di Giacinto 61c54dc833 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>
2026-11-03 10:43:37 +00:00
Ettore Di Giacinto c1dd87e0d5 feat(addons): declare a resource listing in the manifest
An add-on that keeps resources can now ask the host for a read tool
that lists the ones the caller may use, by declaring
capabilities.resources.list. The declaration names the tool and,
optionally, the key of the result array. It needs manifest API 2 and
must not reuse the name of a tool the add-on declares.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:24:42 +00:00
Ettore Di Giacinto 0cd265e86e merge: bring master into the proposal UI branch
Union of both case lists in the add-on suite inventory; the swagger
files are regenerated from the merged code.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:21:41 +00:00
Ettore Di Giacinto 0c08e17a30 feat(addons): add an execution descriptor to the tool contract
A tool that shadows bash can now declare an execution descriptor: its
class, a title and a note for the model, and the skill kinds whose
runtime it supplies. The host reads it to build its text and to decide
which skills need isolated execution, instead of knowing add-on IDs.

The field is optional and needs manifest API 2. The host now speaks
API 2 and still accepts add-ons that declare only API 1, so manifests
that declare nothing new keep working unchanged.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:07:46 +00:00
Ettore Di Giacinto b4734eef68 merge: bring master into the proposal UI branch
The Memory page reads the context-file sources that master added.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:42:29 +00:00
Ettore Di Giacinto f7c8dd15d6 feat(contract): add the change proposal record
A model can ask to change the person's own state (a memory note, a
profile, a skill). Add the versioned wire record, its kinds, scopes,
statuses, source and limits, so every surface that runs a model, and
any add-on, shares one type without platform code.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:23:59 +00:00
Stefan Walcz dcd37a10ef fix(xsysinfo): read AMD GPU memory from sysfs when rocm-smi is missing
getAMDGPUMemory only asked rocm-smi. ROCm 7+ images no longer ship it
(only amd-smi, whose Python module is missing in the image), so on an
AMD Strix Halo (gfx1151, ROCm 10.1 image) GET /api/resources returned
"gpus": null and aggregate.gpu_count 0, and the Models page said
"No GPU found. Fit is measured against 122.7 GB of memory" while
inference ran on the GPU.

The amdgpu driver exposes the same figures in sysfs, readable inside the
container:

  /sys/class/drm/card0/device/vendor              0x1002
  /sys/class/drm/card0/device/mem_info_vram_total 536870912
  /sys/class/drm/card0/device/mem_info_vram_used  240300032
  /sys/class/drm/card0/device/mem_info_gtt_total  128849018880
  /sys/class/drm/card0/device/mem_info_gtt_used   33055911936

When rocm-smi is absent or reports nothing, walk the DRM tree with the
existing scanSysfsGPUs (cardN only, connectors skipped, vendor 0x1002)
and report every card that has mem_info_vram_total. The GPU report is
built by one helper shared with the rocm-smi parser, so the
shared-memory APU rule from #12058 (GTT >= 8x VRAM folds GTT into the
totals) applies identically. GetResourceInfo, GetGPUAggregateInfo and
the fit check all read GetGPUMemoryUsage, so they now see the GPU;
DetectGPUVendor already consulted sysfs.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit bf222cadba)
2026-11-03 09:17:42 +00:00