Commit Graph
403 Commits
Author SHA1 Message Date
Ettore Di Giacinto 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>
2026-11-03 11:47:50 +00:00
Ettore Di Giacinto 3f5b23e3e7 feat(addons): add a host ceiling for connector files to the owner policy
The key connector_files_ceiling_bytes sets the largest file a connector
moves. Zero means the default of 100 MB. The key is always written, and
a policy document stored before it reads as the default. The connector
manager reads the ceiling from the owner policy at each resolution.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:47:50 +00:00
Ettore Di Giacinto 6d3aec30b3 feat(studio): build connector file limits and sentences from one resolver
The limit of a file is the smallest of the host ceiling, the ability the
connector kind declares, the owner's setting for the binding and the cap
of the inline path. One function decides it and names the layer that
won. The sentences to the sender and to the model are built from that
number and layer, in KiB or MiB, instead of a fixed 1 MB. A guard spec
fails when these files carry a size or a byte count of their own.

The owner can lower the limit of a binding; a value above the limit of
the kind is refused.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:47:50 +00:00
Ettore Di Giacinto a6c74acd76 feat(connectors): store a file limit per binding
A binding keeps an optional limit for the files it reads and the files it
sends. Zero means the owner sets none. Both stores keep the values and the
validation bounds them.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:47:50 +00:00
Ettore Di Giacinto d676d629a0 feat(studio): filter the chat list by origin
GET /api/chats takes an optional origin parameter: person (chats a
person started), automated (every other chat) or one origin kind such as
trigger or connector. The limit counts the chats kept, so a flood of
chats that a connector or a trigger started cannot push the chats of a
person off a page, and the other way round. Without the parameter the
list is unchanged.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:41:13 +00:00
Ettore Di Giacinto 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>
2026-11-03 11:31:37 +00:00
Ettore Di Giacinto da60e435be fix(session): give host tools the product scope of their turn
A harness may call a tool with a context that carries nothing of the
turn. Lend the values of the turn to the tool, so a status read in the
tool shares the turn's observation instead of starting without one.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:31:37 +00:00
Ettore Di Giacinto a728722c29 fix(studio): read the execution status inside a scope on every turn path
load_skill runs with the context the harness gives it, which has no
execution scope, so the status of the commands read as not selected and
the document skills were dropped. Make the descriptor and skill
resolution open a scope when the context has none, and report a scope-less
status read as a programming error: logged once in production, a failure
of the spec in tests.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:31:36 +00:00
Ettore Di Giacinto 67a503c224 test(studio): load_skill must load a document skill under an isolated Sandbox
The spec builds the turn tools under the product scope and then calls
load_skill with a context of its own, as the harness does. It fails
with the not_selected text on master.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:31:36 +00:00
Ettore Di Giacinto 88aae9aa41 feat(slack): follow only the threads the bot started, and keep them
The option followThreads says which threads the bot follows without a new
mention: own (default) follows the threads that a top-level mention
started, any also follows a thread that other people started once the bot
is mentioned in it, never needs a mention each time. A mention inside a
thread that people started is answered and, in own mode, not followed.
The followed threads are kept in the add-on's data directory, written
atomically, and expire after a day without a message, so a restart or a
reconnect does not make the bot forget them.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:30:11 +00:00
Ettore Di Giacinto 0e50c3f9ca feat(addons): let a connector kind declare per-binding options
A kind can declare options with a few values and a default (manifest
api 2). The host checks the owner's choice against the declaration,
stores it on the binding, shows it in the REST API and sends the value in
effect with every poll. The add-on decides what the option means.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:30:11 +00:00
Ettore Di Giacinto 96a2a7c104 test(studio): run the thread history through the Slack and Telegram fixtures
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:30:11 +00:00
Ettore Di Giacinto f6f0dce5e5 feat(studio): give a new thread chat the earlier messages of the thread
When a message in a thread makes a new chat, the host asks the add-on of
a kind that declares history for the earlier messages, once, and hands
them to the first turn as a delimited block of untrusted content. The
block is bounded in messages and bytes, keeps the newest, is flattened
and escaped, and is not kept in the transcript. A failure or a missing
ability leaves the turn as it was.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:30:11 +00:00
Ettore Di Giacinto 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>
2026-11-03 11:30:11 +00:00
Ettore Di Giacinto fca8eacf2f fix(addons): use a neutral example for the network title
The provider guard rejects a shipped add-on name in a string literal,
and the swagger example of the network title in the apps view named one.
Use a neutral example value.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 11:29:36 +00:00
Ettore Di Giacinto c886ba7ec2 test(studio): pass a network name to the prompt guard
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:58:46 +00:00
Ettore Di Giacinto f284c4b84b test(addons): leave the declared network out of the gateway copy
The apps list reads capabilities.network from the receipt's manifest, and
no gateway call depends on it, as for the connector.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:58:46 +00:00
Ettore Di Giacinto f9fdd167a3 test(studio): keep add-on names out of the network text
Fail when a file that builds the text of the command network, the
built-in bash or the host-exec setting writes the name of an add-on.

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

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

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:58:46 +00:00
Ettore Di Giacinto 9dac1214bc feat(addons): list the declared network in the apps API
An app whose network-policy control the host draws now carries its
declared network: the kind, a title for text (the declared one, else the
add-on's own) and the preset titles. The control no longer needs a tool
that shadows bash when the add-on declares capabilities.network.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:58:46 +00:00
Ettore Di Giacinto 87823914f6 test(studio): fail when core names an add-on that provides a role
Read addons/*/manifest.json at run time and find the add-ons that shadow a
built-in tool or skill, serve as a connector or declare the execution
provider. One spec scans the string literals of core and pkg for their
names; another builds the command prompts and the network text for every
state and checks that none names a shipped provider. A short allow-list
holds the strings that name a platform or a token format as content.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:43:37 +00:00
Ettore Di Giacinto 61c54dc833 refactor(studio): word the command provider text without naming an add-on
The prompt, the Tools menu reasons, the network error texts, the CLI help,
the API instructions and the assistant tool descriptions said "Sandbox" for
the add-on that runs commands. Core must not know that name.

The reason a chat has no commands now uses the title the provider declares
("the Zeta add-on is off for this chat") or, when none is installed, says
"the add-on that runs commands". Text that has no provider in hand speaks of
the network of commands or of an add-on that isolates them.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:43:37 +00:00
Ettore Di Giacinto 9b19a1b0c7 test(addons): keep add-on ids out of the core packages that list resources
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:24:42 +00:00
Ettore Di Giacinto 4532ab988b refactor(addons): build the resource listing tool from the declaration
The tool adapter no longer builds the listing for one add-on by id. It
reads capabilities.resources.list of any add-on, names the tool with
the add-on id and the declared name, and rejects a manifest that ships
a tool of that name. A second add-on with a different id now gets the
same tool through the same code path.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:24:42 +00:00
Ettore Di Giacinto 857feba444 feat(addons): carry the resource listing declaration in the apps list
The apps list now holds the resource mapping of each manifest, so the
tool adapter can act on the listing declaration without knowing any
add-on. A manifest of API 1 written before the declaration existed
keeps its listing through one marked compatibility file, with a spec
that tells when to delete it.

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

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:21:41 +00:00
Ettore Di Giacinto c1876803a3 docs(chats): describe the proposal card, the Proposals page and the Memory tab
Document the optional body of the confirm route, the context-files route,
the card the thread draws for any kind and source, and the Memory tab. The
context-file listing is a person's own action, so the actor scan allows it.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:15:03 +00:00
Ettore Di Giacinto a7771d06cf feat(sandbox): declare the execution descriptor in the manifest
The Sandbox add-on states in its manifest that its bash tool runs
commands, under which title, with which note for the model, and that it
supplies the runtime of the document skills. The manifest accepts API 1
to 2. A spec checks that the manifest matches the descriptor in code.

A guard spec fails when the studio package or the add-on tool adapter
compares a string with the ID of the Sandbox again, or when the files
that build the commands text name it.

The contracts suite inventory lists the new specs, and two specs of the
network-policy status that it was missing.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:07:46 +00:00
Ettore Di Giacinto 0c72b2c96f refactor(studio): decide command eligibility from the execution status
Document skills, the Commands block of the prompt and load_skill no
longer compare an add-on ID or provider with "sandbox". The host asks
whether the add-on that ships a skill serves the chat's commands and
whether the typed execution status reports them isolated. Skills that
need that are listed by the broker from the add-on's manifest, and the
text names the provider by the title its manifest declares.

An add-on whose commands tool declares no descriptor behaves as a
provider of commands under its own title and keeps the isolation check
for its document skills.

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

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

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 10:07:46 +00:00
Ettore Di Giacinto 9b22862a2d feat(studio): confirm a change with the owner's own text
The proposal card lets the owner correct the text before confirming.
POST /api/chats/changes/{id}/confirm now accepts an optional JSON body
{"content": "..."}. The text has the same size limit and text check as a
proposal, the file and the audit hash hold what was written, and a
refused text leaves the proposal waiting.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:47:19 +00:00
Ettore Di Giacinto 2c154c892f feat(studio): list the context files of a user
The Memory settings page needs to know which index and topic files the
host loads into the prompt. GET /api/chats/context-files walks the
registered context sources, expands the agent and channel folders that
exist, adds the agent profiles and reports each file with its size, the
line count of the index, and the caps. It only reads; the page edits and
deletes through the Files API.

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

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:42:29 +00:00
Ettore Di Giacinto 532e3a4272 fix(worker): stop backends without Free on model.unload
model.unload calls Free over gRPC and keeps the backend process alive.
llama-cpp does not implement Free, so the call answers Unimplemented,
nothing is released, and the next backend.install for the same replica
reuses the live process. The second LoadModel on that process dies a
second after the slots are initialized, and the controller sees EOF or
Unavailable until the following install starts a fresh process.

When Free answers Unimplemented, stop the process after the unload so
the next install starts a clean one. Backends that implement Free keep
their process, so reloads stay cheap for them.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:40:46 +00:00
Ettore Di Giacinto 14d7fc631c feat(studio): wire the proposal store and document change proposals
Open the proposal store beside the chats (auth database, file or memory)
and document the mechanism for add-on authors: the wire record, the
limits, the gate, the audit, the REST routes and how a cluster shares
it. Update the registry and actor-scan specs for the new tools.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:37:07 +00:00
Ettore Di Giacinto 5dc9ed707d fix(nodes): back off failed replica scale-ups per model
When a scale-up failed, the reconciler tried again on every busy or
pressure tick, which logged a warning and sent a new backend install each
time. The "attempt" field in that warning was the loop index, so it always
read 1 and hid the repeats. A model that no request has loaded yet logged
"no load info" at warn level on every tick.

Track consecutive scale-up failures per model in memory. After a failure,
wait 30s, doubling on each further failure up to 10 minutes, before the
next try. A successful scale-up resets the count. Log the failure count
and the wait. Mark the missing load record with ErrNoLoadInfo and log it
at debug level, because only a first request can fix it.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:33:41 +00:00
Ettore Di Giacinto 695928e393 fix(studio): tell the model how to give an existing file
The turn block of a connector chat said that files made in a turn are
attached, but not how to give a file that already exists. Name the
attach_file tool in the block. The block stays under 700 characters.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:33:24 +00:00
Ettore Di Giacinto eca78c3f97 feat(studio): let the agent attach an existing file in a connector chat
The host sent only the files a turn made or changed. A person who asked
in a messaging app for a file that already existed got nothing.

Add the attach_file tool. It is offered only in a chat that a connector
started whose binding sends files, so a chat in Studio has no such tool.
It takes a path as the file tools show it, checks the file against the
chat's access (a scoped agent reaches only its folders), the type
allowlist, the size limit and the count, and marks it. When it refuses, the
result says why in plain words so the model can tell the person.

sendFiles now sends the files the turn made, then the attached ones, each
path once and at most five in all. It reads them with the chat's access
resolved at send time. Delivery is the existing send_file op, so every
connector add-on that implements it gets the feature with no code of its
own. A failed send says "I could not send X" and adds the reason the add-on
reports, cut to one short line.

The marks are kept in the memory of the process, like the other
per-turn state of the connector manager.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:33:24 +00:00
Ettore Di Giacinto ef79f5336d fix(studio): tell the model what to do with a binary file
read_file and edit_file answered "X.docx is not a text file (43963
bytes)" and gave the model nothing to go on.

Make the error typed and let the chat's tool list add the hint. If the
chat has a skill for the file type (docx, xlsx, pptx, pdf), the error
names it. Otherwise it suggests a script run with the commands tool, with
a library for office types, when the chat can run commands. A chat with
neither is told that no tool opens the file. The skills are resolved only
when a read fails.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:33:24 +00:00
Ettore Di Giacinto d32440096d feat(studio): add propose_change and revert_change
A model asks to change the person's memory or an agent profile with
propose_change. The host stores a proposal and writes nothing until the
owner confirms; the write is one workspace change set, so revert_change
and the Files history can undo it. A proposal expires after 24 hours,
is refused when the target changed meanwhile, and is capped at 10
pending per person and 5 per hour per agent.

The host holds no platform code. The origin of the chat is recorded as
the source, so a Studio chat and the connector chats of any platform
use the same path. A gate decides if a chat may propose; the default
refuses a chat that runs a saved agent until grants fill it. Every step
is audited with a content hash, not the content.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:30:26 +00:00
Ettore Di Giacinto 9ee1407989 feat(studio): load context files and memory_read in every chat
Each turn the chat prompt gets the agent profile and the indexes of the
registered context sources, after the persona and before the skill index.
The scope of a source is derived from the chat record: the saved agent, and
the binding when a connector started the chat. The text is the same at each
turn, so the transcript does not grow, and it feeds the refresh key so the
harness restarts only when a file changes.

memory_read reads a topic next to an index as the user, outside the chat
fence. A scoped agent reads its own notes and the user notes and gains no
access to the files. The service exposes ContextSources and
SetContextWriter so other parts of the host can register a source or plug in
the save path.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:28:57 +00:00
Ettore Di Giacinto b64b35b5a4 feat(studio): add a generic loader for context files
A source declares a scope (user, agent or channel), a home-relative index
path and a byte cap. The loader reads the indexes that apply to a chat and
returns one bounded block under a fixed header, plus a key that changes with
it. Details of a topic are read next to the index; the path is built from
the registry and the chat, so the model can only name a topic.

The package only reads. A Writer interface lets a later change plug in the
save path, and CheckIndex and CheckProfile return the current entries when a
write would pass a cap, so the model merges them.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:28:57 +00:00
Ettore Di Giacinto b988cae89d feat(studio): map change kinds to files and add the grant gate
Memory and profile changes write to fixed places in the owner's home;
the target a model names is checked against a small pattern so it
cannot leave Memory/. Other features register their kinds. The gate is
the hook the agent grants fill; by default it refuses a chat that runs
a saved agent.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:27:04 +00:00
Ettore Di Giacinto b1df05a800 feat(files): read and write a home file for the host
The host must write a confirmed change as the owner, in one titled
change set whose revision it can undo later. Add ReadHome and
WriteHome, which resolve the path inside the home and refuse a path
that leaves it.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:26:21 +00:00
Ettore Di Giacinto 26f79638f2 feat(studio): add the change proposal store
Keep what a model asked to change, until the person answers, in a
store with memory, JSON file and GORM implementations and one shared
conformance suite. Create checks the pending cap and the hourly rate in
the same step as the insert, Settle is a compare-and-set so two
frontends cannot both answer, and the audit holds hashes, never
content.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:25:40 +00:00
Ettore Di Giacinto 0b07d7fa83 fix(studio): tell the model of a connector chat that files are attached
A chat started from a messaging app has its answer read in that app, and
the host attaches the files a turn makes to it. The model did not know
this: it said it could not send files and quoted a sandbox path.

Add a short block to the turn prompt of connector chats. It names the
platform, says that files saved in the chat folder are attached after the
answer, gives the limits the host enforces, and says that files are not
sent when the binding turns them off. If the binding cannot be read the
file part is left out. Other chats are unchanged.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 09:15:50 +00:00
Ettore Di Giacinto 0c0cc77d38 refactor(addons): share the upgrade conflict and wire checks
The test for "the same pinned transition under another operation id" was
written out three times in manager.go and upgrade_operation.go, and the
"completed or failed" phase test five times. They are now sameTransition
and isActive.

OwnerHandler rejected a request that carried a second routing payload with
a hand-written list of nil checks per branch, each list slightly
different. wireRequest.exclusive counts the payloads once. The earlier
branches return, so every branch rejects the same requests as before.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 08:45:52 +00:00
Ettore Di Giacinto 6a7a2db672 fix(addons): do not panic on an unknown upgrade phase
upgradeTransition ended in panic for a phase or event it did not know.
The phase of a submitted operation is read from the journal on disk, so a
corrupted file would crash a background goroutine. The reducer now
returns errInvalidUpgradeTransition, and submit marks the operation as
failed and returns ErrOperationFailed. The current call paths only pass
known phases, so this is a guard, not a fix of a known crash.

upgradeViews also grew without bound, and every persist walked it. Views
of finished upgrades are now kept for the newest 256 only; the journal
still answers for older ones. The journal itself is rewritten on each
persist, so the walk stays proportional to what is written anyway.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 08:45:52 +00:00
Ettore Di Giacinto 564ee50df2 refactor(addons): bound a connector call once per hop
ConnectorCall, localConnectorCall and Carrier.connectorRequest each set
their own 45 second timeout. On a frontend the first and third nested
inside each other, and on the owner the first and second did. Each hop
now sets it once (connectorTimeout): the forwarding carrier on a frontend,
and localConnectorCall on the owner, which must bound the work whichever
door the call came through. ConnectorCall no longer adds a third one.

The kind lookup in localConnectorCall uses slices.IndexFunc instead of a
loop that kept scanning after a match.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 08:45:52 +00:00
Ettore Di Giacinto 4fbfc4586b fix(addons): keep the cause of a connector failure
localConnectorCall and the kinds lookup turned every error from the
connector source into a bare ErrUnavailable. VerifyTokenWithMe treats
that as "could not tell" and saves the binding, so a definite refusal by
the add-on was indistinguishable from an outage, and the cause was lost.

An add-on refusal (invalid request, or denied for this person) now keeps
its type through Refused. Other errors still read as ErrUnavailable but
wrap the cause, so errors.Is and logs see it. The forwarding carrier maps
the owner's 400 back to ErrInvalidRequest instead of ErrUnavailable.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-11-03 08:45:52 +00:00