Skip to content

feat(agents): support native launch protection providers - #415

Merged
shellz-n-stuff merged 3 commits into
block:mainfrom
shellz-n-stuff:codex/protection-launch-hook
Oct 1, 2026
Merged

shellz-n-stuff merged 3 commits into
block:mainfrom
shellz-n-stuff:codex/protection-launch-hook

Conversation

@shellz-n-stuff

@shellz-n-stuff shellz-n-stuff commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Protected local agents run through a verified launcher snapshot using the merged ACP launch-prefix hook from block/buzz#7985. The bundled runtime and native Buzz dependency are pinned to 95b018c8. Missing or changed providers fail closed; the host has no sandbox-engine dependency.

The existing supervisor owns staged launcher/context and scratch directories until confirmed teardown. Workers receive the shared run-controls namespace, covering concurrent and future runs while OAuth caches and scratch remain writable. Store owns protection persistence, validation, atomic writes and JavaScript-safe revision bounds. The native provider contract ships in docs/agent-security.md.

Stack

  1. This PR: native launch protection → main
  2. #420 — Copied global defaults
  3. #421 — Plugin API

All three PRs are merged into upstream main in this order. Each successor was rebased and retargeted to main after its parent was squash-merged.

Validation

Rebased onto main at 5d5094e2; the launch-path conflict preserves both upstream Pi preflight verification and protected scratch setup. Native validation at a65b1d8d; final head 95e1a1ad adds only the one-line CI repair below:

  • 148 controller-package tests passed; two opt-in tests excluded from ordinary runs.
  • Real-ACP enforcing-provider integration passed separately (27.61s): concurrent/future control protection, staged launchers, delayed startup and crash recovery, subsequent prompts, OAuth/scratch writes, persistence and supervisor cleanup.
  • 40 native agent-host tests passed, including Pi launch/Stop fencing; one opt-in test excluded.
  • The rebase exposed an upstream duplicate dismissOnOutsideClick attribute in the channel-members dialog. Removing that duplicate preserves behavior and fixes the same lint failure present on main. Full lint, TypeScript, 162 related frontend tests and design checks passed after this one-line repair.
  • Mandatory staged checks and workspace Clippy passed. Independent integration review found no blockers.

The enforcing integration uses synthetic relay/workers. Real Goose/inference, native UI/human acceptance and non-macOS enforcement have not been verified by this work.

Hosted CI

All required CI lanes passed at 95e1a1ad, including all twelve browser shards. DCO and security checks passed. Squash-merged as 5e241502.

let tools = tempfile::tempdir().unwrap();
let mut saved = agent(dir.path());
let runtime = bundle(tools.path());
if worker == "goose" {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This bit could be further generalised

});
controller.action(&saved.id, Action::Stop).unwrap();
let provider = dir.path().join("provider");
fs::write(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Why do this?

@shellz-n-stuff
shellz-n-stuff force-pushed the codex/protection-launch-hook branch from caf36d3 to 7cf819d Compare September 29, 2026 18:46

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested. Three actionable launch-boundary findings are detailed inline. Address them with regression coverage through real ACP, including delayed launch and respawn; the current recording fixture replaces that boundary.

Reviewed head 7cf819d8e0c4fb56a8d535044d9dd1316dc2179a against base 6b4de53359b852e14f4c5f31afb1bb61793e3dc2. Independent security and lifecycle lanes reconciled. Hosted CI, including Rust/tool integration, JavaScript, browser checks and DCO, is green; Windows validation was skipped.

Validation: disposable fake workers under the hash-verified pinned ACP demonstrate direct/wrapped argument divergence. A disposable Seatbelt probe confirms the protected-path write conflict. The delayed-executable issue is source-traced. No live agents, credentials or relay were used; actual enforcement-plugin and live Goose UI behavior remain unverified.

Comment thread crates/agent-controller/src/security.rs Outdated
Comment on lines +229 to +238
let mut protected_paths = self.protection_paths.clone()?;
protected_paths.push(self.store.root().to_path_buf());
protected_paths.push(
provider
.executable
.parent()
.ok_or("Invalid protection launcher")?
.to_path_buf(),
);
protected_paths.push(temporary.to_path_buf());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] Separate protected control data from writable runtime state

This list marks both the entire controller store and temporary as protected, but start_with_key sets BUZZ_AGENT_CONFIG_DIR=store.root() and TMPDIR/TMP/TEMP=temporary (runtime.rs:808-814). The pinned bundled worker stores its OAuth cache beneath $BUZZ_AGENT_CONFIG_DIR/buzz-agent/oauth; refresh requires creating/writing lock and cache files (auth.rs). A provider honoring these directory protections therefore breaks Databricks refresh and tools that write temporary files. A disposable Seatbelt probe denying writes beneath these supplied paths rejects both operations with Operation not permitted.

Keep writable temp/OAuth state outside the protected control-plane directories, or supply an explicit safe per-path contract. In particular, move launch-protection.json out of the worker-writable temp directory before allowing those writes: mode 0400 does not prevent same-user unlink/replacement through a writable parent, and subsequent workers reuse this context. Add a representative enforcing-provider test covering required writes while the launch context remains immutable.

Comment thread crates/agent-controller/src/security.rs Outdated
Comment on lines +261 to +265
command
.env("BUZZ_ACP_AGENT_COMMAND", &provider.executable)
.env(
"BUZZ_ACP_AGENT_ARGS",
format!("--launch,{}", path.display()),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Preserve the worker identity when inserting the launcher

BUZZ_ACP_AGENT_COMMAND is not merely the executable path: the pinned ACP uses its basename for harness argument normalization and runtime-specific setup. Replacing it with the provider hides the actual worker before those steps, while the JSON context freezes the raw, pre-normalized arguments.

Concrete case: save a Goose agent with Advanced args [] (accepted by the controller), then enable protection. Unprotected, ACP supplies acp; protected, the provider receives args: [] and launches Goose without its ACP subcommand. An isolated probe with the actual hash-verified pinned ACP and a recording worker produced ['acp'] directly versus [] through the documented launcher protocol. The normalization is in config.rs:871-895; other identity-dependent setup is affected too.

Keep actual harness identity separate from the launch prefix, preferably at ACP's existing worker-spawn boundary, so wrapping happens without losing its normalization/environment behavior. Test direct/protected parity through real ACP; the new shell substitute bypasses this logic entirely.

Comment thread crates/agent-controller/src/security.rs Outdated
Comment on lines +209 to +211
if executable_digest(&provider.executable)? != provider.digest {
return Err("Protection launcher changed; re-enable the plugin before starting".into());
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Bind delayed worker launches to the verified launcher bytes

This digest is checked only when starting the listener, not when the provider executable is actually run. The controller always sets BUZZ_ACP_LAZY_POOL=true and a 900-second idle sleep. The pinned ACP retains the provider pathname and executes it later on first work, idle wake and crash respawn (lib.rs:3036 onward). None of those paths returns through this check.

Register launcher A, start a protected listener without sending work, then replace/update the executable to B before the first event: B is launched without re-registration or digest refusal. The same occurs after idle or crash recovery. This is an unbounded lifecycle gap, not a narrow concurrent file-swap race, and contradicts this PR's changed-executable refusal contract.

Ensure each worker launch uses the verified version (or revalidates in a trusted spawn boundary). Add a gated regression that changes the launcher after listener start but before first worker launch, then exercises a later respawn. Existing running workers need not be terminated to fix this.

shellz-n-stuff added a commit to block/buzz that referenced this pull request Sep 30, 2026
## Summary

Replacing `BUZZ_ACP_AGENT_COMMAND` with a sandbox launcher hides the
real adapter from Buzz: for example, Goose loses its default `acp`
argument. Add optional `BUZZ_ACP_LAUNCH_PREFIX`, a JSON argument array
applied at the shared subprocess boundary, after normal worker
configuration.

On Unix, every spawn runs `prefix... worker args...` without shell
interpolation. Worker identity, environment setup, stdio and existing
process cleanup are preserved. Invalid or unavailable prefixes fail
launch without falling back to the worker; unset keeps direct launch on
every platform. Configured prefixes fail closed on non-Unix because
per-worker process-tree cleanup is unavailable. Desktop reserves the key
against saved user-environment overrides, and spawn failures name the
executable.

### Related issue

Prerequisite for [Buzz-App
#415](block/buzz-app#415). One commit directly
on `main`, independent of #7942–#7944. No duplicate launch-prefix PR
found. Policy enforcement, verified launcher staging, protected paths
and the supporting runtime pin remain Buzz-App/plugin work.

### Testing

- Original head `9ae9d82f8`: complete ACP suite (990 passed, three
existing ignored), shipped local-task test, real-Goose probe, and full
repository-wide `just ci` passed.
- Review fixes: five launch tests and 44 Desktop environment-filter
tests pass locally, covering the real production platform gate, save
rejection, both merge layers, case variants, and executable error
context.
- Subprocess tests cover direct/wrapped argument defaults, Pi skills,
Hermes/Codex environment, adapter identity, repeated spawns and
invalid/missing prefix refusal.
- Shipped `buzz-acp run` entrypoint completes a task through the wrapper
and deterministic ACP peer.
- Real Goose 1.52.0: direct and wrapped `auth-methods` return identical
results; the wrapper receives `goose acp` with initially empty worker
args.
- Independent agent review: no blocking findings.
- Required push checks passed on the updated head. The existing native
Windows CI job now explicitly runs the platform-contract tests. Its
result is pending; the non-Unix refusal branch was not exercised on this
macOS host.

Repeated-spawn coverage is not a full lazy-pool wake/crash test.
Protected Goose in the Buzz-App UI and human acceptance remain pending.

To try the hook after building `buzz-acp`, use an installed Goose path:

```sh
BUZZ_ACP_LAUNCH_PREFIX='["/usr/bin/env"]' target/debug/buzz-acp auth-methods \
  --agent-command /absolute/path/to/goose --agent-args '' --json
```

Expect the same authentication methods as without the prefix. Changing
the prefix to `["/missing-launcher"]` must fail. Draft pending human
testing.

Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
@shellz-n-stuff
shellz-n-stuff force-pushed the codex/protection-launch-hook branch from 7cf819d to 5be6a64 Compare September 30, 2026 16:18
@shellz-n-stuff
shellz-n-stuff marked this pull request as draft September 30, 2026 16:19
@shellz-n-stuff
shellz-n-stuff marked this pull request as ready for review September 30, 2026 19:06

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested: one P2 remains, detailed inline. The previous worker-identity and delayed-launch snapshot findings are resolved in source. OAuth/scratch writes are restored, but the control-isolation correction needs multi-run coverage before that finding is fully addressed.

Reviewed head 5be6a642eb901e50431a8f4ad79f56bf367380e4 against base 9ab4a1792b0d2cc60c9734baf1dc45911ee8bc83. Independent runtime and security lanes reconciled; static review and hosted evidence only, with no local runtime or enforcement probes in this pass.

Native CI passed on merge 3376bc0 of these exact revisions. Required CI is still red because WebKit 4/6 was cancelled during setup, before its journeys ran. App Windows validation was skipped. The real-provider test is macOS-only/opt-in; the PR’s reported local results cover the combined stack rather than this isolated head. Live-listener recovery and human acceptance remain separate, unverified gates.

Exit criteria: address the inline control-path omission with multi-run regression coverage and obtain a complete required CI run. This review does not attest live acceptance.

Comment thread crates/agent-controller/src/security.rs Outdated
Comment on lines +240 to +244
let mut protected_paths = self.protection_paths.clone()?;
protected_paths.extend(self.store.protected_control_paths());
protected_paths.push(self.ownership_root.clone());
protected_paths.push(directory.to_path_buf());
protected_paths.push(control.clone());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Protect control snapshots across concurrent runs

protected_paths.push(control.clone()) protects only the current run. The other store entries cover settings/staging, not the shared runs namespace. Because start_with_key places every agent’s run under the same store (runtime.rs:843-850), a provider enforcing exactly the supplied paths can still permit writes to another run’s host-owned control snapshots when its policy allows writes beneath the app profile. That leaves the verified-launcher/context immutability contract incomplete for concurrent agents. The file modes are not a substitute for provider-enforced isolation between processes owned by the same user.

Give every protected worker coverage of all current and future run controls, for example by separating them into a shared protected control namespace, while keeping OAuth and per-run scratch writable and preserving supervisor-owned cleanup. Add a two-run regression asserting that each launch protects the other run’s controls without denying either run’s required writable state. Enumerating only currently existing runs would miss later launches.

This is a source-confirmed omission from the host/provider contract; I did not execute an enforcement-bypass probe or claim every provider policy permits the affected writes.

@shellz-n-stuff
shellz-n-stuff force-pushed the codex/protection-launch-hook branch from 5be6a64 to f5d41be Compare September 30, 2026 20:54
@shellz-n-stuff
shellz-n-stuff marked this pull request as draft September 30, 2026 20:55
@shellz-n-stuff
shellz-n-stuff marked this pull request as ready for review September 30, 2026 21:42

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Prior P2 addressed; no remaining blocking findings in this re-review. The correction meets the 9/10 bar. Every protected launch now includes the shared run-controls namespace, covering existing and future sibling snapshots while keeping runtime scratch and OAuth separate. Ownership transfers to the existing supervisor, which removes both per-run directories only after confirmed teardown and attempts both removals on cleanup failure. Independent security/path review agrees.

Carl, an automated reviewer, commenting via Wes’s GitHub account. COMMENT only, not approval. This supersedes the outstanding code finding in my previous review.

Reviewed head f5d41bea2038f054325d8ebe5f1414aee5983f91 against base 61d0bd57e097aae8755d306f6228ef76404056b4, concentrating on the changes since 5be6a642 and their lifecycle consequences.

  • Verified evidence: hosted native CI checked merge 4dbfd910bbab29ce366733957712522dce3647c9 of those exact revisions. The full workspace run passed, including controller 127 passed/1 ignored and the updated start/restart/stop, startup-abort, app-death, and cleanup-error regressions. Required CI and DCO are green; Windows validation is skipped.
  • Enforcement/recovery coverage: the new opt-in macOS fixture explicitly orders A's worker startup before B's controls exist, checks both directions, exercises staged launchers across lazy startup and worker crash replacement, and preserves the other run on Stop. I inspected the fixture, not executed it. The reported 26.48-second pass and fail-then-pass evidence belong to combined stack bbe8e3d7; ordinary hosted CI does not exercise that macOS-only test.
  • Remaining acceptance: native-app/real-Goose and human acceptance remain outstanding, as disclosed. No local tests, live agents, or enforcement probes were run in this pass. The green browser rerun is not evidence that its intermittent clock failure was repaired.

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 thanks for turning this around. This head, f5d41bea, got two independent code reviews and an end-to-end run on macOS. None of them found a blocker.

All of the earlier feedback checks out in the code:

  • OAuth/temp vs protected state: TMPDIR is now runs/agent-*/tmp, the launch context lives under run-controls, and only the specific store control files are protected (plus the control-write staging dir), not the profile or scratch
  • worker identity: wrapping goes through ACP's BUZZ_ACP_LAUNCH_PREFIX from block/buzz#7985 (the 95b018c8 pin). The prefix is applied at ACP's spawn boundary, after the worker's identity and args are normalized
  • delayed launches: the launcher is hashed and staged from a single read, and lazy starts and respawns run the staged copy
  • cross-run controls: every launch protects the whole run-controls namespace, and the supervisor removes the snapshot along with scratch after confirmed teardown
  • your two questions on the test file are answered by the PROTECTION_WORKERS table and the comment on the fake provider, so those threads can be resolved

Gurney ran both opt-in tests that CI skips, at this head, with the real pinned ACP and an enforcing Seatbelt launcher:

  • real_listeners_protect_future_run_controls_and_recover_worker_crashes passed. Current and future peer controls resisted chmod/unlink/overwrite/atomic replace/rename. OAuth locking, atomic cache replacement and scratch writes still worked. A Goose-named worker got its normalized acp args. The first lazy launch and a crash replacement both used the staged bytes after the original launcher was swapped out, recovery and a follow-up prompt completed, and stopping one run left its peer alone
  • actual_bundled_acp_lazy_listener_start_restart_stop_and_quit_cleanup passed
  • with both enabled, the full controller package ran 147 tests: all passed, none skipped

That closes the "inspected, not executed" gap on the macOS test. The workers are still synthetic, so this doesn't cover real Goose/inference, native UI, Databricks refresh, or enforcement on other OSes.

Two small things inline (nonblocking), plus:

  • the description still says "Draft pending native-app and human acceptance", but the PR is marked ready and doesn't have buzz-review-completed. Either move it back to draft or update the description once human acceptance is done
  • nonblocking: docs/agent-security.md, which documents this layer's provider contract and how to run the opt-in test, lands in #421. If #421 trails, main has the contract without docs, so it might be worth moving the doc into this PR

assert_eq!(context["policy"], json!({"synthetic":true}));
assert_eq!(context["workspace"], saved.workspace);
let paths = context["protectedPaths"].as_array().unwrap();
for path in [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this checks the paths that have to be protected, but nothing checks that writable state stays out of the list. right now only the opt-in macOS test would catch someone bringing back the broad profile/temp protection from the first review. could we also assert that no protectedPaths entry equals, or is an ancestor of, config/buzz-agent/oauth or this run's TMPDIR? nonblocking

Comment thread crates/agent-controller/src/security.rs Outdated
} => {
validate(&binding)?;
self.require_provider(&binding)?;
let mut doc = self.store.read()?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nonblocking: every other agent mutation lives on Store (save, use_here, enabled, ...), but this one does its own read/check/write, which is why Document/read/write had to become pub(crate). the revision bump here also skips the 9_007_199_254_740_991 cap that use_here and apply enforce, so hitting it fails later in validation with a generic error. moving this into something like Store::set_launch_protection(id, revision, binding) would keep read/write private and pick up the same cap. the Defaults request in #420 could go the same way

@shellz-n-stuff
shellz-n-stuff marked this pull request as draft September 30, 2026 22:25
@shellz-n-stuff
shellz-n-stuff marked this pull request as ready for review September 30, 2026 23:30

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No blocking findings in this re-review. The delta meets the 9/10 bar. Store now owns protection validation/persistence without changing the public security::Binding API; the revision-limit regression preserves saved bytes, and both worker fixtures check writable OAuth/temp paths on Start and Restart. The provider contract documentation is included in this layer. Independent persistence and test/docs reviews reconciled.

Carl, an automated reviewer, commenting via Wes’s GitHub account. COMMENT only, not approval.

Reviewed head 15370100e405d07faab81a16d534c1763af786ff against reported base 61d0bd57e097aae8755d306f6228ef76404056b4, concentrating on changes since the previously cleared f5d41bea2038f054325d8ebe5f1414aee5983f91.

  • Validation: hosted CI and DCO passed. Native CI checked merge c0be46eabc253dbd5fa188b39036cc45bf3103d3 of this head into 73064e34dfa5f14c472c3661589d05f509c17884; the full Rust workspace passed, including controller 128 passed / 1 ignored and both changed regression tests. Those controller/native-agent sources are unchanged between the reported base and CI base. Clean working tree and diff checks; no local tests or enforcement probes run in this pass.
  • Remaining gates: GitHub currently reports merge conflicts; resolve them and validate the integrated head before merge. Native-app/real-Goose and human acceptance, plus non-macOS enforcement, remain unverified as disclosed. Windows CI was skipped; Linux CI does not exercise the opt-in macOS enforcing-provider test. These are integration/acceptance limits, not new code findings.

Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
@shellz-n-stuff
shellz-n-stuff force-pushed the codex/protection-launch-hook branch from 1537010 to a65b1d8 Compare October 1, 2026 14:11
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
@shellz-n-stuff
shellz-n-stuff merged commit 5e24150 into block:main Oct 1, 2026
21 checks passed
johnmatthewtennant added a commit that referenced this pull request Oct 1, 2026
* origin/main: (82 commits)
  Test provider connections before model selection (#500)
  Bundle Goose ACP with Buzz (#497)
  Discover saved identities across joined communities with names, pictures and retry (#291)
  Clarify design-system documentation and unify component examples (#498)
  feat(composer): convert typed Markdown live and refuse control characters committed as text (#455)
  fix(messages): stop three timeline scroll races that flake CI (#456)
  Improve Agent defaults pickers and provider keys (#392)
  fix(threads): keep thread history painted after scroll corrections (#493)
  feat(plugins): expose the agent protection service (#421)
  perf(sidebar): re-render only the changed row on a channel-list publish (#480)
  feat(agents): copy protection defaults into new agents (#420)
  feat(agents): support native launch protection providers (#415)
  fix(composer): prevent WebKit overpainting mention selections (#490)
  fix(composer): prevent arrow keys from inserting control characters (#488)
  perf(channels): fall back to one exact roster read when confirming agent adds (#485)
  fix(media): pause video only on comment composer focus (#483)
  fix(channels): dismiss management modals with outside clicks (#479)
  perf: reuse message date formats and stable reaction shortcuts (#477)
  feat(profile): run an unattended scenario file in web profiling (#476)
  feat(channels): administer channel members and roles (#453)
  ...

Signed-off-by: John Tennant <jtennant@block.xyz>

# Conflicts:
#	src/app/shell/usePanelLauncher.ts
#	src/bundled/agents/AgentsPage.tsx
#	src/bundled/agents/InventoryIdentityCard.tsx
#	src/bundled/agents/InventoryView.tsx
#	src/bundled/agents/UnifiedInventory.tsx
#	src/bundled/agents/index.tsx
johnmatthewtennant pushed a commit that referenced this pull request Oct 1, 2026
* origin/main: (82 commits)
  Test provider connections before model selection (#500)
  Bundle Goose ACP with Buzz (#497)
  Discover saved identities across joined communities with names, pictures and retry (#291)
  Clarify design-system documentation and unify component examples (#498)
  feat(composer): convert typed Markdown live and refuse control characters committed as text (#455)
  fix(messages): stop three timeline scroll races that flake CI (#456)
  Improve Agent defaults pickers and provider keys (#392)
  fix(threads): keep thread history painted after scroll corrections (#493)
  feat(plugins): expose the agent protection service (#421)
  perf(sidebar): re-render only the changed row on a channel-list publish (#480)
  feat(agents): copy protection defaults into new agents (#420)
  feat(agents): support native launch protection providers (#415)
  fix(composer): prevent WebKit overpainting mention selections (#490)
  fix(composer): prevent arrow keys from inserting control characters (#488)
  perf(channels): fall back to one exact roster read when confirming agent adds (#485)
  fix(media): pause video only on comment composer focus (#483)
  fix(channels): dismiss management modals with outside clicks (#479)
  perf: reuse message date formats and stable reaction shortcuts (#477)
  feat(profile): run an unattended scenario file in web profiling (#476)
  feat(channels): administer channel members and roles (#453)
  ...

Signed-off-by: Sol <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz>

# Conflicts:
#	src/app/shell/usePanelLauncher.ts
#	src/bundled/agents/AgentsPage.tsx
#	src/bundled/agents/InventoryIdentityCard.tsx
#	src/bundled/agents/InventoryView.tsx
#	src/bundled/agents/UnifiedInventory.tsx
#	src/bundled/agents/index.tsx
zrmarley added a commit that referenced this pull request Oct 5, 2026
…ad-on-send

* origin/main: (155 commits)
  Discover saved identities across joined communities with names, pictures and retry (#291)
  Clarify design-system documentation and unify component examples (#498)
  feat(composer): convert typed Markdown live and refuse control characters committed as text (#455)
  fix(messages): stop three timeline scroll races that flake CI (#456)
  Improve Agent defaults pickers and provider keys (#392)
  fix(threads): keep thread history painted after scroll corrections (#493)
  feat(plugins): expose the agent protection service (#421)
  perf(sidebar): re-render only the changed row on a channel-list publish (#480)
  feat(agents): copy protection defaults into new agents (#420)
  feat(agents): support native launch protection providers (#415)
  fix(composer): prevent WebKit overpainting mention selections (#490)
  fix(composer): prevent arrow keys from inserting control characters (#488)
  perf(channels): fall back to one exact roster read when confirming agent adds (#485)
  fix(media): pause video only on comment composer focus (#483)
  fix(channels): dismiss management modals with outside clicks (#479)
  perf: reuse message date formats and stable reaction shortcuts (#477)
  feat(profile): run an unattended scenario file in web profiling (#476)
  feat(channels): administer channel members and roles (#453)
  fix(shell): simplify top-bar controls and refine profile dropdown (#435)
  Fix macOS window dragging during identity setup (#424)
  ...

# Conflicts:
#	src/features/messages/MessageComposer.tsx
abipalli pushed a commit to abipalli/buzz that referenced this pull request Oct 7, 2026
## Summary

Replacing `BUZZ_ACP_AGENT_COMMAND` with a sandbox launcher hides the
real adapter from Buzz: for example, Goose loses its default `acp`
argument. Add optional `BUZZ_ACP_LAUNCH_PREFIX`, a JSON argument array
applied at the shared subprocess boundary, after normal worker
configuration.

On Unix, every spawn runs `prefix... worker args...` without shell
interpolation. Worker identity, environment setup, stdio and existing
process cleanup are preserved. Invalid or unavailable prefixes fail
launch without falling back to the worker; unset keeps direct launch on
every platform. Configured prefixes fail closed on non-Unix because
per-worker process-tree cleanup is unavailable. Desktop reserves the key
against saved user-environment overrides, and spawn failures name the
executable.

### Related issue

Prerequisite for [Buzz-App
block#415](block/buzz-app#415). One commit directly
on `main`, independent of block#7942–block#7944. No duplicate launch-prefix PR
found. Policy enforcement, verified launcher staging, protected paths
and the supporting runtime pin remain Buzz-App/plugin work.

### Testing

- Original head `9ae9d82f8`: complete ACP suite (990 passed, three
existing ignored), shipped local-task test, real-Goose probe, and full
repository-wide `just ci` passed.
- Review fixes: five launch tests and 44 Desktop environment-filter
tests pass locally, covering the real production platform gate, save
rejection, both merge layers, case variants, and executable error
context.
- Subprocess tests cover direct/wrapped argument defaults, Pi skills,
Hermes/Codex environment, adapter identity, repeated spawns and
invalid/missing prefix refusal.
- Shipped `buzz-acp run` entrypoint completes a task through the wrapper
and deterministic ACP peer.
- Real Goose 1.52.0: direct and wrapped `auth-methods` return identical
results; the wrapper receives `goose acp` with initially empty worker
args.
- Independent agent review: no blocking findings.
- Required push checks passed on the updated head. The existing native
Windows CI job now explicitly runs the platform-contract tests. Its
result is pending; the non-Unix refusal branch was not exercised on this
macOS host.

Repeated-spawn coverage is not a full lazy-pool wake/crash test.
Protected Goose in the Buzz-App UI and human acceptance remain pending.

To try the hook after building `buzz-acp`, use an installed Goose path:

```sh
BUZZ_ACP_LAUNCH_PREFIX='["/usr/bin/env"]' target/debug/buzz-acp auth-methods \
  --agent-command /absolute/path/to/goose --agent-args '' --json
```

Expect the same authentication methods as without the prefix. Changing
the prefix to `["/missing-launcher"]` must fail. Draft pending human
testing.

Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants