The approval mode of proposed triggers sat at the bottom of the
connection page, after roles, channels and pairing, and was easy to
miss. Show it as an option of the permission Propose triggers: nested
and indented, greyed with a reason while the permission is off, with
its limits in a closed Limits list. The row says the current choice
("Proposals: asks first") without opening anything. The bottom section
is gone; an older host with no capability list still shows it as one
block. The saved approval object does not change.
Assisted-by: Claude:claude-sonnet-5-5
An allowlisted person can use the ticked permissions and only the
sensitive ones also need a pairing. Say that rule under the title of the
permissions form, and show the pairing notice only when a sensitive
permission is ticked while nobody holds a live pairing. Add a notice for
an allowlist that holds only channels, whose members are not trusted one
by one.
Assisted-by: Claude:claude-sonnet-5-5
The agent form switched the same capability in two places: a Tools
switch and a permission at the bottom that changed what the switch
meant. Show one switch per capability and put its permissions under it
as options, greyed with a reason while the switch is off. The stored
data and the API do not change.
Triggers, memory and profile changes, files and saved agents each hold
their options. Saved agents is one switch: either stored value turns it
on, off clears both, and on adds only the group so it never turns off
the defaults of the messaging connections. Reply, start new channels
and the permissions that apply when nobody is watching have a section
each. Every switch and option has one line that says what it lets the
model do, and the three trigger switches have names that cannot be
mixed up. The Permissions fold is gone: its list is now in Tools.
The form names a messaging connection of the agent that does not allow
an option the agent holds, with a link to that connection. The chat
Tools menu draws the same rows and shows the held options read only.
Assisted-by: Claude:claude-sonnet-5-5
The permissions form of a connection gets two buttons that fill it from
the agent of the binding. "Allow the usual set" ticks the usual
capabilities the agent holds and says which ones it left out because
the agent does not hold them. "Same as the agent" copies, once, every
capability the agent holds that has a binding capability, after a
confirmation that lists the sensitive ones. Both only fill the form, and
are off with a reason when the binding has no agent or its permissions
cannot be read. A capability the agent does not hold cannot be switched
on.
The form also says when ticked capabilities cannot be used yet: the host
lets a sender use one only with a pairing or a role that holds it, so
with nobody paired and no role grant the ticked ones do nothing. The
notice links to pairing. A link with ?binding=<id> opens that binding at
its permissions.
Assisted-by: Claude:claude-sonnet-5-5
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
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
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
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
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
The lifecycle and apps suites run every spec of core/services/addons,
including core/services/addons/updates_test.go. The two specs added there
(the pending update of a turned-off add-on, and the refused start of an
installed package the host cannot run) were listed only under the updates
suite, so the required-case comparison failed in the Add-on Tests job
with an inventory mismatch for both reports. List them in both.
Assisted-by: Claude:claude-sonnet-5-5 go
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
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>
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
A chat reports the window of its model. The cloud-proxy test models set
no context_size, so they got the 4096 default and the prompt did not fit.
Three saved-agent specs saw no request reach the upstream.
Assisted-by: Claude:claude-sonnet-5-5 go
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
A chat reports the window of its model, so the mock model with no
context_size got the 4096 default, and the real-server chat specs ended
with a context window error. Set a window that fits, as the Go e2e
suite does.
Assisted-by: Claude:claude-sonnet-5-5 go
A chat now reports the window of its model, so the mock model with no
context_size got the 4096 default and the tool schemas of a chat no
longer fit: six Chats specs got no answer. Give the mock model a
window that fits.
The MCP route scan read every file under the package, including the
test binaries that a macOS run builds there, and found the route names
in them. It reads only Go source now.
Regenerate the add-on library index for the new Memory description.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo sh
Eleven suites listed fewer cases than their specs run, after the host
operation grants, the add-on zip install, the user settings and the
connection selection landed. The required-case comparison stopped at
the first suite that differed. List every spec that runs and passes;
no listed spec was missing. The file keeps its order: new names follow
the existing ones in report order.
Assisted-by: Claude:claude-sonnet-5-5 python go
The host believes X-Forwarded-Prefix only from a trusted proxy, so the
specs that mount the app under a prefix got a 404. The test server now
trusts loopback, where its browser clients connect from.
Assisted-by: Claude:claude-sonnet-5-5 go-test
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
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
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
addons-tests.yml now cancels an older run on master, as the other test
workflows do. addons-package.yml puts all master pushes in one group
without cancelling: a running publish finishes, and a newer queued run
replaces an older queued one. Tag, release and PR runs keep separate
groups. Document both in the workflow headers and .agents/ci-caching.md.
Assisted-by: Claude:claude-sonnet-5-5 python3
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
Describe the zip, the trust model of a local file, the install, upgrade
and downgrade rules, the routes and their error types, the split
deployment upload and the make target. Regenerate the Swagger spec.
Assisted-by: Claude:claude-sonnet-5-5 swag
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
Write one zip and one sha256 file per add-on and platform into
dist/addons. The script runs package.sh to build and verify the OCI
package and then addonpkg zip on that layout, so there is one build
path. ADDON, PLATFORMS and ADDON_EXECUTABLES pick what to build.
Assisted-by: Claude:claude-sonnet-5-5 sh make
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
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
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
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
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
Add the zip format of a package (a digest manifest plus the files of the
OCI package for one platform) and an addonpkg zip subcommand that writes
it from a verified layout, with a sha256 file. The zip comes from the
same layout the registry package comes from, so there is one build path.
Version comparison moves to contract.CompareVersions so the installer
can use it for upgrades and downgrades.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
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
The section on host policy still said that policy is restart-only. UI
grants and denies apply to the next call and re-sync the add-ons; only
the policy file and flags are read at startup.
Document the lock of UI grants while sign-in is off and the flag that
lifts it, the hold on a package from another library or signer, the
failed uninstall, the audit-before-write rule and that a blanket mode
fixed by a flag is not stored.
Assisted-by: Claude:claude-sonnet-5-5
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
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
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
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
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
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
Declare the save-mode user setting (ask, auto, never; default ask) and
edit it on the Short memory page with the settings kit, a line for each
option, and the rule that messaging apps and background runs always ask.
The page reads and writes through sdk.userSettings and keeps the earlier
choice on screen when a save fails. The memory skill now says what to
tell the user for each answer of propose_change.
Assisted-by: Claude:claude-sonnet-5-5 node
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
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
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
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
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