ui/js/tree.js draws subagents.md §12's data: the main agent at the root, each
subagent under its parent in the order added (name, id, position, state in
team.read.states' words, model, mode with request options, context % and a
meter, its progress line, its room messages and the last one), open slots, and
the ended ones folded apart, dimmed, with how each ended; open its session,
its findings, stop a live one. Not wired into the activity panel yet: the
server's /api/v2 does not carry the tree, the transcript or the findings yet.
tests/ui: the spec's own example tree, drawn and acted on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A job of its own on ubuntu-24.04: Node 20, playwright-core 1.65.0 installed
into the runner's temp directory for the run (the repository keeps no
package.json), its own Chromium with the system libraries it needs, then
node tests/ui/run.mjs. Nothing is built and no server started.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui/js/progress.js draws harness spec 3.4.0's `progress` (progress.md) with the
spec's texts as the page's sentences: the thirteen phases, their variants and
field words, the line with its clock in whole seconds (5s, 1m 05s, 1h 02m),
none for stopped and failed, retrying counted down. A live reply takes
`progress` from any stream frame that carries it and ticks once a second; with
no progress (an older server, or null) it draws the status text as before.
The sentences are the server's to translate: gen-i18n.py takes them from its
catalogues once they are there (SERVER_PENDING), English until then, without
failing.
tests/ui: every case of the spec's conformance/progress.json (copied, 3.4.0),
and a live stream's progress, clock, fallback and stopped line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The closing callback gets the End as it is (End::unauthorized), instead of
lib.rs reading the code back out of the JSON sent to the page -- which left
End::unauthorized unused and CI's -D warnings refused the build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Terminal panel opens a shell on the device an agent chat works on, in its
folder: for a new agent chat as soon as the device and folder are chosen (the
server's /api/v2/devices/{device}/terminal/ws?directory=, which the chat's first
message takes over -- the same socket carries on), and for an open agent chat
from the header (/api/v2/chats/{chat}/terminal/ws, the web page's shell).
Offered where GET /devices says the device opens a terminal.
The socket is Rust's (src-tauri/src/terminal.rs, tungstenite on the rustls
ureq already brings): the bearer token goes in the handshake and never reaches
the page, which gets the frames as `terminal` events and sends keys, sizes and
close through commands. 4401 signs the app out as a 401 does.
xterm and its fit addon are the server's vendored build, pinned through
fetch-vendor.sh like CodeMirror and loaded on the first open. Send to chat puts
the selection, the last marked command and its output, or the screen into the
composer, never sent. The terminal and the activity panel take turns in the
panel column. Continue in a terminal stays in the chat's menu.
tests/ui: seven B3 cases with a mocked socket, at 1280 and 390 px.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Subagents chip on a new chat follows `/models` -> model.subagents.offered, the same
team_run.offered the web page renders from. The app read /models once, at sign-in, and kept
it while it stayed open in the tray: subagents switched on afterwards (or a model added)
never reached a new chat until a restart. startNew() now re-reads it (at most every 5 s,
keeping the last list if the read fails) and redraws the draft's chips (modelsChanged()).
An open chat already re-reads its own `subagents` with GET /chats/{id}.
tests/ui: A5 cases -- parallel model offered: chip, slider to the server's cap, no note;
one-request model offered named_only: the `own` note; offered false everywhere: no chip;
switched on while open: the next new chat has it; an open chat follows its `subagents`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent chat's bash approval left "Waiting for you to allow bash…" with no card: cardOf()
called append(actions, a, b, c) while append() took one array, so `children.flat` threw for
every approval and plan card (questions used el.append and worked). renderCards() had already
unhidden #cards, which stayed empty. append() now takes children as rest arguments too.
- The editable command is `detail` (services/device_link.py puts the fixed "Run a command" in
the title); it is sent back as the item's text only when changed. Non-editable approvals
show the title and the device's input. `purpose` ("It says: …"), `reason` and the asking
subagent (`job`) are shown, as on the web page.
- renderCards() keeps a card already on the screen (by id) instead of drawing every card again
on each `ask` frame, which the server re-sends whenever the countdown changes; a card that
cannot be drawn shows the error instead of an empty box.
- tests/ui/: headless harness with a mocked Tauri bridge; five A1 cases (device bash card
shows and is visible, Allow/edit/Deny post to /chats/{id}/interactions/{card}, redraw keeps
edits, reopen gets the card from the stream's first pass, 390 px).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chip's popover is one range from 1 to the server's cap (subagents.cap, /models
subagents_cap, else 32) with two handles: two range inputs overlaid on one track, the span
filled, aria-labels Min and Max with value text, keyboard as any slider, Min never past Max,
the values said above it ("Min 1 · Max 4") and the two ends under it. "Fewest"/"Most" are
gone. The hint says what the numbers count: the chat's own agent -- 1 is the agent alone, Max 4
is the agent and up to 3 subagents. Untouched on a new chat it still leaves the server's
defaults (1-4, shown). i18n: Min, Max and the new hint in all eight languages. CHANGELOG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Models' `subagents {offered, named_only}` and /models' `subagents_cap`; a chat's
`subagents {offered, named_only, cap}` decides the chip for an open chat (an agent chat
gets none where the server says so); `named_only` shows the spec's `own` line in the
popover and marks the chip. GET /chats/{chat}/team stays the fallback for older servers.
- Context: `auto_compact`, `until_compact` and `compactable` from the server (worked out from
`threshold` with an older one); Compact greyed while not compactable.
- "Tasks: N" for a task's nested fold, the server's sentence; the subagent words aligned with
the server's catalogues (Podagenti, Sottoagenti, Podagenci); hint says the same-model rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- "Subagents" everywhere a person reads it (chip, popover with Fewest/Most, scheduled task,
panel); offered per the server's `subagents` on a model or chat where it says it, else as
before; the fields' most is the server's cap, else 32. No subagent-model setting.
- Panel: always reachable from the header's button; the line above the composer only while
something runs; Environment for an agent chat; Context with "N until auto-compact" and a
Compact button (POST /chats/{chat}/compact; a server without it is said so); Background
tasks under Running with elapsed time and a stop square each, unfolding to kind, step,
live text and conversation, nested tasks folded under their parent, Finished N folded;
Room and Plan as sections.
- i18n: 24 new sentences x 8 languages, 9 retired; CHANGELOG [Unreleased].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-wired to api/v2/schedules.py and its contract fixtures: description and
next_run(s), pause/resume verbs, runs paged with `before`, a run's result
(GET runs/{id}, Markdown) unfolded under its row, run.model with the switch from
a busy one, waiting and its retry, timing (late, caught up, by hand). The form
serves new and edit: words -> compile -> title, instruction and next runs; the
rule set by hand (days and a time, read back by preview) when the words give
none; notify policy, fresh or continue, model from the scheduling models,
effort and team size. Models on the list: the person's narrowing (PUT
/schedules/models), each free or busy. Live from the `schedules` event.
i18n: 39 new sentences x 8 languages (312 in all); CHANGELOG [Unreleased].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app follows GET /api/v2/events instead of asking /schedules every 15 s; each
`schedules` frame goes to the page (the Scheduled page redraws from it) and a
schedule's newly finished last run is told by the server's decision: `notified`
when it already told a device, else its `notify` policy (matters: a result, not
a silent one; always: silent too; never: nothing), and a failure the first time
in a row and on the third, which asks whether to pause. A run is told once, also
when it finished under the id it had while running. Signing out or another
server ends the stream; a server without it is asked again every 5 minutes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 sentences of the app's own in all eight languages (gen-i18n.py OWN), the rest from the
server's catalogues; 11 sentences no page uses any more are gone. The title bar's pills stay
inside its height at touch sizes; a failed scheduled run has its own notification words.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built against the scheduling plan's /api/v2 routes (GET/POST /schedules, /schedules/compile,
PATCH/DELETE /schedules/{id}, POST /schedules/{id}/run); a server without them answers 404 and
the page says so. A new task is words, the server's reading of them (the rule in words, the
instruction, the next five runs), then Save.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Sidebar: New chat, search, Scheduled, Files, the chats of both kinds by day (Today, Earlier),
Settings with the account at the foot; folds away (Ctrl+B), a drawer on a narrow window.
- A new chat is the chat page empty: a greeting, Agent or Chat, and the composer.
- The composer is a card: the text, then attach, the chips (folder and mode for an agent chat,
model with its picture, effort as the model offers it, the team's size), and a round Send that
Stop replaces while a reply is written. An open chat's chips change the chat.
- Messages: the person's in a bubble on the right with attached pictures as thumbnails, a reply
full width; thinking and tool calls as compact rows that unfold.
- Activity: a chip above the composer for every open chat, the panel on the right as before.
- The agent: a chip in the title bar opening its state and the one thing to do about it; "This
computer is not linked yet" with Link this computer right above the composer of an agent chat,
and "refused" only with the server's reason.
- Files: a page with the two trees as tabs. Settings: grouped, the groups listed beside them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The picture command also serves a message's attached file (/attachments/<id>, one plain id,
8 MB at most) as a data: URL, for thumbnails; the page still reaches the server only through
the bridge.
The watch asks /schedules beside the chats and tells, by the schedule's title, of a new run the
server says is worth telling (`notified`); a silent run never, a server without the routes
never.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sign-in asked for the agent's token only when the CLI had no login for the server on file,
so a computer with an old, refused login got the app's token alone, and the agent stayed
refused. It now asks unless the CLI says the link works, and hands the token over (the CLI's
--token-stdin login replaces the one it had) or gives it up when the link came up meanwhile.
Link this computer is a device login for the agent's scopes alone, handed to the CLI the same
way, with a cancel: `lembas login <server>` only refreshed a login the CLI already had.
A service that ends for want of a login is "not linked"; another reason is "stopped"; only the
server's refusal, with its reason, is "refused".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The start job on d04bc4b saw the app running after 25 s on X11 and Wayland,
then failed in its trap: the document portal had mounted itself at
$XDG_RUNTIME_DIR/doc, and rm cannot remove a mount point. Unmount it first,
and let the cleanup's leftovers be.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The events stream is held while it opens, so a reconnect and the window
showing again cannot open two; while the window is hidden in the tray it is
not reopened (the server polls for each), and showing the window opens it
again. The Thinking switch says 'Think before answering' in the header too:
'Thinking' is the catalogues' word for a reply in progress. The chat's header
restacks by the chat column's own width (a container query at --bp-narrow),
so a chat beside the activity panel keeps its title.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every new sentence in the nine languages: 54 more, of which all but four come
from the server's own catalogues (its activity panel, team setting and
Thinking switch use the same words); the four about continuing in a terminal
are the app's own, in all eight.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The activity panel beside a chat, from /api/v2's activity routes: Context
(used share, tokens against the window, a segmented bar and a row per
category), Outputs (the files written, lines added and removed, each opening
in the viewer), Background tasks (a team's members as a tree, helpers and
commands: state, elapsed time counting on between reads, model, latest step,
what it is writing, its whole conversation read-only, Stop -- a member after
a confirmation, as it takes those under it), the team's room (who to whom,
reports marked, the words through the page's own markdown) and the plan.
The chat follows GET /chats/{chat}/activity/events while it is open: the strip
above the message box sums the agents up and opens the panel; a new version
reads the panel again while it is open, the costly context part on opening,
after a reply and at most every 15 s. The stream is reopened when the server
closes it, and more slowly while it fails. Beside the chat on a wide window
(the viewer then goes over the chat), over it below --bp-wide, a full-height
sheet at --bp-narrow. The header's Activity button, the palette and Escape
open and close it.
An agent chat with a device session (device_session_id) can be continued in a
terminal: the chat's menu and a header button show 'cd <folder> && lembas -s
<id>' to copy, with lembas the CLI as it starts here (on PATH, else the app's
own copy by its path). Shown rather than run: no terminal opens the same way
on every desktop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/api/v2/models now says each model's levels, start level and control. A model
with one level shows no effort control; off and one effort is a Thinking
switch (a checkbox with the switch role); more keep the selector. In the
chat's header the switch and selector save at once; in the new-chat form they
start at the model's start level and redraw when the model changes.
Where a team is offered -- an open chat asks GET /chats/{chat}/team; a new
chat on the server needs a model that calls tools and serves two requests at
once -- the Agents fewest/most fields sit beside it. The header saves the pair
on change (the server refuses a pair outside its bounds and says them; the
fields go back); a new chat sends them only when set, else the person's
default stands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/api/v2/models names each model's picture (image, readable with the app's
token at /api/v2/models/images/<file>). A command reads it in Rust -- one
plain file name on the app's door, one of the four types the server accepts,
at most its 2 MB -- and hands the page a data: URL, which its policy already
allows. The avatar shows it once it arrives; the coloured initial stays for a
model without one and until then.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Attachments from the file dialog, a drop on the window (Rust holds the dropped paths; the page
never names one) or a paste (the bytes as the raw body of the call), sent at once to
/api/v2/attachments as multipart and shown above the composer.
- Edit message (rewind), Regenerate reply on the last reply, Copy on both.
- The chat's menu (header and a right click in the sidebar): rename, open in the web interface,
archive, delete.
- The chat's model and effort in its header; effort for a new chat. Model avatars coloured as
the server's stable_hue colours them (MD5 of the id, checked against Python), mixed into the
theme's surface so no colour is hard-coded.
- Ctrl+K palette over commands and every chat's title; Ctrl+N, Ctrl+,.
- The agent's new states in the page; 34 new sentences in all eight languages, the server's own
words where it has them; a Czech row that was Slovak; two untranslated strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Desktop said "running" as soon as `lembas service run` was started, while the CLI was still
dialling, waiting for a terminal that holds the link, or refused by the server -- the reason went
only to agent.log. Now the supervisor reads what the CLI itself writes: `service.json` in its
state directory (connecting, linked, waiting + why, stopped + why) for the pid it started, the
line it printed before that, and its last line when it ends. A service the app did not start is
described the same way.
A run the CLI ends with 0 because trying again will not help (a refused token, no login) is no
longer restarted every second: it waits five minutes, or until linking again starts it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor's vendored modules come from server v2.0.0-beta.4 with the
process stub @lezer/lr needs, so languages load and code is highlighted
again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lezer-lr.mjs imports esm.sh's /node/process.mjs, which the rewrite left
as an absolute path: a 404 in the app, so every parsing language failed.
It becomes ./node-process.mjs, copied from the server at the same ref,
and an absolute import surviving the rewrite now fails the script.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The app's device login asks for `app models` and, where discovery says
login.device.agent_scope, `agent_scope=models memory link` in the same
approval. The agent's token goes to the bundled CLI on stdin
(`lembas login --token-stdin --server <url> --json`) and is never kept
here; if the CLI does not take it, it is given up on the server. A
computer linked already keeps its link; a server without agent_scope
gets the old two steps.
- The server pane reads GET /api/v2/files and /files/content (mounts open
like folders; `withheld` shown in the server's words). The "not offered
yet" state is gone.
- A show card's file comes from GET /api/v2/chats/{chat}/show?node=&version=
or ?shown= as plain text (new `api_text` command, 1 MB cap); args.path is
read only for agent chats on this computer, locally.
- The gate is link protocol 3 plus app_api 2 (APP_API = 2), not the
server's version number, which says 1.7.2 on dev until its first beta.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci.yml runs on dev too, adds the hygiene check, check-ui.sh (the modules parsed
as modules; nothing sets HTML) and the vendor pin, and a start job: a debug
build with the bundled page, started under Xvfb and under a headless Weston with
GTK's Wayland backend, which must still be running after 25 seconds.
release.yml refuses a tag that already has a release unless run by hand with
replace, marks betas as pre-releases, and gives a beta's .deb a ~beta version
so it sorts below its release.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plain HTML, CSS and ES modules in ui/, no build step. The sidebar opens on agent
chats (this computer first), with chats on the server as the second tab; the
transcript draws the /api/v2 steps, follows a reply as it is written, answers
cards, and starts agent chats in one of the agent's folders. Two explorers kept
apart (this computer's, the server's) and a viewer: Markdown drawn by a safe
renderer that never parses HTML, code in read-only CodeMirror with the server's
token colours, HTML in the sealed frame, show calls opened in it. Settings for
the sign-in, the agent and its folders, notifications and updates; no admin
pages. Layout on fixed and shared tracks; the sidebar is a drawer below 48rem,
the viewer goes over the chat below 64rem. All 124 sentences in the server's
nine languages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two webviews (local title bar, remote web UI) become one WebviewWindow on
ui/index.html. The window refuses every navigation but the app's page and its
sealed frames; the capability still names only main and no remote URL.
- server.rs: discovery refuses a server older than 2.0 (protocol 3 and major
version 2) and an app API newer than this app knows; the app's own device
login for the app scope, the token kept in account.json (0600) and never
given to the page; a proxy limited to /api/v2 paths; the SSE stream.
- files.rs: list and read the agent's working directory, resolved (links and
.. included) and refused outside the agent's folders.
- sealed.rs: HTML for the viewer on its own scheme under the server's sealed
policy: no bridge, no network, no navigation.
- watch.rs + tauri-plugin-notification: a notification when a reply or an
agent chat finishes out of sight.
- A failed start and panics go to desktop.log; a missing tray no longer stops
the app; the DMA-BUF renderer is off under NVIDIA's driver.
- The unstable multiwebview feature, the gtk dependency and the theme report
are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui/tokens.css from the server's dev (tokens.lock.json; repin to the beta tag
at release). scripts/fetch-vendor.sh copies the server's vendored CodeMirror and
Lezer modules into ui/vendor/, each checked against the server's own pin first,
with the bare module names rewritten to sibling files (the page has no import
map); vendor.lock.json records both hashes, and the script with no argument
verifies the copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Linking this computer again leaves one device, and the agent's status says why
it cannot connect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first 0.7.1 aborted on Linux before its window appeared (it read
WebKitWebView's write-only 'settings' property). The owner asked for the
patch to be released again rather than followed by a 0.7.2: this commit is
0.7.1 as it should have been, and tag v0.7.1 moves here. The release
workflow now replaces the release of a tag that is pushed again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0.7.1 read WebKitWebView's 'settings' property through GLib; it is write-only,
GLib panicked, and the app aborted before its window appeared. The read is
gone; the microphone permission handler stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The microphone: WebKitGTK refuses a permission request its app does not
answer, and has no prompt of its own, so dictation on the server's page was
always blocked. The content view now allows the microphone -- only that, and
only there -- and media capture is switched on explicitly for the older
WebKitGTK the AppImage carries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>