Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 2 additions & 49 deletions apps/ui/src/agent-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import type { AgentState } from "./stub-data";
// agent-state.ts is the pure D9 projection: it maps the daemon's coarse,
// authoritative `AgentSessionState` (compass.v1, #443) plus optional stream
// refinements onto the fine-grained UI dot. These tests pin the projection
// row-by-row so a future edit to the switch, a leaked refinement, or a reorder
// of the `wardenPaused` override can't silently render the wrong dot.
// row-by-row so a future edit to the switch or a leaked refinement can't
// silently render the wrong dot.

// The complete UI dot union (stub-data AgentState) as a membership table.
// Enumerated on purpose: the return-type guard below asserts every branch lands
Expand Down Expand Up @@ -131,51 +131,6 @@ describe("turnDoneUnopened refinement (READY → done)", () => {
});
});

describe("wardenPaused override (precedence)", () => {
// The strongest contract: a Warden pause is a Compass overlay that wins over
// the enum regardless of what the session was doing. `wardenPaused` is the
// first check in the function; moving it below the switch (so a WORKING/READY
// session returned early) would redden every row here.
for (const state of ALL_SESSION_STATES) {
test(`paused wins for ${AgentSessionState[state]}`, () => {
expect(agentDotState(state, { wardenPaused: true })).toBe("paused");
});
}

// Precedence over the other refinements: pause beats an open ask and an
// unopened-done turn. If the override were reordered after the refinement
// branches, these would resolve to waiting/done instead.
test("paused beats awaitingInput", () => {
expect(
agentDotState(AgentSessionState.WORKING, {
wardenPaused: true,
awaitingInput: true,
}),
).toBe("paused");
});

test("paused beats turnDoneUnopened", () => {
expect(
agentDotState(AgentSessionState.READY, {
wardenPaused: true,
turnDoneUnopened: true,
}),
).toBe("paused");
});

// A falsy wardenPaused must NOT trigger the override — the enum mapping still
// governs. Guards against `if (refinement.wardenPaused !== undefined)` or a
// truthiness slip that treats an explicit `false` as a pause.
test("wardenPaused:false does not override the enum mapping", () => {
expect(
agentDotState(AgentSessionState.WORKING, { wardenPaused: false }),
).toBe("working");
expect(
agentDotState(AgentSessionState.READY, { wardenPaused: false }),
).toBe("idle");
});
});

describe("return type is always a valid AgentState", () => {
// Totality guard: across every enum value and a spread of refinement combos,
// the result is one of the seven union members. Catches a future branch that
Expand All @@ -185,8 +140,6 @@ describe("return type is always a valid AgentState", () => {
{},
{ awaitingInput: true },
{ turnDoneUnopened: true },
{ wardenPaused: true },
{ awaitingInput: true, turnDoneUnopened: true, wardenPaused: true },
];

for (const state of ALL_SESSION_STATES) {
Expand Down
8 changes: 0 additions & 8 deletions apps/ui/src/agent-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ export interface AgentStreamRefinement {
/** The agent completed a turn and no human has opened its view yet. Refines
* `READY` → `done` (emerald check, deliberately not idle grey). */
turnDoneUnopened?: boolean;
/** A Warden security-pause is in force (compass-0.4 `pause_agent`) — a
* Compass overlay, not an ACP/`AgentSessionState` value. Wins over the enum
* mapping while set. */
wardenPaused?: boolean;
}

/**
Expand All @@ -47,15 +43,11 @@ export interface AgentStreamRefinement {
* | UNSPECIFIED | idle (defensive; a well-behaved daemon never |
* | | sends it as a live state) |
*
* `wardenPaused` overrides to `paused` regardless of the enum, since a pause is
* a Compass overlay applied on top of whatever the session was doing.
*/
export function agentDotState(
sessionState: AgentSessionState,
refinement: AgentStreamRefinement = {},
): AgentState {
if (refinement.wardenPaused) return "paused";

switch (sessionState) {
case AgentSessionState.STARTING:
case AgentSessionState.WORKING:
Expand Down
5 changes: 1 addition & 4 deletions apps/ui/src/app.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* Compass ADE — dev UI styling. Dark, dense, board-first. An Orca-inspired
* shell: a left folder tree of agents, a central Bridge (swimlane board) or
* agent (ACP + terminals) view, and a right sidebar carrying the fleet
* conversations (Supervisor · Warden · Status) above the issue files/VCS/PR
* conversations (Supervisor · Status) above the issue files/VCS/PR
* tabs, plus a bottom usage bar. All read one store. */

:root {
Expand Down Expand Up @@ -528,9 +528,6 @@ button {
.role-pip[data-role="supervisor"] {
color: var(--accent);
}
.role-pip[data-role="warden"] {
color: var(--purple);
}

/* ── Main area ──────────────────────────────────────────────────────────── */

Expand Down
45 changes: 1 addition & 44 deletions apps/ui/src/comms-stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,14 +427,6 @@ export const STUB_TOPICS: Topic[] = [
createdByAccountId: "acc-supervisor",
archived: false,
},
{
id: "top-dm-warden",
channelId: "dm-warden",
name: "general",
createdAtUnixMs: min(69),
createdByAccountId: "acc-warden",
archived: false,
},
];

export const STUB_MESSAGES: Message[] = [
Expand Down Expand Up @@ -623,7 +615,7 @@ export const STUB_MESSAGES: Message[] = [
blocks: [
{
kind: "text",
text: "Fleet snapshot: cook + livingstone in review, cousteau waiting on a merge gate, warden holding the sandbox lane. No collisions on the compass-ui zone right now.",
text: "Fleet snapshot: cook + livingstone in review, cousteau waiting on a merge gate. No collisions on the compass-ui zone right now.",
},
],
},
Expand Down Expand Up @@ -689,41 +681,6 @@ export const STUB_MESSAGES: Message[] = [
},
],
},

// ── DM: matt <-> warden ──
{
id: "msg-dm-war1",
topicId: "top-dm-warden",
authorAccountId: "acc-warden",
atUnixMs: min(70),
blocks: [
{
kind: "text",
text: "Sandbox policy check: every worker is confined to its own clone under ~/agents/workspaces. No writes to main, allowlisted owners only.",
},
],
},
{
id: "msg-dm-war2",
topicId: "top-dm-warden",
authorAccountId: MATT,
atUnixMs: min(73),
blocks: [
{ kind: "text", text: "Flag anything that reaches outside its lane." },
],
},
{
id: "msg-dm-war3",
topicId: "top-dm-warden",
authorAccountId: "acc-warden",
atUnixMs: min(75),
blocks: [
{
kind: "text",
text: "Will do — no boundary crossings observed this cycle.",
},
],
},
];

/** The fixture as a `CommsState` — the shape `createAppStore` takes as
Expand Down
11 changes: 5 additions & 6 deletions apps/ui/src/components/AgentView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe("AgentView (T3)", () => {
// a rendered placeholder — so every one would FAIL against the disabled variant.
//
// Fixture ground truth (verified from stub-data.ts STUB_AGENTS, quoted here):
// - acc-supervisor / acc-warden: `terminals: []` — ZERO fixture terminals, so
// - acc-supervisor: `terminals: []` — ZERO fixture terminals, so
// `nextFreeTerminalPane` returns undefined from the first "+" click. The old
// code disabled "+" immediately for them → the cleanest always-open proof.
// - acc-cook: terminals `t-c1` + `t-c2` (2) — exhausted after two opens.
Expand All @@ -203,9 +203,8 @@ describe("AgentView always-open '+'/split (regression)", () => {
return agent.terminals.length;
};

test("fixture ground truth: supervisor/warden have zero terminals, cook has two", () => {
test("fixture ground truth: supervisor has zero terminals, cook has two", () => {
expect(agentTerminalCount("acc-supervisor")).toBe(0);
expect(agentTerminalCount("acc-warden")).toBe(0);
expect(agentTerminalCount("acc-cook")).toBe(2);
});

Expand Down Expand Up @@ -294,10 +293,10 @@ describe("AgentView always-open '+'/split (regression)", () => {
// disabled the split buttons the moment `nextFreeTerminalPane` was undefined.
test("split stays open after fixtures: clicking a split button grows the tree with a placeholder pane", () => {
const { store, container } = mountAgentView();
store.openAgent("acc-warden");
store.openAgent("acc-supervisor");

// Open the warden's first (minted) terminal tab so its pane shows split
// buttons — warden has zero fixtures, so this pane is itself a placeholder.
// Open the supervisor's first (minted) terminal tab so its pane shows split
// buttons — supervisor has zero fixtures, so this pane is itself a placeholder.
const newTab = container.querySelector<HTMLButtonElement>(".av-tab-new");
if (!newTab) throw new Error("'+' new-tab button not rendered");
fireEvent.click(newTab);
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/components/LeftSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const AgentLeaf: Component<{ agent: Agent; badge?: number }> = (props) => {
<span class="name">{a().account.handle}</span>
<Show when={a().role !== "worker"}>
<span class="role-pip" data-role={a().role} title={a().role}>
{a().role === "supervisor" ? "◆" : "🛡"}
</span>
</Show>
<Show when={props.badge !== undefined}>
Expand Down
10 changes: 5 additions & 5 deletions apps/ui/src/components/RightSidebar.fleetpane.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { testQueryClient } from "../test-support";
import { RightSidebar } from "./RightSidebar";

// Render acceptance spec for the compass-0.7 fleet pane (design compass-0.7,
// FleetPane in RightSidebar.tsx). A fleet tab (Supervisor · Warden) used to
// FleetPane in RightSidebar.tsx). A fleet tab (Supervisor · Cook) used to
// render CONTROL-ONLY — just a button into the agent's workspace. It now renders
// the agent's home-DM conversation INLINE above a compact "Open workspace"
// control, and — per Matt's kill-the-gate ruling (2026-07-20) — any ask in that
Expand Down Expand Up @@ -54,11 +54,11 @@ function mountRightSidebar(): { store: AppStore; container: HTMLElement } {

// The two visible fixture agents whose home-DM the fleet pane renders. The fleet
// tabs are CONFIGURABLE PINS keyed `agent:${accountId}` (Record A §T2), not a
// hardcoded Supervisor · Warden pair. The pane arm reads the active tab's item
// hardcoded Supervisor · Cook pair. The pane arm reads the active tab's item
// out of `rightTabGroups()` (SEA-1645 P2), which emits only PINNED agents, so a
// test must pin the agent before activating its tab. Both ids resolve in the
// fixture, so once pinned the pane renders their home-DM inline.
const FLEET_TABS = ["acc-supervisor", "acc-warden"] as const;
const FLEET_TABS = ["acc-supervisor", "acc-cook"] as const;

// The agent account's home-DM channel id — resolved through the SAME account set
// the store exposes (STUB_ACCOUNTS carries agent accounts with homeChannelId),
Expand Down Expand Up @@ -191,8 +191,8 @@ describe("RightSidebar fleet pane (compass-0.7)", () => {
// stops resolving a live agent.
test("a resolved fleet tab renders the live pane, not the unreachable block", () => {
const { store, container } = mountRightSidebar();
store.pinAgent("acc-warden");
store.setActiveRightTab("agent:acc-warden");
store.pinAgent("acc-cook");
store.setActiveRightTab("agent:acc-cook");

expect(container.querySelector(".fleet-pane")).not.toBeNull();
expect(container.querySelector(".fleet-unreachable")).toBeNull();
Expand Down
10 changes: 5 additions & 5 deletions apps/ui/src/components/RightSidebar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe("rightTabGroups() derivation (Record A §T2)", () => {
// The two visible fixture agents to pin — both resolve in STUB_AGENTS, so the
// derivation must surface a fleet item for each, in pin order.
const SUP = "acc-supervisor";
const WARDEN = "acc-warden";
const LIVINGSTONE = "acc-livingstone";

// Fleet must sit ABOVE issue (D2). A swapped order renders the bar upside-down.
test("orders the groups fleet-first, issue-second", () => {
Expand All @@ -164,7 +164,7 @@ describe("rightTabGroups() derivation (Record A §T2)", () => {
const ids = groups.flatMap((g) => g.items.map((item) => item.id));
expect(ids).toEqual([
`agent:${SUP}`,
`agent:${WARDEN}`,
`agent:${LIVINGSTONE}`,
"status",
"files",
"vcs",
Expand All @@ -173,7 +173,7 @@ describe("rightTabGroups() derivation (Record A §T2)", () => {
// No duplicate, independent of order.
expect(new Set(ids).size).toBe(ids.length);
},
[SUP, WARDEN],
[SUP, LIVINGSTONE],
);
});

Expand Down Expand Up @@ -220,7 +220,7 @@ describe("rightTabGroups() derivation (Record A §T2)", () => {
);
expect(withAgent.map((item) => item.id)).toEqual([
`agent:${SUP}`,
`agent:${WARDEN}`,
`agent:${LIVINGSTONE}`,
]);
for (const item of withAgent) {
expect(STUB_AGENTS.some((a) => a.account.id === item.agentId)).toBe(
Expand All @@ -234,7 +234,7 @@ describe("rightTabGroups() derivation (Record A §T2)", () => {
expect(status).toBeDefined();
expect(status?.agentId).toBeUndefined();
},
[SUP, WARDEN],
[SUP, LIVINGSTONE],
);
});
});
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/components/RightSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ const StatusPane: Component = () => {

/** The right sidebar (design D5, dock-in-sidebar D2/D3/D5): an icon-per-tab
* activity bar mirroring Orca, grouped fleet-over-issue with a divider.
* Fleet tabs (Supervisor · Warden) render the agent's home-DM conversation
* Fleet tabs (Supervisor) render the agent's home-DM conversation
* inline (read-only asks) above an open-workspace control;
* issue tabs render the repo/branch dropdown + detail head above the pane
* for the active tab — Files (with a search box), VCS (changed files + commit
Expand Down
4 changes: 2 additions & 2 deletions apps/ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export interface ActivityBarItem {
* issue tabs (Files / VCS / PR). The agent conversation tabs are no longer
* hardcoded here — they are derived per pin from the store's pin set
* (`rightTabGroups()`), so the fleet group is a configurable pin layer, not a
* fixed Supervisor · Warden pair (Record A §T2). */
* fixed Supervisor pin (Record A §T2). */
export type StaticRightTab = "status" | IssueTab;

/** The static tabs in activity-bar order, keyed on the static-tab union in a
Expand All @@ -98,7 +98,7 @@ export const RIGHT_SIDEBAR_ISSUE_ITEMS: readonly ActivityBarItem[] =
* §T2; SEA-1645 P1). The tab id is the `agent:`-prefixed account id (the open
* arm of `RightSidebarTab`); the icon is the agent handle's initial (matching
* the UI's glyph-icon convention — a per-agent glyph, no hardcoded Supervisor
* ◆ / Warden 🛡), and the title is the LIVE agent handle. The item is left
* ◆), and the title is the LIVE agent handle. The item is left
* unmarked (`unreachable` absent) so its `agentId` badges a real `StateDot`.
* An unresolvable pin is built by `unreachableFleetItem` instead — so a
* marked item can carry an `agentId` that resolves no agent. */
Expand Down
Loading
Loading