Commit Graph
130 Commits
Author SHA1 Message Date
Ettore Di Giacinto b5fecb1bdc feat(ui): show the provider notice and a forget action in Settings > Memory
The provider picker now draws the slot's health notice (the circuit breaker
is open) with the shared Notice component. When the selected add-on serves
the slot and declares the forget role, it also offers "Forget everything from
this provider", behind a confirmation dialog that says whether the change can
be undone. The action calls DELETE /api/chats/memory; the built-in provider,
Off and a provider that is down have none.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 09:15:38 +00:00
Ettore Di Giacinto 75c21675fc feat(studio): scan knowledge proposals for secrets
A text that a model proposes for a collection is checked with the secret
patterns that already keep a memory note from being saved at once. A
flagged text is still stored and shown, with a warning on the card, and
the owner can confirm it in the web interface. A messaging chat cannot
confirm it: the host does not write it in the chat, and a yes does not
apply it. A no still drops it.

The proposal carries the result in a new warning field, worked out at
each read.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 09:15:21 +00:00
Ettore Di Giacinto a0e32049d4 fix(addons): stage a release once for the permissions preview and keep update checks warm
The install dialog and the upgrade approval ask the owner for the
permissions of a release. The owner answers from the verified manifest of
the staged package, so every read downloaded the package again, checked
its signature, and held the operation lock the whole time. A second read
(a retry, the upgrade approval, a reopened dialog) paid the full cost
again and waited behind the first.

Keep the verified answer per release, keyed by the digest the library
pins, for ten minutes, and let concurrent reads share one download. The
operation lock is held for the stage only. A moving tag has no fixed
bytes and is still read every time. The installed flag is read on every
call, and the nonce still binds the digest, so nothing is shown as
allowed that the owner has not confirmed.

The dialogs now say what they wait for after three seconds instead of a
bare "Reading what this add-on asks for...".

The update check cache is empty after every start, so a moving channel
read as "not known" on each visit until an administrator asked. The owner
now checks the installed add-ons in the background shortly after start
and every 30 minutes.

Measured with a package that takes 500 ms to download: the second read
went from 510 ms to 13 microseconds, and two concurrent reads from 410
and 820 ms to 404 ms each.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 09:11:56 +00:00
Ettore Di Giacinto d07d52854f feat(studio): estimate the embedding work on a knowledge proposal
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
2026-11-05 09:07:58 +00:00
Ettore Di Giacinto ace0dfcae8 feat(collections): show how a search finds passages
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
2026-11-05 09:03:52 +00:00
Ettore Di Giacinto 1143acb292 feat(agents): effective view and Try it in the agent editor
The agent form now has an Effective view that says in plain words what
the agent can do right now, worked out from the form as it stands, saved
or not: tools, skills, files, extra rights, rights it uses on its own,
the memory choice and the save level that applies, knowledge
collections and messaging connections. Each line names the section it
explains. Anything blocked shows its reason code and one fix: a button
that moves to the switch on this form, or a link to the page that owns
it. A connection that is saved is checked with the existing access
preview endpoint. A line says when memory is off in the person's
Settings.

Try it sends a message to a short-lived test chat built from the form
(model, tool switches, instructions as the system prompt) without saving
the agent. The panel says that permissions, memory, knowledge and
folders are not used by the test, and the chat is deleted on close. The
header gets a More menu with Export for a saved agent.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 06:56:40 +00:00
Ettore Di Giacinto f0caddf8f1 feat(studio): let the assistant propose a text for Knowledge
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
2026-11-05 01:21:17 +00:00
Ettore Di Giacinto 8cf8079b25 feat(agents): tool levels, templates and connections in the agent editor
Part 2 of the agent editor redesign.

- Tool levels (Look only, Assistant, Full) set the stored switches and
  grants. Applying one, or a starting template, shows what changed and
  can be undone before saving. A level never grants a sensitive right.
- Templates are cards that say which level they start at.
- A Connections section holds the choice of connections an add-on may
  use and lists where the agent is connected. It uses the existing
  connections field and the messaging list.
- The model picker has search and shows what each model can do.
- Memory choices sit two by two to shorten the section.
- Advanced collapses, and the section nav sticks under the header on a
  phone.
- The overflow guards now cover 360 px, and the agent editor 320 px.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 01:15:34 +00:00
Ettore Di GiacintoandClaude Sonnet 5.5 62d75c0de0 feat(memory): serve the notes through a provider slot
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
2026-11-05 00:59:43 +00:00
Ettore Di Giacinto 849d985f81 feat(agents): one-page agent editor with a sticky summary header
The agent form was an accordion of nine folds with two Save buttons and no
summary. It is now one page: a sticky header with the agent name, mode,
unsaved-change count, model, tool and memory chips and a single Save,
a section nav that scrolls to Identity, Capabilities, Memory and
knowledge and Advanced, and one card per section.

Nothing the form saves changed, and the API is untouched. The parts keep
their data-section ids, test ids and summary lines. The folds are gone,
so the e2e specs no longer click them open; the "needs a name" and
"changed" checks now read the card header (data-group). The nav wraps
on a phone instead of scrolling sideways, and the page has no
horizontal overflow from 320 px up.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 00:57:58 +00:00
Ettore Di GiacintoandClaude Sonnet 5.5 69bbd61b44 feat(ui): name the knowledge command and explain the Conversations notes
The Home slash command that opens collections is now /knowledge. /memory
still finds it, and no skill can take either name.

The Conversations tab of Settings > Memory gets a line that says what
the notes are and that a sender who is not paired always waits for
approval. Add an end-to-end test that reads and deletes one of them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 00:42:58 +00:00
Ettore Di Giacinto adbb71cfdf feat(memory): add Always confirm, an agent save rule and a messaging row
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
2026-11-04 19:45:08 +00:00
Ettore Di Giacinto 917b54e6c1 feat(memory): let each saved agent choose its notes
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
2026-11-04 18:26:20 +00:00
Ettore Di Giacinto ea1f5f449f feat(studio): let the model search and read earlier chats
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
2026-11-04 18:04:50 +00:00
Ettore Di Giacinto 2316e14632 feat(memory): add a Memory On/Off switch and private chats
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
2026-11-04 17:50:26 +00:00
Ettore Di Giacinto cde3a2d6d9 feat(ui): show search hits with highlights, filters and a link to the message
The chat search of the Chats page and of the Ctrl or Cmd+K dialog kept
the list of the newest chats and only filtered it, so a chat that was not
in the list could never be found. From two characters the list now shows
the answer of GET /api/chats/search: the hits best first, each with the
chat, who wrote the line, its date and a snippet. The matched words are
marked from the offsets the server sends and drawn as text nodes, so
message text is never read as HTML. Filters for agent, source and date
narrow the search. Arrow keys move through the hits, Enter opens one, and
Escape clears the search before it closes the dialog. The list shows a
searching, empty, failed (with Try again), still-indexing and partial
state.

A hit opens its chat at the message: /app/chats/<id>?seq=<n> scrolls to
the line when the transcript has it and marks it for a moment, again on a
second click. The new strings are in every locale.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:47:19 +00:00
Ettore Di Giacinto 26a5e46c2b fix(ui): keep the add-ons list on screen while the server is busy
A page loaded while the owner was busy had no list to draw, and a failed
read could look like an empty install. The apps list read now carries
the stale mark of the server. The page keeps the last list (from the
server, or from the copy this tab stored), shows a notice that names the
add-ons being upgraded, offers a retry, and asks again with the existing
backoff. Each add-on that is changing shows "Upgrading..." with its
phase. An error, or a stale copy of an empty list, is a load error, and
the empty state appears only when the owner answered with an empty list.

The new strings are in all eight locales.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:41:18 +00:00
Ettore Di Giacinto 78044d4def feat(ui): call the collections page Knowledge, not Memory
The word "Memory" named collections of documents, notes about the
person, and RAM in the same navigation. Collections are now Knowledge:
the sidebar item, the page title and text, the Home action, the agent
form section and the chat tool group. The tool group "Memory and
profile changes" is now "Suggested changes", since it is the generic
ask-first mechanism for notes, rules, skills, pages and the dashboard.
The Settings section "Memory and models" is now "Models in memory".

Only display strings change. Ids, routes, locale keys, group ids,
grants and stored data stay as they are. All nine locales are updated;
the docs name the Knowledge page and say which search the default
engine does.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:17:38 +00:00
Ettore Di Giacinto 3e84fe671f Add one notice for off or unavailable states, and use it in chat and add-ons
Notice draws what is off, why, who can fix it and one fix button, in
that order, with a live role set by severity. FixNotice draws the same
markup for add-on UIs, styled by the host's ui.css.

The chat notice for commands now says that the server's built-in bash
is off. An administrator gets the Settings row and a link to the
Add-ons page; anyone else is told to ask an administrator. Hide keeps
working. The Add-ons library card, the update notice and a turn on
refused as installed_incompatible use the notice, with an Upgrade fix.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 10:19:45 +00:00
Ettore Di Giacinto 22ca7403f1 feat(ui): one switch per capability in the agent form
The agent form switched the same capability in two places: a Tools
switch and a permission at the bottom that changed what the switch
meant. Show one switch per capability and put its permissions under it
as options, greyed with a reason while the switch is off. The stored
data and the API do not change.

Triggers, memory and profile changes, files and saved agents each hold
their options. Saved agents is one switch: either stored value turns it
on, off clears both, and on adds only the group so it never turns off
the defaults of the messaging connections. Reply, start new channels
and the permissions that apply when nobody is watching have a section
each. Every switch and option has one line that says what it lets the
model do, and the three trigger switches have names that cannot be
mixed up. The Permissions fold is gone: its list is now in Tools.

The form names a messaging connection of the agent that does not allow
an option the agent holds, with a link to that connection. The chat
Tools menu draws the same rows and shows the held options read only.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 10:17:25 +00:00
Ettore Di Giacinto cbe84cf062 feat(ui): install an add-on from a file
Add an Install from file button to the Add-ons page. The file goes to the
owner, then the install dialog opens with the host access step, the file
sha256 and the notice that nothing signs a local file. An upgrade does
not turn the add-on on, an older version needs a tick, and the same
version is refused. The link that local-ai addons install prints opens
the dialog for the staged file. The list, the details and the host access
page show that the add-on came from an unsigned local file.

Assisted-by: Claude:claude-sonnet-5-5 eslint playwright
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto a2e83e7329 feat(ui): show locked host grants in the install dialog and the Host access page
When the owner keeps UI grants locked because sign-in is off, the
overview and the preview say so. The Host access page and the install and
upgrade dialogs show the operations read-only with one sentence that
says why, and the dialogs approve no operation. The sentence is added to
every locale.

Assisted-by: Claude:claude-sonnet-5-5 node
2026-11-03 22:52:09 +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 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 c5c952a117 feat(react-ui): add the host access step, approval and page
Show the host operations an add-on asks for in the install dialog, hold
an upgrade that asks for more until an administrator approves it, and add
a Host access tab with the grants of each add-on, the deny rules and the
blanket switch. All strings are in every locale.

Assisted-by: Claude:claude-sonnet-5-5 playwright eslint
2026-11-03 21:45:05 +00:00
Ettore Di Giacinto 9738667124 fix(ui): show the web tools group as "Look things up online"
The server renamed the group title, but the English locale kept "Web"
and the Tools menu reads the locale first. The menu, the home composer
and the agent form showed a name that the specs and the server did not.

Assisted-by: Claude:claude-sonnet-5-5 playwright
2026-11-03 21:28:48 +00:00
Ettore Di Giacinto 89e29e6008 feat(react-ui): show and edit the acts of a trigger
The trigger form has a Permissions section that lists the act.*
grants like the grants of an agent: a plain-words line on each,
sensitive ones marked, and a grant the chosen agent does not hold
cannot be ticked. It says in a callout when a run only reads and why:
no acts, the agent holds none of them, the proposal said read only,
or a read only connection made it. The details page shows the same,
the trigger list marks a read only trigger, and the proposal card has
a Permissions row, so the person sees the acts before pressing Create.

The editor always sends the acts it loaded, so saving a form never
drops them silently. New strings are in every locale.

Assisted-by: Claude:claude-sonnet-5-5 node playwright
2026-11-03 20:53:02 +00:00
Ettore Di Giacinto 9b40c24a30 feat(agents): warn in the editor when a first grant turns off connector features
An agent with no grant list keeps its non-sensitive connector features
(trigger tools, talk-back, files from senders). Granting any one built-in
makes the list the whole answer, so the others stop working. The editor
had no grants list at all, so nobody could see this.

Add a Permissions section to the agent editor with the declared grants.
When the first grant is ticked on a saved agent that had none, a notice
names the connector grants that would turn off and a button adds them.

Also fix the files.upload text. The gate enforces it on files a sender
attaches to a message, not on the agent sending files out, so the grant
is now "Receive files".

Assisted-by: Claude:claude-sonnet-5-5 go-test playwright
2026-11-03 19:38:55 +00:00
Dedy F. Setyawan 9a704b83b7 feat(i18n): complete Indonesian translation in chat.json
Signed-off-by: Dedy F. Setyawan <dedyfajars@gmail.com>
(cherry picked from commit 2413f19b7d)

Conflict resolved against current master: keep the strings master already
has for chat.json and add the translations this change supplies for keys
master lacks. Keys added to master after the PR was opened are still
untranslated and fall back to English.

Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
(cherry picked from commit 8d4799d91b33b2af204e7cc5386d8b984d09f04b)
2026-11-03 19:20:44 +00:00
Ettore Di Giacinto 4bea5f7775 fix(ui): translate the new Home, Chats and Skills strings
Four recent changes added English strings without the other locales, so
the locale completeness gate failed in the UI unit tests. Add the keys
to the eight locales. The baseline stays as it is.

Assisted-by: Claude:claude-sonnet-5-5 node
2026-11-03 19:18:26 +00:00
Ettore Di Giacinto 3cef8d5a91 feat(ui): show the builtin grants in the shared Tools list
The Tools list gains a group, "What the agent may change", with one
row per builtin grant: its title and a plain-words description, and a
mark on the sensitive ones. The agent form switches them through
useAgentTools; a chat of a saved agent lists the grants the agent
holds, read only. Both draw the group with the same useToolGroups and
SwitchPanel code.

The group has no group setter, so Select all and Disable all, and the
"N of M on" count, leave it alone: a bulk click must not grant a
right.

Assisted-by: Claude:claude-sonnet-5-5 node
2026-11-03 19:10:39 +00:00
Ettore Di Giacinto cf1041ca96 feat(ui): list agent skills on the Skills page
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
2026-11-03 19:10:39 +00:00
Ettore Di Giacinto c007ccf994 fix(ui): re-check add-ons before showing the off notice on Home
The notice came from navigation state, which survives a refresh, so it
came back after add-ons were on again. A frontend also reports add-ons
as off when the owner does not answer for a moment, which looked the
same as the instance switch being off.

Home now reads /api/features on mount, drops the notice and clears the
history state when add-ons are on, and clears the state on dismiss.
/api/features gains addons_enabled (the instance switch alone), so the
notice can say "not answering right now" with a retry button when the
switch is on and the owner is silent.

Assisted-by: Claude:claude-sonnet-5-5 eslint node go-test
2026-11-03 17:19:38 +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 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 0772b68bd9 feat(ui): give the agent form the same Tools and Skills lists as the chat
The agent form offered a different set of choices than the chat: grants
as checkboxes for add-ons, connectors and skills, a free-text list of MCP
servers, and nothing for the built-in tool groups, so a person could not
tell which tools an agent can use.

The form now draws the lists of the Tools menu with the same components
(SwitchPanel, SkillsPanel) and the same rows (useToolGroups), fed by
useAgentTools: the draft lists of a new chat on the agent's model, with
the agent's grants and switches applied. A switch changes the form, and
the agent is saved with it:

  - built-in groups: builtins_off and builtins_on (an opt-in group such
    as saved agents starts on only when listed);
  - add-ons, connectors, skills: the existing grant lists, so what is
    switched on is what is granted. A granted entry this account cannot
    see stays in the list, marked, so it can be removed.

The network choice and the folder and mode choice stay where they were.
The MCP servers of the model stay a free-text list in their own fold,
since a model that is not chosen yet has none to list.

Assisted-by: Claude:claude-sonnet-5-5 eslint playwright
2026-11-03 14:30:07 +00:00
Ettore Di Giacinto f34e27e913 feat(ui): describe every switch of the chat Tools menu
The Tools menu built its rows inside the composer, drew only some of
them with a description, and showed a group it had no words for ("changes",
the memory and profile edits) by its raw ID.

The rows now come from useToolGroups, and every row has the same three
parts: a title, a description of what the model can do with the switch on
(and, once it is off, what it loses), and a state line (who serves the
tools, whether commands are isolated, why a switch cannot be used). The
words come from the server, which declares a title and a description for
each group and each add-on; a translation by group ID wins when the
locale has one, so a group added later needs no change here. An add-on
row shows the title its manifest declares instead of its ID.

SwitchMenu is split into the chip and SwitchPanel, and SkillsMenu into
the chip and SkillsPanel, so another page can draw the same lists inline.
New strings are in every locale.

Assisted-by: Claude:claude-sonnet-5-5 eslint playwright
2026-11-03 14:30:07 +00:00
Ettore Di Giacinto 81106dd0d2 feat(ui): edit several folders on a saved agent
The Files section of the agent editor is now a list of rows. Each row
has a folder picker, a read or write switch and a remove button. An
Add folder button stops at eight. A folder inside another one shows the
mode of the outer folder, because that is the mode it works at. The
agent chips, the Tools menu line and the Files page marks read the whole
list. The strings are in all nine locales.

Assisted-by: Claude:claude-sonnet-5-5 eslint playwright
2026-11-03 14:10:52 +00:00
Ettore Di Giacinto ab78cd0301 feat(studio): propose triggers from a connector chat and report back there
A connector chat can now propose a trigger, the person answers it in the
chat, and the run reports to the chat.

The trigger tools propose_trigger and list_triggers are offered in a
connector chat when a capability gate allows triggers.propose for the
binding. The gate is a small interface on the service with a deny-all
default, so nothing changes until the application sets one. Change and
delete stay in Studio.

A proposal from a chat stores the binding, chat and thread, a four
character code and a 24 hour expiry. The host writes the proposal and the
code in that chat. "confirm CODE" or "cancel CODE", as a whole message in
the same chat and thread, is handled before any model turn and needs
proposals.confirm. A binding may make 3 proposals an hour. Proposals that
lapse are marked expired, and the proposal card shows that state.

A trigger carries a delivery: the Inbox (default) or the chat of the
proposal, with the modes always, on_change and on_problem. The host writes
the last message of the run, cut and with broadcast mentions broken; a
message of exactly [SILENT] writes nothing; three failures in a row raise
one alert per 6 hours. A failed delivery falls back to the Inbox. Host
messages of a binding go through an outbox of 20 a minute that queues the
rest. While triggers.run is off, runs of such a trigger are skipped, not
replayed, and the record is unchanged. The audit records each step with a
hash and no text.

Assisted-by: Claude:claude-sonnet-5-5 go test ginkgo eslint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 14:00:31 +00:00
Ettore Di Giacinto dae3d34a21 feat(ui): show chats that no person started apart from the person's chats
Chats that a trigger, a connector, a job or a page started no longer fill
the sidebar, Jump back in or the default All chats list. All chats gets a
second tab, From add-ons and automations, that groups them by origin kind
and by origin, using the name of the binding or the trigger (a kind this
release does not know falls under a generic group). The All chats entry
in the sidebar shows how many of them wait for the person. Ctrl+K still
finds every chat and marks these with a badge, and a transcript of such a
chat says what started it, with a link to its page. The browser remembers
the tab.

The lists ask the server for origin=person or origin=automated, so a
burst of bot chats cannot push a person's chats off a page.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:41:13 +00:00
Ettore Di Giacinto 1f00993cb8 refactor(ui): take provider names from the add-on list, not the locales
The locale files and the Settings labels said "Sandbox" for the add-on that
runs commands. The strings now say "commands" or "an add-on that isolates
commands", in every shipped locale. The name of the serving add-on in the
network section and in a transcript step comes from its declared title in
the add-on list.

Remove the strings that no component reads (the Sandbox notice texts, the
Tools menu sandbox lines and the Home wizard step). Add the seven
execution-status reasons to the locales that lacked them and lower the
i18n baseline, which master was over by seven keys.

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 34b4a5bb0e feat(ui): add a Memory tab to the account settings
The tab lists the context files that the host puts in the prompt (the
user's index and topics, each agent's and conversation's notes, and the
agent profiles) with their sizes against the caps. The owner can read,
edit, delete and clear them; an index over its cap cannot be saved.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:15:03 +00:00
Ettore Di Giacinto c6f004436c feat(ui): show change proposals as a card and on a Proposals page
A propose_change tool line now draws a card in the thread, for any kind
and any source: the kind, file and scope, the source, the time left and a
line diff against the file as it is now. The owner can Confirm, edit the
text then confirm, or Dismiss. A confirmed change shows a receipt with
Undo. Expired and stale proposals say nothing was saved and offer to ask
the assistant again; a failed write leaves the proposal waiting.

The inbox button counts the waiting changes and its panel links to a new
Proposals page that lists them with the same card.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:15:03 +00:00
Ettore Di Giacinto dbb8e8a022 feat(ui): make the home model picker the shared model picker
Move HomeModelPicker to components/ModelPicker and give it what the
other pickers had: multiple selection with chips, free text, a clear
button, a search box on demand, a form-field look, and loading and
empty states. Every entry shows 'alias of X', disabled, pinned or
broken-alias tags from the capabilities listing, and entries that
cannot be used are not selectable. Auto-select skips them.

Add-ons keep HomeModelPicker as a name for the same component.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 08:40:07 +00:00
Ettore Di Giacinto 9fafea8fdf fix(chats): show the network switcher and say why execution status is unknown
The Network row of the Tools menu could never show the Off, Registries and
All switcher: the host stopped setting `applies` because isolation alone does
not prove network-policy enforcement, and nothing else set it.

Let the provider report enforcement in its typed execution status
(`networkPolicyEnforced`). The host sets `applies` only when that report is
true and the status is available and isolated. The Sandbox reports it for the
gVisor and KVM backends, whose guests reach the network only through the proxy
that asks the host. Providers that do not report it keep the "not enforced"
text.

When the host cannot read the status, give the unknown state a reason
(`unknown_reason`) in the API and the Tools menu: owner unreachable, not
reported, stale, invalid, not selected, no answer or reported unknown. Log the
class of each failure, rate limited. After a failure that a restart of the
add-on owner can cause, let a long turn bind and read again, at most once every
five seconds, instead of keeping the first failure for the whole turn.

The Sandbox now reports "unavailable" with a reason when it started without
settings, and explains a backend it does not know.

When the Sandbox document skills are hidden because the Sandbox is not known to
be isolated, say so in the skill list, in the turn note and in the
`load_skill` error, instead of "This chat has no skills now".

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 08:39:30 +00:00
mudler-agentandEttore Di Giacinto 7904b3d0ab feat(addons): declare typed host capabilities (#13034)
* feat(addons): model execution status observations

Keep observation lifetimes explicit before runtime integration. The pure
reducer rejects retries and stale replies using exact binding identity.

Cover all eleven trace families, the complete transition matrix and
bounded histories with safety and liveness checks.

Assisted-by: nib:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): visualize execution status traces

Generate a self-contained viewer from the real reducer and shared traces.
Check independent expectations, coverage and bounded invariants before
exporting data. Detect stale artifacts and nondeterministic generation
without maintaining a second reducer in JavaScript.

Assisted-by: nib:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): honor the viewer artifact root

Keep default viewer output under SDD_LOCAL_ROOT when configured.\nLet go generate use the same default without forcing a source artifact.\nRetain the portable fallback and explicit output and check semantics.

Assisted-by: nib:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): add typed extension contracts

Validate optional home commands, execution bindings and requested host
operations without changing manifest versions or granting authority.
Decode bounded execution status payloads conservatively and document
strict staging compatibility for older hosts.

Assisted-by: nib:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): reject noncanonical extension keys

Reject case aliases before typed decoding can override canonical fields
or bypass presence checks. Keep locale maps outside exact-key checks.

Add regression coverage for status, declaration objects, and nested
entries. All status errors return unknown readiness and isolation.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): reject root enums for status tools

Status tools must accept the empty object. The supported schema enums
contain only strings, so a root enum excludes every object.

Reject root enums only for execution status bindings. Cover incompatible
root constraints and optional properties without changing shared validation.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): project typed home commands

Resolve declared home destinations and display metadata in the installed
Apps view. Pin packaged command icons to immutable asset URLs.

Cover strict declaration transport, immutable receipts, exact health echo,
and process restart. Keep legacy packages and disabled installed intent.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): preserve typed lifecycle declarations

Copy execution and host-operation declarations from installed receipts.
The existing capability completeness test exposes the missing fields.

Exercise real staged Manager upgrades, activation rollback and recovery.
Check installed Apps metadata through pinned mTLS frontends for each actor.
Verify mutation isolation and name receipt replay coverage accurately.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): stabilize fixture archive digests

Map iteration changes tar entry order between identical publishes.
Sort fixture filenames so unchanged packages retain their digest.

Check repeated publish bytes, digests and lexical archive entry order
with and without a manifest transform. Register the regression case.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): resolve explicit host policy grants

Load strict administrator policy files once before application startup.
Honor explicit empty overrides without reading ignored policy files.
Copy programmatic and manager grants to isolate startup authority.

Keep the legacy CLI fallback until the official deployment policy ships.
The generic resolver already defaults to empty grants.

Verify config, CLI, hostops and execution-status checks with GOMAXPROCS=2,
-p=2 and -ldflags="-s -w" to fit the available build disk.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): select explicit deployment grants

Move official host grants out of the generic executable into reviewed
host deployment policy. Stock Docker explicitly selects the shipped file;
unconfigured binaries and nil or empty programmatic grants grant nothing.

Document the standalone upgrade break and deliberate operator selection.
Do not infer consent from installed addons or merge explicit overrides.
Guard policy packaging and generic configuration boundaries with tests.

Verify executionstatus, viewer -check, config, CLI, packaging and root
architecture tests with auth, GOMAXPROCS=2 and bounded build parallelism.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): gate activation on required host ops

Refuse unmet required requests before package publication or runtime
withdrawal. Keep installed intent unavailable when startup grants change.
Optional requests permit degraded operation and never create authority.

Carry bounded admin diagnostics through Apps and the owner carrier.
Keep startup policy immutable and broker authorization authoritative.

Verify executionstatus, viewer -check, lifecycle, packages, updates,
hostops, Apps and gateway offline with auth and bounded parallelism.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(addons): guard host operation registry parity

Compare startup admission names against real builtin registrations so
registry drift cannot silently change request satisfaction.

Cover dynamic progress and stop operations and connector inbound.
Reject missing, extra, duplicate and case-changed names with negative
controls. Record both regression cases in the suite inventory.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): fence status observation effects

Keep observation ownership in admitted runtime operations. Deny all
correlated callbacks before dispatch, including inbound-class callbacks.
Reject status outputs before callers can open or adopt artifact handles.

Retain bounded denial correlations until the runtime endpoint closes so
late callbacks cannot recover ordinary authority. Preserve independent
ordinary operations and document the dedicated read API for T9.

Verify the status model, viewer, and race-enabled gateway, hostops and
runtime suites with the pinned offline environment.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): drain callbacks before retirement

Server.Close does not wait for active callback requests. Clearing retired
observation correlations first can restore inbound authority.

Stop endpoint admission before closing connections. Drain admitted requests
before releasing correlations, and refuse dispatch after Stop. Keep locks
out of handlers and cleanup waits so cancellation cannot deadlock teardown.

Cover pending inbound callbacks, decoded authorization paused across Stop,
and handlers that ignore cancellation. Preserve ordinary authorized work
on other endpoints. Document delayed reclamation under the existing bound.

Verify the status model and viewer, then race-test gateway, hostops,
runtime, the model and root architecture checks with auth enabled.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): bind owner status to exact clients

Add authenticated, versioned status reads over the owner carrier.
Revalidate actor permissions and provider selection before dispatch and
before returning an observation. Keep status separate from authority.

Admit the captured client against its immutable identity under the
withdrawal lock. Never dispatch an old binding through a replacement.
Generate runtime operation IDs on the owner and retain the T8 effect fence.

Cover forged identities, strict DTOs, stale replies, restart ordering,
wire discriminators, and unchanged model exclusion of uiOnly tools.
Document the T10 callback and request-shell integration requirements.

Verify the model and viewer, then race-test gateway, runtime, tools,
and the model with auth enabled.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(addons): compose owner status with runtime

Exercise installed addon processes through the authenticated mTLS carrier.
Keep runtime clients real while channel barriers control lookup ordering.

Reject captured clients after same-generation replacement before dispatch.
Reject completed status reads when replacement precedes owner consumption.
Prove fresh bindings succeed and record named tools and host operation IDs.

Verify the model and viewer, then race-test gateway, runtime, tools,
the model, and the protocol fixture package with auth enabled.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(studio): scope execution status observations

Run the execution status reducer within one explicit request lifetime.
Coalesce consumers without retries and withdraw stale or canceled reads.

Keep owner admission and production lifecycle integration separate for
review before wiring the shell into requests and turns.

Assisted-by: OpenAI:ChatGPT
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(studio): isolate consumer cancellation

Consumer cancellation during validation must not withdraw a valid
observation from other consumers. Return unknown locally before shared
invalidation, while preserving parent closure and freshness failures.

Cover cancellation and deterministic deadline expiry with one-read
reducer traces and live-consumer checks.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): track selection mutation epochs

Detect selection ABA at config and owner mutation boundaries. Clone
provider pins for Studio readers and share synchronized selection state
across config value copies.

Track lifecycle publication, candidate rollback and recovery transitions.
Keep reads and unchanged publications stable. These lifetime epochs do
not replace current-incarnation checks or grant execution authority.

Verify race tests for config and the add-on Manager, plus application
compilation with auth tags. Request binding remains separate work.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): expose owner binding transport

Add a host-only binding API with an optional owner resolver. Forward
conversation claims over authenticated mTLS with strict versioned DTOs.
Compare full bindings during validation and deny missing resolvers.

Keep ordinary tool execution unchanged through an optional backend.
Test trusted resolver fixtures, stale bindings, denial and compatibility.
Application authority and store revisions remain a separate milestone.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(studio): fence selection snapshots with epochs

Read actor-scoped records and opaque mutation tokens under store locks.
Invalidate tokens after successful eligibility changes, including ABA.
Keep reads, rejected writes and turn history from invalidating tokens.
Clone mutable records so callers cannot bypass mutation tracking.

Support authoritative revisions only for memory-only JSON stores.
File stores lack exclusive ownership; GORM lacks persisted revisions.
Fail closed for both rather than trust frontend-local state.
Ordinary chats remain unchanged; application integration stays separate.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(studio): enforce persistent JSON ownership

Acquire lifetime sidecar locks before loading or repairing JSON data.
Enable selection revisions only under cooperating exclusive ownership.

Close drains operations before releasing ownership and rejects later use.
Clean up unpublished stores when application initialization fails.
Retain published locks through Shutdown because HTTP users may remain live.

Document coordinated upgrades and the advisory local-filesystem boundary.
Keep GORM revisions unsupported pending a separate approved design.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Codex:gpt-5

* test(application): isolate warning log capture

Run warning assertions in isolated test subprocesses. The suite leaves
background log producers running, which race with global logger changes.

Keep the parent logger unchanged and check child exits and completion
markers. Preserve pin persistence and ownership warning assertions.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(studio): resolve local owner execution bindings

Resolve execution providers from owner-held chat and agent snapshots.
Fence bindings with selection epochs, mutation tokens and live runtime
identity. Recompute every component during status authorization.

Keep named-user and SQL observations unavailable until authoritative
access and shared selection revisions can detect ABA changes.

Drain restart test producers before releasing JSON store ownership.
Register six product resolver cases in the gateway suite inventory.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(studio): bound product scopes to chat turns

Keep request-local product state alive through tool calls, checkpoints
and persistence. Give chat priming its own scope instead of seeding the
next turn with a closed observation.

Exercise one reducer read per scope across prompt and tool consumers.
Check closure of pending reads on normal completion and interruption.
Provider wiring and safety projection remain separate work.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(studio): consume typed execution providers

Replace the Sandbox-specific status heuristic with owner-bound typed reads.
Share one reducer observation across turn consumers and close it at the
outer scope boundary. Keep priming and standalone requests independent.

Sandbox exposes a separate callback-free typed read while preserving its
private Settings payload. Unknown backends never imply isolation.
Provider isolation does not prove host network-policy enforcement.

Local-owner JSON bindings are supported. Named-user and SQL bindings
remain explicitly unavailable and fail closed.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(application): isolate auth startup lifetime

Shutdown does not join every background producer. Auth startup tests
can therefore delete their temporary directory while a writer remains.

Run each case in a bounded child process. The parent waits for child
exit before deleting the fixture, preserving every startup assertion.
Production shutdown and JSON ownership retention remain unchanged.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(ui): resolve installed Home app commands

Project Home navigation from installed addon declarations, not product IDs.
Reserve native and skill names and suppress every colliding addon alias.

Re-read the authenticated registry before navigation and reject stale
actors, generations, immutable entries and destinations. Keep mounted
consumers polling through disabled and failed states. Abort pending reads
on actor changes and ignore replies from obsolete request epochs.

Studio declares its own alias. Preserve legacy redirects and pin migration.
Add unit coverage, localized Home copy and command lifecycle documentation.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(ui): accept resolved Home command metadata

Apps resolves inherited titles beyond the explicit manifest label cap.
Bound resolved labels to 147 Unicode characters, including secondary pages.
Keep the backend's 60-character limit for explicit manifest labels.

Match packaged icon paths to the contract's ASCII alphabet and image types.
Accept dotted names while rejecting traversal, encodings and foreign assets.

Verify 68 focused cases, 657 total cases, lint, i18n checks and the UI build.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(ui): bind safety notices to actual providers

Warn for addon host execution and unknown isolation without changing tool
permissions or built-in host consent. Reset dismissal when the admitted
binding, observation, actor or chat context changes.

Expose presentation-only status identity from the admitted request.
Keep configured network policy visible when enforcement is unverified.
Render bounded provider explanations as escaped React text.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(ui): isolate command status across sessions

Invalidate command observations when actors or authentication sessions
change, before effects can expose a previous actor’s reply.

Keep actual host execution warnings generic instead of recommending
Sandbox installation or repair. Cover both regressions with source-hook
lifecycle tests and provider notice assertions.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): declare audited host requests

Declare actual official hostclient operations without inferring requests
from deployment grants. Keep optional settings and egress checks separate
from required workspace and job operations.

Verify all twelve manifests through strict package fixtures and assert
that requests never self-grant. Guard generic provider and Home dispatch
without banning historical routes, native Studio, or deployment policy.

Document the audited operations and unresolved named-user and SQL gaps.
Refresh Sandbox catalog metadata for its existing typed status tool.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(addons): wire exact extension inventories and real provider selection

Run contract, official packaging and root architecture gates through actual report inventories. Exercise two installed execution providers through the real owner resolver and distinguish callback pass-through coverage. Share strict report validation with focused-scope handling and missing/skipped artifact self-tests; document direct evidence and remaining harness limits.

Assisted-by: Codex:gpt-5

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(ui): expire registry reads on auth refresh

Bind registry authority to the actual authentication session epoch.
Same-user refresh now rejects old polls and pending Home navigation
before effect cleanup, even when permissions and metadata stay unchanged.

Exercise both races through the production registry provider harness.
Verify 59 focused tests, 669 total tests, lint, i18n, and the UI build.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(studio): resolve current execution selection

Use existing principal-scoped chat and agent reads for advisory status.
Revalidate current named credentials instead of requiring historical SQL
revisions. Local-owner mode also works with the existing GORM store.

Remove resolver nonces and historical revision prerequisites. Keep exact
runtime binding and existing status admission hooks. Document current-read
consistency without promising revoke-and-restore detection.

Verify the SQLite regression fails before the fix. Two separate full race
runs pass: application 262 specs (3 skips), auth 257, gateway 115, tools 91.
Cover named chat, API key and session credentials and current-state denial.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactor(addons): drop selection revision tracking

Use current selection comparisons for advisory status instead of mutation
history. Keep actor, credential and conversation identity explicit in the
model. Hash current inputs at the gateway without exposing raw store data.

Remove unused epochs, revision interfaces and JSON ownership lifetimes.
Preserve current authorization, runtime identity, mutexes and atomic writes.
Document current-read semantics without historical ABA guarantees.

Verify all affected packages with the race detector and auth tag. Check
the trace viewer, prerequisite self-tests and 18 chat-command unit tests.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(ui): scope Apps reads and provider status

Bind Apps replies and registry ingestion to their originating auth session.
Reject late replies before effect cleanup on refresh or account switch.

Use selected-provider command DTOs in the composer instead of private
Sandbox discovery. Keep unknown isolation unknown and remove specific
add-on installation recommendations from the affected native surfaces.

Test production Apps, registry, switches and composer paths with controlled
cleanup and two arbitrary providers. Update the matching feature docs.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(ui): guard composer and session publication

Remove the deleted Sandbox variable from the full composer render.
Report unknown command isolation explicitly in the about card.

Bind Apps records and admin action results to their originating actor
and actual authentication epoch before cleanup can run. Reject stale
request admission and verification without changing server authorization.

Add full-component render and pending session-transition regressions.
Enable no-undef for the production composer in normal UI lint.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(ui): expire cancelled upgrade reconciliation

Bind cancellation reconciliation to the initiating session authority.
Discard status replies after expiry without publishing old outcomes.
Match the exact operation identity before clearing a held action.

Cover pending cancellation reads, expiry before reads, and preservation
of a newer attempt with the same action key.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(ui): bind background upgrades to auth lifetime

Keep background upgrade authority in AuthProvider, independent of views.
Expire pending observations before auth consumers render or logout waits.
Preserve same-session navigation and exact-operation cleanup.

Cover cancellation, completion, and verification after the origin unmounts
with actual provider transitions and the real upgrade store.
Thread captured authority through Activity retries and document expiry.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactor(addons): remove implicit sidebar pins

Start every account without implicit Studio or Talk pins. Preserve saved
user selections and durable explicit pins across reopen and frontends.

Remove startup seeding, seed metadata APIs, and first-install exceptions.
Apply equal retention to all absent IDs without dropping database tables.
Keep store initialization, cached errors, and the leader sweep unchanged.

Verify genuine RED regressions before removal. Race checks pass for all
42 pin specs, 10 focused application specs, and 10 pin endpoint specs.
Update pin documentation; renamed specs have no exact inventory entries.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(addons): declare typed model destinations

Let installed addons own model launch destinations without teaching core
about addon identities or private routes. Keep native chat and completion
outside the declaration vocabulary.

Validate strict keys, unique capabilities, declared pages and SDK route
bounds. Carry cloned typed metadata through Apps using the shared
contract type, and add owning Studio, Talk, Face and Voice declarations.

Verify strict contract RED before implementation, then 1276 passing
specs across contract, Apps, packages, runtime, protocol, config and manager.
Verify 178 packaging and architecture specs and exact carrier inventories.
Frontend dispatch and route cleanup remain a separate milestone.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactor(ui): resolve declared model launch actions

Resolve installed model actions through the shared addon registry. Keep
native chat and completion launches, and never promote absent providers.

Refresh actor-filtered Apps before navigation and reject stale authority,
provider metadata, generation, immutable entry and destination claims.
Use named menu choices for collisions without loading addon modules.

Remove unreleased addon-specific redirects and migrate browser fixtures
to canonical declared pages. Preserve native chat handoff and addon-local
navigation adapters. Update user documentation and historical plans.

Verify three production UI consumers fail against their previous code,
then pass all 13 mocked-HTTP browser regressions. Verify 696 host unit
cases, full quiet lint, targeted no-undef, locales, inline styles and build.
Full backend/browser integration remains outside these mocked-HTTP checks.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): validate Ginkgo hooks before focus

Ginkgo emits null container hierarchies for suite hooks. Require hooks\nto pass before applying focus only to It cases. Normalize null\nhierarchies without weakening exact required-case comparisons.\n\nRun reporter regressions in prerequisites. Register the declared uiOnly\ntool case and remove an update fixture case excluded by the existing\nmanager focus. Both inventories match the complete CI case output.\n\nVerify 12 reporter regressions, 39 prerequisite cases, three existing\nreports, and fresh runtime, tools and focused update manager suites.

Assisted-by: Codex:gpt-5 [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): restore Go CI contract checks

Align route fixtures with explicit pins and declared network controls.
Keep network enforcement unknown instead of inferring it from isolation.

Propagate viewer output errors and select output paths with CLI arguments.
Check test writes and use Ginkgo assertions without copying atomic state.
Register the three migrated binding cases in the exact suite inventory.

Document two narrow security false positives for administrator file access
and symbolic credentials in reducer traces. Focused race tests pass.
The available golangci-lint reports zero issues in affected packages.

Assisted-by: OpenAI ChatGPT [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(ui): align browser coverage with addon contracts

The generic provider refactor removes implicit install advice and private
Sandbox discovery. Update browser fixtures to use the installed command
metadata and typed execution observations without restoring those paths.
Keep command warnings, consent, disabled switches and privacy assertions.

Home now holds a live command consumer. Exercise polling shutdown on a
route without that consumer, after its lazy transition finishes. Preserve
the bounded cadence, error backoff and disable-stop assertions.

Restore the remembered-voices link through the addon's compatibility
router. Cover native migrations and NotFound for removed generator aliases.

Validation: 293 affected browser cases and three native service cases pass.
The gallery command passes all 74 inventoried cases. Full UI units, quiet
lint, locale parity, production build and diff checks pass.

Assisted-by: OpenAI ChatGPT [functions]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): bind status to owner provider policy

Resolve typed status with the same current owner pins as execution.
Fail closed on owner policy outages and fingerprint authoritative pins.

Exercise CAS updates, conflicts, stale bindings, and policy outages.
Retain host settings when the named-account fixture replaces its gateway.
Refresh exact gateway and Apps inventories from passing reports.

Validation: the new regression fails before the fix. All 16 focused
binding and policy cases pass. Gateway passes 361 exact cases and Apps
passes 265 exact cases across their configured package reports.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactor(addons): share qualified tool identities

Use one formatter and contract-component parser for add-on identities.
Keep callable names bounded to 64 characters while recognizing long
unavailable stand-ins without granting dispatch authority.

Validate selected execution names and their provider ID at the owner
binding boundary. The real adapter already emits canonical identities.
Update exact importer and case inventories from passing reports.

Migrate the binding backend test to Ginkgo and check shutdown errors
reported by lint in the affected packages.

Validation: behavioral RED proves malformed stand-in acceptance and
plain selected-name acceptance. Affected full race suites pass: tools
94 and Studio 638, with zero skips. Application passes 275 of 278;
three existing PostgreSQL cases skip without their configured URL. All 12 owner-binding cases
pass and match the exact inventory. Scoped golangci-lint reports no issues.

Assisted-by: Codex:gpt-5 golangci-lint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactor(addons): share execution declarations

Share pure declaration predicates across manifest validation, gateway
observations, and owner bindings. Keep authorization and exact-runtime
admission checks local. Reuse the strict manifest schema validator so
raw status definitions fail closed without changing manifest acceptance.

Share the workspace-operation prefix scan without changing grant sources.
Add declaration parity and workspace-prefix tables and update exact
inventories from passing reports.

Validation: two new gateway cases fail before the fix; all 14 pass after.
Race tests pass: contract 245, gateway 136, tools 94, and focused
application binding/workspace cases 22. Scoped golangci-lint: 0 issues.

Assisted-by: Codex:gpt-5 golangci-lint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(addons): retain document skill cases

Match the contract inventory to the passing report after rebasing.\nKeep the new document-provider case and updated reserved-name cases.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

Assisted-by: OpenAI:gpt-6.1-sol

* test(addons): follow owner-managed upgrades

Accepted upgrades belong to the owner and cannot be cancelled by UI.
Exercise observer expiry during reads, completion and verification.
Supply the real upgrade store to source-executed auth harnesses so
logout reaches its synchronous authority invalidation.

Match whole-package inventories to fresh Manager and gateway reports.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-02 23:50:40 +00:00
mudler-agentandEttore Di Giacinto 10722d8e3b fix(addons): run accepted upgrades on the owner (#13035)
* fix(addons): run accepted upgrades on the owner

HTTP cancellation during a verified backup can prevent package commit.
Accept browser upgrades durably before replying and execute them under the
owner lifecycle, with bounded admission and same-operation polling.

Keep synchronous lifecycle callers unchanged. Reuse the publication and
data recovery journals rather than replay interrupted migration. Expose
real stages and safe terminal causes, and recover browser observers after
reload without resubmitting work.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): isolate upgrade observers by session

Reset action identities when the principal changes and restore pending
identities before polling. A different account must never submit an ID
owned by the previous account.

Clear stored upgrade observers directly in logout before navigation.
Cleanup also handles a fresh store with no bound principal.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactor(addons): use lifecycle phase constants

Share named lifecycle phases between the journal and upgrade reducer.
Use typed events in transitions and trace tests instead of repeating wire
literals. Keep operation DTOs, serialized values and rule labels unchanged.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(addons): verify accepted upgrades to completion

Preserve typed pin refusals before accepting an upgrade and report the
same transition conflict while execution holds the manager lock.

Poll accepted operations in HTTP and real-process acceptance tests before
asserting publication or failure. Exercise reload and truthful stages in
the rendered Activity test, retaining version details without cancellation
controls or estimated percentages. Register the new verification cases.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(addons): await the Tables upgrade receipt

An accepted upgrade can still serve its previous generation. Poll the
same operation through another frontend until it reaches a terminal state
before checking the new version, retained data, grants and backup.
Verify that a repeated submission returns the same completed receipt.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(addons): poll the winning upgrade operation

Concurrent submissions return acceptance before publication. Observe the
winning browser session and operation until completion before asserting
the installed generation. Keep the losing transition conflict check and
require the winning ID to replay the same terminal receipt.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(ui): model authoritative owner settings

Host controls now read and patch revisioned owner policy through dispatch.
Update rendered fixtures to serve that strict envelope rather than local
runtime settings. Keep private confirmation browser-only, send per-key
provider changes, and keep node-local audit retention outside owner forms.

Preserve the skill default in the tools fixture so an unpinned choice
matches the provider the owner reports.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(ui): assert the owner policy write envelope

Verify that the private-network switch sends no owner write before its
confirmation phrase. After confirmation, assert the actual dispatch action,
operation identity, expected revision and policy-only patch.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* test(addons): cover shutdown and lifecycle exclusion

Exercise owner shutdown during the real backup path and verify Close
joins cleanup before releasing storage. Cover synchronous disable while
an upgrade is accepted, including the journal guard before reconciliation.

Document that accepted upgrades have no UI cancellation control. Register
the new lifecycle cases in the canonical suite inventory.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-02 22:47:17 +01:00
mudler-agentandEttore Di Giacinto 74e293ef38 feat(sandbox): own document authoring skills (#13038)
* feat(skills): reserve document provider slots

Separate document provider slots from protected core skill names.
Keep existing same-name shadows declarations compatible.

Normalize legacy document OFF markers in JSON and database stores.
Persist normalization under the existing lock or transaction so an
explicit re-enable cannot be overwritten by a stale read.

Cover persistence, rollback, ownership and concurrent replacement.
Refresh the contracts inventory from the passing Ginkgo report.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* feat(skills): gate Sandbox document procedures

Resolve document providers against the selected isolated command backend.
Keep Office guidance available without command execution and preserve
explicit provider pins when their backend is unavailable.

Reuse authorized chat access during tool construction to avoid reentering
the session lock. Recheck access and capabilities when loading guidance.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* fix(skills): resolve saved-agent child command grants

Describe saved-agent child commands from their current agent scope rather than the inherited tool map reserved for ad-hoc children. Cover index, picker and loader behavior for permitted, absent and revoked Sandbox grants without changing saved skill selections.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* feat(sandbox): install and exercise guest-owned PDF helpers

Preserve PDF helper sources and MIT attribution in the guest image. Exercise valid, empty-text and defective PDFs during the image build and through the packaged isolated command route. Register the packaging regression in the exact suite inventory.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-5

* feat(sandbox): own document skills and consolidated references

Move the four document authoring trees out of core and package their text-only procedures with Sandbox. Preserve source coverage with offline fingerprints, guest PDF helper paths, and package-local license notices.

Exercise actual skill receipt materialization across install, upgrade and uninstall; keep stale built-in cleanup and report-derived suite inventories current.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(ui): explain document skill providers and unavailable selections

Show provider-owned document procedures without removed built-in fallbacks, retain remaining built-in replacement notices, and translate install guidance in every locale. Cover picker labels, saved names and real API OFF normalization, and document grants, isolated execution and unchanged extraction.

Acknowledge the intentionally injected GORM callback error in preference regression tests for lint.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* docs(agents): fix document guidance formatting

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* fix(ui): correct rebased skill label import

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-02 21:03:50 +01:00