Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
cc941bb
feat(desktop): add developer mode display style (AGNTOPS-354)
joahg Jul 29, 2026
31574ab
feat(desktop): dev mode prompt cards with split-screen thread side chat
joahg Jul 29, 2026
e06e4f0
feat(desktop): dev mode command palette, channel navigator, split-pan…
joahg Jul 29, 2026
9e6cac3
feat(desktop): place channel and side-chat composers side by side in …
joahg Jul 29, 2026
7bdca28
feat(desktop): persistent categorized channel list, reactions, and ch…
joahg Jul 29, 2026
46d855d
dev-mode: name session channels via a one-shot agent completion
joahg Jul 29, 2026
3906a3d
Merge remote-tracking branch 'origin/main' into joah/dev-mode-display…
joahg Jul 29, 2026
1a8ef7a
dev-mode: auto-grow composers with a draggable resize handle
joahg Jul 29, 2026
9bf4123
dev-mode: clickable Slack-style links with fetched page titles
joahg Jul 29, 2026
74c10fa
dev-mode: draggable channel navigator width, no-wrap keyboard hint
joahg Jul 29, 2026
54aff41
dev-mode: align top-bar title with traffic lights, show channel name
joahg Jul 29, 2026
09cbf18
dev-mode: keyboard-only card selection, clicks never steal composer f…
joahg Jul 29, 2026
672892e
dev-mode: full-width composer in channel navigator view
joahg Jul 29, 2026
272f33e
dev-mode: hide prompt-card borders until keyboard selection
joahg Jul 29, 2026
71feefe
dev-mode: palette add-member and leave-channel actions, channels-firs…
joahg Jul 29, 2026
1cf6596
dev-mode: leave archives the channel when you're the last human member
joahg Jul 29, 2026
ebd3002
dev-mode: replace channel categories with pinned chats
joahg Jul 29, 2026
016676d
dev-mode: render known @mentions as composer-style pills
joahg Jul 29, 2026
edc8be7
dev-mode: drop channel name/topic header from the transcript
joahg Jul 29, 2026
66bb911
dev-mode: move the caret out of the composer while a prompt card is s…
joahg Jul 29, 2026
59913d3
dev-mode: add new channel and draft thread shortcuts
joahg Jul 29, 2026
616fba3
dev-mode: stack name/timestamp above left-aligned message content
joahg Jul 29, 2026
e6c3c0e
dev-mode: add pin/unpin channel action to the command palette
joahg Jul 29, 2026
c114faf
Merge remote-tracking branch 'origin/main' into joah/dev-mode-display…
joahg Jul 29, 2026
44dfb37
dev-mode: add archive channel action to the command palette
joahg Jul 29, 2026
b0a5eab
dev-mode: autocomplete clickable channel references
joahg Jul 29, 2026
4ebe379
dev-mode: opt+arrow channel stepping from the composer
joahg Jul 29, 2026
1577a36
dev-mode: stop composer measurement from scrolling the chat
joahg Jul 29, 2026
137b5df
dev-mode: track unread channel-level posts only
joahg Jul 29, 2026
9c936f4
dev-mode: render agent direction as a 'to Name' line, not an inline tag
joahg Jul 29, 2026
33e926c
dev-mode: composer target renders as a 'to Name' line atop the chat box
joahg Jul 29, 2026
8ddadf2
dev-mode: keep chat content within its pane
joahg Jul 29, 2026
a4c7084
dev-mode: render agent markdown as rich formatting
joahg Jul 29, 2026
1ffed6e
dev-mode: show channel members and narrate joins/leaves
joahg Jul 29, 2026
0c18463
dev-mode: use JetBrains Mono for monospace text
joahg Jul 29, 2026
1ec4a66
dev-mode: keep agent mentions inline
joahg Jul 29, 2026
714513f
Merge remote-tracking branch 'origin/main' into joah/dev-mode-display…
joahg Jul 29, 2026
49f5c46
dev-mode: autocomplete relay user mentions
joahg Jul 29, 2026
c5910eb
dev-mode: rank typed action verbs first; land on recent chat after ar…
joahg Jul 29, 2026
b306362
Merge remote-tracking branch 'origin/main' into joah/dev-mode-display…
joahg Jul 29, 2026
8920a1e
dev-mode: default composer to an agent target instead of chat
joahg Jul 29, 2026
2873b31
dev-mode: always show the first thread reply inline, collapse the rest
joahg Jul 29, 2026
01fca69
dev-mode: parent/sub-channels with tabs, spawn draft, and CLI --parent
joahg Jul 29, 2026
e133811
Merge remote-tracking branch 'origin/main' into joah/dev-mode-display…
joahg Jul 29, 2026
7d19ace
dev-mode: scale the member list to 1000+ member channels
joahg Jul 29, 2026
78678ae
dev-mode: surface sub-channels as keyboard-driven tabs
joahg Jul 29, 2026
93e8258
dev-mode: rename channels and tabs from the command palette
joahg Jul 29, 2026
cc902de
dev mode: contextual unread indicators bubble up from tabs and threads
joahg Jul 29, 2026
4b373ed
fix(desktop): clean up crunchy default notification sound
joahg Jul 28, 2026
ec80e05
fix(desktop): clean clicks and truncation artifacts in all notificati…
joahg Jul 28, 2026
02ae5d4
dev mode: show all leading agent replies inline until a human responds
joahg Jul 29, 2026
0085401
dev mode: align composer 'to agent' line with the message text
joahg Jul 29, 2026
7add5ed
dev mode: inline 'to agent' in the header line, flatten reaction chips
joahg Jul 29, 2026
e15613b
dev mode: swap draft banner into the mode line row to avoid layout shift
joahg Jul 29, 2026
3367acf
dev mode: paste image/video attachments and render media in transcripts
joahg Jul 29, 2026
661e080
Merge remote-tracking branch 'origin/main' into joah/dev-mode-display…
joahg Jul 29, 2026
675e88d
dev mode: only route to the side chat when unread replies are collapsed
joahg Jul 29, 2026
dfafdc3
dev mode: palette joins non-member channels; developer community rail
joahg Jul 29, 2026
5cb96ef
Merge remote-tracking branch 'origin/main' into joah/dev-mode-display…
joahg Jul 29, 2026
f0e0f04
dev mode: open the command palette with cmd+k instead of ctrl+o
joahg Jul 29, 2026
de7a08f
Merge remote-tracking branch 'origin/main' into joah/dev-mode-display…
joahg Jul 29, 2026
484a485
dev mode: drop traffic-light title clearance when the community rail …
joahg Jul 29, 2026
bf82711
dev mode: align top bar columns, hide key hints behind a ? pane, clic…
joahg Jul 29, 2026
2bacfec
Merge remote-tracking branch 'origin/main' into joah/dev-mode-display…
joahg Jul 29, 2026
ff7fd0f
dev mode: drop top-bar palette/standard-ui buttons, right-align membe…
joahg Jul 29, 2026
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
21 changes: 21 additions & 0 deletions crates/buzz-acp/src/acp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ pub struct AcpClient {
/// deltas. Both goose and buzz-agent emit this notification; goose gates
/// on client capability advertisement, buzz-agent emits unconditionally.
goose_usage: UsageTracker,
/// Accumulates `agent_message_chunk` text while a one-shot helper (the
/// `complete` subcommand) is capturing the agent's reply. `None` disables
/// capture — the normal harness path never allocates here.
captured_agent_text: Option<String>,
}

/// Recursively merge `overlay` into `base`, with `overlay` winning on scalar/shape
Expand Down Expand Up @@ -550,9 +554,23 @@ impl AcpClient {
steering_supported: false,
steer_rx: None,
goose_usage: UsageTracker::default(),
captured_agent_text: None,
})
}

/// Start capturing `agent_message_chunk` text from `session/update`
/// notifications. Used by the one-shot `complete` subcommand to collect
/// the agent's reply text; the harness path never enables this.
pub fn begin_text_capture(&mut self) {
self.captured_agent_text = Some(String::new());
}

/// Take the text captured since [`begin_text_capture`](Self::begin_text_capture)
/// and disable capture.
pub fn take_captured_text(&mut self) -> String {
self.captured_agent_text.take().unwrap_or_default()
}

/// Attach a local observer feed to this ACP client.
pub fn set_observer(&mut self, observer: Option<ObserverHandle>, agent_index: usize) {
self.observer = observer;
Expand Down Expand Up @@ -1715,6 +1733,9 @@ impl AcpClient {
"agent_message_chunk" => {
if let Some(text) = update["content"]["text"].as_str() {
tracing::info!(target: "acp::stream", "{text}");
if let Some(buffer) = self.captured_agent_text.as_mut() {
buffer.push_str(text);
}
}
false
}
Expand Down
10 changes: 10 additions & 0 deletions crates/buzz-acp/src/base_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ Run `buzz --help` or `buzz <group> --help` for full usage. For multiline message

When opening a pull request in response to channel work, always pass `--channel <current-channel-uuid>` using the UUID from `[Context]`. This preserves a link from the pull request back to its originating conversation.

## Sub-channels

Sub-channels are focused working sessions under a main channel. They use the one-level naming convention `parent--sub`; a sub-channel cannot itself be a parent.

Spawn one with `buzz channels create --name <slug> --parent <parent-channel-id> --description "<task>"`. The sub-channel inherits the parent's type and visibility; the command constructs the full name, announces the spawn in the parent, and updates both canvases.

Only ever add someone to a sub-channel if they are already a member of its parent. Humans may join a sub-channel to steer the work or stay out of it.

Before considering the task done, post a final summary to the parent channel as a thread reply to the spawn announcement. The sub-channel canvas records both the parent channel and the announcement event ID under `spawned-from`.

## Conversational Agent Creation

When someone asks to create an agent, ask for at most two things: the agent's name and what it should do day-to-day. Turn the user's rough purpose into the `--system-prompt` yourself; do not separately ask for purpose, tone, constraints, access, runtime, provider, or model unless the user's request is genuinely ambiguous.
Expand Down
29 changes: 29 additions & 0 deletions crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,35 @@ pub struct AuthAgentArgs {
pub agent_args: Vec<String>,
}

/// CLI args for `buzz-acp complete` — one-shot prompt completion via the
/// configured agent. Spawns the agent, opens a session, sends a single
/// prompt, and prints the accumulated agent reply text. No relay connection.
#[derive(Debug, Parser)]
#[command(
name = "buzz-acp complete",
about = "One-shot prompt completion via the configured agent"
)]
pub struct CompleteArgs {
#[command(flatten)]
pub agent: AuthAgentArgs,

/// Prompt text to send to the agent.
#[arg(long)]
pub prompt: String,

/// Optional system prompt for the session.
#[arg(long)]
pub system_prompt: Option<String>,

/// Optional model id to select via `session/set_model` (best-effort).
#[arg(long)]
pub model: Option<String>,

/// Output structured JSON (`{"text": ...}`) instead of raw text.
#[arg(long)]
pub json: bool,
}

/// CLI args for `buzz-acp auth-methods` — query adapter-advertised login methods.
#[derive(Debug, Parser)]
#[command(
Expand Down
104 changes: 103 additions & 1 deletion crates/buzz-acp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use buzz_core::observer::{
};
use clap::Parser;
use config::{
AuthAgentArgs, AuthMethodsArgs, AuthenticateArgs, Config, DedupMode, ModelsArgs,
AuthAgentArgs, AuthMethodsArgs, AuthenticateArgs, CompleteArgs, Config, DedupMode, ModelsArgs,
MultipleEventHandling, RespondTo, SubscribeMode,
};
use filter::SubscriptionRule;
Expand Down Expand Up @@ -1251,6 +1251,16 @@ async fn tokio_main() -> Result<()> {
return run_models(args).await;
}

if is_subcommand("complete") {
let filtered: Vec<String> = std::env::args()
.enumerate()
.filter(|(i, _)| *i != 1)
.map(|(_, a)| a)
.collect();
let args = CompleteArgs::parse_from(&filtered);
return run_complete(args).await;
}

if is_subcommand("auth-methods") {
let filtered: Vec<String> = std::env::args()
.enumerate()
Expand Down Expand Up @@ -4176,6 +4186,98 @@ async fn run_models(args: ModelsArgs) -> Result<()> {
Ok(())
}

/// Setup timeout for `buzz-acp complete` (spawn + initialize + session/new).
/// Longer than [`MODELS_TIMEOUT`] because agents cold-start here (goose can
/// take >10s to initialize its first session).
const COMPLETE_SETUP_TIMEOUT: Duration = Duration::from_secs(30);

/// Idle timeout for `buzz-acp complete` — resets on any agent stdout activity.
const COMPLETE_IDLE_TIMEOUT: Duration = Duration::from_secs(30);

/// Hard wall-clock cap for `buzz-acp complete`.
const COMPLETE_MAX_DURATION: Duration = Duration::from_secs(90);

/// Flow: spawn → initialize → session/new → session/prompt → print captured
/// agent text → shutdown. No relay connection, no MCP servers.
///
/// Powers desktop features that need a bounded request/response completion
/// from the configured agent (e.g. Developer Mode channel naming).
async fn run_complete(args: CompleteArgs) -> Result<()> {
let agent_args = config::normalize_agent_args(&args.agent.agent_command, args.agent.agent_args);
let cwd = std::env::current_dir()
.unwrap_or_else(|_| std::path::PathBuf::from("/"))
.to_string_lossy()
.to_string();

// Spawn outside the timeout so we always own the child for cleanup.
let mut client =
match AcpClient::spawn(&args.agent.agent_command, &agent_args, &[], false).await {
Ok(c) => c,
Err(e) => {
eprintln!("error: failed to spawn agent: {e}");
std::process::exit(1);
}
};

let setup_result = tokio::time::timeout(COMPLETE_SETUP_TIMEOUT, async {
client.initialize().await?;
let session = client
.session_new_full(&cwd, vec![], args.system_prompt.as_deref(), None)
.await?;
Ok::<_, acp::AcpError>(session.session_id)
})
.await;

let session_id = match setup_result {
Ok(Ok(id)) => id,
Ok(Err(e)) => {
client.shutdown().await;
eprintln!("error: agent communication failed: {e}");
std::process::exit(1);
}
Err(_) => {
client.shutdown().await;
eprintln!("error: agent timed out ({COMPLETE_SETUP_TIMEOUT:?})");
std::process::exit(1);
}
};

// Best-effort model selection — not all agents support session/set_model.
if let Some(model) = &args.model {
let _ = client.session_set_model(&session_id, model).await;
}

client.begin_text_capture();
let prompt_result = client
.session_prompt_with_idle_timeout(
&session_id,
&args.prompt,
COMPLETE_IDLE_TIMEOUT,
COMPLETE_MAX_DURATION,
)
.await;
let text = client.take_captured_text();
client.shutdown().await;

if let Err(e) = prompt_result {
eprintln!("error: prompt failed: {e}");
std::process::exit(1);
}

let trimmed = text.trim();
if trimmed.is_empty() {
eprintln!("error: agent returned no message text");
std::process::exit(1);
}

if args.json {
println!("{}", serde_json::json!({ "text": trimmed }));
} else {
println!("{trimmed}");
}
Ok(())
}

fn build_mcp_servers(config: &Config) -> Vec<McpServer> {
if config.mcp_command.is_empty() {
return vec![];
Expand Down
Loading
Loading