The modal renders in a portal at the end of the document, outside the
Models page's form, so twui's Form found no form element on a change there
and threw building FormData. The fields get a form of their own, nested
inside the page's, to give it something to read and to keep Enter from
submitting the page.
A fresh install had no generation settings until someone set one, so every
model ran on whatever its provider happened to use. Each harness now has a
default (text 0.3 temperature, vision 0.2, image the provider's own), shown
in the model settings modal so what a model runs on is never a guess.
The defaults are only sent to a model whose catalog lists the field, so a
provider that says nothing about its models still gets no parameter it
might reject; anything saved on a harness or bot wins over the default.
Each text and vision harness setting now carries the generation settings
its model takes (temperature, sampling limits, penalties, seed, reasoning
effort), edited in a modal under the model pick so the harness block stays
short. Nothing is set by default, so models keep their provider's own
values until a super admin or admin changes them.
A setting is only offered and only sent when the catalog says the model
takes it: OpenRouter's supported_parameters is now stored on
harness_models and refreshed by the hourly sync. Other providers list no
parameters, so their models get them all.
The README was a bare file list. It now opens with the horizontal
lockup, a theme-aware hero and a workspace dashboard, both taken from
the running app, so GitHub's preview shows the product instead of
describing it.
AGENTS.md records that the design handoff is outdated: its screens and
briefs are history, while its brand assets (stones, coordinator, app
icon, wordmark) and their copies in src/data are still the foundation.
Tailwind read ref literals like [file:12] in prompts and docs as arbitrary-property utilities and emitted invalid CSS; those files hold no className, so they are excluded from the scan. Links in the user's ink bubble get a readable blue instead of the bubble's own ink, and the bubble no longer needs its own CSS class.
The chips under the composer clogged it once a bot had a handful of MCP servers and tools. They are now one button reading "N tools" (with an alert icon when one is off or not working) that opens them in a modal, where any the bot was given can still be removed and more added.
The header's sessions, new session, computers, models and settings icons
become rows in a single dropdown off an ellipsis on the right, so the
header keeps one control whatever width it has. A row that opens a modal
closes the menu first, so the modal never sits behind an open menu, and
each row shows what it acts on (the computer's name, the text model).
A production deploy leaves its service worker on the device, and it
claims every tab. It serves /.bunext/public/* cache first without
knowing which build asked, while every cache-busting mechanism the app
has (the precache gate, /build-info.json, the loading page) is
production only. So switching to `bun run dev` kept serving the old
build, vendor files included, and wrote the dev files back into the
production build's cache.
Development now clears that worker and every cache it wrote
(`clear-development-service-worker.ts`, from ServiceWorkerRegistrar,
which did nothing in development before), reloads once into the
uncontrolled tab, and /sw.js answers with a stub that unregisters
itself so Web Push and the loading page can't register it back.
The app never terminates TLS: a reverse proxy or tunnel does, and
isSecureRequest still reads X-Forwarded-Proto so cookies stay Secure.
Removes the cert generator, TLS resolution and HTTP redirect, the
local CA download, and the TLS envs, and trims the installer, Dockerfile
health check and docs to match. Also softens the composer's loading row.
Bot and workspace lists in page props leave out instructions, memory and
workspace context, and carry the last message as a short preview. The
header avatar's edit form loads the bot's instructions when it opens.
The conversation renders its latest 30 messages and loads older ones as
the thread scrolls to its top.
Also: the composer shows a placeholder until its editor loads, and
`bun start` no longer builds first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In production, a page request without the running build's cookie gets a
small loading page instead of the app. It downloads every file in
.bunext/public (listed at /build-info.json) into that build's cache,
checks they are all there, hands over to a new service worker waiting
behind an older one, sets the cookie and reloads. The service worker
then serves the app's code from the cache, so later visits load it from
the device.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A workspace bot asked to "update the workspace record" wrote to its own
memory there, called it the workspace's record, and then, when the user
said nothing had changed, denied making any write at all: it checked
nothing and confessed to work it had done.
- Say in the prompt that the workspace's context is the user's, and that
memory in a workspace is the bot's own notes, readable in its settings
- Say the same in update_memory and update_instructions results, with
where the user reads it and that the workspace's own fields didn't
change
- Have ActionCheckNote look at what actually happened before agreeing
with a user that nothing was done, and never deny work that happened
The composer now puts the bubble and clears the input as soon as you send, so a slow connection never holds either. The bot's status row reads "<name> is getting your message..." until the saved message arrives (by response or push), then switches to thinking. A failed send removes the bubble and restores the text and files.
- Cache the model catalog once per user and each conversation's setting
separately; clear both on logout and after model saves
- Compress hashed assets asynchronously with a byte budget and set Vary
- Memoize message parsing and throttle selection reads
- Debounce draft writes to localStorage
- Keep the composer editor on the page and focused, as before
- Add a bundle budget check and run it in CI
- Show a loading block while sessions load and a workspace avatar in the
mobile conversation header
- Compress responses: page scripts and styles once (brotli or gzip, in
memory) and cached immutable; pages and API JSON gzipped as they stream
- Service worker serves content-named assets cache-first
- Load the models modal's data when it opens (cached in localStorage)
instead of shipping the model catalog in every conversation page
- Lazy-load the Tiptap composer and memoize thread messages
- Import lucide icons directly; twui 0.1.4 drops by-name icons from Button
- bun run start builds the pages minified before serving
Co-Authored-By: Claude Opus 5.5 <[email protected]>
TLS no longer depends on NODE_ENV: the TLS_* variables win, else certs/
is used whenever it exists (the container's /app/certs mount lands there
too), and TLS_DISABLED=1 is the only way to serve plain HTTP with a
certificate present. The plain-HTTP redirect listener now starts whenever
TLS is on, not only in development, and HTTP_REDIRECT_PORT replaces
DEV_HTTP_PORT. The installer writes TLS_DISABLED=1 for --no-https, and the
health check follows the same rule.
A fresh install asks whether to serve HTTPS, with the reasons, before
touching anything. --yes keeps the default, --no-https answers it, and an
update keeps the saved mode. The prompt reads /dev/tty so `curl | bash`
still works; without a terminal the default is explained instead.
Also state why a production process is serving plain HTTP (no TLS vars),
instead of only giving that hint in development.
The installer generates a CA and a 397-day certificate into
$COBBLE_HOME/certs (in the app image, with the host's addresses), mounts
the certificate, key and CA into the container and serves TLS from Bun on
the published port. --no-https opts out; --https-cert/--https-key use a
real certificate, --https-host adds names and --https-regen replaces the
CA. The installer and the image health check use curl -k when TLS is on.
The CA is served at /ca.crt and as an iOS/macOS profile at
/ca.mobileconfig for one-tap install. Development picks up certs/
automatically, `bun run dev:phone` generates and starts, and a plain-HTTP
listener on PORT + 1 redirects to HTTPS. Port sharing is off, so a stale
dev server fails instead of silently answering on the same port.
Scheduled runs are pre-approved: they carry out their brief instead of
drafting for confirmation. A run that can't do its brief calls
report_run_failed and is retried 5 and 10 minutes later; only then does
the user get one "failed after N tries" message, and the routine keeps
why in last_error.
Every reply and task now gets ActionCheckNote: look for an existing
record before creating one, and verify an action took effect before
saying it's done. Memory and instructions saves are read back, and
create_bot, create_schedule, start_task and delegate_task refuse
duplicates.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The composer now writes markdown with bold, italic, strike, code,
links, quotes and lists, from shortcuts, markdown typing or a bubble
over the selection. @ mentions a bot and # a workspace, shown as chips
and saved as [bot:ID] / [workspace:ID]. Text selected in a message can
be quoted into the composer. User messages render as markdown, and
replies tell the bot who and what the user mentioned.
Adds an ESM stand-in for use-sync-external-store, since bunext keeps
React external in page bundles and the CommonJS original requires it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The user's own messages no longer get the reply and react row. A bot's
reaction to one still shows, read-only, and react-message refuses a
reaction on the user's own message.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every message has a row under it to reply or react. Replying quotes the
message above the composer, and the sent message stores
reply_to_message_id (a plain id, not a foreign key, so deleting the
quoted message only drops the quote). send-message accepts a quote from
any session of the same conversation, so a rollover or a thread that
moved on still sends; the conversation drops a picked quote when its
session changes. The bot reads the quote as "[Replying to ...]" before
the user's turn, file-only replies included, and relayed messages carry
it too.
Reactions are a fixed set (BotMessageReactions) in
bot_message_reactions, one per message, emoji and user or bot. Users
toggle their own through api/admin/bots/react-message, and every bot
gets react_to_message for the session it answers (the user's latest
message by default). Both push the fresh list as server:bot-reaction,
with mine worked out for the session's user.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Speech section of every model settings form now has "Hear a
sample", which streams a short sentence (AppData.VoicePreviewText) in
the form's model and voice before they're saved, through
GET api/admin/harness/preview-voice (super admins and admins, that
model only via grabModelCandidate). pickVoice is shared with
speakText, and explainAudioError reads why a streamed player failed
for both read-aloud and the sample.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The timestamp no longer takes its own line under a bot group: it sits
before the read-aloud button on the last message, after the needs-you
chip when there is one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A mic in the composer records, transcribes with the bot's
transcription model (api/admin/bots/transcribe) and writes the text
into the input at the cursor to edit before sending. Escape cancels;
recordings aren't stored.
- Every bot message has a read-aloud button that plays
GET api/admin/bots/speak-message, streamed from the provider as the
audio is made, so it starts in a couple of seconds. One message plays
at a time; only the user's own sessions can be read.
- Both buttons stay visible but disabled while the bot has no model for
them (bot_voice page prop).
- Transcription and speech no longer pick free models automatically:
they're not available until a model is picked, and Settings says so.
Transcription lists speech-to-text models only, so the chat-model
transcript path and its missing-audio check are gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The input row had a fixed 88px height, so a growing textarea overflowed
into the thread and tools strip. It now has a minimum height, grows to
eight lines then scrolls, keeps the buttons at the bottom, and falls back
to a JS auto-grow where field-sizing isn't supported. A click anywhere on
the row focuses the input.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The "+" beside "Workspace bots" now opens a modal listing the crew's
workers not in the workspace yet (api/admin/workspaces/linkable-bots),
each added in one click, with "New bot" stacking the bot form over it.
Adding a bot to a workspace now pushes server:bot-created so other
rails pick it up live.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Resolve the crew and workspaces fresh inside each tool run, so a bot or
workspace created earlier in the same reply is usable: create_bot,
assign_computer, add_bot_to_workspace, delegate_task and
set_bot_workspace_instructions.
- Add coordinator-only update_bot, find_bot, find_workspace, delete_bot,
delete_workspace and remove_bot_from_workspace tools.
- Push server:workspace-removed when a workspace is deleted, and
workspace_ids on server:bot-updated so a workspace's crew drops a bot
that was taken out of it.
A computer linked to a workspace now shows in the composer (and is usable
by tasks and replies) when chatting with any bot attached to that
workspace, not only in the workspace conversation. Members see those
computers through their assigned workers too, and assign_computer still
describes direct grants only.
Sessions no longer take their title from the first message: once the first exchange is sent, the text model writes the title and summary, which the rail and sessions list show. The summary records how many messages it covers so a stale one is rewritten before it is carried into the next session.
The sessions hook now reloads on live message and session events, so a session started by the first message, a rollover or another tab appears without a reload. New sessions are pushed as server:bot-session, both from the process that changed them and through the live relay.
Admin settings gains a super-admin Backups page at
/admin/settings/backups, with restore nested at .../backups/restore.
Both use SettingsShell and SettingsGroup like the other settings sub
pages instead of the retired admin hero.