Commit Graph
16 Commits
Author SHA1 Message Date
Ettore Di Giacinto 894692ca16 feat(github): allow classic tokens and any-repository lists, both opt-in
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>
2026-11-05 09:53:17 +00:00
Ettore Di Giacinto 7df9b263ed feat(addons): carry translated text in the manifest and the library
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
2026-11-04 17:46:53 +00:00
Ettore Di Giacinto e48bf37995 feat(addons): translate the add-on pages into every language of the web UI
The shared connector page kept its words in English text maps inside the
components, and only Studio shipped translations. Move the maps to JSON
bundles (addons/internal/connector-ui/src/i18n) and add bundles for the
Telegram, Slack, Discord, GitHub, Jellyfin, Home Assistant and Talk pages
in German, Spanish, French, Indonesian, Italian, Korean, Brazilian
Portuguese and Simplified Chinese. English stays the default, and a key
that a bundle lacks reads in English.

Plural sentences now go through the translator, so each language picks
its own plural form. The "More" and "optional" labels, the sender line and
the dates follow the language of the page instead of the browser.

A new gate, addons/internal/i18ncheck, fails when a language lacks a
bundle, a bundle lacks a key or has an extra one, a placeholder differs
from English or a value is empty. It runs in make test-i18n and so in the
UI unit tests of CI, with no baseline. Playwright specs render the pages
in Italian, check that no language runs text out of its box on a phone,
and save screenshots when UI_SHOTS_DIR is set.

Assisted-by: Claude:claude-sonnet-5-5 node
2026-11-04 17:30:57 +00:00
Ettore Di Giacinto ad39954af1 feat(addons): let a chat enable several connections of a credentialed add-on
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
2026-11-03 22:33:28 +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 1a64a33f3d feat(github): show a real approval card for GitHub write proposals
A GitHub write that waits for the person used to show the raw preview
JSON in the host's approval frame. The add-on now declares an
approval-card component for each of its six write tools. It shows what
will be done, where (with a link to the issue or pull request), as which
connection, and the exact stored text that will be posted, plus a note
that the text is public.

The proposal does not name the connection, so the card reads the saved
connections and names the one that lists the repository.

Assisted-by: Claude:claude-sonnet-5-5 node playwright eslint
2026-11-03 21:43:57 +00:00
Ettore Di Giacinto 68d555a986 fix(github): fit the write mode cards and the repository hint on one row
The three write mode cards now sit on one row when the form is wide
enough and stack on a phone. The repository hint is shorter so the two
line clamp no longer hides its end behind a More link at 390 px.

Assisted-by: Claude:claude-sonnet-5-5 playwright
2026-11-03 21:43:57 +00:00
Ettore Di Giacinto 6abe5b1882 feat(github): rework the connection page of the GitHub add-on
Token help now works: a button opens GitHub's fine-grained token page,
the steps are open while there is no connection, and a table lists the
permissions for read only and for write modes. The page says it does not
answer mentions yet.

Errors sit under their own field. The repository list splits on lines and
commas only, so "not a repo" is one entry and is named in the message.
The connection name defaults to the first repository.

The write mode cards each carry one consequence line, and "Allowed" is
now "Acts without asking". The Test result is drawn per repository in the
row, with a tone, a busy line, and a plain text when GitHub refuses the
token. The unit tests and a browser spec cover the page.

Assisted-by: Claude:claude-sonnet-5-5 node playwright eslint
2026-11-03 21:43:57 +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 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 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 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 3d687a209c feat(addons): GitHub add-on skeleton and read tools
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]
2026-11-03 14:20:12 +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