Skip to content
Draft
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
4 changes: 4 additions & 0 deletions crates/plugin-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ pub fn bundled_manifests() -> Vec<Manifest> {
.expect("projects manifest"),
serde_json::from_str(include_str!("../../../src/bundled/agents/manifest.json"))
.expect("agents manifest"),
serde_json::from_str(include_str!(
"../../../src/bundled/agent-channels/manifest.json"
))
.expect("agent channels manifest"),
]
}
fn is_bundled(id: &str) -> bool {
Expand Down
1 change: 1 addition & 0 deletions crates/plugin-manager/tests/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ fn bundled_plugins_have_independent_flags_and_all_ids_are_reserved() {
"buzz.bestie",
"buzz.projects",
"buzz.agents",
"buzz.agent-channels",
"buzz.emoji",
"buzz.mentions",
] {
Expand Down
89 changes: 89 additions & 0 deletions docs/agent-channels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Agent dashboard: selected-community relationship view

## First slice

Agent dashboard is a bundled page plugin that gives an agent-first view of
currently selected community where an identity from the current Buzz agent
library is a member now or has authored activity returned by a finite verified
relay read. It is a relationship view, not a directory, ownership proof,
runtime monitor, or cross-community index.

The plugin injects `pages`, `relay`, and the shared external-object provider
registry. It reuses the session-owned agent library, identity archive evidence,
channel roster, verified finite reader and media helper. It does not create a
relay connection, cache, outbox or host route. Switching community or
reconnecting remounts its session-owned subtree using scope plus generation.

## Relationship model

`bundled/agent-channels/relationships.ts` produces three display-independent
records:

- agent nodes group exact non-archived library identity pubkeys by saved agent
definition; definitions without an active linked identity are omitted;
- channel nodes retain channel ID, name, relay-authored type and archive state;
- edges retain exact agent/channel IDs, current/past/unknown membership, first
and last observed activity, and observed message count.

Current membership comes only from the channel summary's relay-signed exact
member list. Activity comes only from verified or relay-accepted kind-9 events
authored by exact library identity pubkeys and carrying an authorized channel's
`h` tag; pending, unknown-delivery and failed local outbox events are excluded.
Display names never establish a relationship.

## Dashboard projection

The page projects the relationship records into one row per agent, ordered by
current channel membership and recent observed activity. Selecting an agent
centers its avatar in a bounded relationship map: channel nodes form the first
ring, and other agent avatars connect through channels they share with the
selected agent. The map deliberately limits visible nodes for legibility; the
selected agent's full channel list remains available below it and supplies the
non-visual equivalent for keyboard, touch and narrow layouts.

The dashboard summary retains current/past/unknown membership, sampled message
count and last observed activity. It does not infer presence, running state,
task status or complete lifetime work from messages.

## Pull request outcomes

The optional **Outcomes** view extracts canonical pull request links directly
from eligible agent-authored activity and retains each signed message ID,
channel, agent and share timestamp as association evidence. Repeated shares of
the same pull request are grouped rather than double-counted.

External details come through `features/objects`, a typed contribution registry
that is separate from panel presentation. The GitHub plugin registers both its
existing panel and an object provider, so Agent dashboard never imports GitHub
implementation code. The provider supplies current public state, title, author,
branch and change facts with bounded four-at-a-time loading; disabling GitHub
removes both capabilities. Private or unavailable GitHub objects remain visible
from their signed Buzz reference with a truthful enrichment error.

A shared link establishes observable association, not pull request authorship or
causation. The UI therefore says **shared by** and **associated work**, never
claims that an agent authored a pull request or caused its merge.

## Bounds and truthful states

Activity uses background-priority finite reads in bounded author and authorized-
channel batches. A shared 2,000-event request budget is allocated across all
batches, then the eligible results are merged by recency. The relationship
projection is separately capped at 10,000 edges and reports when that display
bound is reached. The read contract
does not return a historical completeness bound, so the page says **observed**
activity and never claims complete lifetime history. A channel omitted from the
current authorized roster is never displayed from activity alone.

The page exposes disconnected, connecting, unavailable library, loading, empty,
partial roster, failed update and retry states. Existing edges remain visible
when a later update fails. Archive evidence affects which saved identities are
shown; it is not channel access evidence.

## Validation

The pure projection tests cover exact identity grouping, current/past/unknown
edges, authorized-channel filtering and shared-reader inputs. App composition
covers registration, disable/re-enable and relay-session lifetime. The native
plugin manager catalogs the manifest independently so Settings and CLI reserve
and enable the same bundled ID as the browser runtime.
14 changes: 10 additions & 4 deletions docs/plugin-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ app/ host, startup, navigation, Settings
plugins/ installation, lifecycle, contribution ownership
features/pages/ page contract and host rendering
features/panels/ target resolution, launcher contract and reusable card/frame
features/objects/ typed external-object identification and data loading
features/shortcuts/ in-app binding dispatch, focus rules and plugin ownership
features/relay/ shared channel data, queries, profiles and durable delivery
features/messages/ reusable timeline, message, thread and composer UI
bundled/channels/ Channels navigation, sidebar, page layout and panel placement
bundled/projects/ title-only Projects page scaffold
bundled/agents/ read-only current-Buzz agent library page
bundled/agent-channels/ agent-first dashboard and relationship map
features/agents/ shared session-owned local library view
bundled/github/ builtin GitHub panel plugin
bundled/bestie/ builtin companion panel and its snake launcher
Expand Down Expand Up @@ -83,16 +85,20 @@ and returns false after the originating opening, channel, session or host
presentation retires. This is not a global navigation API or an access grant.
Channel-header launchers use the separate public `channelContext` metadata
contract described below; launcher/fallback panels need not have either context.
A plugin that needs shared data declares `relay` in its
injection list and passes those capabilities to its components using a closure,
just as the bundled Channels page does. The conversation preview adds only the two demonstrated component surfaces.
A page that needs integration facts without panel UI uses
`objects.resolve(target)` / `objects.load(target, signal)`; providers own
recognition and bounded external loading, and disappear when their plugin is
disabled. A plugin that needs shared data declares `relay` in its injection list
and passes those capabilities to its components using a closure, just as the
bundled Channels page does. The conversation preview adds only the two
demonstrated component surfaces.

Channels owns its selected channel and docked target. The panel view isolates
render failures and remounts on target or revision changes. Unloading a plugin
removes its contributions and closes its panel. Other pages can use these same
contracts with their own layout and local navigation.

The initial distribution contains Channels, Projects, Agents, GitHub, Bestie, Emoji, Mentions, Profiles and Terminal. Projects
The initial distribution contains Channels, Projects, Agents, Agent dashboard, GitHub, Bestie, Emoji, Mentions, Profiles and Terminal. Projects
is an enabled-by-default scaffold with only a centered title and no relay dependency.
GitHub recognizes repository,
pull request, issue, and commit URLs and loads public object details on demand.
Expand Down
29 changes: 27 additions & 2 deletions src/app/pages.integration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test("the app runtime exposes ready bundled pages and removes them on disable",
services = createServices();
assert.deepEqual(services.pages.snapshot(), []);
await settle();
assert.equal(services.pages.snapshot().length, 3);
assert.equal(services.pages.snapshot().length, 4);
await vi.waitFor(() =>
assert.equal(services.conversation.tools.snapshot().length, 2),
);
Expand Down Expand Up @@ -110,7 +110,7 @@ test("the app runtime exposes ready bundled pages and removes them on disable",
.some((panel) => panel.pluginId === "buzz.bestie"),
false,
);
assert.equal(services.pages.snapshot().length, 3);
assert.equal(services.pages.snapshot().length, 4);
await services.plugins.change("enable", "buzz.bestie");
// Management completion is not activation completion; Cordis still owns import/disposal barriers.
await vi.waitFor(() =>
Expand Down Expand Up @@ -139,6 +139,31 @@ test("the app runtime exposes ready bundled pages and removes them on disable",
/Connect to a community/,
);
const session = services.relay.snapshot().session;
const agentChannels = services.pages
.snapshot()
.find((page) => page.pluginId === "buzz.agent-channels");
assert.equal(agentChannels.title, "Agent dashboard");
assert.match(
renderToStaticMarkup(createElement(agentChannels.component)),
/Connect to a community to see where your agents are working/,
);
await services.plugins.change("disable", "buzz.agent-channels");
assert.equal(
services.pages
.snapshot()
.some((page) => page.pluginId === "buzz.agent-channels"),
false,
);
assert.equal(services.relay.snapshot().session, session);
await services.plugins.change("enable", "buzz.agent-channels");
await vi.waitFor(() =>
assert.ok(
services.pages
.snapshot()
.some((page) => page.pluginId === "buzz.agent-channels"),
),
);
await services.plugins.change("disable", "buzz.agent-channels");
await services.plugins.change("disable", "buzz.agents");
assert.equal(
services.pages.snapshot().some((page) => page.pluginId === "buzz.agents"),
Expand Down
3 changes: 3 additions & 0 deletions src/app/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConversationService } from "../features/conversation/service";
import { createAppearance } from "../shared/theme/service";
import { createCommunities } from "../features/communities/service";
import { PanelsService } from "../features/panels/service";
import { ObjectsService } from "../features/objects/service";
import { Context } from "@deepseek-ai/cordis";
import { PagesService } from "../features/pages/service";
import { bundledPlugins } from "../bundled";
Expand All @@ -22,6 +23,7 @@ export function createServices() {
const shortcuts = new ShortcutsService(ctx);
const pages = new PagesService(ctx);
const panels = new PanelsService(ctx);
const objects = new ObjectsService(ctx);
const conversation = new ConversationService(ctx);
const communities = createCommunities(
ctx,
Expand All @@ -36,6 +38,7 @@ export function createServices() {
conversation,
pages,
panels,
objects,
plugins,
relay,
communities,
Expand Down
Loading
Loading