fix(acp): improve hints in pi agent setup guide (#7594)

## Summary

Handle missing Pi adapter setup and update its install guidance.

### Related issue

None found.

### Testing

No manual testing.

Generated with Amp

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Salman Mohammed
2026-09-12 13:40:15 -04:00
committed by GitHub
parent f3940ff21e
commit 6c35e82bd5
5 changed files with 87 additions and 57 deletions

View File

@@ -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'
```
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.

View File

@@ -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 {
@@ -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")
}
}
}
}
@@ -262,6 +267,10 @@ 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()
@@ -269,6 +278,8 @@ impl SetupPayload {
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.
@@ -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();

View File

@@ -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.",
@@ -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| {
@@ -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."
);
}

View File

@@ -28,6 +28,7 @@ globalThis.window = {
import {
focusTargetForRequirement,
missingBinaryRecoveryMessage,
shouldOpenDoctor,
} from "./config-nudge-attachment.tsx";
import {
@@ -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", () => {

View File

@@ -43,11 +43,10 @@ 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"],
@@ -55,6 +54,12 @@ function hasGitBashRequirement(
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");
}
@@ -62,7 +67,15 @@ function isAllCliLogin(reqs: ConfigNudgePayload["requirements"]): boolean {
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";
}
/**
@@ -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.
@@ -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 {
@@ -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>
);