Commit Graph
1667 Commits
Author SHA1 Message Date
Ettore Di Giacinto 5df9c43578 fix(studio): trust a sender who is on the allowlist for capabilities that are not sensitive
A binding with its capabilities ticked still gave nothing to the
owner's own allowlisted account: Authorize wanted a pairing or a role
even for capabilities that are not sensitive, so the agent had no
reminder, memory or talk-back tool.

A sender whose ID or @name is an entry of the allowlist now holds every
capability that is not sensitive, within the ceiling of the binding and
while no switch is off. The decision says Via "allowlist". A sensitive
capability still needs a pairing, and a role never grants it.

The entry of a channel or group lets its members talk to the bot but
does not trust them: the trust is matched against the sender only, and
a binding that accepts anyone lists nobody. Such a sender still gets
capabilities from a pairing or a role. The capability gate takes the
handle of the sender for the same match, and a sensitive request
ignores the allowlist as it ignores roles.

When a tool is held back for a missing right, the turn now carries a
short note with the reason in plain words and tells the model to relay
it, instead of saying that it has no such tool. The reasons are kept
in the grant of the turn, so a worker reports the same words. The
access view lists a "listed" sender apart from one let in through a
channel.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 10:11:25 +00:00
Ettore Di Giacinto a1157fb811 Tell the sender when an answer shows a file that was not attached
A model could write a paperclip line with a made-up file name and
never call attach_file. Nothing was sent and the sender got no note.

When the answer of a connector turn has a paperclip followed by the
name of a sendable file, and no file went and no other note was
sent, the host now adds one plain note. The answer is not changed.
Prose that names a file and a paperclip alone do not match.

The attach_file description and the connector guidance now say to
use the exact path the tools showed, and never to write a file name
or a paperclip line by hand. A spec covers a folder name with a
space.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 09:14:18 +00:00
Ettore Di Giacinto e6fe327203 fix(studio): let a binding read the history of its thread
AuthorizeConnector had no case for the op history, so the default
branch refused it. A mention in a Slack thread then logged "connectors:
forbidden" and the first turn of the new chat got no thread block.

History now follows the rules of a send: the binding is the whole
authority, the token comes from owner storage and a disabled binding
keeps it. Only the poll stops with the binding.

The studio specs ran over a transport that skipped the scope check, so
none of them saw the refusal. The rig now authorizes every call with the
real manager, and a new spec table pins each op of the contract.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 09:12:09 +00:00
Ettore Di Giacinto 81a56608d1 test(studio): wait for the answer of a talk-back turn, not its working message
The turn helper of the talk-back specs ended its wait on the first text in
the chat. That text is the working message that goes out when the turn
starts, so a spec could read the tool results before the tool had run.
The refused-channel spec failed that way under load and the race
detector. Count only the replies that are not the working message.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-04 08:00:34 +00:00
Ettore Di Giacinto 8b7163f301 Show the update status on an installed add-on's library card
The library card of an installed development build showed only the
installed version when the check had no answer, so a missing or failed
check looked like nothing to update. The card now says when the status
is unknown, when the check failed and when the add-on is up to date.

Add checker coverage for an install that recorded the channel name and
for a registry lookup that fails: neither reads as up to date.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 07:27:40 +00:00
Ettore Di GiacintoandClaude Sonnet 5.5 c3566febd3 Report every add-on update in one check and say why an old package will not start
Checking several add-ons of one library in a row reached the registry
only for the first, because the library interval also throttled the
per-add-on tag resolution. The others answered unknown at once, so a
check-all reported one update and the next pass the next one.

The library interval now spares only the library fetch; each add-on is
throttled by its own interval.

Turning on an installed package whose manifest this host refuses now
fails with the typed installed_incompatible cause and a message that
says to upgrade, instead of the generic owner-unavailable 503. The
upgrade of such a package stays allowed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 07:26:08 +00:00
Ettore Di Giacinto 8857fd36f4 test(studio): wait for the delivery row of a trigger report
The text of the report reaches the channel before the access row is
written, so reading the rows once right after the text was a race.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-04 07:01:13 +00:00
Ettore Di Giacinto 1e75fc11bc test(connectors): wipe the migration and pairing tables before each PostgreSQL spec
The coverage run and the no-skips run share one database. The migration
claim left its rows behind, so the second run found the claim taken and
two specs failed. Wipe the two tables the setup left out.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-04 06:59:46 +00:00
Ettore Di Giacinto e351118f85 fix(addons): deny a stale generation before the credential check
A read of an add-on that declares credentials looked up its capabilities
before it compared the generation. A stale generation made that lookup
fail, and the failure was reported as an outage of the owner (503)
instead of a denial (403). Classify the lookup error like the tool check
does.

Assisted-by: Claude:claude-sonnet-5-5 go-test
2026-11-04 00:09:39 +00:00
Ettore Di Giacinto 48bc71c38a test(ui): compare the composer height with a sub-pixel tolerance
Chromium reported 175.99998 px before focus and 176 px after, so the
exact toBe check failed in about half of the runs. Allow half a pixel.

Assisted-by: Claude:claude-sonnet-5-5 playwright
2026-11-03 23:14:30 +00:00
Ettore Di Giacinto ca284491aa test(addons): expect the Host access tab last in the keyboard spec
The host access step added a fourth tab after Sources, so End now
selects Host access. The spec still expected Sources and failed on
every run.

Assisted-by: Claude:claude-sonnet-5-5 playwright
2026-11-03 23:14:30 +00:00
Ettore Di Giacinto 653ba1afb8 fix(addons): stage no file while the UI host grants are locked
Sign-in off, or a policy file that is the only source, locks the UI host
grants. An unsigned file must not be staged or installed then: the owner
refuses file-begin with locked_by_deployment and the install dialog shows
the lock and disables the install.

Assisted-by: Claude:claude-sonnet-5-5 go eslint
2026-11-03 22:53:34 +00:00
Ettore Di Giacinto 398c1d56cb test(addons): list the file routes in the registered route inventory
Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto 826bb21f9d style(addons): sort the imports of application.go
Assisted-by: Claude:claude-sonnet-5-5 gofmt
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto cbe84cf062 feat(ui): install an add-on from a file
Add an Install from file button to the Add-ons page. The file goes to the
owner, then the install dialog opens with the host access step, the file
sha256 and the notice that nothing signs a local file. An upgrade does
not turn the add-on on, an older version needs a tick, and the same
version is refused. The link that local-ai addons install prints opens
the dialog for the staged file. The list, the details and the host access
page show that the add-on came from an unsigned local file.

Assisted-by: Claude:claude-sonnet-5-5 eslint playwright
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto d71df56a66 fix(addons): take a local file only with the dialog's approval
The plain typed install and the owner's automatic upgrade never install
a package from a file. Only the host access install, which needs the
administrator's own browser session, sets an approval, so a key or a
tool cannot install a staged file. Add auth tests for the file routes.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto 3d737a0755 feat(cli): add local-ai addons inspect and install
inspect verifies an add-on zip on this machine and shows what it holds.
install checks it, uploads it to a running server and prints the page
where the administrator confirms the install and the host access. The
command never installs by itself.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto cbec432ed6 feat(addons): upload a zip to the owner and install it from the Add-ons API
POST /api/addons/files streams the zip to the owner in chunks over the
host access call, so a frontend of a split deployment keeps no copy and
the owner enforces the size limit while it receives. GET and DELETE read
and remove a staged file. The install is the existing host access
preview and install with sourceId local-file, so it keeps the human
surface rule, the nonce and the digest binding. The install body gains a
downgrade confirmation.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto c604f3a133 feat(addons): install, upgrade and downgrade from a staged zip file
The manager takes a staged file as a release: sourceId local-file and
the file ID as the version. It runs through the one lifecycle path, so
generations, backup, recovery and the host access approval apply as for
a library package. The approval digest is the sha256 of the zip.

The same version is refused, and an older one needs a confirmed
downgrade. Files wait in a private per-user staging area for an hour.
An add-on from a file has no library, so no update check answers for it.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto e6ff1ab0f9 feat(addons): stage a package from a local zip file
Add Installer.StageFile and InspectFile. A zip is checked against the
digest manifest it carries, refused for unsafe or duplicate paths,
links, wrong executable bits, too many or too large files and bomb
ratios, then staged through the same inspection as a library package.
The receipt records the source local-file and the origin "local file,
unsigned".

Stage now shares its manifest, executable, UI, icon and skills checks
with StageFile.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:50 +00:00
Ettore Di Giacinto 2140e56aa5 fix(addons): keep grants made under a deployment blanket as the install's own
The document validator requires a blanket grant to name a revision of the
switch in the document. A deployment-fixed mode is never written there,
so its grants have no revision to name. Record them as the grant of the
install or upgrade that made them; the audit line still carries the
blanket tag.

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

Assisted-by: Claude:claude-sonnet-5-5 node
2026-11-03 22:52:09 +00:00
Ettore Di Giacinto 3ca176d15d feat(addons): lock UI host grants while sign-in is off
With authentication off every caller that reaches the port is the same
local owner, and the checks that tell a person from a script are
headers the caller chooses. Any process or agent that could reach the
port could grant host operations, set deny rules and turn the blanket
approval on.

UI host grants are now locked by default in that mode. The policy file
and flags still grant, and every write route answers 403
locked_by_deployment with a sentence that says why. The overview and the
preview carry the lock and its reason for the page. The new flag
--addon-host-ui-grants-without-auth turns the UI grants on knowingly and
logs a startup warning. With sign-in on nothing changes.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:09 +00:00
Ettore Di Giacinto 60f67856da fix(addons): hold a package from another library or signer, and fail an uninstall that leaves grants
The UI grants of an add-on name the storage and the add-on ID. A plain
upgrade through dispatch could resolve to a package from another
configured library or signer and inherit every grant without a person
looking at it.

An install or upgrade without an approval is now held when its package
comes from another library or signer than the installed one, like an
upgrade that asks for more operations: the old version keeps running
and the refusal says to approve in the browser. A request that carries
an approval for the exact digest goes through.

Uninstall deleted the grants after the journal commit and only logged a
failure, so a reinstall under the same ID before a restart could meet the
old entry. The grants are now deleted before the commit, and a failure
stops the uninstall with the add-on still installed.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:09 +00:00
Ettore Di Giacinto fd634c5f5c fix(addons): fail closed on audit errors at install, audit refused callers and restarts
Staging the grants of an install now goes through Store.UpdateAudited, so
an install fails and the document stays as it was when the audit line
cannot be written.

A write from an API key, an MCP session or a tool call was refused in the
HTTP layer before it reached the owner, so the refusal left no audit line.
The route now hands the refused write to the owner without the human
mark; the owner refuses it and records the credential kind, never the
credential.

Manager.HostGrantsChangedReport names the add-ons the re-sync started or
stopped, and the owner writes a restart-for-grant line for each. The
event constant was never used before.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:09 +00:00
Ettore Di Giacinto b8823e91d1 fix(addons): re-sync the manager after a host grant change that removes access, and audit before writing
Revoke, an edit that only removes, a deny rule change and a blanket
switch-off with revoke never asked the manager to look again, so an
add-on that lost a required operation kept running, and one that was
refused for a deny stayed refused after the deny was lifted. Each of
these routes now calls the refresh hook, as the grant routes did.

The audit lines of these writes were discarded on error, so a grant
could exist without its record. Store.UpdateAudited runs the audit
append after the patch is validated and before the document is
written. When the append fails nothing is written and the route
answers 503.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt
2026-11-03 22:52:09 +00:00
Ettore Di Giacinto 65f8116b1f fix(addons): keep a deployment blanket mode out of the grants document
Stage copied the blanket mode that a flag fixes into host-grants.json
at the first install. After the flag was removed the document still
said "any", so the switch stayed on without anyone typing the
confirmation text.

The deployment mode is now applied at read time only. The document keeps
what a person set, and a grant made under the flag records revision 0,
which the validator accepts for a blanket grant of the deployment.

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

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

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

Assisted-by: Claude:claude-sonnet-5-5 go node playwright
2026-11-03 22:46:22 +00:00
Ettore Di Giacinto 7ce4507da1 feat(addons): keep per-person settings that an add-on declares
An add-on can declare userSettings in its manifest: a key, the allowed
values and a default. The host keeps each person's answer in the account
database every frontend shares (one file when accounts are off), checks
every write against the declaration, and never asks the add-on.

GET and PUT /api/addons/{id}/user-settings read and write them. A write
needs the person's own browser session, so no API key, model or tool can
change one. A setting may carry a role that the host reads for its own
rules; the host fixes the values and the default of a role so a manifest
cannot loosen it. Pages reach their settings through sdk.userSettings,
which is bound to the add-on that calls it.

Assisted-by: Claude:claude-sonnet-5-5 go node
2026-11-03 22:46:22 +00:00
Ettore Di Giacinto eaf5401e11 fix(ui): draw one focus indicator on the composer
The message box kept the global 2px focus-visible outline: that rule
ties with .home-textarea on specificity and loads later, so the box
showed its own ring inside the card ring, a double border.

The message box now draws no outline or shadow on focus. The card keeps
the one indicator, a 1px inset accent edge plus a faint 2px ring (was
3px), with no transition under prefers-reduced-motion. The chips and
buttons keep their own keyboard rings. Home and chat share these rules.

Assisted-by: Claude:claude-sonnet-5-5 playwright eslint
2026-11-03 22:42:47 +00:00
Ettore Di Giacinto ad39954af1 feat(addons): let a chat enable several connections of a credentialed add-on
A chat now enables a set of connections for an add-on whose tools run with
a credential, and the model names one of them in each call. This applies to
every add-on that declares a credential in its manifest.

- The Tools menu row of the add-on lists the connections with a checkbox
  each. The saved agent names a set too (an empty set means each chat
  chooses). A chat that a connector binding started keeps that binding
  alone. With one connection, it is the set. With none enabled, the tools
  are not offered.
- With one connection enabled the tools are unchanged. With two or more,
  the host adds a required `connection` argument to every tool of the
  add-on in the definitions it gives the model: an enum of the connection
  names (the display name folded to lower case, digits and hyphens; the
  description keeps the display name). The add-on never sees the argument.
- The name is checked against the set the chat has now, not against all the
  connections of the person. A missing or unknown name returns a sentence
  that lists the allowed names, and nothing is sent. A write tool never runs
  without the name. Text in a tool result cannot widen the set, and the
  owner checks the chosen binding against the set again.
- The resource list of the named connection is the only one that applies.
  Results, the approval card and the text for a pending change name the
  connection; the audit line has the binding ID.
- A connection name must be unique per person and add-on among the
  credential-only connections, compared by name fold. Older duplicates get
  a numeric suffix in the model's list.
- The stored chat, draft and agent values move from one binding ID per
  add-on to a list of IDs. A single string is read as a set of one in the
  stores and in every request, so older data and clients keep working.
  PUT /api/chats/{id}/connections takes `connections`; the older
  `connection` field still works.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo node eslint playwright swag
2026-11-03 22:33:28 +00:00
Ettore Di Giacinto 271e7cad67 fix(ui): draw proposal cards after the final text of a turn
A turn can hold several model messages and tool runs. The cards of its
proposals (memory and profile changes, triggers) were drawn under the
message that followed the tool call, so a turn like "propose, say a
first line, read a file, explain" showed the card above the explanation.
While a turn streamed, a card read mid-turn sat above the streaming
text as well.

placeProposals now moves the cards of a turn to the end of it: onto the
last model message, or, while the turn streams or has no message yet,
into a row that the page draws after the streaming text. The tool lines
and results keep their place in the fold. Cards that were answered
already draw as compact lines, so they need no other rule.

Assisted-by: Claude:claude-sonnet-5-5 playwright eslint
2026-11-03 22:30:01 +00:00
Ettore Di Giacinto e28dd8a2a0 feat(addons): connect the host grants store, the Authority and the routes
Open the grants document and its audit file under the owner state
directory, and make the Authority read them on every call, with the
deployment floor, the deny rules and the blanket switch from the flags.
An unreadable document refuses every operation. Adapt the store to the
manager's GrantStore and the manager to the host access service of the
routes, and wire that service on the owner only. A frontend keeps the
floor and stores nothing.

Add an end-to-end test of install with approval, revoke, deny, a held
upgrade, restart and the split deployment.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 22:06:43 +00:00
Ettore Di Giacinto 0dae0a2609 feat(addons): list installed add-ons and preview releases for host access
The host access page needs the verified requests of each installed
add-on, the operation that completed its generation, optional operations
an upgrade added, and an upgrade that waits for approval. It also needs
to read a release's manifest without installing it. Add both to the
manager. An automatic upgrade that is held for approval is remembered.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 22:06:43 +00:00
Ettore Di Giacinto ef26fa19f7 feat(addons): let a caller ask whether the grants store is usable
View hides a closed or doubtful store behind an empty document, which
would drop the deny rules kept in it. A caller that must fail closed asks
Usable first.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 22:06:43 +00:00
Ettore Di Giacinto 173a97302f feat(config): record where the startup host grants came from
The startup log of the host grants names the source of the policy
(flag, environment or file) and never a path.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 22:06:43 +00:00
Ettore Di Giacinto f257406d5c feat(addons): expose the trusted identity of an installed add-on
The Authority binds a grant to the identity of the installed package. It
runs inside admission, which holds the manager lock, so it needs a read
that does not take that lock. Keep the identities beside the completed
operations and update them when an operation applies.

Also export the check for the URL of the official library, which the
blanket approval needs to tell the official library from a source an
administrator named the same.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 22:06:43 +00:00
Ettore Di Giacinto 112a753f06 feat(addons): make an operation added by an upgrade pending on its own
An upgrade adds operations to an entry that already holds others. With one
operation ID per entry, the whole entry went pending until the upgrade
completed, so the running old version lost its grants for that time, and a
crash removed all of them.

Give an operation its own optional operation ID. Only that operation waits
for its lifecycle operation, and reconciliation removes only what an
unfinished upgrade added.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 22:06:43 +00:00
Ettore Di Giacinto c56c1ac343 feat(addons): choose the connection of a credentialed add-on explicitly
A chat never uses a connection that the host picked for it. For an add-on
whose tools run with the token of a connection (such as GitHub):

- With one connection, it is selected. With several, the Tools menu row
  of the add-on has a Connection selector, and until the person picks one
  the tools are not offered and the row says so. With none, the row says
  so and links to the Connectors page. The choice is stored with the chat
  and its draft, like the other switches, and follows a fork, a
  sub-agent and the runs of a trigger the chat proposed.
- A saved agent can name its connection per add-on (agent form field, API
  field connections). Its chats use it, show it read only and cannot
  change it. An agent with no choice follows the same rule as a chat. A
  chat that a connector binding started keeps using that binding.
- CredentialFor takes the selected binding and uses only it, so the
  resource list of that binding is the only one that applies. A call with
  no selected binding is refused with the code select_connection and a
  note the model can relay. The first-match code (connectors.Pick) is
  removed.
- Tool results and proposal cards say which connection acted, for example
  "as Work, repository acme/widgets". A proposal is applied with the
  connection it was previewed with. The credential use is logged with the
  binding ID.

Contract: the credential slot takes an optional resourceLabel (the word
for the resource in that line). The agent export file does not carry the
connections, since a binding ID belongs to one server.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo node playwright protoc swag
2026-11-03 22:02:30 +00:00
Ettore Di Giacinto 270a3a12ea feat(addons): match the host access routes to the React client
The confirm header names what the administrator acted on: addonId@digest
for an install, the add-on ID for an edit or a revoke, "blanket" and "deny"
for the two switches. The operation ID is optional on every write but the
install; the receiving host makes one when the caller sent none.

Preview and page statuses are allowed, not_allowed, blocked or unsupported
in both. A preview names what an upgrade adds, using the identity of the
installed package. The page carries the library, the new optional access and
the required operations that keep an add-on from running. "keep" relabels
named blanket grants as the administrator's own. The blanket switch ignores
scope options when it is off.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo swag
2026-11-03 21:55:38 +00:00
Ettore Di Giacinto 48b32619b5 docs(api): describe the host access routes and keep them out of the MCP tools
Add the host access routes to the /api/instructions text of the add-ons
group. Add a spec that no MCP tool maps to them and that nothing under
pkg/mcp names them, so an agent cannot reach them through a tool.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo
2026-11-03 21:55:38 +00:00
Ettore Di Giacinto c5400edd3f feat(addons): add the /api/addons/host-grants routes
Seven routes: read the host access state, preview an install, install or
upgrade with an approval, edit or revoke the grants of an add-on, change the
blanket approval and change the deny rules (with a dry run).

Reads take an administrator session or API key. Every write takes only the
administrator's own browser session: same-origin fetch and the confirm
header equal to the operation ID. An API key, bearer token, MCP session or
tool call gets 403 not_human_surface. The dry run of a deny change is a read.

The first path segment host-grants is reserved as an add-on ID, and the
routes are listed in the feature registry.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo
2026-11-03 21:55:38 +00:00
Ettore Di Giacinto 18e667cafb feat(addons): add the owner service and split forwarding for host access
The owner now serves the host access calls: read the grants, preview what an
install would ask for, install or upgrade with an approval, edit or revoke
the grants of one add-on, change the blanket approval and the deny rules.
Every write needs the human surface mark and is refused when the deployment
locks the store. Typed refusals (revision_conflict, approval_stale,
blocked_by_policy, locked_by_deployment and others) cross the carrier
unchanged, so every frontend gives the answer of the owner.

A frontend carries the mark as a signed field of the forwarded request, and
only for a host access call. The owner honors it and nothing else: a
frontend with its own flags or store still forwards, and an owner without the
calls is reported as unavailable with its reason.

The preview issues a single-use nonce bound to the credential, the add-on,
the package digest and the grants revision. The install must present it.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt ginkgo
2026-11-03 21:55:38 +00:00
Ettore Di Giacinto 0cb1490a12 fix(http): believe X-Forwarded-Prefix only from a trusted proxy
StripPathPrefix and the redirect from / and /browse still read
X-Forwarded-Prefix from any peer, after BaseURL and BasePathPrefix
were gated on --trusted-proxies. A client could make the server strip
a path prefix or add one to a redirect.

Gate both on the same trusted-proxy resolver. The resolver is now
stamped into the request before StripPathPrefix runs, and the check is
exported as ForwardedHeadersTrusted. The explorer has no
--trusted-proxies option and keeps its old behavior by trusting every
peer for this one header. The docs say that the prefix needs a trusted
proxy.

Assisted-by: Claude:claude-sonnet-5-5 go-test
2026-11-03 21:50:57 +00:00
Ettore Di Giacinto 055c456d9e fix(studio): use the backend's default window when context_size is unset
A chat on a model whose config has no context_size got a window of zero,
so the harness fell back to its own default for compaction and for the
context meter, while the backend actually runs with its default window.
The meter and the compaction trigger then disagreed with the model.

Report the window that a request can use, the same value that
/v1/models/capabilities gives (backend.EffectiveRequestContextSize). A
model with no local config, such as a remote one, still gives zero.

Assisted-by: Claude:claude-sonnet-5-5 go-test
2026-11-03 21:50:57 +00:00
Ettore Di Giacinto 06adb4b94e fix(config): lock the chats limits that background goroutines read
go test -race on core/http/endpoints/localai reported data races
between POST /api/settings and the goroutines that read the result:
ApplyRuntimeSettings wrote chats_egress_audit_days and the
chats_loop_* limits as plain fields while the audit prune and the loop
runner read them through the Effective getters.

Guard those six fields with one RWMutex, taken by the getters and by
the registry setters. A new spec applies the settings while four
goroutines read them and fails under -race without the lock.

Assisted-by: Claude:claude-sonnet-5-5 go-test
2026-11-03 21:50:57 +00:00
Ettore Di Giacinto beceddf474 test(addons): wait for the expiry audit in the transfer spec
The handle leaves the table before the audit hook runs, so a loaded
machine could read the list too early. Wait for the outcome.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 21:50:00 +00:00
Ettore Di Giacinto 8aa81f1401 feat(addons): approve host operations at install and upgrade
An administrator can now grant host operations when an add-on is
installed or upgraded. The manager checks the approval against the
package it actually stages: the digest must match, and only operations
the verified manifest declares and the host registers are granted, never
a denied one. A required operation that is neither held nor approved
refuses the install with approval_required, or blocked_by_policy when a
deny rule forbids it. An upgrade that needs more required operations is
held and the old version keeps running. Optional operations are never
granted without approval, and a manifest that asks for fewer operations
shrinks nothing.

Grants are written to a GrantStore before the journal commits the
operation and removed if it fails, and they count only once the
operation completed. Uninstall deletes them after the journal. A start
reconciles the store with the journal. The automatic upgrade never
approves; only the blanket hook of the store can. HostGrantsChanged
stops an add-on that lost a required operation and retries one that was
refused.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 21:50:00 +00:00
Ettore Di Giacinto 8a78f99525 feat(addons): describe each host operation in plain words
The install dialog and the host access page need a title, one sentence
and a risk word for each operation an add-on may ask for. Add
DescribeOperation next to the supported list, and a test that fails when
an operation has no description.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-03 21:50:00 +00:00
Ettore Di Giacinto 84652348b5 test(react-ui): cover host access in the add-ons pages
Assisted-by: Claude:claude-sonnet-5-5 playwright eslint
2026-11-03 21:45:05 +00:00