mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 12:33:16 -05:00
3f765dcdf5fb530a37791b749fc0fbf470be0921
205
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c70566d4f7 |
feat(connectors): keep two connections of one bot from answering twice
Two enabled connections can hold different token pairs of the same upstream account, such as one Slack app registered twice. Both then receive every message and both answer it. The op "me" may now return accountId, a stable name of the upstream account. Slack returns the workspace and bot user, Telegram the bot ID and Discord the application ID. The host stores it on the binding when a token is saved and, for a binding that has none, when its add-on runs. An add-on that does not return it has no identity and is never compared. When two enabled bindings of one owner and kind share an identity: - the binding carries a duplicate marker with the code duplicate_connection, with a sentence in all eight languages of the connection texts; - saving one of them is a 409 of that type until the request carries confirm_duplicate, and nothing is saved before; - only the oldest enabled binding answers a message that has a platform message ID. The other logs one line and stays silent. The choice reads the stored bindings only, so every frontend makes the same one. Assisted-by: Claude:claude-sonnet-5-5 |
||
|
|
528ce11c63 |
fix(chats): retry a failed turn from where it stopped
The Retry button of the failure card called regenerate, which cut the
transcript back to the user's message and ran it again. Every message,
tool call and result the failed run had produced was lost, on the
server and on the page.
Add POST /api/chats/{id}/resume. It starts a turn from the transcript
as it is: nothing is removed, the model keeps its own saved state (so
finished tool calls are not repeated and a compaction summary stays),
messages that waited are delivered first, and the model gets a note to
go on instead of the question again. It does nothing when the chat is
not at a failed turn or a turn runs, so a double click starts one run.
The reason of a failure is now kept as a transcript line, so a reload
shows the same card text as the live page. The card stands after the
kept messages.
Assisted-by: Claude:claude-sonnet-5-5
|
||
|
|
31aa996272 |
feat(addons): let the owner relax the confirmation of a write tool
A write tool with approval "always" always waits for a person. A tool can now also declare approvalOverridable, and the owner can then choose not to be asked for that one tool, with a user setting approval-<tool> (ask or skip, default ask). It is the only way to relax the ceiling. The host honors the setting only for the owner's own chat and for a messaging chat whose senders are all paired with the bot. Allowlisted but unpaired senders, anyone-mode senders, roles, triggers, jobs, pages, sub-agents and calls without a chat still wait for a person. The setting is read again at the apply. Every run without a card is logged with the tool, the connection and the outcome, never the arguments, and is added to the latest decisions of the connection. The access view and the access preview list the relaxable tools and who they run for. Assisted-by: Claude:claude-sonnet-5-5 |
||
|
|
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> |
||
|
|
27556acbba |
feat(chats): reply language auto follows the owner's interface language
With the reply language on auto, the sentences the host writes in a chat used the language detected from the sender, and the interface language of the owner only when a server hook supplied it. No hook was wired, so the interface language was never used. The page of a connector add-on now sends its interface language with each save (ui_language). The binding keeps it, so every frontend of a cluster knows it. With auto, the host speaks that language when its catalog has it, and reads the language of the sender only when the owner's language is unknown or not in the catalog. A save without the field keeps the stored value, and a value that is not a language tag is ignored. A fixed reply language still wins. The label of the Automatic option says what it does now, in all nine languages of the page. Assisted-by: Claude:claude-sonnet-5-5 |
||
|
|
8df6954c5a |
feat(chats): translate capability titles and refusal texts by code
The access view, the preview and the kinds call returned English text
for each capability and each refusal. They now add the same text in the
language of the request (lang query, then Accept-Language), in new
localized_* fields. Codes, English fields and the model-facing text stay
as they were.
The texts live in the new connectortext package, one JSON file per
language (it, de, es, fr, id, ko, pt-BR, zh-CN), keyed by capability
and by refusal code. A test fails when a capability or a code lacks a
text in any language, or when {action} is lost.
The shared connector page asks for the page language and shows the
localized fields, with English as the fallback for each field.
Assisted-by: Claude:claude-sonnet-5-5
|
||
|
|
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 |
||
|
|
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 |
||
|
|
3167c58351 |
feat(memory): call the provider of the notes from the host
The host now calls the roles of the notes slot itself, not only the tools the model sees. - At the start of a turn the context role is called with a 3 s limit and a 6 KiB cap. The text goes in front of the message as data, in markers that it cannot close, and not in the prompt, so it does not restart the chat engine. The session passes the turn message to the hook and takes an optional per-turn context block from it. - A write through a provider follows the save level, the agent grant and the messaging rules. Only Save and tell me applies a write at once, and not for a remote provider that cannot undo. Other writes are held for a confirmation. - The export and the forget of a provider are routed by GET and DELETE /api/chats/memory. - A provider that fails three times in a row is left alone for a minute, the slot shows a plain notice and the chat goes on without notes. - Every call writes one audit line with the outcome and the size, and never any text. - The add-on that the built-in provider replaces is an alias: it is not listed and a choice that names it means the built-in provider. Assisted-by: Claude:claude-sonnet-5-5 go |
||
|
|
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
|
||
|
|
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 |
||
|
|
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
|
||
|
|
9422011f20 |
feat(connectors): write the bot's own sentences in the language of the person
The confirmations, refusals and notes that the host writes in a chat
without the model were fixed English: "Started a new chat", the question
about a proposal, the file notes, the pairing replies. They now come from
a catalog (core/services/studio/hostmsg) with a template for English,
Italian, German, Spanish, French and Portuguese (Brazil), and the same
{placeholders} in each. Text for the model (tool descriptions, the held
back note, the thread block) stays English.
A connection has a reply_language setting: auto (default), en, it, de,
es, fr or pt-BR. With auto the host scores the sender's message against
the common words of the six languages, with no model and no dependency.
A message that does not show a language ("ok", "yes", "thanks") changes
nothing, and a short message cannot move a conversation to another
language. The last clear language of a sender and of a chat is kept for
24 hours; a trigger that writes on its own uses the chat's. The setting
is stored in the JSON and the gorm store, is kept by an update that
leaves it out, and is on the connection page under "How it answers".
Every sentence goes through msg or a hostmsg.Voice. A guard test fails
when a fixed text reaches say, Post or Outgoing, when a key is not in the
catalog, or when a key is never used. The reply parser now reads the
yes, no and undo words of the six languages, which the new texts ask for.
Assisted-by: Claude:claude-sonnet-5-5
|
||
|
|
f52f10c7b0 |
fix(studio): name the file store search engine scan, not memory
The provider guard test refuses core code that names an add-on, and "memory" is the name of one. The file store searched its in-memory transcripts and reported the engine as "memory". It now reports "scan", as every store without an index does. Assisted-by: Claude:claude-sonnet-5-5 |
||
|
|
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 |
||
|
|
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 |
||
|
|
4d58fa0056 |
feat(studio): search all chat text with a real index
Search read the newest 500 chats and scanned them in memory, so older chats dropped out silently. The chat store now keeps a search index of the text of user and assistant messages: FTS5 with the trigram tokenizer on SQLite, a pg_trgm GIN index on PostgreSQL, and a scan of the same table when neither is there. The file store scans its in-memory transcripts for up to two seconds, newest chat first, and says when it stopped early. The index row is written in the transaction that writes the entry and removed in the one that deletes the chat or truncates the transcript. A query joins the chat row for the owner, so one user never sees the chats of another, and sub-agent chats stay out. Messages written before the index existed are added in the background with a resumable cursor and progress in the log; startup does not wait for it. GET /api/chats/search keeps its fields and adds the role, time, match count and highlight offsets of the hit, and filters for agent, origin, role and date range. A phrase in double quotes is one word. Hits rank by match quality and recency. group=message lists every matching line. Assisted-by: Claude:claude-sonnet-5-5 |
||
|
|
80e4c61610 |
feat(studio): explain the first blocker of each connector capability
The access view said only that a capability was refused, in free text.
It now reports, for every capability of every kind of sender, the first
link of the permission chain that fails: a closed code, who holds the
link (server, tools, agent, connection, sender), one plain sentence, one
fix as a kind and a stable target, and the links not yet checked. The
chain order is server setting, Tools switch, agent permission, the
permission of the connection, then sender trust. The code is produced
where Authorize and the capability switches decide, so the English
reason and the decision cannot drift apart. The Tools switch of the
agent and the server triggers setting are now switches in that chain.
POST /api/chats/connectors/{id}/access/preview runs the same chain on a
form that is not saved (id "new" works with a kind), optionally for one
kind of sender. It writes nothing, records nothing and never takes or
returns the token.
The held-back note for the model, the tool errors and the trigger tool
reason take their sentence from the same table of codes, in plain words
with no setting names.
Assisted-by: Claude:claude-sonnet-5-5
|
||
|
|
e0d77e410c |
Report the mode of the built-in bash in the chat add-on list
A chat with no add-on that runs commands could not tell a person whether the server's built-in bash was off or simply not selected. The add-on list now carries commands_host_exec (off, ask or on), a read-only copy of the chats_host_exec setting as it applies now. Assisted-by: Claude:claude-sonnet-5-5 |
||
|
|
3edcec1813 |
feat(studio): let the owner allow acts for triggers proposed from a chat
A trigger proposed from Slack or Telegram was always read only, and the person had no way to let its runs act except to edit the trigger on the Triggers page afterwards. The approval of a binding now has an acts list, the act.* permissions that a proposal from its chats may carry. It is empty by default, so nothing changes until the owner sets it. It is stored in the same approval JSON (no migration), checked against the closed list in the API, and always limited to what the agent holds. The acts of a proposal are what the model asks for, within the list of the binding and the grants of the agent. The rest is dropped and named in the proposal text, and the model is told which acts it may ask for and to ask the person first when the job changes things. A read only proposal now says what the person can do next. For a proposal with acts, auto_readonly still asks. auto starts it at once only when every act is allowed for the binding, nothing was dropped and every sender of the turn is paired; otherwise it asks. A trigger made without asking also keeps to the acts of its binding at run time. Assisted-by: Claude:claude-sonnet-5-5 |
||
|
|
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
|
||
|
|
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 |
||
|
|
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 |
||
|
|
d8bd3879f2 |
feat(studio): let a trigger proposal carry its acts, shown before the yes
A trigger run with nobody present only reads unless its trigger holds act.* grants, and until now only the REST API could set them. An agent that proposes a trigger can now ask for acts too: - propose_trigger takes an optional acts list. The names must be act.* grants the agent of the chat holds; anything else is refused and nothing is stored. A read_only proposal cannot carry acts. - The proposal stores the list. Confirming it, in Studio, by a plain yes or by confirm CODE, grants exactly that list. No tool changes the acts of a trigger afterwards. Two proposals that differ only in their acts are kept apart. - The text of a proposal in a messaging app now has a Permissions line, in plain words with sensitive classes marked: read only, or what a run may do. The confirmation repeats it. A proposal that asks for acts is never set up without a yes, whatever the approval mode. - The trigger REST object gains acts_held, read_only and read_only_why, computed with the same rules as the run, so a client can show why a run only reads. Assisted-by: Claude:claude-sonnet-5-5 go ginkgo |
||
|
|
23839b74a2 |
feat(api): accept request-level chat_template_kwargs
Clients written for llama-server, vLLM or SGLang switch template variables
per request with a top-level chat_template_kwargs object, most often
{"enable_thinking": false}. OpenAIRequest had no such field, so it was
dropped: with a Qwen3-style template thinking stayed on, the reply landed
in message.reasoning and content came back empty.
Fold the field's scalar values into the request metadata, so it takes the
path metadata already takes (gRPCPredictOpts, ResolveChatTemplateKwargs).
Booleans become "true"/"false", strings pass through, numbers become their
decimal string; nested values are skipped. An explicit metadata key wins.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Cristi An <c4xp@msn.com>
(cherry picked from commit
|
||
|
|
6b181cd20b |
feat(studio): enforce the action policy of unattended runs
A trigger run or an agent job has nobody to approve a step, and so far the approval mode auto_readonly and the act.* grants were only recorded. The prompt was the only thing that kept such a run from acting. Every tool call of an unattended run now goes through one check at the call. A tool that only reads runs. Any other tool needs the grant of its class, and the check fails closed: - act.files for write_file, edit_file, move_file, delete_file and the built-in bash; - act.addons, or act.addons:<id>, for add-on tools whose declared kind is not read, plus act.spend when the add-on gates the tool with a permission; - act.spend for MCP tools. A trigger holds its new acts list within the grants of its agent. A trigger that declared read only runs, or that a binding in auto_readonly mode made without asking, is refused every non-read tool whatever it holds. A binding that cannot be read counts as auto_readonly. A job holds the act grants of its agent. Sub-agents follow their parent, and a saved agent cannot be started from an unattended run. A refused call answers with a plain sentence the model can pass on, not an error, and writes one audit log line. Triggers set acts through the REST API; a model never can. A trigger whose plan names a file it writes keeps act.files. Assisted-by: Claude:claude-sonnet-5-5 go ginkgo swag |
||
|
|
dcd180ced8 |
feat(studio): add the agent skills layer
A saved agent keeps its own skills in LocalAI/agents/<agent>/skills. Only a chat that runs that agent sees them. They sit between the folder and personal layers, so an agent's version of a skill wins for that agent, and they count as granted without being listed in the agent's skills. An agent writes one by a skill change proposal, as before; the new layer only reads them. A proposal for an agent skill cannot take the name of a core skill or of a document slot (read from slots.json). When writing-skills loads in a chat of a saved agent it starts with "Do not write files. Call propose_change with kind skill.", and the skills.write grant makes it visible to the agent. The Skills list gains the agent layer, with the agent's ID and name, and a skill can be read or removed (to the Trash) with the agent query parameter. Assisted-by: Claude:claude-sonnet-5-5 go swag |
||
|
|
c5f636d1b9 |
refactor(addons): move connector file bytes by handle only
A connector add-on no longer carries file bytes in the connector envelope. The ops media and send_file need a transfer handle, and the gateway refuses a call to them without one, or for a kind that declares no files. - The manifest field files.transfer, the inline file of a request and of an answer (ConnectorFile) and the 1 MiB cap (MaxConnectorFileBytes) are removed. The 2 MiB limit of a connector call between hosts drops to 1 MiB, because it holds text and no file. - A kind that declares no files moves no file: the host answers a sender who attaches one with a plain sentence and does not offer attach_file. A direction left out of a declared files block still gets the host ceiling. - The inline layer is gone from the file limit, from the API (inline_bytes) and from the Files section of the connector page. - The Telegram and Slack add-ons keep only the streaming paths. The tests moved to the handle path, and the studio test rig gives every connector add-on a table of handles. Assisted-by: Claude:claude-sonnet-5-5 go test node |
||
|
|
15e2321d50 |
feat(connectors): edit the permissions of a binding on the shared connector page
Bindings saved before permissions were enforced have no capabilities, so they deny files from senders, triggers, memory writes and talk-back, and the owner could only fix that through the REST API. The shared connector page now has a section for it, so every connector add-on gets it: a switch and a description for each capability with the sensitive ones marked, the role matrix (never a sensitive capability, never above the ceiling), pairing with a code shown once, the approved channels for talk-back, and a read-only view of the access check with the latest decisions. A binding with no capability shows a notice that says why the features are off, with an "Allow the usual" button that turns on the non-sensitive everyday set. The kinds endpoint returns the closed list of capabilities with a title, a description and the sensitive and usual flags. The field is additive. Assisted-by: Claude:claude-sonnet-5-5 go node playwright swag |
||
|
|
fd0f676722 |
fix(studio): stop the web tools group title from matching an add-on name
The provider guard flags any core string that contains the name of a shipped add-on that provides a generic role. The web add-on has the id and title "Web", so the built-in tools group titled "Web" in the tools picker (and the schema example for it) failed the guard on master. Rename the group title to "Look things up online", which matches its "cannot look anything up online" off-state text. Update the schema example and the Playwright fixtures and specs that read the title. Assisted-by: Claude:claude-sonnet-5-5 go test eslint |
||
|
|
eaea737b53 |
feat(connectors): let the owner choose how chat triggers are approved
A connector binding gets an approval setting: ask, auto or auto_readonly, with four limits for the two automatic modes (live triggers, minutes between runs of a recurring trigger, runs a day, lifetime in days). The setting is generic, so every connector add-on can show it, and the shared connector page of the add-ons now draws it. A binding that sets nothing asks, as before. This change stores, validates and exposes the setting. The next change acts on it. Assisted-by: Claude:claude-sonnet-5-5 go swag node |
||
|
|
24c837e453 |
feat(studio): grant built-in capabilities to a saved agent
A saved agent now has a Builtins list: the capabilities its owner gave it (memory.write, profile.write, skills.write, pages.write, dashboard.write, triggers.propose, triggers.run, talkback.channels, talkback.new_channel, files.upload, act.files, act.addons, act.send, act.spend and the opt-in group saved_agents). The list is empty by default, and nothing turns a grant on for a template, an import or a write that omits the field. A chat of the agent holds the grants the user may use at the turn (agent scope). A tool exists when the grant is held, the chat did not switch its group off and the run can hold rights: a sub-agent or a run that started by itself never does. propose_change and revert_change now exist only for a chat that holds the grant of at least one kind, and the schema lists only those kinds. propose_change gains the kinds skill, page and dashboard. A skill is a SKILL.md for the personal or the agent layer, and a skill with locked: true is refused, also as a shadow of a locked personal skill. A page is a set of files checked against the Pages rules and written as one change set. The dashboard is a home.json checked against its schema. All three are re-checked when the owner confirms or edits. GET /api/chats/agents/builtin-grants lists each grant with its title, description and sensitive flag. The agent record, the write body and the export file carry builtins; an old file imports as before and an unknown name is dropped in an import and refused in a write. Assisted-by: Claude:claude-sonnet-5-5 go gofmt swag |
||
|
|
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 |
||
|
|
b8626132ae |
feat(connectors): let the agent write to listed channels with send_message
A connector chat whose sender holds talkback.channels gets the tool send_message. It writes in the conversation, or as a new top-level message in a channel that the owner listed on the binding (id, label, approved; at most 10). The tool names no platform. An unlisted channel is refused and the error lists the ones that are. A listed channel that the owner did not approve needs the sensitive talkback.new_channel for each message, so only a paired sender can use it. The agent cannot add or approve a channel, so text it read in a page or a file cannot widen where it writes. Messages go through the outbox (rate limit and queue), with the size cap and the mention breaking. Each attempt adds an audit row with channel, kind, capability, length and a hash, never the text. A trigger can report to an approved channel with delivery target channel. The binding, the capability and the listing are checked again when the run ends; any failure sends the notice to the Inbox. LOCALAI_DISABLE_CONNECTOR_TALKBACK turns both off. Assisted-by: Claude:claude-sonnet-5-5 go swag |
||
|
|
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 |
||
|
|
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
|
||
|
|
15740acbb9 |
feat(studio): describe every built-in tool switch and let a saved agent set them
The Tools menu of a chat listed a ninth built-in group, "changes", by its raw ID with no text, and only some rows said what a switch did. The registry now carries a title, a description of what the model can do when the group is on, and what happens when it is off, for every group. Groups that nobody described fall back to the label and description of their first tool, so a new tool group needs no change in the interface. An add-on row can show the title and description its manifest declares: the broker may implement the optional AddonLister, and the lists carry both fields next to the ID. A saved agent gets builtins_off and builtins_on. builtins_off is a ceiling: a chat of the agent shows the group off with a reason and cannot switch it on, and a sub-agent follows it. builtins_on lists the opt-in groups the agent's chats start with. Both are additive columns, a write that does not name them keeps the stored ones, and groups the server does not offer are dropped so an imported file still loads. An agent with switches exports as version 3, so an older server refuses the file instead of giving the agent every tool; a single-folder agent without switches still exports as version 2. Assisted-by: Claude:claude-sonnet-5-5 go gofmt swag |
||
|
|
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] |
||
|
|
db596e1696 |
feat(studio): accept a list of agent folders in the API and the agent file
POST and PUT on /api/chats/agents take folders, a list of path and mode rows, up to eight. The old folder field still works and is read as a list with one entry. Responses carry folders, the effective mode of each row and a notice with the count of shared folders a chat does not mount. A folder inside another folder of the same agent takes the mode of the outer one, because the file store gives a node the highest mode among the roots above it and cannot hold a read only folder inside a write folder. The editor data shows that mode. An agent with one folder still exports as version 2. An agent with several exports as version 3 with folders. A server that reads version 2 refuses version 3. Import keeps a folder that is not found as a row that is not chosen. Assisted-by: Claude:claude-sonnet-5-5 go gofmt |
||
|
|
1f481b0747 |
feat(connectors): add-on roles and permissions, and sender pairing
A connector kind can now declare the roles it gives to a sender and the capabilities it can grant to a role. The owner of a binding enables a ceiling of capabilities and maps roles to them. The host stores these values and returns them with the binding and the kind list, so an add-on settings page can show them. Capability names are a closed list owned by the host. Sensitive names may be listed by a kind but are never granted to a role; only a paired sender can use them. Pairing proves that the owner controls a platform account. The owner asks for a one-use code (valid ten minutes) and sends it as a direct message to the bot from an allowlisted account. The host checks the code before any model turn and never logs it. A binding holds three paired senders, a pairing ends after 90 days, and five wrong codes in an hour lock the sender out for an hour. Contract: connector kinds gain optional roles and permissions, and a message gains optional roles. They need manifest api 2, which the host already accepts; protocol is unchanged and manifests that declare nothing new behave as before. The host drops roles a kind does not declare. Enforcement of the capabilities by feature is not part of this change. Assisted-by: Claude:claude-sonnet-5-5 go swag |
||
|
|
c8cb68b434 |
refactor(studio): always read a thread when the bot is mentioned in it
The thread_context binding setting is removed. When the kind declares the history ability and the bot is mentioned in a thread, the host reads the thread once for a new chat, as before. The other guards stay: the ability check, the bound, the untrusted block, and a failure never blocks the answer. A binding stored by an earlier build still loads. The JSON store ignores the unknown field. The GORM row no longer maps the thread_context_off column; AutoMigrate does not drop it and it accepts NULL, so reads and writes keep working. No data is migrated or removed. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> |
||
|
|
11d1f972ed |
feat(chats): report the host ceiling with the file limits
The Settings page states the ceiling in effect from this field. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> |
||
|
|
86a93259ce |
feat(chats): show and set the file limits of a connector binding
The connector list shows, for each binding and each kind, the limit in effect in both directions, the layer that sets it, the ability the kind declares and the most an owner may set. A binding accepts file_receive_max_bytes and file_send_max_bytes; a value above the limit of the kind is a 400. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> |
||
|
|
d0ac371e01 |
fix(studio): do not report a draft chat as not selected
A chat that is not created yet has no add-on bound to it, so its execution status cannot be read. The host now says so with the reason draft, and the Home composer shows no commands warning for it. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> |
||
|
|
0e50c3f9ca |
feat(addons): let a connector kind declare per-binding options
A kind can declare options with a few values and a default (manifest api 2). The host checks the owner's choice against the declaration, stores it on the binding, shows it in the REST API and sends the value in effect with every poll. The add-on decides what the option means. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> |
||
|
|
f20b7250ad |
feat(studio): add the thread_context switch to connector bindings
A binding reads the earlier messages of a thread by default. The new thread_context option turns that off, and the kind list says which kinds can give a thread's messages. It is stored like send_files: the zero value keeps the default. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> |
||
|
|
fc6abfca8c |
refactor(studio): name the network from the add-on's declaration
The chat's network view, the administrator view and the line the model reads about the network of its commands now take their name from the capabilities.network an add-on declares, or from the title of its commands tool when it declares none. The sentences no longer say "Sandbox" or "the sandbox". The API gains a network object with the title and the preset titles next to the existing addon field. The text of the built-in bash and of the host-exec setting names an add-on that provides bash instead of one add-on. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> |
||
|
|
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> |
||
|
|
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> |
||
|
|
b39d37f559 |
feat(agents): limit saved agents to one folder (#13009)
* Store the folder and its mode on a saved agent
A saved agent gets FolderID, the workspace node of its folder, and
FolderMode, read or write. The folder is kept by node so that a rename
or a move is followed. A mode without a node is an agent whose folder
is not chosen yet; Validate accepts it, and refuses a node without a
mode.
The GORM store adds two columns with defaults, so AutoMigrate fills
the rows of existing agents as agents without a folder. The JSON store
reads a file without the keys the same way. Both stores pass the new
contract case.
Assisted-by: Claude:claude-opus-5-5
* Keep on a chat whether its saved agent had a folder
A chat whose agent is deleted runs today without the agent's grants.
When that agent had a folder, nothing is left to say which folder the
chat may read, so the chat must be refused instead of getting the whole
home. ChatRecord.ScopedAgent records that the agent had a folder.
CreateChat writes the flag and SetScopedAgent changes it. UpdateChat
leaves it alone, like the tool switches, so the loop's stale copy of
the record cannot clear it. Both chat stores read chats stored before
the flag as not scoped; the GORM column has a default, so AutoMigrate
is the migration.
Assisted-by: Claude:claude-opus-5-5
* Add the design and plan for agent folder scope
A saved agent works on the whole home folder of its user. The spec
lets the user give an agent one folder and a mode, read only or read
and write, and confines every chat that runs it to that folder.
The workspace store enforces the folder through a fence on the actor,
so a caller that forgets it gets less access, not more. The spec maps
every caller that builds a user actor today, including the host bash,
the sandbox runner and the add-on workspace operations, and how each
is moved to the chat's access.
The plan splits the work into ten lanes; the store fence and the agent
record come first.
Assisted-by: Claude:claude-opus-5-5
* Resolve the folder of a saved agent by node
A person chooses an agent's folder by its path in their home; the
agent keeps the node, so a rename or a move is followed. The new
FolderResolver turns the path into the node and back, always as the
user. A path is refused when it is not a live folder, when it is
LocalAI/ or Chats/ or inside them, or when it names a parent. The
home itself ("/") is allowed.
The workspace stores cannot find a node by its ID yet. PathOf reads
the newest change sets of the home first, checks the path it finds
with Stat, and else lists the folders the user can read. A store that
implements NodeByID is used instead. Nothing is cached, so every
frontend answers the same.
SaveAgent keeps the stored folder when it replaces an agent, so a
caller that knows nothing of folders cannot widen one.
SaveAgentWithFolder sets or clears it. ImportAgent looks the folder of
a file up in the importer's home, and saves a folder that is not there
as unset: the mode without a node, which does not start.
Assisted-by: Claude:claude-opus-5-5
* Add a fence to the workspace actor
A chat whose saved agent has a folder must reach only that folder and
the chat's own folders. The store needs to know which folders those
are on every call, so the actor carries them.
Actor.Fenced returns a copy that reaches only the given folders, by
node ID, each at most at its mode. The fence is an unexported field:
a copy of the actor keeps it, and only Fenced can set it. Fencing a
fenced actor adds a layer, and a node is reached only when every
layer allows it, so a fence never grows.
The helpers in export.go let both store implementations apply the
same rules: the mode a fence allows on a node, its roots, and the
limit of MaxFenceRoots roots per call.
Assisted-by: Claude:claude-opus-5-5
* Show and choose the folder of an agent in the API
The agent routes carry folder. A write takes {path, mode}: the server
turns the path into the folder's node, and refuses with 400 a path
that is not a folder of the user's home, or is LocalAI or Chats. A
write without folder keeps the agent's folder, so a client that does
not know the field cannot widen the agent; {path: ""} clears it. A
read answers the folder's path now, its name, the mode and a state:
ok, missing or unset. The node never leaves the server.
An agent with a folder exports as version 2, with the folder's path
and mode. A server that reads only version 1 refuses that file, so a
scoped agent is never imported elsewhere as one with all the files.
An agent without a folder still exports as version 1. The import
reads both versions and looks the folder up in the importer's home;
a folder that is not there saves the agent as unset, with a warning.
The Swagger spec, the API instructions and the agents docs page show
the field and the new file version.
Assisted-by: Claude:claude-opus-5-5
* Enforce the actor fence in both workspace stores
The store is the one place every file call of a chat goes through, so
it applies the fence: a caller that forgets the agent's folder gets
"not found", never the whole workspace.
- The mode of a fenced actor on a node is the lower of its grants and
the fence. A root in the Trash counts for nothing, and is back after
Undo, because the fence holds the node ID.
- A fenced actor never takes the administrator shortcut, so it cannot
grant, revoke or list grants, and ReadablePrefixes gives only fence
roots or folders inside them.
- A root cannot be deleted, moved or renamed by the fenced actor, by
any path: Move, Delete, Undo, an upper layer, or a delete of a folder
above a nested root.
- A fence with too many roots or a bad root is ErrInvalid.
The shared suite in wstest runs the new specs against the JSON store
and the GORM store (SQLite and PostgreSQL).
Assisted-by: Claude:claude-opus-5-5
* Map the model's paths through the chat's place
A chat's file tools spelled every path as a path in the user's home.
A chat whose agent has a folder sees that folder at /workspace/home and
its own chat folder at /workspace/chat, so the mapping must come from
the mounts of the chat's view, not from the principal.
session.Place maps a path the model writes to a workspace path: "/x",
"x", "home/x" and "/workspace/home/x" are x in the primary mount,
"/workspace/<name>/x" is x in the mount of that exact name, and the
skill layers keep their names. It refuses "..", NUL, control characters
and invalid UTF-8 wherever they point, and a /workspace path that is no
mount. Encoded and look-alike separators stay part of a name. Place is
only a mapping: the store's fence enforces the access.
session.Access holds a turn's actor, view and place. It has no exported
fields and one constructor, so a tool cannot be built from a principal
string. Owner gives the user's own actor without the fence.
Assisted-by: Claude:claude-opus-5-5
* Build a chat's view from its folders and describe it
BuildView took the home of the principal as the folder at
/workspace/home. A chat whose agent has a folder needs that folder
there, at the agent's mode, and its own chat folder beside it.
BuildView now takes the primary folder and the chat folder. The chat
folder is mounted at /workspace/chat, read and write; the name chat is
reserved like the skill layer names. The store caps each mount at the
actor's mode, and a fenced actor gets the shared folders read only.
Every caller passes the home and no chat folder, so an unscoped chat
keeps today's view.
LayoutPrompt takes a ScopedPrompt. A scoped chat hears only of its
folders: the agent's folder by its name and mode, with a line that it
cannot see the user's other files, the chat folder and where its
working files go, and the shared folders. It gets no hint to build a
page, because pages live outside its folder. An unscoped chat keeps
today's text.
Assisted-by: Claude:claude-opus-5-5
* Find a workspace node by its ID
A caller that keeps a node ID, such as the folder of a saved agent,
had no way to ask the store where that node is now. It had to search
the journal and then list folders, which costs one query per folder
in a large home.
Store.NodeByID returns a live node at its current path, so a rename
or a move of the node or of a folder above it is followed. It answers
ErrNotFound when the node or a folder above it is deleted, and when
the actor cannot read it; a fenced actor finds only nodes inside its
fence. Both stores implement it and the shared suite checks it. The
remote store of the Sandbox add-on refuses it, like its other calls.
Assisted-by: Claude:claude-opus-5-5
* Name the work folder of a scoped chat at /workspace/chat
A chat whose agent has a folder keeps its chat folder in the home, but
sees it as a mount of its own at /workspace/chat. Its work folder is
then /workspace/chat/work, not a path under /workspace/home.
workfiles reads /workspace/chat as the chat folder: a helper file under
its work folder is working, a script beside the attachments is a stray
helper, and a folder named chat in the agent's folder is an ordinary
folder. ScopedPrompt steers the model to that work folder and sends the
deliverables to the agent's folder, or to the chat folder when the
agent may only read. It says nothing of pages or of the settings
folder, which are outside the agent's folder. The chat picks the text
by its view.
Assisted-by: Claude:claude-opus-5-5
* Look up the folder of an agent by node
The folder resolver searched the journal of the home and then listed
folders to find where an agent's folder is now. The stores can now
find a node by its ID, so the resolver asks them directly and the
search is gone.
Assisted-by: Claude:claude-opus-5-5
* Resolve the file and media tools through the chat's access
The file tools mapped paths to the user's home and acted as the chat's
actor, and the media tools did the same through files.HomePath. A chat
whose agent has a folder must reach only its folders, and search must
start in the agent's folder, so the count of documents still being
indexed cannot count files elsewhere in the home.
files.Service.Tools and media.Service.Tools take a session.Access. Every
path goes through its place and every call acts as its actor. A write
to a read-only folder is refused before the store sees it. A search
with no path starts at the primary folder, and in a scoped chat also
looks in the chat folder; the pending count is taken below that root.
files.HomePath is removed.
UnscopedAccess builds today's access of a chat (the home and the
shared folders), and the studio service takes the turn's access from
one function, turnAccess, which gives that access until the chat
access resolver replaces it.
The new specs run the paths that try to leave the folder (parents,
absolute and doubled paths, encoded and look-alike separators, NUL,
other cases) through every file tool and the media tools, and check
that nothing outside the folder is read, named or changed, and that
search counts no pending file outside it.
Assisted-by: Claude:claude-opus-5-5
* Resolve the access of a chat at every turn
A chat whose saved agent has a folder must reach only that folder. Up
to now every chat got the user's actor, which reaches the whole home.
The chat's access is now resolved from the stores at every turn
start. With no agent folder it is the actor and the view of today.
With a folder the actor is fenced to the agent's folder at its mode,
the chat's own folder read and write, and the shared folders and the
skill layers read only; the chat's folder is made before the fence,
because a fenced actor cannot make it. A sub-agent takes the folder
of its parent's agent, and its own agent's folder inside it.
A folder that is deleted, unreadable or not chosen refuses the chat
with ErrAgentFolderMissing; it never falls back to the whole home.
A chat records whether its agent had a folder, so a chat whose agent
was deleted is refused too. CreateAs refuses such an agent at once.
session.Access carries the actor and the view, has no exported fields
and one constructor. Open takes it, the turn tools are built from it,
and an open chat compares the fence as well, so an edit of the agent
restarts the harness at the next turn on any frontend. The person's
uploads and renames use the owner's actor, without the fence. The
files a command changed are read as the chat can read them.
Nothing is kept between turns, so frontends cannot disagree.
Assisted-by: Claude:claude-opus-5-5
* List a scoped chat's files by its place in the prompt
The turn prompt lists the chat's files with the paths the model uses.
For a chat whose agent has a folder those paths come from its mounts,
and a file the chat touched outside its folders, for example before
the agent got a folder, must not be named.
Manager.FilesIn is Files with a place. For a scoped place, each file
is named by its mount (/workspace/home for the agent's folder,
/workspace/chat for the chat folder) and a file outside every mount is
left out. Other places give what Files gives. The prompt's file list
uses the turn's place; the chats API keeps the user-wide list.
Assisted-by: Claude:claude-opus-5-5
* Answer 409 when an agent's folder is missing
A chat with a saved agent whose folder is missing or not chosen does
not start. The chat endpoints now say so with 409 and the type
agent_folder_missing, both when the chat is created and when a
message is sent to an open chat, instead of a server error.
The agent docs say what a chat with a folder reaches, when a change
of the folder applies, and that nothing is kept between turns.
Assisted-by: Claude:claude-opus-5-5
* Point a scoped chat at rules and work folders in its own folders
The rules nudge walked from the chat folder up to the home root and
named AGENTS.md files there, and the stray-helper note named the work
folder under /workspace/home. A chat whose agent has a folder must hear
only of its own folders.
The nudge takes the turn's access: it reads paths through the place,
starts at the primary folder (the agent's folder) and stops there, so
it never points above the folder or into the chat folder mount. The
helper note names /workspace/chat/work/ in a scoped chat, and a
sub-agent of a scoped parent gets the scoped work folder text.
Assisted-by: Claude:claude-opus-5-5
* Find workspace actors built outside the chat access
An actor built from a principal has no fence and reaches the whole
home. A spec now parses the studio service, the command runners, the
add-on host operations and the application wiring, and fails on a
workspace.Actor literal, a files.UserActor call or an Actor(principal)
call in a function that is not on the allowlist. Each allowed entry
says why: a host read of the user's settings, a user surface, or a
caller that a later change moves to the chat's access. An entry that
builds no actor any more fails the spec too, so the list stays exact.
Assisted-by: Claude:claude-opus-5-5
* Build the expected view of a chat with the view's folders
The spec of a chat without an agent folder compared its view with
BuildView called with the old arguments, so the test package did not
compile after the merge of the view builder. It now passes the home
as the primary folder and no chat folder, which is the view of an
unscoped chat.
Assisted-by: Claude:claude-opus-5-5
* Give every file tool of a turn the chat's resolved access
The resolver of a chat's access built the view of a scoped chat
itself, with no shared folders, while every other chat got its view
from the session manager. And the file tools, the media tools, the
chat's file list in the prompt, the rules nudge and the work folder
lines each asked for an access of their own, the view of the whole
home. A chat whose agent has a folder therefore had a fenced actor
but home-relative paths: it reached its folder only through paths
such as /Projects/Acme/a.txt, and its prompt named the home.
The session manager now builds every chat's view: UserAccess mounts
the home, and the new ScopedAccess mounts the agent's folder at home,
capped at the fence's mode, the chat folder at chat and the shared
folders read only. It refuses an actor without a fence, and a folder
outside the fence is an error, never a view of the home. The
resolver calls it with the innermost folder, so a sub-agent of a
saved agent still works in its own folder.
The access that the resolver builds once at the start of the turn
now goes down to every consumer. A sub-agent of a saved agent acts
for its parent chat with the same access (Access.ForChat), and a
generic sub-agent gets its tools from the parent's access. The seam
that gave the unscoped access, and the files service's own view
builder, are removed, so a new consumer cannot build a view by
mistake. A turn whose access has no folder gets no tools rather than
the home.
A scoped chat now sees its folder at /workspace/home and its chat
folder at /workspace/chat in its tools, its search, its prompt and
its work folder lines. A chat whose agent has no folder is unchanged.
Assisted-by: Claude:claude-opus-5-5
* Find chat views built outside the chat access
The turn's access is resolved once and handed down, but nothing
stopped new code from building a view of its own, which shows a
scoped chat the whole home. A second scan now parses the same
sources as the actor scan and fails on a call of BuildView,
SkillLayerMounts, NewAccess, UserAccess or ScopedAccess, or on a
store's View, in a function that is not on its allowlist. The
resolver is the one allowed caller; the host bash, the sandbox
runner and the add-on host operations are listed until they take
the chat's access. A stale entry fails the scan too.
Assisted-by: Claude:claude-opus-5-5
* Keep a sub-agent inside its parent's folder
A saved agent started from a scoped chat could name a folder
outside the chat's own, and the child was made before its first
turn failed with a message about a missing folder. spawn_agent now
checks the agent against the chat's agent from the stores before it
makes the child: no folder is fine, the same folder or one inside
it is fine, anything else fails with "this agent works in another
folder than this chat". The child never has more reach than its
parent, in folder or in mode.
The ScopedAgent flag of a sub-agent was reset to what its own
agent had at each turn. A child of a scoped chat with an agent
without a folder lost the mark, so deleting the parent's agent
would have given that child the whole home. The flag now follows
both folders.
New specs cover a generic child, a saved-agent child outside,
inside and at the parent's folder with the lower mode, a child
without a folder, and the work folder wording of a child of a
read-only or read-write parent.
Assisted-by: Claude:claude-sonnet-5-5
* Show agent folders in list_agents and limit them by scope
list_agents gave a scoped chat every saved agent, including ones
that spawn_agent would refuse, and told the model nothing about
where an agent works. Each entry now carries the name of the
agent's folder and its mode, or folder_missing when the folder is
deleted or not chosen. The path is left out, since it would show
the folders above the chat's own. A chat whose agent has a folder
lists only the agents that fit under that folder, by the same check
spawn_agent makes.
The docs for saved agents as sub-agents and for the agent folder
describe both.
Assisted-by: Claude:claude-sonnet-5-5
* Keep the triggers of a scoped agent to its folder
A chat of an agent with a folder reaches only that folder, but its
triggers still worked on the whole home. A file trigger could watch
any folder, the rules of a run were read from the home root down
with the user's actor, the first message named home paths, and a run
whose agent's folder was gone fell through to a failed chat start
after the host had already read the home's rules. The trigger tools
of a scoped chat could also list, change or propose triggers that
reach the whole home.
The agent's folder now bounds every step:
- Saving a file trigger (create, update, edit, confirm) refuses a
folder outside the agent's folder, and a trigger of an agent whose
folder is missing or not chosen.
- Each run checks the agent's folder before it reads anything. A
missing or unset folder skips the run with "the agent's folder is
missing" and switches the trigger off; a watched folder that is not
inside the agent's folder does the same with its own reason. No
chat is made and the owner is told.
- The folder check and the rules are read with an actor fenced to
the agent's folder, read only, so no AGENTS.md above it is read.
The rules of a schedule run start at the agent's folder. The first
message names folders and the file as the chat sees them, under
/workspace/home, and the rules nudge of the run's chat counts those
rules as given.
- In a scoped chat the trigger tools read folders as paths of the
chat's place, refuse one outside the agent's folder, and see only
the triggers of that agent.
The trigger watcher takes the service's administrator actor, made
once next to the change feed's, and the trigger functions leave the
actor scan's allowlist.
Assisted-by: Claude:claude-opus-5-5
* Send connector replies through the chat's access
The files a connector reply sends were listed from every change made
in the chat's name and read with the user's actor, so a reply of an
agent limited to one folder could send a file from anywhere in the
home that some host-side write had attributed to the chat. A message
to an agent whose folder was missing made or reopened a chat, failed
to start it, and answered with a generic error.
A reply now resolves the chat's access once, lists the turn's files
through its place (a file outside the chat's folders is left out)
and reads each one with its fenced actor. A file the access cannot
reach is skipped without being named. Before it makes a chat, a
message checks the bound agent's folder; when it is missing or not
chosen, the sender gets one reply, "This agent cannot work right
now: its folder is missing.", the refusal is logged and the binding
stays on. The same reply covers a folder that goes away between the
check and the turn. readChatFile leaves the actor scan's allowlist.
Assisted-by: Claude:claude-opus-5-5
* Name a read-only refusal of a commit by its guest path
The store names an upper entry that a read-only mount refused by
its path in the view (/home/x), not by its store path. The note
that a command's output ends with took every path but an
outside-view one for a store path, so it told the model that
"/home/x was not saved", a path the guest does not have. A path
that no mount holds but that starts with a mount name is now taken
as a path of the view and gets the /workspace prefix.
Assisted-by: Claude:claude-opus-5-5
* Take the actor and view of command runners from the chat's access
The host bash and the sandbox runner made their own actor and view:
the host bash a user actor from the principal and the view of the
whole home, the sandbox runner a plain actor by default and the view
from its own mount requests. Neither knew the chat's agent folder,
so a command of a scoped chat could reach the whole home.
The runner now has no default actor or view. Options.Actor takes a
context and returns an error, and Options.View returns the view
itself; both are asked at every call. A view of another actor than
the chat's is refused, and a sandbox whose actor or mounts changed
is made again, so an agent's new folder or mode applies from the
next command. A failing resolver fails the call, also with a sandbox
running.
The server wires both hooks to the chat's resolved access: the new
Service.CommandActor and Service.CommandView, the latter refusing an
actor that is no longer the chat's. Access.SandboxView adds the skill
layers to the turn's view, at the names the file tools use. The host
bash refuses a fenced actor, at the tool and again at each call, as a
command on this machine reads what the server can read. The Sandbox
add-on passes a label actor and its home view: the host binds each
workspace operation to the call's chat.
The lane 5 entries leave the actor and view scan allowlists.
Assisted-by: Claude:claude-opus-5-5
* Offer no host bash to a chat whose agent works in one folder
A command of the built-in bash runs on this machine and reads every
file LocalAI can read. No workspace rule confines it, so a chat whose
agent works in one folder cannot have it, whatever chats_host_exec
says. The tool's build now refuses a fenced actor, so the chat gets
no bash, its prompt says nothing runs commands, and the shell group
of the Tools menu gives the reason: the agent works in one folder
and runs commands only in the Sandbox add-on. Sub-agents of such a
chat have the fenced actor too.
The docs of saved agents and of the commands on this machine state
the rule and the Sandbox layout of a scoped chat.
Assisted-by: Claude:claude-opus-5-5
* Tell a scoped chat where its Sandbox commands start and what they keep
The commands block of the prompt told every chat with the Sandbox
that a command starts in /workspace/home, the user's home, and that
what it saves there or in a writable shared folder is kept. In a
chat whose agent works in one folder, /workspace/home is that folder,
the chat folder is /workspace/chat and the shared folders are read
only. The block now says so, and with a read-only folder it names
only /workspace/chat as kept.
Assisted-by: Claude:claude-opus-5-5
* Name a scoped chat's attachments where its commands see them
The model learns of an attached file by its guest path, which was
always /workspace/home/Chats/<chat>/<file>. In a chat whose agent
works in one folder, /workspace/home is that folder: the file and
media tools still found the file through the place's chat alias, but
a command sees the chat folder only at /workspace/chat, so the path
named nothing for it.
The text the model gets now names a file of the chat folder at
/workspace/chat/<file> in a scoped chat, through the chat's place:
when a message is sent, queued or injected, in the summary of the
chat, and in the model state rebuilt after an edit, a regenerate, a
fork or an import. What is stored keeps the home spelling, which
SplitMessage reads back, and an unscoped chat is unchanged.
Assisted-by: Claude:claude-opus-5-5
* Remove the duplicated allowlist entries of the actor scan
Assisted-by: Claude:claude-opus-5-5
* Read and write the folder of an agent in the agent helpers
The agent API now answers an agent's folder with its path, mode and
state, and a write keeps the stored folder unless it names one. The
page helpers learn that shape: the folder field of a save is sent only
when the person changed it, an empty path clears it, a missing or
unset folder that the person did not replace is kept, and the mode is
read unless write was chosen. Small helpers say whether a listing
entry can be chosen (not a folder LocalAI manages, not a shared
folder), which folders agents use for the Files page marker, and
whether a chat error is the missing-folder conflict.
Assisted-by: Claude:claude-opus-5-5
* Move the folder walk of the Move dialog into a folder picker
The agent editor needs the same walk through the person's folders as
the Move dialog, so the breadcrumb and the folder list move into
FolderPicker, with their styles, and the Move dialog draws it. The
picker can show a folder as one that cannot be chosen, with the
reason next to it, and a folder that cannot be read now offers to try
again; a folder still loading says so instead of an empty list.
Assisted-by: Claude:claude-opus-5-5
* Choose the folder of an agent in the agent form
The agent form gets a Files section after the instructions: All my
files, or One folder chosen in the folder picker, and whether the
agent's chats can read the folder or read and change it. The picker
shows the folders LocalAI manages as ones that cannot be chosen, and
Home itself can be chosen. A folder that was deleted, or an imported
agent whose folder was not found, is said in place with an offer to
choose another; the rest of the form still saves and keeps it.
A save sends the folder only when the person changed it, so editing
another field never touches it. Picking One folder and choosing
nothing stops the save, as it would otherwise save an agent on all
the files. The Export section now downloads the file the server
writes, which names the folder, and says so.
Assisted-by: Claude:claude-opus-5-5
* Show the folder of an agent on the agents list and its page
An agent with a folder shows it in the list's line and on its page,
with its access in words and a lock when its chats can only read it.
A folder that is gone, or an agent with no folder chosen yet, shows
a warning that opens the form; the agent's page also says so above
its facts and offers to choose another folder.
Assisted-by: Claude:claude-opus-5-5
* Mark the folders that agents work in on the Files page
A folder that a saved agent works in gets a small marker with the
agent's name, or the number of agents, and a tooltip that names each
one with its access. Inside such a folder, a line over the list says
the same. The page reads the agent list it can already fetch, so the
files API is unchanged, and the page still shows all the files.
Assisted-by: Claude:claude-opus-5-5
* Show a chat's folder and say when its agent's folder is missing
A chat with an agent that has a folder shows the folder next to the
agent in the message box, with a lock for read only, and the Tools
menu says which files the chat's tools reach. When the folder is
gone, the chip says so and opens the agent's form.
A chat whose agent's folder is missing does not start, and the server
answers with a conflict. The message box now says that in plain words,
keeps the message, and offers Edit agent, instead of the server's
text.
Assisted-by: Claude:claude-opus-5-5
* Test the folder of an agent across the web interface
The agent stubs now list folders by path, read a written folder back
as the server does, export a version 2 file for an agent with a
folder, import one whose folder is not found as unset, and refuse to
start a chat whose agent's folder is missing.
The specs choose a folder and see it on the list, switch the access,
leave an unchanged folder out of the save, clear it, keep and replace
a missing folder, keep Chats and LocalAI out of the picker, retry a
folder that cannot be read, see the Files marker, the chat's chip and
Tools line, the missing-folder chat error with Edit agent, and the
import warning. A render smoke opens the picker on the real server,
and one spec checks that the copy names no internal path.
Assisted-by: Claude:claude-opus-5-5
* Describe the agent's folder in the web interface
The agent docs said what the folder field does in the API. They now
also say where the web interface sets it, how the agents pages, the
Files page and a chat show it, and what a chat whose folder is
missing offers.
Assisted-by: Claude:claude-opus-5-5
* Record the chat an add-on call was made for
An add-on's workspace operations run as the user of the live request,
with no idea which chat made it. Only an action carries a chat, and
only as a key in its resource ID; a read tool such as the Office
add-on's read-docx carries none. So a chat that should reach only one
folder could reach the whole home through an add-on.
The in-flight record of a request now keeps, next to the envelope, a
binding to the chat the request was made for: the user's principal
and the chat ID. It is host-side only and never a field of the
envelope, which installed add-ons decode with unknown fields refused.
A context carries the binding from the chat turn to the place that
records the request. The broker gives each host operation the chat
and the scope of its live request. An operation ID that requests of
two chats share is not live, so neither chat is trusted.
Assisted-by: Claude:claude-opus-5-5
* Keep the chat binding of a runtime call on the host
The runtime client records each call's envelope for the host operation
channel. It now records the chat binding too when the call's context
carries one, so the add-on's host operations know which chat made the
request. The envelope sent to the add-on is the same bytes with or
without a binding: a spec checks that the body of a bound call equals
the body of an unbound one and still decodes with unknown fields
refused.
Assisted-by: Claude:claude-opus-5-5
* Carry the chat binding in a forwarded add-on request
A frontend that is not the add-on owner forwards an add-on call in a
signed request. The chat the call was made for now rides in that
request as a signed field, chat, and the owner puts it back on the
context of the call it serves, where the runtime client records it.
A changed, added or removed binding fails the signature check, and a
signed but incomplete one is refused. A request that no chat made has
no chat field and signs as before.
An owner that does not know the field refuses a forwarded call that a
chat made, so the owner must be upgraded before the frontends.
Assisted-by: Claude:claude-opus-5-5
* Bind every add-on tool call of a chat to the chat
The chats consumer ran an add-on tool with the conversation ID only,
which the owner used to resolve a selection and, for an action, as the
key of the add-on's state. Nothing told the host which chat a read
tool or a change came from.
The tool adapter now has ExecuteChat, which takes a ChatCall (the
chat's user and ID, from the chat turn's own record) and binds the
call to the chat on the host side for every tool kind: a read with no
resource, an action, and a change made at once. The chats adapter
refuses a call that names a conversation without that binding, so a
chat's call cannot be made unbound by mistake. Other consumers, such
as the OpenAI-compatible route, keep calling without a binding.
Assisted-by: Claude:claude-opus-5-5
* Run add-on workspace operations with the chat's access
The workspace operations of add-ons (materialize, commit, changes,
export and import) worked on the whole home of the request's user. A
chat whose agent has a folder could therefore read and write any file
through the Office or Sandbox add-on.
They now resolve their access from the chat binding of the live
request, through a new Access hook that the chats service answers
(Service.AddonAccess). A chat's call gets the chat's access, as its
file tools do: in a chat whose agent has a folder, the add-on's "/" is
that folder, /workspace/chat names the chat folder, the agent's mode
applies, and paths resolve through the chat's place, so a parent
reference is refused and an encoded separator is part of a name. The
mounts go through Access.SubView, which builds a view only inside the
access, for its fenced actor. When an add-on mounts its "/" at home,
the host adds the chat folder at chat, so the Sandbox sees the chat's
layout without a change. Changes list only what the access may read,
and a commit entry outside the view is a conflict.
A call whose chat cannot be resolved is refused and never runs
user-wide. An action request without a binding is resolved by its
chat key, as before, and refused when it names no chat. A call that
no chat made (an add-on page, the OpenAI-compatible route) still works
on the whole home, through Manager.PersonAccess.
The four lane 6 entries leave the actor scan and the view scan.
PersonAccess joins the view scan's matcher, with AddonAccess as its
one allowed caller. An end-to-end spec runs the real Office and
Sandbox add-ons in a scoped chat.
Assisted-by: Claude:claude-opus-5-5
* Build the chat access of the file index route test
files.Service.Tools takes a session.Access since the chat access
change, so the route test of the text index passed a bare actor and
its package did not compile under the auth tag.
The test now builds the access as the chats service does for a chat
whose agent has no folder: the user's home as the primary folder of
the view, then session.NewAccess. The assertions are unchanged.
Assisted-by: Claude:claude-opus-5-5
* Test the folder of an agent through every surface of a chat
The specs of the folder feature each test one surface. This suite
runs one chat of an agent limited to a folder through all of them at
once, on an indexed SQL workspace: the file and media tools, the search
and its pending count, a command of the Sandbox in a fake sandbox, a
read call and an action call of an add-on through the real workspace
operations bound to the chat, a generic and a saved sub-agent, the
trigger tools, a file trigger run, a connector reply, a renamed folder,
a mode change, an edit that narrows the folder (also seen by a second
frontend) and a deleted folder.
The files outside the folder hold a token that the scripted model never
writes. After each spec the suite collects every text a person or a
model can see: tool results and errors, prompts, messages, transcripts
with their cards, live events, chat file lists, tool panels,
notifications, connector replies and files, the add-on's data directory
and the connector log. The token must be in none of them, and the files
outside must be unchanged.
A control spec runs the same probes from an agent without a folder and
checks that they do find the token, so the probes and the collection
cannot pass by seeing nothing.
Assisted-by: Claude:claude-opus-5-5
* Complete the docs of the folder of an agent
Each change of the folder feature documented its own part. This
makes the pages one account of it:
- Saved agents: the missing and unset states in one bullet, what the
folder does not limit (MCP servers, web tools, collections, pages),
that the Files page stays user-wide, when an edit applies, the
downgrade caveat, and the folder as a stateless row of the cluster
table. The Sandbox of a scoped chat mounts the agent's folder and the
chat folder only, as the add-on asks for them; the page said it also
had the shared folders and skills.
- Chats: the chat folder at /workspace/chat for the model, sub-agents
of a scoped chat, and commands only in the Sandbox.
- Add-ons: the Sandbox and Office sections say what / is in a chat whose
agent has a folder.
- First agent: the optional folder step and the "Folder missing" case.
Assisted-by: Claude:claude-opus-5-5
* Draft the PR description and release note of agent folders
No release notes file exists for the next release yet, so the draft
text lives next to the spec and the plan. It carries the release note
paragraph with the downgrade caveat and the upgrade order of a cluster.
Assisted-by: Claude:claude-opus-5-5
* List the chat binding specs in the per-report add-on inventories
The chat binding change added five specs to the gateway and runtime
packages and named them in the case lists of the gateway, artifacts and
apps suites. These three suites also keep one case list per Ginkgo
report, and the verifier compares each report with its own list. The
new names were missing there, so the three suites failed with an
inventory mismatch.
A dry run of every Ginkgo suite's commands now lists the same It names
as each report's inventory, in both directions.
Assisted-by: Claude:claude-opus-5-5
* Hide stale paths from a fenced actor's history
A folder moved into a fence makes its parent identities readable.
Their historical paths can still name locations outside the fence.
Hide each historical side when its readable parent's current path
differs. Reuse the generic title when filtering hides a side. Keep
the journal and unfenced history unchanged so Undo uses identities.
Assisted-by: nib:gpt-6.1-sol
* List the chat binding cases in Tables tools
The Tables tools runner includes every adapter spec in its output.
List the four chat binding cases so the exact inventory accepts them.
Assisted-by: nib:gpt-6.1-sol
* Keep shared agent folders unset in exports
A followed folder outside the home cannot round-trip through a home path.
Export its mode with an empty path so import warns and stays fail-closed.
Responses keep public shared paths without exposing other internal paths.
Distinguish deleted scoped agents from unscoped agents in the guidance.
Assisted-by: nib:gpt-6.1-sol
* Refuse scoped chats over the folder limit
Keep every required shared root in each intersecting fence. Refuse
creation and turn access when the complete set exceeds 32 roots.
Reserve a chat root before its folder exists and fence listings empty
on refusal. Propagate shared enumeration failures instead of omitting
roots.
Map capacity refusals to a fixed 409 response and give connectors a
safe capacity message. Check boundaries, retries, background access,
and HTTP handlers with focused regressions.
Assisted-by: nib:gpt-6.1-sol
* Verify unset imports refuse chat startup
Assert the folder-specific refusal without a harness start or saved chat.
Clarify public response paths and unset exports in the API metadata.
Assisted-by: nib:gpt-6.1-sol
* Keep the folder retry test under user control
A background refresh consumes the one-shot successful response before
this test observes its load error. Isolate the mocked Files page from
the live workspace stream and agents list. Keep reads failing until
the error assertions pass, then allow the actual Retry click to recover.
Check that Retry reads again and removes the error, without changing
the existing assertions or their timeouts.
Assisted-by: nib:gpt-6.1-sol
* Test and document folder capacity refusals
Use the existing Ginkgo suites for capacity regressions. Exercise a
successful turn, refusal after shares change, and a retry through a
second service on the same stores. Check that refusal leaves no claim
or pending message.
Verify that connectors send one fixed refusal and that triggers stay
enabled after capacity failures. Document the 32-root budget and the
all-required-roots-or-refuse contract.
Assisted-by: nib:gpt-6.1-sol
* Control crashes in the restart budget test
The crash fixture exits after 100 ms, before startup necessarily finishes.
A busy runner can fail the readiness handshake instead of testing restarts.
Start a normal fixture and kill each published incarnation explicitly.
Keep the exact restart count and persistent failed-state assertions.
Assisted-by: nib:gpt-6.1-sol
* Check capacity test cleanup errors
Assert service and workspace cleanup through Ginkgo. Keep service cleanup before workspace cleanup and report close failures instead of discarding them.
Assisted-by: nib:gpt-6.1-sol
---------
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
|