Commit Graph
9286 Commits
Author SHA1 Message Date
Ettore Di Giacinto ace0dfcae8 feat(collections): show how a search finds passages
The collections list now reports search_mode: semantic for the chromem
engine and hybrid for the postgres engine, which also matches words.
The Knowledge page shows it in plain words, so a person knows whether
an exact code or name can be found.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 09:03:52 +00:00
Ettore Di Giacinto eceb57e1a2 test(studio): allow the built-in memory in the effective view of the agent editor
The effective view and the test chat list name the built-in memory. The
provider guard reads that as an add-on name, so list the three files with
the reason.

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

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

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 06:56:40 +00:00
Ettore Di Giacinto 71283448da test(addons): list the host-held proposal spec in the tables-tools suite
The adapter spec 'stores the proposal and applies nothing when the host
holds the call for a person' passes in the suite but was not in the
required case inventory, so the inventory check failed with a mismatch
of one extra case. Add it, taking the name from the passing run.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 06:55:53 +00:00
Ettore Di Giacinto 8eb15ee172 test(memory): build the Memory add-on bundle in the e2e global setup
The global setup builds a fixed list of add-on bundles and sets
E2E_ADDON_BUNDLES_BUILT so workers do not build. The Memory add-on was
not in the list, and its notice spec skips its own build when the
variable is set, so on CI no dist existed, every asset answered 403 and
the notice never drew. Locally the dist of an earlier run was still
there, which hid it.

Add the Memory bundle to the list.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 06:55:53 +00:00
Ettore Di Giacinto 63777004a5 test(studio): remove two timing races from the distributed e2e run
The circuit breaker spec used a real 150 ms cool-down. Under the race
detector on a loaded runner, the turns between opening the breaker and
the "no call goes out" check took longer than that, so the rest was
already over and a call went out. The spec now runs on a service clock
that only moves when the spec moves it.

The reply-language spec sent "/new" as soon as the previous answer
reached the chat. The answer is delivered before the turn is marked
finished, so on a slow run the command was refused as busy and the
refusal text (in the fixed language) came back instead of the new-chat
text. The spec helper now waits for the turn to end first, as a person
does after reading the answer.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 02:22:39 +00:00
Ettore Di Giacinto 3a8414efc2 docs(addons): describe the host's calls to a provider and how to write one
Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-05 01:45:30 +00:00
Ettore Di Giacinto 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
2026-11-05 01:45:30 +00:00
Ettore Di Giacinto 21006e1390 feat(addons): require a network declaration for a remote provider
A provider that says remote: true sends notes to a service, so its
manifest must declare capabilities.network. The person and the host see
the network access before anything is sent. A manifest without it is
refused.

Add aliases to a slot descriptor: add-on IDs that the built-in provider
stands in for. A copy of a retired add-on that is still installed keeps
working without serving a second time, and a stored choice that names it
means the built-in provider.

Assisted-by: Claude:claude-sonnet-5-5 go
2026-11-05 01:45:30 +00:00
Ettore Di Giacinto f0caddf8f1 feat(studio): let the assistant propose a text for Knowledge
Add the change kind `knowledge` and the sensitive grant and capability
`knowledge.write`. A text for a collection goes through the same
proposal flow as the other kinds, but it is never applied unasked:
the Ask, Auto and Always confirm levels all wait for the owner, and the
Never level takes the kind away. Confirm adds one entry to the
collection and Undo removes it. Text is capped at 64 KiB.

A paired sender can answer yes, no or undo in a messaging chat; a
sender who is only on the allowlist cannot. The connection page
explains a block with the shared reason codes, plus knowledge_none and
knowledge_no_embedding. The Suggested changes card names the
collection and previews the text. A kind can now declare a destination
other than the home folder, so core code stays generic.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 01:21:17 +00:00
Ettore Di Giacinto 8cf8079b25 feat(agents): tool levels, templates and connections in the agent editor
Part 2 of the agent editor redesign.

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

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 01:15:34 +00:00
Ettore Di GiacintoandClaude Sonnet 5.5 62d75c0de0 feat(memory): serve the notes through a provider slot
Memory is the first provider slot. The person picks one provider for the
notes: the built-in one, Off, or an add-on that declares the slot. The
choice is kept per person with the other host settings.

Core stays generic. providerslots.go lists the providers of a slot,
stores and resolves the choice, and gates an add-on that declares a slot:
its context files, skills and role tools count only while it is the
provider picked, and the chat can narrow a slot off for itself. The
notes wire into it in memory.go: an adapter gives the add-on's files and
skills behind the MemoryProvider interface, the built-in provider stops
reading while another serves, and the On or Off switch and the choice
stay one answer.

If the add-on that was picked stops, is disabled or is removed, memory is
off with the code memory_provider_unavailable and the add-on is named.
It never falls back to the built-in provider, so a note is not read or
saved where the person did not choose. The choice stays stored.

Add GET /api/chats/providers, GET and PUT /api/chats/providers/{slot}.
Settings > Memory lists the providers from that answer with a generic
picker, shows the failure with the shared Notice and offers to use the
built-in provider. Document the facility for add-on authors, and test it
with a fake add-on that provides the slot.

Assisted-by: Claude:claude-sonnet-5-5 go
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrQyk8ovFXvy5zrjHX1BD
2026-11-05 00:59:43 +00:00
Ettore Di GiacintoandClaude Sonnet 5.5 fad0bf08eb feat(addons): let a manifest declare the provider slots it serves
A provider slot is a job the host does itself and lets an add-on do in
its place. The host owns the slot, its gates and a built-in provider;
the person picks one provider.

Add the closed list of slots as data (provider-slots.json) with a
descriptor per slot: the choice a person has, the built-in provider, the
roles a provider can fill, the contributions the slot activates and the
facts a provider may declare. Add the optional manifest key
capabilities.provides, validated against the descriptor: the slot and
its version, the tools of each role (declared, of the right kind, not
page-only, not shadowing, one role each), typed properties, and the
context file scopes. The manifest stays at api 2.

Carry the declaration through the apps list and the tool adapter so the
host can list the add-ons that provide a slot.

Assisted-by: Claude:claude-sonnet-5-5 go
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrQyk8ovFXvy5zrjHX1BD
2026-11-05 00:59:43 +00:00
Ettore Di Giacinto 849d985f81 feat(agents): one-page agent editor with a sticky summary header
The agent form was an accordion of nine folds with two Save buttons and no
summary. It is now one page: a sticky header with the agent name, mode,
unsaved-change count, model, tool and memory chips and a single Save,
a section nav that scrolls to Identity, Capabilities, Memory and
knowledge and Advanced, and one card per section.

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

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 00:57:58 +00:00
Ettore Di GiacintoandClaude Sonnet 5.5 9012d3220c feat(addons): say on the Memory add-on page that memory is built in
Add a Notice component to the add-on settings kit so a package can show
the host's own notice: severity, title, reason and one action. The
Memory add-on page uses it to say that memory is built in and to link to
Settings > Memory. A host without Notice falls back to Callout.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 00:42:58 +00:00
Ettore Di GiacintoandClaude Sonnet 5.5 69bbd61b44 feat(ui): name the knowledge command and explain the Conversations notes
The Home slash command that opens collections is now /knowledge. /memory
still finds it, and no skill can take either name.

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 00:42:58 +00:00
Ettore Di Giacinto f79d4ef29f test(ui): guard the plain agent editor against sideways scroll
Draw the editor of a saved agent with short names, one add-on and two
messaging connections (one missing a permission) at desktop and phone
widths, with the sidebar open and collapsed, every fold open.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-05 00:36:17 +00:00
Ettore Di GiacintoandClaude Sonnet 5.5 c557b82755 test(quantization): read the English title through Default()
The manifest title is a language map now, so Text is empty for it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrQyk8ovFXvy5zrjHX1BD
2026-11-04 23:24:36 +00:00
Ettore Di Giacinto c9b5d0ff5c test(ui): give the packaged add-on mock plain titles and let the Settings tabs scroll
The add-on manifests now carry the title and description as language
maps. packagedApp copied the map into the title field, which the apps
list sends as plain English text, so the sidebar, the drawer and the
Home slash menu drew nothing for Studio and Talk. It now sends the
English text in title and description and the maps in titles and
descriptions, as the server does.

The Settings tab strip scrolls sideways on a phone by design, and it
outgrew 390 px when the Memory tab was added. Allow it in the overflow
spec like the other tab strips.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 22:00:22 +00:00
Ettore Di Giacinto 8ec12b740a test(addons): rename the busy-owner cases in the suite inventory
The fail-closed change renamed one unit case and replaced three Go
cases of the apps list while the owner is busy. The inventory in
suites.json still listed the old names, so the gallery-ui, gateway and
artifacts suites reported an inventory mismatch.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 20:29:17 +00:00
Ettore Di Giacinto 5df6818bb8 fix(addons): fail closed on the apps list when the owner does not answer
A frontend kept the last apps list per person and answered 200 with a
stale mark when the owner timed out. Two frontends then disagreed: one
with a cached list answered 200, the other a typed 503. The acceptance
contract is that every frontend gives the same typed answer when the
owner cannot answer, so the server cache is gone.

The route now answers owner_busy (timeout) or owner_unavailable with a
503 from every frontend. An owner that is up and busy still answers
from its snapshot, with the operation of each add-on in the list. The
Add-ons page keeps the last list in the browser and shows it with the
busy notice, so it never goes empty. The specs, the docs and the
swagger spec follow the new contract.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 20:00:36 +00:00
Ettore Di Giacinto a530d65c7c fix(ci): compare toolchain series, not patch releases, in the fixture identity
The shared production fixtures were refused when the producer and a
consumer job ran on runners with different patch releases of Go (1.26.9
and 1.26.10). A patch release builds and runs the same fixtures, so the
identity now keeps the Go minor and the Node major. protoc stays exact.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 19:47:07 +00:00
Ettore Di Giacinto adbb71cfdf feat(memory): add Always confirm, an agent save rule and a messaging row
A person can now choose four save levels on the Memory tab: Save it and
tell me, Ask me first (the default), Always confirm and Never. Always
confirm is like ask, and nothing can loosen it: no agent or messaging
setting overrides it, and the model cannot undo or forget a note by
itself (revert_change is refused, and the person uses the Undo).

One function, effectiveSaveLevel, combines the rules. The strictest wins,
in the order auto, ask, always, never. The inputs are the level of the
person, the rule of the saved agent, the messaging row and the chat.
A table test covers every combination.

A saved agent has an optional memory_save field: always or never. It can
only be stricter than the person, so auto is not offered and the API
refuses it. A write without the field keeps the stored rule, and use_mine
clears it. It is stored by both stores and is part of an agent export and
import. The agent form shows three cards under When it wants to save a
note.

The new row In messaging apps (messaging_save on GET and PUT
/api/chats/memory) takes ask (the default), paired_private or never. With
paired_private a note is saved at once only when the sender is paired and
the chat is one-to-one, and it goes to the notes of that conversation,
with an Undo. The limits stay: five notes a reply, the size limit, and a
note that looks like a secret still waits. The row cannot loosen the level
of the person when that is always or never. A setting that cannot be read
reads as ask.

The connector turn now carries whether the chat is one-to-one and whether
the sender is paired, in the turn grant and in the worker JSON. Both read
as false when unknown. The Telegram, Slack and Discord transports already
set Direct; new specs pin it for private, group and supergroup chats,
direct messages, channels, group conversations and threads.

A note from a messaging chat goes to the conversation first. It reaches the
notes of the agent or of the person only from a paired sender, only when
the memory choice of the agent allows that scope, and only after that
sender confirms it. Under Always confirm only the paired sender who asked
can confirm. An agent whose memory is None still saves nothing.

The script of the confirm-in-chat specs now proposes a note for the
conversation, because a sender who is only listed cannot write to the
notes of the person.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 19:45:08 +00:00
Ettore Di Giacinto f345b54a53 test(addons): expect the stale banner in the apps polling spec
The list stays on screen with a banner when a refresh fails. The banner
text changed from "may be out of date" to "Showing the last known
state.", so the polling spec waited for text that is no longer shown.
Assert the banner by its test id, as the busy-owner spec does.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 19:41:48 +00:00
Ettore Di Giacinto 9da4a1dd62 fix(studio): stop background work before shutdown and wait for tool results in specs
The connector spec "keeps asking when the setting is ..." read the tool
results as soon as the bot had an answer. The harness records a result
after it emitted the turn events, so under load the answer could come
first and the list was empty. The spec now waits for the result.

Shutdown left two background writers running: the file text indexer loop
ran on the application context, and the chat search backfill had no stop.
A test that removed its temp directory right after Shutdown could meet a
write into the "textindex" folder ("directory not empty"). The indexer
loop now has its own context and Shutdown waits for it, and the chat
store gets a Close that stops the backfill and waits.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 19:02:54 +00:00
Ettore Di Giacinto 912fb8ab03 test(memory): list the memory choice among the names the guard allows
The memory choice of an agent names the built-in memory in the agent
form, the notes list and the agent delete. The add-on name guard flagged
those four lines after the previous commit.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 18:26:41 +00:00
Ettore Di Giacinto 917b54e6c1 feat(memory): let each saved agent choose its notes
A saved agent now has a memory choice: Mine and its own, Its own only,
Mine, or None. The host enforces it. The notes in the prompt and the
scopes of context_read follow the choice, and a memory note may go only to
the scope the choice allows: the agent's own notes, or the person's. The
propose_change tool offers the model only those scopes, a note needs no
scope, and any other scope is refused. None offers no memory kind.

An agent saved before the field has no choice and behaves as before: it
reads notes when its add-ons name Memory and the model picks the scope.
A write without the field keeps the stored choice. The person's Off, a
private chat and the agent's grant still decide first. Sub-agents get no
notes and cannot save; triggers read per the choice and cannot save.
Messaging chats read as before.

The agent form has a Memory section with the four choices, an "As before"
note for older agents, a switch for the permission to save, and the line
that memory is off in the settings. New agents start with Mine and its own.
Deleting an agent that has notes asks to keep, export or delete them
(DELETE /api/chats/agents/{id}?memory=keep|export|delete). Kept notes show
on the Memory page under Deleted agents, and the notes list has a tab for
each agent. Copying an agent never copies its notes.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 18:26:20 +00:00
Ettore Di Giacinto ea1f5f449f feat(studio): let the model search and read earlier chats
A new opt-in tool group, Past chats (history), gives a chat two read-only
tools. search_chats finds messages in the earlier chats of the person
(query, scope this_chat, this_agent or all, limit, since) and read_chat
reads one chat from a message number. A call returns at most 10 hits with
300 character snippets, or 20 messages and about 6 KiB. Every result is
wrapped as untrusted text, a closing line in an old message cannot end
the wrapper, and the tool says plainly when nothing is found.

What the tools never reach: another user's chats, private chats (a chat
with the notes of memory switched off), sub-agent runs, and every chat
that no person started, which leaves out the messaging chats that hold
the words of other senders. A saved agent needs the new grant
history.read, which is off by default and turns the group on when it is
saved. An agent that works in a folder may search only its own chats. A
run that starts by itself, a messaging chat and a sub-agent do not get
the tools. The grant, the switch and the agent are checked again at each
call. Each call writes one log line with the tool, user, chat, scope, a
hash of the query and the chats returned, and never a query or a message.

The Tools menu and the agent form show the group and the grant through
the existing lists. The docs and the API notes describe both.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 18:04:50 +00:00
Ettore Di Giacinto 72e862ccea fix(ui): show the search filters only while a search runs
The filter row of the chat search showed from the first typed character,
when a search has not started yet, so a change of a filter did nothing.
It now shows from two characters, or while a filter is on.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 18:04:50 +00:00
Ettore Di Giacinto 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
2026-11-04 18:03:27 +00:00
Ettore Di Giacinto 6dedf17b7a test(addons): read the English title of the manifest in the page specs
The Fine-tuning and Quantization page specs compared ui.title with the
English string. The title is a language map now, so they compare its
English entry.

Assisted-by: Claude:claude-sonnet-5-5 javascript
2026-11-04 18:02:25 +00:00
Ettore Di Giacinto 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
2026-11-04 18:01:50 +00:00
Ettore Di Giacinto 2316e14632 feat(memory): add a Memory On/Off switch and private chats
Memory now has one switch, On or Off, in the Memory tab of the account
settings. Off means nothing from the notes is put in the prompt, the
model has no memory skill, tool or note kind, nothing is proposed or
saved (also by triggers and other runs that start by themselves), and
the model is told in one line that memory is off. The notes stay and can
still be read, exported and deleted.

A chat can be private: the Memory row of its Tools menu is the memory
switch group, stored with the chat. The narrowest level wins: chat,
agent, person, server. A saved agent cannot widen the choice of the
person. The tools list says which level is off (memory.code, blocked_by,
fix); the chat shows a chip and one line, and the agent form shows the
person's switch as a read-only line.

New API: GET and PUT /api/chats/memory for the switch and the save level,
and GET /api/chats/memory/export for a zip of the notes. The Memory tab
gains the switch, the three save levels, Export and Forget everything.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:50:26 +00:00
Ettore Di Giacinto cde3a2d6d9 feat(ui): show search hits with highlights, filters and a link to the message
The chat search of the Chats page and of the Ctrl or Cmd+K dialog kept
the list of the newest chats and only filtered it, so a chat that was not
in the list could never be found. From two characters the list now shows
the answer of GET /api/chats/search: the hits best first, each with the
chat, who wrote the line, its date and a snippet. The matched words are
marked from the offsets the server sends and drawn as text nodes, so
message text is never read as HTML. Filters for agent, source and date
narrow the search. Arrow keys move through the hits, Enter opens one, and
Escape clears the search before it closes the dialog. The list shows a
searching, empty, failed (with Try again), still-indexing and partial
state.

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

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:47:19 +00:00
Ettore Di Giacinto 851e4dc7df test(addons): list the translation specs in the suites that run them
The contracts suite runs the new language and manifest translation
specs of the contract and packaging packages, the catalog suite the
translated library entry specs, the apps suite the localized app
view, and every suite that runs the add-ons HTTP package the negotiated
language spec. The gallery suite lists the unit and browser specs of the
translated titles. The required-case comparison needs each of them.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:46:53 +00:00
Ettore Di Giacinto fe9e2d53e6 feat(pikvm): translate the title and the description of the add-on
The add-on joined master after the manifest translations were designed.
It gets its title, description and connector kind text in the eight
languages of the web UI, like the other add-ons, so the packaging gate
that requires them passes for it.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:46:53 +00:00
Ettore Di Giacinto 21bec51bad feat(ui): show add-on titles and descriptions in the language of the user
The library cards, the first-run list, the install dialog and the
upgrade dialogs now name an add-on by its translated title and
description, taken from the library entry or the installed record.
A language without text, and English, keep the English text, field by
field. The installed list and the library page are in the alphabetical
order of the language of the user, and a search finds an add-on by its
translated name and by its English name.

The Home commands test and the provider strings test read the English
title now that the title of an add-on is a language map.

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

The new strings are in all eight locales.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:41:18 +00:00
Ettore Di Giacinto 8011e54ddd fix(addons): answer reads while the owner runs an upgrade
An install or upgrade holds the owner's state lock through the backup,
the commit and the start of the new generation. The apps list, the host
access overview and the installed list took the same lock, so they
waited for the whole operation and the frontends timed out after 65 s.
The Add-ons page then had no list to show.

Reads now work on a snapshot of the journal that every journal write
publishes, and never wait for an operation. The apps list reports the
running operation of each add-on (kind, phase, since), and the owner
records the stage of an upgrade through the start of the candidate. A
second upgrade of an add-on that is changing is refused with a conflict.

A frontend waits eight seconds for a quick read of the owner. When the
owner does not answer, the apps list returns the last list it got for
the same person, marked stale for ten minutes. Without such a list the
route answers a typed owner_busy or owner_unavailable 503, and the pins
route no longer drops pins it could not check.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:41:18 +00:00
Ettore Di Giacinto 783bea59f8 fix(ui): stop long unbroken names from widening the page
A name with no spaces (an agent name, a path, a URL, an id) pushed the
document wider than a phone and made the page scroll sideways.

- page header: on a phone the header is a wrapping column, which sizes
  its one line to the widest item. Make it a non-wrapping column and let
  the title and supporting line break anywhere.
- agent form: the back button, the collection and name chips, the agent
  page facts and the connection group heading now wrap long text.
- add e2e/no-horizontal-overflow.spec.js: it draws the main pages with
  unbroken long content at 390 and 768 px, light and dark, and asserts
  that the document is not wider than the window.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:36:23 +00:00
Ettore Di Giacinto a24045185a feat(pikvm): translate the PiKVM page into every language of the web UI
The PiKVM page landed with English words only, so the add-on locale gate
failed. Add its bundles for the eight languages of the web UI and read
the check time in the language of the page.

Assisted-by: Claude:claude-sonnet-5-5 node
2026-11-04 17:32:04 +00:00
Ettore Di Giacinto e48bf37995 feat(addons): translate the add-on pages into every language of the web UI
The shared connector page kept its words in English text maps inside the
components, and only Studio shipped translations. Move the maps to JSON
bundles (addons/internal/connector-ui/src/i18n) and add bundles for the
Telegram, Slack, Discord, GitHub, Jellyfin, Home Assistant and Talk pages
in German, Spanish, French, Indonesian, Italian, Korean, Brazilian
Portuguese and Simplified Chinese. English stays the default, and a key
that a bundle lacks reads in English.

Plural sentences now go through the translator, so each language picks
its own plural form. The "More" and "optional" labels, the sender line and
the dates follow the language of the page instead of the browser.

A new gate, addons/internal/i18ncheck, fails when a language lacks a
bundle, a bundle lacks a key or has an extra one, a placeholder differs
from English or a value is empty. It runs in make test-i18n and so in the
UI unit tests of CI, with no baseline. Playwright specs render the pages
in Italian, check that no language runs text out of its box on a phone,
and save screenshots when UI_SHOTS_DIR is set.

Assisted-by: Claude:claude-sonnet-5-5 node
2026-11-04 17:30:57 +00:00
Ettore Di Giacinto 82b9984e06 feat(memory): serve the notes of memory from the host
The Memory add-on only declared three index files, two skills and a save
setting; the host did the rest. Without the add-on a memory proposal
worked but nothing was read back, and removing the add-on silently reset
the save level.

The host now loads the three indexes (user, agent, conversation) with
the same caps and data wrapping, serves context_read, ships the two
skills as built-in skills with the same text, and keeps the save level
as its own per-person setting (ask, auto, never; default ask). The code
sits behind a small MemoryProvider interface.

The add-on stays installed and harmless: its index files and skills are
ignored while the host serves memory, so nothing loads twice. A person
who chose a level on its page keeps it until they choose in the host
setting; auto still needs the add-on to run. A person who disabled the
add-on and never chose keeps memory off. Saved agents still read notes
only when their add-ons list names memory, and messaging chats still
ask before saving.

A person with no note and no add-on gets no extra block in the prompt.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:25:40 +00:00
Ettore Di Giacinto bab8a605cc feat(addons): add the PiKVM add-on
Chats and agents can check a PiKVM (kvmd) device and, when a person
confirms each call, press the power buttons of the machine it controls
and use its keyboard.

Read tools: get-info (model, versions, no serial or host name),
get-power-state (ATX LEDs) and get-keyboard-state. Write tools:
power-action (on, off, reset; off_hard, reset_hard and long_press only
with a per-device switch), type-text (bounded, no control characters,
refuses key, token and password-list shapes) and send-key (a short
allowlist, no Windows or Meta chords). All three declare approval
always. The preview reads the power state; apply reads it again and
rejects a stale operation. The add-on journals before it sends, never
retries and reports unknown when the connection drops.

One connection is one device: the address is the single resource of the
connection and the one sealed credential holds user:password, split at
the first colon. A one-time code is not supported. The add-on speaks
only to that address, refuses redirects, uses no proxy and accepts a
self-signed certificate only through a #sha256= pin in the address.
Typing, the text length and hard power actions are per-device switches
that an administrator sets on the add-on page; all start off. Snapshots,
mass storage, GPIO and the mouse are left out.

The add-on ships a skill, a connection page with a Test button and
approval cards, the package files, the library entry and the docs, and
joins the package workflow and the case inventory.

PiKVM API reference: https://docs.pikvm.org/api/

Assisted-by: Claude:claude-sonnet-5-5 go node
2026-11-04 17:20:57 +00:00
Ettore Di Giacinto 48d6d03f3d fix(studio): write the connector refusal texts as whole sentences
The texts for a blocked capability put a noun phrase into slots that
need a verb, so some read badly ("was not given writing to other
channels", "before reminders and scheduled tasks"). Each capability now
has an action phrase in the infinitive and a noun phrase. Every code is
a complete sentence built from the action phrase, for the owner and for
the sender and the model, and most say what to change.

The note for the model now sets whole sentences apart with a space and
asks the model to relay the reason plainly. A table-driven test renders
every code for every capability and checks grammar, length and that no
internal name leaks. Codes, JSON shapes and behaviour do not change.

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

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

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 17:17:38 +00:00
Ettore Di Giacinto 59d9c7556e fix(connectors): do not list the chosen sender as unchecked
The panel "What this bot can do" showed "Not checked yet: who is
sending" although the sender is the one picked in the selector. It now
lists only the links that were really not checked, and hides the line
when none remain. Capability titles are shown as sentences with the
product name spelled LocalAI. The test host messages now use the words
of the real host, so the specs and the screenshots read as in use.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 16:07:23 +00:00
Ettore Di Giacinto 4298715291 feat(connectors): show what a connection will do, and its readiness
The connection page of a messaging add-on listed what a sender could do
for the saved settings only, low in the form. Two blocks now sit on the
page and read the form as it is.

Readiness, at the top of the form, has one row for the token, agent, who
may use the bot, permissions, triggers, files and notifications. Each row
has a state, one sentence and at most one fix. Only rows that need
attention show at first. A ticked permission that nobody can reach, such
as a list of channels with no person, is named.

"What this bot can do" replaces the old list. For a chosen kind of sender
it shows each permission as allowed or blocked, the sentence of the host
for the first blocker, and one button taken from the kind of fix: jump to
the setting, open the agent in a new tab, open Settings (an administrator
only), pair a sender or add a person. It asks the access preview 400 ms
after the last change, drops stale answers, works for a connection that
is not saved yet, and says when the form has unsaved changes. The latest
decisions stay below it.

Assisted-by: Claude:claude-sonnet-5-5
2026-11-04 16:07:23 +00:00