mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 17:10:50 -10:00
feat(acp): --full-access flag for serve --acp, accepted after the subcommand
Probing the installed 0.10.2 binary over ACP showed three things:
- `codewhale serve --acp` runs under Ask;
- `codewhale --yolo serve --acp` runs under Full Access;
- `codewhale serve --acp --yolo` is rejected ("unexpected argument", exit 2).
That last form is the one editors write in their agent config. Founder
decision: Full Access is yolo (ApprovalMode::Bypass). A configured
`sandbox_mode = "danger-full-access"` must not widen Ask or Auto-Review, and
no state may have the sandbox off while the agent still asks.
Changes:
- `--full-access` is the visible flag; `--yolo` stays an alias
(runtime_options.rs).
- The dispatcher lifts `--full-access` and `--yolo` out of the `serve`
passthrough arguments (`capture_serve_full_access`), stopping at a `--`
separator. It sets the same runtime option as the global flag, so the
value still flows through the CODEWHALE_YOLO env layer, and managed
config above that layer still wins. Setting `config.yolo` after load
would have bypassed a managed `yolo = false`.
- `serve --acp` writes one stderr line naming the ACP permission and
sandbox it will run with, computed with `sandbox_policy_for_turn`. When
Full Access is off, the line says how to turn it on.
- The ACP hint and the approval_policy "never" error name `--full-access`.
- `SandboxPolicy::config_value()` replaces a duplicated match in
`apply_requirements`.
- docs/MODES.md, docs/RUNTIME_API.md and CHANGELOG are updated.
Verified:
- `scripts/dev-cargo.sh build -p codewhale-cli --locked`: built, 0
warnings.
- The built binary was probed over ACP. For each launch, a fresh
CODEWHALE_HOME and runtime dir; then `initialize`, `session/new`, and a
read of the `permission` configOption:
- `serve --acp`: ask. stderr: "ACP permission: ask; sandbox:
workspace-write. For Full Access start with `codewhale serve --acp
--full-access` ...".
- `--full-access serve --acp`: full-access, with sandbox
danger-full-access.
- `serve --acp --full-access`: full-access.
- `serve --acp --yolo`: full-access.
- nextest codewhale-cli, filtered: 36 passed, including
`clap_command_definition_is_consistent` and
`yolo_flag_writes_only_the_codewhale_env_var`.
- nextest codewhale-tui, filtered to acp_server, requirements,
sandbox::policy and runtime_options: 61 passed (38 acp_server, 6
requirements).
- Not run: clippy, Windows, hosted CI.
The Seatbelt half of #4955 is not done.
Refs #6992 #4955
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8b5baa7a5f
commit
135319aa1a
8 files changed
+88
-20
No files matched your search
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- `--full-access` is now the documented flag for Full Access (`--yolo` stays an
|
||||
alias), and `serve` accepts it after the subcommand: `codewhale serve --acp
|
||||
--yolo` used to fail with "unexpected argument". `serve --acp` also writes one
|
||||
stderr line naming the permission and sandbox it runs with, and the ACP
|
||||
permission option's Full Access hint names the working command. A configured
|
||||
`sandbox_mode = "danger-full-access"` still never widens Ask or Auto-Review
|
||||
(#6992).
|
||||
- Preview: `dispatch --computer` starts an account cloud conversation that can
|
||||
also be opened in desktop. `--thread` continues or inspects it; `--stop`,
|
||||
`--approve` and `--deny` control its current task. Explicit `--key-stdin`
|
||||
|
||||
@@ -364,6 +364,7 @@ Forwarded serve options:
|
||||
--cors-origin <URL> Additional CORS origin to allow (repeatable)
|
||||
--auth-token <TOKEN> Require this bearer token for /v1/* runtime API routes
|
||||
--insecure Disable runtime API auth when no token is configured
|
||||
--full-access Run with Full Access: no approval prompts, no Codewhale sandbox (alias: --yolo)
|
||||
|
||||
`codewhale serve --http` and `codewhale serve --mobile` remain compatibility
|
||||
aliases for `codewhale app-server --http` and `codewhale app-server --mobile`.
|
||||
@@ -2283,6 +2284,7 @@ fn run() -> Result<()> {
|
||||
);
|
||||
preserve_exec_separator(&mut cli, &argv);
|
||||
capture_exec_startup_options(&mut cli)?;
|
||||
capture_serve_full_access(&mut cli);
|
||||
|
||||
// The detached log proxy must not depend on user config parsing: its job
|
||||
// is to frame child output and publish a terminal receipt even when the
|
||||
@@ -2986,6 +2988,22 @@ fn preserve_exec_separator(cli: &mut Cli, argv: &[impl AsRef<std::ffi::OsStr>])
|
||||
}
|
||||
}
|
||||
|
||||
fn capture_serve_full_access(cli: &mut Cli) {
|
||||
let Some(Commands::Serve(args)) = cli.command.as_mut() else {
|
||||
return;
|
||||
};
|
||||
let before = args.args.len();
|
||||
let separator = args.args.iter().position(|arg| arg == "--").unwrap_or(before);
|
||||
let mut index = 0;
|
||||
args.args.retain(|arg| {
|
||||
index += 1;
|
||||
index > separator || !matches!(arg.as_str(), "--full-access" | "--yolo")
|
||||
});
|
||||
if args.args.len() != before {
|
||||
cli.runtime_options.yolo = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Admit exec's recognized startup options through the same Clap definitions
|
||||
/// before the one runtime override capture. Unknown forwarded options and all
|
||||
/// tokens after `--` retain their order and their existing exec meaning.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! This module keeps only connection configuration, canonical bindings and
|
||||
//! JSON-RPC correlation. Secure cross-process attachment is not yet qualified.
|
||||
use crate::config::{Config, ProviderKind};
|
||||
use crate::core::authority::{SandboxNetworkAccess, sandbox_policy_for_turn};
|
||||
#[cfg(test)]
|
||||
use crate::runtime_threads::RuntimeThreadManagerConfig;
|
||||
use crate::runtime_threads::{CreateThreadRequest, RuntimeThreadManager, UpdateThreadRequest};
|
||||
@@ -11,7 +12,7 @@ use codewhale_config::AppMode;
|
||||
use codewhale_execpolicy::ApprovalMode;
|
||||
use serde_json::{Value, json};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
#[cfg(test)]
|
||||
@@ -32,6 +33,7 @@ pub async fn run_acp_server(
|
||||
config_profile: Option<String>,
|
||||
) -> Result<()> {
|
||||
ensure_acp_approval_posture(&config)?;
|
||||
eprintln!("{}", acp_startup_posture(&config, &default_cwd));
|
||||
crate::runtime_api::run_http_server(
|
||||
config,
|
||||
default_cwd,
|
||||
@@ -628,12 +630,7 @@ impl AcpServer {
|
||||
Ok(())
|
||||
}
|
||||
fn permission_value(&self) -> &'static str {
|
||||
match acp_approval_mode(&self.config) {
|
||||
ApprovalMode::Bypass => "full-access",
|
||||
ApprovalMode::Auto => "auto-review",
|
||||
ApprovalMode::Never => "never",
|
||||
ApprovalMode::Suggest => "ask",
|
||||
}
|
||||
acp_permission_value(&self.config)
|
||||
}
|
||||
async fn session_configuration(
|
||||
&self,
|
||||
@@ -839,7 +836,38 @@ impl AcpServer {
|
||||
Ok(self.current_model())
|
||||
}
|
||||
}
|
||||
const ACP_FULL_ACCESS_HINT: &str = "Start the server with `codewhale --yolo serve --acp`, or set `yolo = true` in config.toml. Full Access also turns off Codewhale's own sandbox unless sandbox_mode tightens it; Plan stays read-only.";
|
||||
const ACP_FULL_ACCESS_HINT: &str = "Start the server with `codewhale serve --acp --full-access` (alias `--yolo`), or set `yolo = true` in config.toml. Full Access also turns off Codewhale's own sandbox unless sandbox_mode tightens it; Plan stays read-only.";
|
||||
|
||||
fn acp_permission_value(config: &Config) -> &'static str {
|
||||
match acp_approval_mode(config) {
|
||||
ApprovalMode::Bypass => "full-access",
|
||||
ApprovalMode::Auto => "auto-review",
|
||||
ApprovalMode::Never => "never",
|
||||
ApprovalMode::Suggest => "ask",
|
||||
}
|
||||
}
|
||||
|
||||
fn acp_startup_posture(config: &Config, workspace: &Path) -> String {
|
||||
let approval = acp_approval_mode(config);
|
||||
let sandbox = sandbox_policy_for_turn(
|
||||
acp_mode(config),
|
||||
approval,
|
||||
config.sandbox_mode.as_deref(),
|
||||
workspace,
|
||||
SandboxNetworkAccess::from_config(config.sandbox_network_access),
|
||||
);
|
||||
let mut line = format!(
|
||||
"ACP permission: {}; sandbox: {}.",
|
||||
acp_permission_value(config),
|
||||
sandbox.config_value()
|
||||
);
|
||||
if approval != ApprovalMode::Bypass {
|
||||
line.push_str(
|
||||
" For Full Access start with `codewhale serve --acp --full-access` or set `yolo = true`; sandbox_mode alone never turns it on.",
|
||||
);
|
||||
}
|
||||
line
|
||||
}
|
||||
|
||||
fn acp_mode(config: &Config) -> AppMode {
|
||||
if config.sandbox_mode.as_deref() == Some("read-only") {
|
||||
@@ -869,7 +897,7 @@ fn acp_approval_mode(config: &Config) -> ApprovalMode {
|
||||
fn ensure_acp_approval_posture(config: &Config) -> Result<()> {
|
||||
anyhow::ensure!(
|
||||
acp_approval_mode(config) != ApprovalMode::Never,
|
||||
"approval_policy \"never\" is not supported by `codewhale serve --acp`: ACP sessions run under Ask, Auto-Review or Full Access. Set approval_policy = \"on-request\" for this server, or start it with --yolo for Full Access."
|
||||
"approval_policy \"never\" is not supported by `codewhale serve --acp`: ACP sessions run under Ask, Auto-Review or Full Access. Set approval_policy = \"on-request\" for this server, or start it with --full-access for Full Access."
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11114,7 +11114,6 @@ fn apply_requirements(config: &mut Config) -> Result<()> {
|
||||
.map(str::to_ascii_lowercase)
|
||||
.unwrap_or_else(|| {
|
||||
use crate::core::authority::{SandboxNetworkAccess, sandbox_policy_for_turn};
|
||||
use crate::sandbox::SandboxPolicy;
|
||||
use codewhale_execpolicy::ApprovalMode;
|
||||
|
||||
// Sandbox requirements lock approval posture at startup,
|
||||
@@ -11124,18 +11123,14 @@ fn apply_requirements(config: &mut Config) -> Result<()> {
|
||||
.as_deref()
|
||||
.and_then(ApprovalMode::from_config_value)
|
||||
.unwrap_or_default();
|
||||
match sandbox_policy_for_turn(
|
||||
sandbox_policy_for_turn(
|
||||
codewhale_config::AppMode::Agent,
|
||||
approval,
|
||||
None,
|
||||
Path::new("."),
|
||||
SandboxNetworkAccess::from_config(config.sandbox_network_access),
|
||||
) {
|
||||
SandboxPolicy::ReadOnly { .. } => "read-only",
|
||||
SandboxPolicy::WorkspaceWrite { .. } => "workspace-write",
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access",
|
||||
SandboxPolicy::ExternalSandbox { .. } => "external-sandbox",
|
||||
}
|
||||
)
|
||||
.config_value()
|
||||
.to_string()
|
||||
});
|
||||
if !requirements
|
||||
|
||||
@@ -45,8 +45,9 @@ pub struct RuntimeOptions {
|
||||
/// Disable a feature for this run (repeatable).
|
||||
#[arg(long = "disable", value_name = "FEATURE", action = clap::ArgAction::Append, global = true)]
|
||||
pub disable: Vec<String>,
|
||||
/// Legacy compatibility alias for Act + Full Access.
|
||||
#[arg(long, hide = true)]
|
||||
/// Run with Full Access: no approval prompts and no Codewhale sandbox
|
||||
/// (the same as `yolo = true`; `--yolo` is an alias).
|
||||
#[arg(long = "full-access", alias = "yolo")]
|
||||
pub yolo: bool,
|
||||
/// Maximum number of concurrent sub-agents (1-128; default 64).
|
||||
#[arg(long)]
|
||||
@@ -97,7 +98,7 @@ impl RuntimeOptions {
|
||||
flag!(skip_onboarding, "--skip-onboarding");
|
||||
flag!(fresh, "--fresh");
|
||||
flag!(no_project_config, "--no-project-config");
|
||||
flag!(yolo, "--yolo");
|
||||
flag!(yolo, "--full-access");
|
||||
flag!(verbose, "--verbose");
|
||||
flag!(remote_control, "--remote-control");
|
||||
if self.mouse_capture && self.no_mouse_capture {
|
||||
|
||||
@@ -105,6 +105,15 @@ impl Default for SandboxPolicy {
|
||||
}
|
||||
|
||||
impl SandboxPolicy {
|
||||
pub fn config_value(&self) -> &'static str {
|
||||
match self {
|
||||
SandboxPolicy::ReadOnly { .. } => "read-only",
|
||||
SandboxPolicy::WorkspaceWrite { .. } => "workspace-write",
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access",
|
||||
SandboxPolicy::ExternalSandbox { .. } => "external-sandbox",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_only() -> Self {
|
||||
SandboxPolicy::ReadOnly {
|
||||
network_access: false,
|
||||
|
||||
@@ -354,6 +354,7 @@ Run `codewhale --help` for the canonical list. Common flags:
|
||||
- `-r, --resume <ID|PREFIX|latest>`: resume a saved session
|
||||
- `-c, --continue`: resume the most recent session in this workspace
|
||||
- `--max-subagents <N>`: clamp to `1..=128`
|
||||
- `--full-access` (alias `--yolo`): start with Full Access, no approval prompts and no Codewhale sandbox; the same as `yolo = true`. `serve --acp --full-access` and `--full-access serve --acp` are equivalent.
|
||||
- `--mouse-capture` / `--no-mouse-capture`: opt in or out of internal mouse scrolling, transcript selection, right-click context actions, and transcript scrollbar dragging. Mouse capture is enabled by default on non-Windows terminals and on Windows Terminal/ConEmu/Cmder so drag selection copies only transcript text, removes visual wrap-column line breaks from paragraphs, and stays scoped to the transcript pane; hold Shift while dragging or use `--no-mouse-capture` for raw terminal selection. It defaults off on legacy Windows console (CMD without `WT_SESSION` / `ConEmuPID`) and inside JetBrains JediTerm — PyCharm/IDEA/CLion/etc. — where the terminal advertises mouse support but forwards SGR mouse events as raw text (#878, #898). Use `--mouse-capture` to opt in anywhere it's defaulted off. Raw terminal selection may cross the right workbar and include visual wraps because the terminal, not the TUI, owns the selection.
|
||||
- `--profile <NAME>`: select config profile
|
||||
- `--config <PATH>`: config file path
|
||||
|
||||
@@ -785,6 +785,15 @@ posture are server-owned read-only options; the editor cannot widen them.
|
||||
`approval_policy = "never"` is not an ACP posture, so the server refuses to
|
||||
start with it instead of serving it as Ask.
|
||||
|
||||
Full Access is `yolo`. Start it with `codewhale serve --acp --full-access`
|
||||
(the flag may also come before `serve`; `--yolo` is an alias) or set
|
||||
`yolo = true` in config. A configured `sandbox_mode = "danger-full-access"` does
|
||||
not turn it on: that value can only narrow the sandbox the permission picks, so
|
||||
under Ask or Auto-Review it changes nothing, and `ask` is not a config key. At
|
||||
startup the server writes one line to stderr naming the permission and sandbox it
|
||||
will run with, so an editor's agent log shows which posture a launch command
|
||||
produced.
|
||||
|
||||
Tool updates begin `pending`. `in_progress` means Core reached final dispatch;
|
||||
`completed`/`failed` and typed image blocks come from the actual Core result.
|
||||
`session/request_permission` correlates a private JSON-RPC ID to the same
|
||||
|
||||
Reference in new issue
Block a user