Runs record a context manifest once a model turn succeeds; the chat
marks each human message with the agents that read its current version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three third-party keys were hardcoded or read from the environment.
Operators now enter them in Settings > Integrations; they are stored
encrypted in the app-wide integration_keys table (migration 058).
An agent holding connector grants fails loudly when no Composio key is
saved. The goal-review test no longer depends on absolute dates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
buildConversations learns the org's CEO (c_suite, the runtime predicate)
and ranks the viewer's own direct conversation with them above recency —
counterpart resolution already works from the viewer's seat, so agent-to-
agent CEO chats never match. The row carries a CEO badge and, before the
first exchange, invites asking about progress, goals, or direction.
Being first, it is also the default conversation the page opens on.
- 16 brand PNGs land in src/renderer/src/assets/provider-logos (img.logo.dev, size 128, same publishable token as connectors) with a README recording each domain and the refresh command - no runtime fetch, nothing to download again
- shared/ProviderLogo.tsx maps catalog keys to the bundled art on a white tile (transparent marks stay legible in dark theme); custom endpoints keep the letter tile since no official brand exists
- Settings provider rows/cards and the onboarding provider step now share the same component; old letter-tile ProviderLogo removed
- Verified via Playwright screenshot captures: onboarding and Settings both render the real marks; full e2e green
- Work page is now a flush route: the title, project overview, and column
headers stay put and each board column scrolls its own cards (List and
Timeline scroll inside their own region). The page no longer scrolls its
header away under 300 tasks.
- Team page drops the hiring-pipeline block (and its orphaned component and
i18n key) — four columns of mostly-zero counters said nothing the roster
doesn't.
- Clicking outside the search overlay closes it; the Close button stops
being the only exit.
- The sidebar's top strip is a window drag region now (the topbar only
covers the main pane). Interactive children opt out of dragging, and the
org button shrinks to its content so the strip actually has drag area.
- The operator's name hugs their avatar in the sidebar foot: buttons
center text by UA default and the flexed name span inherited it;
text-align: left ends that.
Cards on the Work board overflowed their frame and shouted six facts at
once. Two root causes, both fixed at the source:
- Pills truncated by counting characters in JS (truncate(title, 18)) while
the CSS said white-space: nowrap with no max-width — CJK glyphs are twice
as wide, so 18 Chinese characters punched straight through the card edge.
The Pill component now owns truncation: max-width + an inner .pill-text
with a real ellipsis. Every pill in the app inherits the fix; the JS
truncation helper is gone.
- The card repeated what every other card already said: identical goal,
workspace, and (in the Done column) status pills on all 280 cards carried
zero information. A card now states what is particular to it: a title
clamped to three lines, the project, the run state while the task is
live, the assignee, and the actions. Goal and workspace stay in the task
detail dialog where they belong.
- Done cards are no longer struck through — a six-line Chinese title with a
line through it reads as vandalism, not completion. Done cards just go
quiet.
Verified visually against the rebuilt app's board screenshot; full suite
plus e2e green.
House style now follows the app language: Chinese UI forces the new
bundled humanizer-zh skill into every run, English forces humanizer -
one variant per run, picked by preference with cross-language fallback
so an older catalogue never silently loses house style. Both variants
are protected from grant/revoke, list_skills labels each with its
language, the bundle guard requires both, and startup sync imports the
new skill into existing orgs on next launch. Covered by a unit test
that flips app_state.language and asserts which humanizer compiles.
The chat split is now adjustable: drag the conversation list's right
edge (200-480px, persisted in localStorage) and the composer textarea
grew a native vertical resize grip capped at 40vh.
The pause control was a hand-rolled pill (999px radius, its own
padding and font size) sitting next to the standard "New task" button,
so the two never matched. It now renders as the shared .button class -
one source of truth for height, padding, and corner radius - with a
single amber modifier for the paused state. Verified side by side in
the captured Work-page screenshot.
Two operator asks, one review gate:
Avatar portraits (migration 052): the avatar editor gains "Upload
photo" - the renderer downscales the chosen picture to a ~96px cover
crop and stores it as a small data URL (PNG keeps transparency, the
rest compresses to JPEG), so the snapshot stays light and the CSP needs
nothing new. The portrait wins over emoji and initials everywhere the
shared Avatar renders (chat, org chart, office card, sidebar), and
"Remove photo" falls back cleanly. The main process validates scheme
and size (base64 image data URL, <=300k chars) so a rogue caller cannot
re-bloat the snapshot. e2e uploads a real PNG through the profile
dialog and sees the portrait land in the sidebar.
Global pause (migration 053): when agents start talking in circles,
one topbar toggle - visible on every page - pulls the handbrake.
app_state.autorun_paused survives restarts; the queue worker checks it
each tick and neither enqueues nor claims while set (the run already in
flight finishes). Queued work simply waits and the paused state renders
as an amber "resume" pill. A unit test drives the real worker: paused
leaves a queued run untouched through many ticks, resuming claims it
within moments; e2e flips the toggle in the live shell.
The conversation pane had no scroll management at all - it opened at
the top of history, ignored arriving messages, and offered no way back
down other than dragging through long replies.
- Opening a conversation lands on the newest message (the component is
keyed by space, so mount = open)
- A reader at the bottom stays pinned as new messages arrive; a reader
up in history is left undisturbed
- The moment the reader leaves the bottom, a floating "Jump to latest"
pill appears above the composer; clicking it smooth-scrolls back and
the pill hides itself again
- The scroll region is a proper role="log" landmark; e2e seeds a dozen
multi-line messages over real IPC, scrolls to the top, and drives the
pill's appear -> click -> return-to-bottom -> disappear cycle
The renderer half of the project review surfaced five real defects:
- One unresolvable message sender or mention target blanked the whole
Messages page mid-render; both now degrade to a visible "Former
member" placeholder instead of throwing
- A failed initial snapshot load left a permanently blank window with a
swallowed rejection; loads now log loudly and retry every 1.5s until
the main process answers
- With no conversation selected, the view followed the recency sort:
any incoming message yanked the visible conversation to the newest
space and marked it read unseen. The implicit default now pins itself
once (render-time state adjustment)
- The avatar directory keyed names across ALL organizations, so a
same-named principal in another org hijacked the custom avatar; it
now scopes to the active org
- The conversation search query survived switching spaces, silently
filtering the new space by the old term; ChatConversation is keyed by
space id so per-space UI state resets
Also verified clean by the same sweep: no lingering references to the
removed snapshot fields, no stale closures in the office rAF loop,
org-chart zoom math, and the chat message ordering invariant is
guaranteed at the data layer (created_at desc limit + reverse).
A systematic review of the whole codebase (main process, renderer,
IPC/migration/packaging seams) surfaced four real defects; each is
fixed at its root:
- Legacy catalogue drift: the operator's profile still carried 89
active skills from the removed ~/.agents import era next to the 228
built-ins. Catalogue sync now mirrors the bundle - actives whose slug
the bundle does not ship retire (bindings keep resolving via their
pinned revisions; retired skills leave list_skills and cannot be
granted anew)
- Broken-bundle guard: staging now refuses a bundle without the
humanizer skill, so an empty or quarantined resources folder can
never trigger the mirror pass into retiring a healthy catalogue
- Duplicate agent names: @mentions, DM routing, and task assignment all
resolve by name, so createAgent and updateAgent renames now reject a
name already held by an active agent (case-insensitive); the mention
resolver still refuses to guess between legacy duplicates
- Stale DM titles: agent-to-agent conversation rows titled themselves
from the frozen creation-time space name, so renames never reached
the sidebar; direct titles now derive from members' current names
Verified clean in the same sweep: IPC surface (82 channels, handler +
preload + api aligned), per-migration transactions with rollback,
quota-parking month rollover, retry-budget counting, meeting spot
reuse across floor-plan rebuilds.
Every whip click now lands visibly, wherever the target is standing:
- simulation.whip() always registers a WhipCrackEffect at the struck
agent and starts their hit reaction; step() ages both away. The
discipline rule is unchanged - only snack-bar/restroom slackers are
sent hustling home; desk, CEO office, and meeting stay safe
- office-renderer draws the strike: a lash sweeping in from the upper
right that collapses into an impact starburst with an expanding ring
(~450ms), while the struck agent shakes with anime-style pain sparks
over their head (~600ms), fading as the flinch drains
- The office e2e now collects pageerror during the run and lets the
effect play out after a whip-mode click, so a throwing frame in the
rAF loop fails the suite instead of dying silently
- New bottom toolbar on the pixel office floor with a Whip tool;
toggling it turns the cursor to a crosshair, clicks whip instead of
opening the detail card, Escape puts the whip away
- Simulation.whip(): an agent inside the snack bar or restroom drops
whatever they are carrying and hustles back to their desk at 1.8x
speed for a few seconds; agents at their desks, in the CEO office,
or in the meeting room are working and stay untouched
- Room membership decides the outcome (rectContains against the same
floor-plan rooms the renderer draws), so the rule matches what the
player sees
- Unit tests cover the whip matrix; e2e drives the toolbar toggle in
the live shell
Researched how OKR-run companies cascade (company objective -> C-suite
officers per function -> department objectives -> individual key
results) and encoded that shape into how the CEO builds the org.
- hire_agent gains seniority (director/lead/staff; c_suite stays
reserved for the CEO's privileges) and a business-card title, so the
CEO hires a CTO/CFO/CMO as directors instead of a flat pool of ICs
- CEO persona + goal kickoff now direct the OKR cascade: hire 2-4
officers, hand each a department objective, let them build teams;
department heads get their own doctrine (hire leads/staff, cascade
key results as tasks, report up)
- Migration 050 adds Finance and Sales to every org's canonical
departments (backfilled for existing DBs) so a real C-suite has
somewhere to hire
- Org chart viewport is a fixed-height scroll region: zooming rescales
the diagram inside it instead of resizing the card
- Unit test drives a live three-level chain: CEO -> CTO (director) ->
Engineering Manager (lead) -> staff, with rank/title persisted
- The detail card now rides beside the clicked agent (flipping and
clamping at viewport edges via a pure cardPositionFor helper) instead
of pinning to the top-right corner; it follows a walking agent
- Every agent - the CEO and busy agents included - periodically visits
the coffee machine, snack bar, and restroom; work status no longer
recalls a walker mid-break
- The office convenes meetings around the meeting table every few
minutes: at least two settled attendees walk over, dwell together on
distinct table spots, and head back; a solo office never meets
- Meeting spots ship as lane-wired amenities so pathing stays correct
by construction; floor-plan invariants (collider-free, reachable)
cover them automatically
- e2e asserts the card opens beside the sprite, not in a corner
- Migration 049: avatar_emoji/avatar_color on agents and employees
- updateAgent accepts a new name (mention-name validated) and avatar;
new updateEmployeeProfile renames the operator and sets their avatar
- Avatars are emoji-on-color tiles (renderer CSP blocks remote images);
an AvatarDirectoryContext built from the snapshot lets the shared
Avatar component honor custom looks at every call site - chat rows,
conversation list, org chart, office card, sidebar - without
threading props through pages
- Team edit dialog gains Name + avatar editor (CEO included); the
sidebar foot opens an Edit-profile dialog for the operator
- e2e: rename an agent to a new mention handle with an emoji avatar,
edit the operator profile, both verified in the live shell