Skip to content
Merged
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
49 changes: 10 additions & 39 deletions crates/buzz-acp/TESTING.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,29 @@
# Pi adapter integration

Buzz's Pi preset uses [salman1993/pi-acp](https://github.com/salman1993/pi-acp).
Requires Node.js 22 or newer. Install Pi and configure its model provider,
then install the adapter directly from the fork:
Buzz uses the [buzz-pi-acp fork](https://github.com/salman1993/buzz-pi-acp).
Install Node.js 22 or newer, configure Pi, and install the latest development
adapter from the fork's `main` branch:

```sh
npm install -g @earendil-works/pi-coding-agent
pi
npm install -g --install-links=true git+https://github.com/salman1993/pi-acp.git#main
npm install -g --install-links=true 'git+https://github.com/salman1993/buzz-pi-acp.git#main'
Comment thread
salman1993 marked this conversation as resolved.
```

Restart Buzz, then select **Pi** as the agent harness. Buzz starts `buzz-pi-acp`
automatically. Run the same adapter install command again to update it.
The unscoped `npm install -g pi-acp` command installs the upstream package,
without these extensions. Use fresh sessions to replace old user-framed
standing instructions.
This test setup intentionally tracks `main`; the Desktop runtime catalog pins a
reviewed adapter revision for users.

Buzz adds `-- --skill <harness-cwd>/.agents/skills` when launching `buzz-pi-acp`.
An existing separator and explicit Pi options are preserved. Managed agents
run from the Buzz nest (normally `~/.buzz`): Desktop sets the `buzz-acp` child
CWD through `default_agent_workdir()`, and adapters inherit it. The default
skill directory is that launch workspace's `.agents/skills`. Standalone CLI
launches use the caller's working directory.
The path is fixed at adapter launch and applies to every Pi subprocess.
Make sure `pi` and `buzz-pi-acp` are on PATH, then restart Buzz.

The full composed session prompt is sent as a replacement string through the
provisional `session/new.params._meta.systemPrompt` field. Buzz recognizes
`buzz-pi-acp` by the agent name returned during initialization, regardless of
protocol version. Upstream `pi-acp` does not receive this fork-specific metadata.
No custom capability negotiation or legacy Pi prompt fallback is used.
Session titles continue to use `_meta.sessionTitle`.

## Validation

Activate Hermit from the Buzz repository root, then run the package tests:
## Tests

```sh
. ./bin/activate-hermit
cargo test -p buzz-acp
```

To exercise the real adapter through Buzz's production session composer:
Run the ignored real-adapter test with a built fork checkout:

```sh
BUZZ_TEST_PI_ACP=/absolute/pi-acp/dist/index.js \
BUZZ_TEST_PI_ACP=/absolute/buzz-pi-acp/dist/index.js \
cargo test -p buzz-acp real_pi_preserves -- --ignored
```

This test requires Node and Pi on PATH. It isolates HOME and Pi settings,
disables extensions and context files, and uses a synthetic transcript without
model calls. It inspects Pi's effective prompt through RPC HTML export after
switching sessions and restarting the adapter. Base, persona, team, core memory,
huddle, canvas, and the extra skill must each appear once, without another
session's instructions or Pi's default coding preamble.

HTML export reports the exporting process's current system prompt. It cannot
recover a historical prompt from an old transcript alone.
28 changes: 28 additions & 0 deletions crates/buzz-acp/src/setup_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ pub(crate) enum RequirementPayload {
},
/// Git for Windows is missing; open Agent runtimes for the installation guide.
GitBash,
/// A custom harness command could not be resolved in the current PATH.
MissingBinary { command: String },
}

impl RequirementPayload {
Expand Down Expand Up @@ -190,6 +192,9 @@ impl RequirementPayload {
RequirementPayload::GitBash => {
"install Git for Windows (open Agent runtimes in Settings to diagnose)".to_string()
}
RequirementPayload::MissingBinary { command } => {
format!("install `{command}` or add it to PATH")
}
Comment on lines +195 to +197

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route missing-binary nudges away from Edit Agent

When buzz-pi-acp is absent, this new variant now reaches the desktop card, but ConfigNudgeCard does not classify missing_binary as informational or Doctor-routed: shouldOpenDoctor is false and informationalOnly omits it, so clicking the card opens Edit Agent. That surface cannot install a binary or repair PATH and does not expose the Pi install guidance added to Agent runtimes; route Pi's missing adapter there, or make generic missing-binary cards non-clickable.

Useful? React with 👍 / 👎.

}
}
}
Expand Down Expand Up @@ -262,13 +267,19 @@ impl SetupPayload {
.requirements
.iter()
.all(|r| matches!(r, RequirementPayload::CliConfigInvalid { .. }));
let all_missing_binary = self
.requirements
.iter()
.all(|r| matches!(r, RequirementPayload::MissingBinary { .. }));
let any_external = self
.requirements
.iter()
.any(|r| matches!(r, RequirementPayload::CliConfigInvalid { .. }));

let footer = if has_doctor_requirement {
"Open Agent runtimes in Settings, install Git for Windows, then re-check and restart the agent.".to_string()
} else if all_missing_binary {
"Install the missing binary or update PATH, then restart Buzz.".to_string()
} else if all_external {
// All requirements are external config files — Edit Agent cannot
// help. Don't send the user there.
Expand Down Expand Up @@ -739,6 +750,23 @@ mod tests {
));
}

#[test]
fn setup_payload_deserializes_missing_binary_requirement() {
let payload = SetupPayload::from_raw_env_value(Some(
r#"{"agent_name":"Carol","agent_pubkey":"test","requirements":[{"surface":"missing_binary","command":"buzz-pi-acp"}]}"#.to_string(),
))
.unwrap()
.expect("missing_binary payload must parse");
assert!(matches!(
payload.requirements.as_slice(),
[RequirementPayload::MissingBinary { command }] if command == "buzz-pi-acp"
));
let body = payload.nudge_body();
assert!(body.contains("install `buzz-pi-acp` or add it to PATH"));
assert!(body.contains("restart Buzz"));
assert!(!body.contains("Open Edit Agent"));
}

#[tokio::test]
async fn authorized_workflow_nudge_mentions_effective_owner_not_relay_signer() {
let agent_keys = nostr::Keys::generate();
Expand Down
10 changes: 5 additions & 5 deletions desktop/src-tauri/src/managed_agents/discovery/presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ pub(super) const PRESET_HARNESSES: &[PresetHarness] = &[
label: "Pi",
command: "buzz-pi-acp",
args: &[],
install_instructions_url: "https://github.com/salman1993/pi-acp",
install_hint: "Requires Node.js 22 or newer. Install the Pi ACP adapter with `npm install -g --install-links=true git+https://github.com/salman1993/pi-acp.git#main`. Restart Buzz, then select Pi as the agent harness. Run the same install command again to update the adapter.",
install_instructions_url: "https://github.com/salman1993/buzz-pi-acp",
install_hint: "Requires Node.js 22 or newer. Install the Pi ACP adapter with `npm install -g --install-links=true 'git+https://github.com/salman1993/buzz-pi-acp.git#86b201e'`. Make sure `buzz-pi-acp` is on PATH, then restart Buzz.",
underlying_cli: Some("pi"),
underlying_cli_install_hint: Some(
"Install Pi with `npm install -g @earendil-works/pi-coding-agent`, then run `pi` to configure its model provider.",
Expand Down Expand Up @@ -434,11 +434,11 @@ mod tests {
assert!(adapter_missing.default_args.is_empty());
assert_eq!(
adapter_missing.install_hint,
"Requires Node.js 22 or newer. Install the Pi ACP adapter with `npm install -g --install-links=true git+https://github.com/salman1993/pi-acp.git#main`. Restart Buzz, then select Pi as the agent harness. Run the same install command again to update the adapter."
"Requires Node.js 22 or newer. Install the Pi ACP adapter with `npm install -g --install-links=true 'git+https://github.com/salman1993/buzz-pi-acp.git#86b201e'`. Make sure `buzz-pi-acp` is on PATH, then restart Buzz."
);
assert_eq!(
adapter_missing.install_instructions_url,
"https://github.com/salman1993/pi-acp"
"https://github.com/salman1993/buzz-pi-acp"
);

let cli_missing = preset_catalog_entry(preset, |command| {
Expand All @@ -462,7 +462,7 @@ mod tests {
);
assert_eq!(
not_installed.install_hint,
"Install Pi with `npm install -g @earendil-works/pi-coding-agent`, then run `pi` to configure its model provider. Requires Node.js 22 or newer. Install the Pi ACP adapter with `npm install -g --install-links=true git+https://github.com/salman1993/pi-acp.git#main`. Restart Buzz, then select Pi as the agent harness. Run the same install command again to update the adapter."
"Install Pi with `npm install -g @earendil-works/pi-coding-agent`, then run `pi` to configure its model provider. Requires Node.js 22 or newer. Install the Pi ACP adapter with `npm install -g --install-links=true 'git+https://github.com/salman1993/buzz-pi-acp.git#86b201e'`. Make sure `buzz-pi-acp` is on PATH, then restart Buzz."
);
}

Expand Down
16 changes: 16 additions & 0 deletions desktop/src/shared/ui/config-nudge-attachment.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ globalThis.window = {

import {
focusTargetForRequirement,
missingBinaryRecoveryMessage,
shouldOpenDoctor,
} from "./config-nudge-attachment.tsx";
import {
Expand Down Expand Up @@ -60,6 +61,21 @@ test("shouldOpenDoctor_regularMixedRequirements_routesToEditAgent", () => {
);
});

test("shouldOpenDoctor_missingBinary_routesToAgentRuntimes", () => {
assert.equal(
shouldOpenDoctor([{ surface: "missing_binary", command: "buzz-pi-acp" }]),
true,
"a missing runtime binary must route to Agent runtimes",
);
});

test("missingBinaryRecoveryMessage_requiresBuzzRestart", () => {
assert.equal(
missingBinaryRecoveryMessage(),
"not found in PATH — install it or update PATH, then restart Buzz",
);
});

// ── focusTargetForRequirement — pure function ─────────────────────────────────

test("focusTargetForRequirement_envKey_returnsEnvKeyTarget", () => {
Expand Down
41 changes: 28 additions & 13 deletions desktop/src/shared/ui/config-nudge-attachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,39 @@ function requirementKey(
}

/**
* Returns true when every requirement in the nudge is a `cli_login` surface.
* Non-authOnly all-cli_login cards (at least one install-state row) route to
* Agent runtimes — install/login problems can't be fixed in Edit Agent. AuthOnly cards
* (every row is `availability === "available"`) are purely informational and
* do not route anywhere.
* Requirements owned by runtime discovery route to Agent runtimes. This covers
* Git Bash, unresolved binaries, and all-CLI-login cards with installation work.
* Auth-only cards remain informational because Agent runtimes cannot log in to
* an external CLI for the user.
*/
function hasGitBashRequirement(
reqs: ConfigNudgePayload["requirements"],
): boolean {
return reqs.some((r) => r.surface === "git_bash");
}

function hasMissingBinaryRequirement(
reqs: ConfigNudgePayload["requirements"],
): boolean {
return reqs.some((r) => r.surface === "missing_binary");
}

function isAllCliLogin(reqs: ConfigNudgePayload["requirements"]): boolean {
return reqs.length > 0 && reqs.every((r) => r.surface === "cli_login");
}

export function shouldOpenDoctor(
reqs: ConfigNudgePayload["requirements"],
): boolean {
return isAllCliLogin(reqs) || hasGitBashRequirement(reqs);
return (
isAllCliLogin(reqs) ||
hasGitBashRequirement(reqs) ||
hasMissingBinaryRequirement(reqs)
);
}

export function missingBinaryRecoveryMessage(): string {
return "not found in PATH — install it or update PATH, then restart Buzz";
}

/**
Expand Down Expand Up @@ -165,9 +178,10 @@ export function focusTargetForRequirement(
* the system.
*
* Routing:
* (A) Any card with a `git_bash` requirement, or one whose requirements are all
* install-state `cli_login`, opens Settings → Agent runtimes. A card-level
* Agent runtimes label in `AttachmentActions` confirms the action at rest.
* (A) Any card with a `git_bash` or `missing_binary` requirement, or one whose
* requirements are all install-state `cli_login`, opens Settings → Agent
* runtimes. A card-level Agent runtimes label in `AttachmentActions` confirms
* the action at rest.
* (A-auth) A card whose requirements are all available `cli_login` surfaces is
* purely informational: Agent runtimes cannot authenticate a CLI, and `setup_copy`
* already gives the needed command.
Expand Down Expand Up @@ -211,7 +225,7 @@ export function ConfigNudgeCard({

const handleOpen = () => {
if (shouldOpenDoctor(nudge.requirements)) {
// Git Bash and install-state CLI requirements both resolve in Agent runtimes.
// Runtime installation and discovery requirements resolve in Agent runtimes.
// Informational-only cards never mount this trigger.
openDoctor();
} else {
Expand Down Expand Up @@ -384,15 +398,16 @@ function RequirementRow({
</div>
);
case "missing_binary": {
// Missing-binary rows are purely informational — the user must install the
// binary or update their PATH. No in-app action can fix this.
// Agent runtimes owns installation guidance and the re-check action. The
// restart guidance remains visible here because a normal agent restart does
// not invalidate Desktop's negative command-resolution cache.
return (
<div className="flex items-center gap-2 text-xs leading-4 text-muted-foreground">
<span className="flex-1 [overflow-wrap:anywhere]">
<code className="rounded bg-muted px-1 py-0.5 font-mono text-xs text-foreground">
{requirement.command}
</code>{" "}
not found in PATH — install it or check your PATH settings
{missingBinaryRecoveryMessage()}
</span>
</div>
);
Expand Down
Loading