Repository navigation
Agent kit
packages/agent_kit is the Flutter widget package of the kit. It depends on
bb_ui from bb-ui, pinned to v0.7.2
(primitives, which bring bb_tokens), and agent_kit_core (chat view
models and the backend port). apps/gallery shows its widgets on
agent_kit_core's scripted fake backend. The look is the approved Scratch-skin
mockup (lab/design/scratch-skin/ in the agent GUI lab: theme.css for
values, app.css and app.js for structure, shots/ for the picture, and
NOTES.md for the token mapping and departures). The mockup is the visual
spec.
packages/agent_kit_bubblepaw adds a pure Dart AgentBackend over an owned
BubblePaw stdio JSON-RPC child. It pins protocol 0.1, negotiates capabilities
and recovers snapshots after event gaps. See its
README for the v0.3.1 mapping and
limits. The gallery's optional desktop live mode uses BP_BIN, BP_WORKSPACE
and BP_DATA Dart defines; with no defines the scripted gallery is unchanged.
See gallery usage.
One folder per widget group under packages/agent_kit/lib/src/, so parallel
work does not collide. Each has one barrel (<folder>/<folder>.dart)
re-exported from lib/agent_kit.dart, and a README.md saying what goes
there and which mockup parts it covers.
| Folder | Holds |
|---|---|
theme/ |
BbTheme, BbThemeScope, SizeClass, SizeClassBuilder
|
status/ |
StatusMark |
transcript/ |
items, activity and evidence rows, edits, diff, plan |
composer/ |
composer, approval bar, queue row, picks |
chrome/ |
rail, header, crew strip, ledger |
context/ |
Context pane, meter bar, compaction marker |
outline/ |
chapter cards, headings, dividers, where-am-I strip, contents, OutlineController
|
history/ |
compacted history: chapter, episode and legacy cards, the boundary hairline, HistoryController
|
workspace/ |
split tree, panes, docking, edge tabs |
compositions/ |
AgentWorkbench, AgentChatPane
|
shared/ |
internal helpers: path segments, the one icon drawing (kit_icon.dart) |
Two widgets put the folders together; an app shows a working agent chat by
dropping one in, fed by an AgentBackend.
AgentChatPane(backend, chatId) is one chat: header, transcript with its
plan, approval bars, question prompt, queued inputs, error line and composer,
with the crew strip, or a side column (the ledger) when the host gives one.
It opens a ChatFeed, closes it when the chat or backend changes, and maps
every user action to a backend command (approve, deny, answer, steer or queue,
cancel, edit or remove a queued input, undo an edit, resume, fork, archive,
pick model or profile). A command that fails shows the error's message in a
dismissible line; a failed send gives the text back to the composer. Optional
controls the backend's capabilities lack are hidden. Send, Stop and approval
actions default to their corresponding capabilities; paneActions can
explicitly override them. For a read-only backend, the messages and pending approvals stay, while Send, Stop and Approve, For session
and Deny are drawn disabled and cannot be invoked (the composer field is
read-only). Resolved questions
show their answer lines in the transcript; an interrupted run ends with the
interrupted notice and a Resume action.
A host can fit the pane into its own screen. metrics (PaneMetrics) sets the
transcript gutter, row gap, list padding and the dock's padding and gutter; the
defaults are the kit's own spacing. showHeader: false leaves the ChatHeader
out and headerBuilder replaces it. transcriptBuilder replaces the transcript
list: it gets the snapshot and a PaneTranscriptSlot (the wired actions, the
controller, the content width, the metrics, the layout, the inserts and the
interrupted notice), so a host that draws the history another way (an outline,
compacted summaries) keeps the pane's commands, dock and feed.
The pane passes transcriptActions through whole, so a host's summaryOf,
activityFooter, openEveryEdit, finishedActivityOpen and itemBuilder reach the
transcript.
TranscriptActions.itemBuilder draws one item the host's way: it gets the kit's
widget for the item and returns what to show (the same widget wrapped, or
another one, such as a user message whose chips look up live titles).
transcriptController hands the host the pane's scroll controller; the pane
never disposes it.
composerBuilder replaces the composer in the dock. It gets a
PaneComposerSlot: the snapshot, whether the chat is a draft, running,
canSend, submit (the pane's wired send), onStop, the model and profile
picks with their change callbacks, the steer or queue mode, the draft
controller and the pane's placeholder. The host adds its own chips, pickers,
placeholder and keys and sends through slot.submit. Approvals, the question
prompt, queued rows and the error line stay the pane's.
chatId: null is a draft: an empty "New chat" with a working composer and no
feed. The first send runs createChat (titled from the first line, 60
characters, or draftTitle), then send, then calls onChatCreated with the
new id so the host can show the pane with it. If the send fails, the chat still
exists and onChatCreated is called, and the text goes back to the composer.
A draft needs the createChat capability to send.
AgentWorkbench(backend) is the app shell: the session rail (chats grouped
by day, from listChats), an AgentChatPane in the sheet, the ledger or crew
strip, and an optional AgentWorkspace beside the chat. The whole width picks
the rail and the ledger (expanded and no workspace open) or crew strip; the
area beside the rail picks how the workspace sits (a full-screen sheet when
compact, a 240 to 420 px column when medium, beside a 444 to 584 px chat when
expanded). The host passes what the kit cannot know: the workspace name, the
per-model usage, panes, and callbacks to open files, agents and jobs.
With a paged listChats the rail ends in a "Show older sessions" row that
loads the next nextCursor page; with the children capability the ledger
shows Cancel on a running agent (expanded width only); its heading counts only
running agents ("Crew" with "1 running · 2 finished" when mixed). ProviderSettings(backend: ...) is the
settings section for providers and models: a read-only list, plus Make
default with defaultModel. The composer's model pick, when defaultModel is
on, is titled "Default model" because it sets the host default.
Needs outside the kit: agent_kit_core has no interrupted transcript item (the
pane uses the run's outcome), no answered time or transcript position for a
question (placed by request time), no older-history paging, and no per-model
usage.
package:agent_kit/testing.dart is not in the barrel: it loads the bundled
fonts (and the Lucide icon fonts, which flutter test does not load) for
tests. The font files live in apps/gallery/assets/fonts/ with their OFL
licences; the gallery bundles them and agent_kit's tests read them from there.
Hosts bundle the fonts themselves.
No state-management package and no code generation: widgets take plain values
and ChangeNotifier/ValueListenables around ChatFeed.
BbTheme is the one ThemeExtension, in agent_kit (not bb_ui, which is
Material-free; only the ThemeExtension class is imported from Material).
BbThemeScope(brightness:, formFactor:) installs it together with bb_ui's
AppTheme, so the primitives and the kit read the same palette. Read it with
BbTheme.of(context) or context.bb. Widgets read roles, never raw colours.
Roles (the mockup's theme.css names; values from bb_tokens'
AppColors, light and dark):
| Role | Scratch token |
|---|---|
canvas |
desk |
surface, surface2
|
bgPrimary, bgSecondary
|
ink, muted, faint
|
textPrimary, textSecondary, textTertiary
|
seam, thread, threadStrong
|
border, thread, threadStrong
|
accent, accentInk, onAccent
|
accent, accentText, onAccent
|
constructive, information, attention, danger
|
okBg, infoBg, attnBg, dangerBg
|
constructiveInk, informationInk, attentionInk, dangerInk
|
KitInks (see below) |
rail* |
follows the theme: light from bgSecondary and friends, dark Scratch's rail*
|
Also on the theme: typeScale (Scratch's TypeScale, mobile under the 768 px
form factor), spacing (KitSpacing), radii, motion (with the 2400 ms
breathe loop), layout (KitLayout) and statusStyle(status).
theme.css has values Scratch's tokens lack. They live in bb-ui's bb_tokens
package and remain available through package:bb_ui/tokens.dart:
-
KitLayout:sizeMedium600,sizeExpanded1000,fitComposer460,paneMinWidth200,paneMinHeight120,edgeTab28,controlHeight32,rowHeight28. -
KitInks: the text colours of the state roles (constructive#397748/#93BD9B, danger#AD4B4B/#E0A3A3, attention#874016/#F0D29C, light / dark).informationInkistextSecondary. -
KitMotion:breathe, 2400 ms. -
KitSpacing:theme.csshas no scale; the steps are 2, 4, 8, 12, 16 and 24 px, taken fromapp.css.
The palette is the agent-kit profile of the BubbleBuffer design repository.
The bb-ui repository owns the vendored
palette, colour generator and generated ProfileColors.light and .dark.
AppColors, KitInks, spaceColor and the status styles of BbTheme read
that palette; roles without an AppColors field are on AppColors.palette.
The mockup's theme.css is generated from the same profile. Widgets use
kTransparent for transparent paint and theme or palette roles for colours.
Colour generation, drift checking and the raw-colour check of bb-ui's own code run
in bb-ui. Raw colours in this repository's packages/agent_kit* and apps/ are
caught by scripts/check-raw-colours.sh (vendored from bb-ui v0.7.1; re-sync it
when the pin moves) in the raw-colours CI job, which first runs
scripts/test-check-raw-colours.sh, a failing-case test of the script.
To change the palette, follow its
bb_tokens workflow,
release that dependency and update both bb_ui Git pins here together. Agent
CI then compares the existing agent goldens against the updated palette; it
does not generate a local copy of the shared colours.
Layout depends on the width of the widget's own container, never the window:
SizeClassBuilder wraps a LayoutBuilder and picks SizeClass.compact (below
600 px), medium (to below 1000 px) or expanded, using the theme's
thresholds. Descendants read it with SizeClass.of(context); a nested builder
(a pane inside a workbench) measures its own area. Do not use
MediaQuery.size for layout. The form factor (BbTheme.formFactor, from the
window width as in Scratch) decides only touch sizing and the type scale.
Status is drawn by StatusMark only: every other widget puts one next to its
text. StatusMark(status) is the dot form (list rows, meta lines);
StatusMark.pill(status) is the header form. label replaces the label and detail follows it after " · "; the running mark breathes unless animate: false or the platform
reduces motion. StatusMark.work(WorkState) maps a child agent's work state; StatusMark.askingParent() marks a child waiting for its parent's answer (see StatusMark).
| Status | Role | Dot form | Pill form |
|---|---|---|---|
needsYou |
attention | accent dot, attention ink, bold | attention fill and hairline, attention ink |
running |
ink | breathing square, muted text | same |
failed |
danger | red dot, danger ink |
circle-x, danger ink |
done |
constructive | green dot, constructive ink |
circle-check, constructive ink |
idle |
muted | hollow ring, muted text | same |
interrupted |
muted | ring with a dot (not in the mockup) | same |
Only "needs you" fills a pill and only it is warm.
history/ draws older chat history as cards (scene H of the scratch-skin
mockup, approved 2026-10-27). The host feeds them through
TranscriptList(entries:) and keeps what is open in a HistoryController; the
widgets take plain view models and import no backend or bridge type.
| Widget | Shows |
|---|---|
HistoryChapterCard |
A closed chapter. Closed it reads alone: kicker (Chapter · Oct 26 – 27 · closed), title, summary paragraph, counts (58 messages · 2 episodes). Layered top edge while closed, edge colour from colorIndex (outlineColor), "Show episodes" / "Hide episodes" in the foot. A part of a split chapter is a card with the same title; only the last part has counts (messageCount and episodeCount are null on the others). |
HistoryCurrentChapter |
The open chapter: a plain group header (Current chapter · since Oct 27 · 3 episodes) over its episode cards, which are always listed. |
HistoryEpisodeCard |
A mono header line (Episode · 13:41–13:56 · 38 messages, then N actions · N flagged · N errors at the right, left out when compact), the title and a gist. The header opens the action log; "Show messages" opens the originals (the host's HistoryMessagesBuilder) and "Show action log" goes back. Log rows are verb, a chip for the target, a change marker (+12 −3, created, failed · 2 tests, passed · 14, coloured by its words) and an optional point-of-interest line. Rows past six fold behind +N more. An episode without actions (summarized before logs) has no counts and goes straight to the messages. |
HistoryLegacyCard |
A summary from before chapters and episodes: title when there is one, the summary, the times and the message count. Nothing opens. |
HistoryBoundary |
The hairline Full messages from here · message N (the number is left out when compact). |
A chapter card also shows its decisions (one ◆ line each, in ink) and sections (a coloured bar and a name each, colours from outlineColor) under the summary, and N sections in the counts. Both are plain fields on HistoryChapter, set on the last part of a split chapter only; an empty list leaves its row out, so a chapter without them looks as before. Section names and decisions are data: the card adds no label. The gallery's H scene (HistoryScene) shows a legacy
card, a folded chapter, an opened one with a v2 episode, a log with +N more
and the originals, a split chapter and the current chapter, opened on the
boundary.
apps/gallery (flutter run -d chrome in that folder). The toolbar switches
scene (the mockup's A workbench, C phone, D workspace and F context as
AgentWorkbench on the fake backend, B Scratch pane as a placeholder, G Outline
as a hand-built chat in chapters on an OutlineController, H Compacted history as the
cards over a boundary, plus Components, which
lists each widget in each state), light or
dark, and the width (a 360 to 1600 px slider that constrains the content, so
size classes follow it). The chat is the "Quota bar flickers on reconnect"
script of agent_kit_core/fake.dart on a ScriptedBackend, followed through a
ChatFeed. The default clock is a ManualClock played to the end, so scenes
open on the mid-task state (waiting for approval); "+10 s", "Play" and "Replay"
step it. Pass GalleryController(clock: () => const SystemClock()) for real
time.
- Add the file under the widget's folder in
agent_kit/lib/src/, read onlyBbThemeroles andSizeClass, and export it from the folder's barrel. - Show it in each state in
apps/gallery/lib/src/components_scene.dart(and in the scene it belongs to once the composition exists). - Add a widget test (
packages/agent_kit/test/) and a golden: copystatus_mark_golden_test.dart(taggolden,pumpKitfromtest/harness.dart, light and dark). - Render the golden (below), look at the PNG next to the mockup shot, and
commit it under
test/goldens/. - Add a crop of the specimen to
apps/gallery/test/component_shots_test.dart(its type and position in the scene, see the file header), render it, and copy the PNGs todocs/components/<folder>/shots/as<name>-light.pngand<name>-dark.png. - Write the widget's sheet in
docs/components/<folder>/<Widget>.md(copy a neighbour: demo, parameters, theme roles, size classes, accessibility, gallery and tests) and add it to the index in README.md. - Update the folder's
README.mdand the table above.
Golden tests are tagged golden; the normal test job excludes them and the
goldens job runs them. They render StatusMark in packages/agent_kit
(test/goldens/status_mark_{light,dark}.png) and every gallery scene in
apps/gallery (A at 1440 and 1000, C at 390, D at 1600 and 1000, F context and
G outline at 1600 and 390, and the whole Components sheet at 1000 px, light and
dark;
test/goldens/<scene>_<width>_<theme>.png), with the scripted chat played to
its end. apps/gallery/test/component_shots_test.dart renders the Components, History and Outline scenes again and keeps one crop per widget state as test/goldens/shots/<name>_<theme>.png; the component sheets show those crops. Text is real because the bundled fonts are loaded in the tests.
Rendering happens only in CI, on ubuntu-latest with Flutter pinned to the
version this repo's CI pins (3.47.5); goldens rendered elsewhere will not match.
- Push or pull request: the job compares with the committed PNGs and fails on
a difference (
golden-failuresartifact holds the diffs). - Manual run:
gh workflow run ci.yml --ref <branch>re-renders without comparing. - Either way the job uploads the PNGs as the
gallery-shotsartifact:gh run download <run id> -n gallery-shotsgivescomponents/,scenes/andcrops/(the widget crops of the component sheets). Compare them withlab/design/scratch-skin/shots/. - To accept new renders, download the artifact from a manual run, copy
components/*.pngtopackages/agent_kit/test/goldens/andscenes/*.pngtoapps/gallery/test/goldens/(crops/*.pngtogoldens/shots/, and copy them todocs/components/<folder>/shots/as in "Adding a widget"), and commit them. - Formatting is not run on the PC either: when the format job fails it uploads
format-patch;git apply format.patchfixes it.
Generated from docs/agent-kit.md. Edit the file there; changes made on the wiki are overwritten.
Guides
- Package layout and pinning
- agent_kit_core
- Backend mapping
- agent_kit and the gallery
- Icon and README assets
Component sheets
theme
status
transcript
- ActivityGroupView
- AssistantProse
- ChildQuestionRow
- CodeBlock
- DiffView
- ExecOutput
- FileEditRow
- PlanChecklist
- ReturnLine
- SystemLineView
- TranscriptList
- UserBubble
composer
chrome
context
history
outline
workspace
compositions
providers