9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
818 lines
27 KiB
TypeScript
818 lines
27 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
|
|
|
const SHOTS = "test-results/observer-feed";
|
|
|
|
// A running managed agent whose pubkey maps to the "agents" channel.
|
|
// Using tyler's pubkey so the mock bridge already knows this identity.
|
|
const OBSERVER_AGENT_PUBKEY = TEST_IDENTITIES.tyler.pubkey;
|
|
const CHANNEL_ID = "94a444a4-c0a3-5966-ab05-530c6ddc2301"; // #agents
|
|
const NOW = new Date("2025-06-15T12:00:00Z").toISOString();
|
|
|
|
const MANAGED_AGENTS = [
|
|
{
|
|
pubkey: OBSERVER_AGENT_PUBKEY,
|
|
name: "Observer Agent",
|
|
status: "running" as const,
|
|
channelNames: ["agents"],
|
|
},
|
|
];
|
|
|
|
// Helper: wait until the seed hook is available in the page.
|
|
async function waitForSeedHook(page: import("@playwright/test").Page) {
|
|
await page.waitForFunction(
|
|
() => typeof window.__BUZZ_E2E_SEED_OBSERVER_EVENTS__ === "function",
|
|
null,
|
|
{ timeout: 10_000 },
|
|
);
|
|
}
|
|
|
|
// Helper: open the observer feed panel by navigating to #agents, clicking the
|
|
// agent avatar to open the profile panel, then clicking "View activity".
|
|
async function openObserverFeedPanel(
|
|
page: import("@playwright/test").Page,
|
|
agentPubkey: string,
|
|
) {
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await waitForSeedHook(page);
|
|
|
|
await page.getByTestId("channel-agents").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("agents");
|
|
|
|
// Click any message row button to open the profile panel.
|
|
const messageRow = page
|
|
.getByTestId("message-row")
|
|
.filter({ has: page.getByText("Observer Agent", { exact: false }) });
|
|
await expect(messageRow.first()).toBeVisible({ timeout: 8_000 });
|
|
await messageRow.first().getByRole("button").first().click();
|
|
|
|
const profilePanel = page.getByTestId("user-profile-panel");
|
|
await expect(profilePanel).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Click "View activity" to open the observer feed panel.
|
|
const activityBtn = page.getByTestId(
|
|
`user-profile-view-activity-${agentPubkey}`,
|
|
);
|
|
await expect(activityBtn).toBeVisible({ timeout: 5_000 });
|
|
await activityBtn.click();
|
|
|
|
const feedPanel = page.getByTestId("agent-session-thread-panel");
|
|
await expect(feedPanel).toBeVisible({ timeout: 10_000 });
|
|
return feedPanel;
|
|
}
|
|
|
|
// Helper: seed observer events into the store and wait for the panel to update.
|
|
async function seedObserverEvents(
|
|
page: import("@playwright/test").Page,
|
|
agentPubkey: string,
|
|
events: Array<{
|
|
seq: number;
|
|
timestamp: string;
|
|
kind: string;
|
|
agentIndex: number | null;
|
|
channelId: string | null;
|
|
sessionId: string | null;
|
|
turnId: string | null;
|
|
payload: unknown;
|
|
}>,
|
|
) {
|
|
await page.evaluate(
|
|
({ pubkey, evts }) => {
|
|
window.__BUZZ_E2E_SEED_OBSERVER_EVENTS__?.({
|
|
agentPubkey: pubkey,
|
|
events: evts,
|
|
});
|
|
},
|
|
{ pubkey: agentPubkey, evts: events },
|
|
);
|
|
// Let React re-render after the store update.
|
|
await page.waitForTimeout(300);
|
|
}
|
|
|
|
async function settleAnimations(panel: import("@playwright/test").Locator) {
|
|
// Only await finite animations — live surfaces (e.g. the turn liveness
|
|
// indicator) run infinite loops whose `finished` promise never resolves.
|
|
await panel.evaluate((el) =>
|
|
Promise.all(
|
|
el
|
|
.getAnimations({ subtree: true })
|
|
.filter((a) => {
|
|
const timing = a.effect?.getTiming();
|
|
return timing?.iterations !== Number.POSITIVE_INFINITY;
|
|
})
|
|
.map((a) => a.finished),
|
|
),
|
|
);
|
|
}
|
|
|
|
test.describe("observer feed screenshots", () => {
|
|
test.use({ viewport: { width: 1280, height: 900 } });
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
page.on("pageerror", (err) => {
|
|
console.error(
|
|
"PAGE ERROR:",
|
|
err.message,
|
|
err.stack?.split("\n").slice(0, 5).join("\n"),
|
|
);
|
|
});
|
|
page.on("console", (msg) => {
|
|
if (msg.type() === "error") {
|
|
console.error("CONSOLE ERROR:", msg.text().slice(0, 500));
|
|
}
|
|
});
|
|
});
|
|
|
|
test("01 — prompt context dialog (via Checks ingress)", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
// session/prompt event: per-turn prompt context. The payload contains
|
|
// sections that parsePromptText extracts; the transcript keeps them out
|
|
// of the feed until the CheckCheck footer toggle opens the
|
|
// PromptContextDialog modal.
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_write",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: "turn-001",
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "session/prompt",
|
|
params: {
|
|
prompt: [
|
|
{
|
|
type: "text",
|
|
text: "[Buzz event: Kind 9]\nContent: @Observer Agent help me debug this",
|
|
},
|
|
{
|
|
type: "text",
|
|
text: "[Thread context]\nThis is the thread history with 3 prior messages.",
|
|
},
|
|
{
|
|
type: "text",
|
|
text: "[Channel context]\nYou are in #agents, a channel for AI coordination.",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
// The context stays out of the feed until the CheckCheck ingress opens
|
|
// the dialog.
|
|
await expect(feedPanel.getByText("Prompt context")).toHaveCount(0);
|
|
const contextToggle = feedPanel.getByTestId(
|
|
"transcript-prompt-context-toggle",
|
|
);
|
|
await expect(contextToggle).toBeVisible({ timeout: 5_000 });
|
|
await contextToggle.click();
|
|
const dialog = page.getByRole("dialog");
|
|
await expect(dialog).toBeVisible({ timeout: 5_000 });
|
|
await expect(
|
|
dialog.getByTestId("transcript-prompt-context-sections"),
|
|
).toBeVisible({ timeout: 5_000 });
|
|
await settleAnimations(dialog);
|
|
await dialog.screenshot({
|
|
path: `${SHOTS}/01-prompt-context-dialog.png`,
|
|
});
|
|
});
|
|
|
|
test("02 — system prompt title restored", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
// session/new event without a subsequent session/prompt: the system-prompt
|
|
// item is never consumed by a turn bucket, so the grouper emits it as a
|
|
// standalone single rendered by RawRailActivity with the "System prompt"
|
|
// title. (This is the isolated test; see shot 11 for the full turn bundle.)
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_write",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: null,
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "session/new",
|
|
params: {
|
|
systemPrompt:
|
|
"[Base]\nYou are a helpful AI assistant running in Buzz.\n\n[System]\nYou are Observer Agent. You coordinate multi-agent workflows in the #agents channel.",
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
// The system prompt rail item should show the restored title.
|
|
await expect(feedPanel.getByText("System prompt")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await settleAnimations(feedPanel);
|
|
await feedPanel.screenshot({
|
|
path: `${SHOTS}/02-system-prompt-title-restored.png`,
|
|
});
|
|
});
|
|
|
|
test("03 — permission outcome (approved)", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
// Permission request followed by a selected (approved) response,
|
|
// correlated by JSON-RPC id (fix #3: was broken for numeric ids before).
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
// Request
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_read",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: "turn-001",
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: 42,
|
|
method: "session/request_permission",
|
|
params: {
|
|
title: "Read file system",
|
|
message: "Agent wants to read ~/.config/goose/config.yaml",
|
|
options: [
|
|
{ optionId: "opt-allow", kind: "allow_once", name: "Allow once" },
|
|
{ optionId: "opt-deny", kind: "reject_once", name: "Deny" },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
// Response (numeric id 42 — the exact case fix #3 restores)
|
|
{
|
|
seq: 2,
|
|
timestamp: NOW,
|
|
kind: "acp_write",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: "turn-001",
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: 42,
|
|
result: {
|
|
outcome: {
|
|
outcome: "selected",
|
|
optionId: "opt-allow",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
// The permission row should show the "Approved (allow_once)" outcome.
|
|
await expect(feedPanel.getByText(/Approved.*allow_once/)).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await settleAnimations(feedPanel);
|
|
await feedPanel.screenshot({
|
|
path: `${SHOTS}/03-permission-approved.png`,
|
|
});
|
|
});
|
|
|
|
test("04 — permission outcome (cancelled)", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
// Request
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_read",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: "turn-001",
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: "perm-cancelled-1",
|
|
method: "session/request_permission",
|
|
params: {
|
|
title: "Write to output file",
|
|
options: [
|
|
{ optionId: "opt-yes", kind: "allow_once", name: "Allow" },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
// Cancelled response
|
|
{
|
|
seq: 2,
|
|
timestamp: NOW,
|
|
kind: "acp_write",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: "turn-001",
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: "perm-cancelled-1",
|
|
result: {
|
|
outcome: {
|
|
outcome: "cancelled",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
await expect(feedPanel.getByText("Cancelled")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await settleAnimations(feedPanel);
|
|
await feedPanel.screenshot({
|
|
path: `${SHOTS}/04-permission-cancelled.png`,
|
|
});
|
|
});
|
|
|
|
test("05 — prompt context dialog (sections expanded)", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_write",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: "turn-001",
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "session/prompt",
|
|
params: {
|
|
prompt: [
|
|
{
|
|
type: "text",
|
|
text: "[Buzz event: Kind 9]\nContent: @Observer Agent help me debug this",
|
|
},
|
|
{
|
|
type: "text",
|
|
text: "[Thread context]\nThis is the thread history with 3 prior messages.",
|
|
},
|
|
{
|
|
type: "text",
|
|
text: "[Channel context]\nYou are in #agents, a channel for AI coordination.",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
// Open the dialog via the CheckCheck ingress, then expand every section.
|
|
const contextToggle = feedPanel.getByTestId(
|
|
"transcript-prompt-context-toggle",
|
|
);
|
|
await expect(contextToggle).toBeVisible({ timeout: 5_000 });
|
|
await contextToggle.click();
|
|
const dialog = page.getByRole("dialog");
|
|
await expect(dialog).toBeVisible({ timeout: 5_000 });
|
|
|
|
const sectionButtons = dialog
|
|
.getByTestId("transcript-prompt-context-sections")
|
|
.getByRole("button");
|
|
for (const btn of await sectionButtons.all()) {
|
|
await btn.click();
|
|
}
|
|
await settleAnimations(dialog);
|
|
await dialog.screenshot({
|
|
path: `${SHOTS}/05-prompt-context-expanded.png`,
|
|
});
|
|
});
|
|
|
|
test("06 — system prompt sections expanded", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_write",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: null,
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "session/new",
|
|
params: {
|
|
systemPrompt:
|
|
"[Base]\nYou are a helpful AI assistant running in Buzz.\n\n[System]\nYou are Observer Agent. You coordinate multi-agent workflows in the #agents channel.\n\n[Team Instructions]\nAlways tag on handoff.\n\n[Agent Memory — core]\nI am Observer Agent.\n## Lessons Learned\nAlways tag on handoff.\n\n[Channel Canvas]\nCanvas revision (event ID): a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\nLast modified: 2026-07-11T10:00:00Z\nFetch current content with: buzz canvas get --channel 94a444a4-c0a3-5966-ab05-530c6ddc2301",
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
await expect(feedPanel.getByText("System prompt")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
|
|
// All five section headings must be present in the card — expand it first.
|
|
await feedPanel.getByTestId("transcript-metadata-item").evaluate((el) => {
|
|
if (el.tagName === "DETAILS") (el as HTMLDetailsElement).open = true;
|
|
for (const details of el.querySelectorAll("details")) {
|
|
details.open = true;
|
|
}
|
|
});
|
|
await expect(feedPanel.getByText("Base")).toBeVisible({ timeout: 5_000 });
|
|
await expect(feedPanel.getByText("System", { exact: true })).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await expect(feedPanel.getByText("Team Instructions")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await expect(feedPanel.getByText("Core Memory")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await expect(feedPanel.getByText("Channel Canvas")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
|
|
// Open the outer ActivityRow <details> to reveal the section content,
|
|
// then click each section accordion button to expand it (mirrors shot 05 —
|
|
// system-prompt sections now render as React button accordions, not native
|
|
// <details> elements).
|
|
await feedPanel.getByTestId("transcript-metadata-item").evaluate((el) => {
|
|
if (el.tagName === "DETAILS") (el as HTMLDetailsElement).open = true;
|
|
for (const details of el.querySelectorAll("details")) {
|
|
details.open = true;
|
|
}
|
|
});
|
|
const sectionButtons = feedPanel
|
|
.getByTestId("transcript-metadata-item")
|
|
.getByTestId("transcript-prompt-context-sections")
|
|
.getByRole("button");
|
|
const allSectionButtons = await sectionButtons.all();
|
|
expect(allSectionButtons.length).toBeGreaterThan(0);
|
|
for (const btn of allSectionButtons) {
|
|
await btn.click();
|
|
}
|
|
await settleAnimations(feedPanel);
|
|
await feedPanel.screenshot({
|
|
path: `${SHOTS}/06-system-prompt-expanded.png`,
|
|
});
|
|
});
|
|
|
|
test("07 — current_mode_update lifecycle status line", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_read",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-007",
|
|
turnId: "turn-007",
|
|
payload: {
|
|
method: "session/update",
|
|
params: {
|
|
sessionId: "session-007",
|
|
update: {
|
|
sessionUpdate: "current_mode_update",
|
|
currentModeId: "plan",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
await expect(feedPanel.getByText("Mode")).toBeVisible({ timeout: 5_000 });
|
|
await settleAnimations(feedPanel);
|
|
await feedPanel.screenshot({
|
|
path: `${SHOTS}/07-current-mode-update.png`,
|
|
});
|
|
});
|
|
|
|
test("08 — usage_update lifecycle status line (coalesced)", async ({
|
|
page,
|
|
}) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
// Two usage frames — only the last should be visible.
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_read",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-008",
|
|
turnId: "turn-008",
|
|
payload: {
|
|
method: "session/update",
|
|
params: {
|
|
sessionId: "session-008",
|
|
update: {
|
|
sessionUpdate: "usage_update",
|
|
used: 1200,
|
|
size: 8192,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
seq: 2,
|
|
timestamp: NOW,
|
|
kind: "acp_read",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-008",
|
|
turnId: "turn-008",
|
|
payload: {
|
|
method: "session/update",
|
|
params: {
|
|
sessionId: "session-008",
|
|
update: {
|
|
sessionUpdate: "usage_update",
|
|
used: 3450,
|
|
size: 8192,
|
|
cost: { amount: 0.0018, currency: "USD" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
await expect(feedPanel.getByText("Usage")).toBeVisible({ timeout: 5_000 });
|
|
await settleAnimations(feedPanel);
|
|
await feedPanel.screenshot({
|
|
path: `${SHOTS}/08-usage-update-coalesced.png`,
|
|
});
|
|
});
|
|
|
|
test("09 — available_commands_update lifecycle status line", async ({
|
|
page,
|
|
}) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_read",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-009",
|
|
turnId: "turn-009",
|
|
payload: {
|
|
method: "session/update",
|
|
params: {
|
|
sessionId: "session-009",
|
|
update: {
|
|
sessionUpdate: "available_commands_update",
|
|
availableCommands: [
|
|
{
|
|
name: "create_plan",
|
|
description: "Create a structured plan for the task",
|
|
},
|
|
{
|
|
name: "research_codebase",
|
|
description: "Research and understand the codebase",
|
|
},
|
|
{
|
|
name: "execute_steps",
|
|
description: "Execute the planned steps",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
await expect(feedPanel.getByText("Commands", { exact: true })).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await settleAnimations(feedPanel);
|
|
await feedPanel.screenshot({
|
|
path: `${SHOTS}/09-available-commands-update.png`,
|
|
});
|
|
});
|
|
|
|
test("10 — config_option_update lifecycle status line", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "acp_read",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-010",
|
|
turnId: "turn-010",
|
|
payload: {
|
|
method: "session/update",
|
|
params: {
|
|
sessionId: "session-010",
|
|
update: {
|
|
sessionUpdate: "config_option_update",
|
|
configOptions: [
|
|
{
|
|
id: "model",
|
|
name: "Model",
|
|
type: "select",
|
|
currentValue: "gpt-4o",
|
|
},
|
|
{
|
|
id: "mode",
|
|
name: "Mode",
|
|
type: "select",
|
|
currentValue: "auto",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
await expect(feedPanel.getByText("Config")).toBeVisible({ timeout: 5_000 });
|
|
await settleAnimations(feedPanel);
|
|
await feedPanel.screenshot({
|
|
path: `${SHOTS}/10-config-option-update.png`,
|
|
});
|
|
});
|
|
|
|
test("11 — first-turn bundle: standalone system-prompt card + Checks ingress (per-turn context only)", async ({
|
|
page,
|
|
}) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
const feedPanel = await openObserverFeedPanel(page, OBSERVER_AGENT_PUBKEY);
|
|
|
|
// Full realistic pool.rs first-turn wire sequence for a team-pack agent:
|
|
// turn_started → session/new → session_resolved → session/prompt
|
|
// Verifies the consolidated presentation: session/new.systemPrompt always
|
|
// renders as a standalone top-level "System prompt" card (never injected into
|
|
// the CheckCheck bundle). The CheckCheck dialog contains only per-turn context
|
|
// (Buzz event / Thread context) — no Base/System/Team Instructions/Core Memory/Channel Canvas sections.
|
|
await seedObserverEvents(page, OBSERVER_AGENT_PUBKEY, [
|
|
{
|
|
seq: 1,
|
|
timestamp: NOW,
|
|
kind: "turn_started",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: null,
|
|
turnId: "turn-001",
|
|
payload: { source: "channel", triggeringEventIds: [] },
|
|
},
|
|
{
|
|
seq: 2,
|
|
timestamp: NOW,
|
|
kind: "acp_write",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: null,
|
|
turnId: "turn-001",
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "session/new",
|
|
params: {
|
|
systemPrompt:
|
|
"[Base]\nYou are a helpful AI assistant running in Buzz.\n\n[System]\nYou are Observer Agent. You coordinate multi-agent workflows in the #agents channel.\n\n[Team Instructions]\nAlways tag on handoff.\n\n[Agent Memory — core]\nI am Observer Agent.\n## Lessons Learned\nAlways tag on handoff.\n\n[Channel Canvas]\nCanvas revision (event ID): a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\nLast modified: 2026-07-11T10:00:00Z\nFetch current content with: buzz canvas get --channel 94a444a4-c0a3-5966-ab05-530c6ddc2301",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
seq: 3,
|
|
timestamp: NOW,
|
|
kind: "session_resolved",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: "turn-001",
|
|
payload: { sessionId: "session-001", isNewSession: true },
|
|
},
|
|
{
|
|
seq: 4,
|
|
timestamp: NOW,
|
|
kind: "acp_write",
|
|
agentIndex: 0,
|
|
channelId: CHANNEL_ID,
|
|
sessionId: "session-001",
|
|
turnId: "turn-001",
|
|
payload: {
|
|
jsonrpc: "2.0",
|
|
id: 2,
|
|
method: "session/prompt",
|
|
params: {
|
|
prompt: [
|
|
{
|
|
type: "text",
|
|
text: "[Buzz event: Kind 9]\nContent: @Observer Agent help me debug this",
|
|
},
|
|
{
|
|
type: "text",
|
|
text: "[Thread context]\nThis is the thread history with 3 prior messages.",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
// User message bubble should be visible (anchors the prompt bundle).
|
|
await expect(feedPanel.getByTestId("transcript-prompt-bundle")).toBeVisible(
|
|
{ timeout: 5_000 },
|
|
);
|
|
|
|
// Scroll to the top of the feed to bring the standalone "System prompt"
|
|
// card (which precedes the user bubble) into view.
|
|
await feedPanel.evaluate((el) => el.scrollTo({ top: 0 }));
|
|
|
|
// Standalone "System prompt" card is visible as a top-level feed row —
|
|
// it must remain present even after the first prompt arrives.
|
|
await expect(feedPanel.getByText("System prompt")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
|
|
// The standalone card shows "5 sections" collapsed — expand it to reveal
|
|
// the section headings, then assert all five are present.
|
|
await feedPanel.getByTestId("transcript-metadata-item").evaluate((el) => {
|
|
if (el.tagName === "DETAILS") (el as HTMLDetailsElement).open = true;
|
|
for (const details of el.querySelectorAll("details")) {
|
|
details.open = true;
|
|
}
|
|
});
|
|
await expect(feedPanel.getByText("Base")).toBeVisible({ timeout: 5_000 });
|
|
await expect(feedPanel.getByText("System", { exact: true })).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await expect(feedPanel.getByText("Team Instructions")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await expect(feedPanel.getByText("Core Memory")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await expect(feedPanel.getByText("Channel Canvas")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
|
|
// Per-turn prompt context (Buzz event / Thread context) does NOT appear
|
|
// as a standalone feed row — it lives behind the CheckCheck toggle.
|
|
await expect(feedPanel.getByText("Prompt context")).toHaveCount(0);
|
|
|
|
// Open the CheckCheck dialog: it contains ONLY per-turn context sections
|
|
// (Buzz event, Thread context). Base/System/Team Instructions/Core Memory/Channel Canvas must NOT appear.
|
|
await feedPanel.getByTestId("transcript-prompt-context-toggle").click();
|
|
const dialog = page.getByRole("dialog");
|
|
await expect(dialog).toBeVisible({ timeout: 5_000 });
|
|
const sectionArticles = dialog
|
|
.getByTestId("transcript-prompt-context-sections")
|
|
.locator("article");
|
|
const sectionTitles = await sectionArticles.allInnerTexts();
|
|
// Only per-turn context sections (Buzz event + Thread context) — no system-prompt sections.
|
|
expect(sectionTitles.length).toBe(2);
|
|
expect(sectionTitles[0]).toContain("Buzz event");
|
|
expect(sectionTitles[1]).toContain("Thread context");
|
|
// Collect all article heading text and assert none of the five
|
|
// system-prompt section labels appear — including exact "System" which
|
|
// would be ambiguous via substring search on the full dialog text.
|
|
const forbidden = [
|
|
"Base",
|
|
"System",
|
|
"Team Instructions",
|
|
"Core Memory",
|
|
"Channel Canvas",
|
|
];
|
|
for (const title of sectionTitles) {
|
|
for (const label of forbidden) {
|
|
expect(title).not.toContain(label);
|
|
}
|
|
}
|
|
await settleAnimations(dialog);
|
|
await dialog.screenshot({
|
|
path: `${SHOTS}/11-first-turn-ordering.png`,
|
|
});
|
|
});
|
|
});
|