Use ApiPathString in app-server filesystem permission paths (#28367)

## Why

Clients running an app-server on one OS and an exec-server on another OS
need to be able to pass sandbox config to app-server that refers to
resources on the executor's foreign OS.

## What

`AbsolutePathBuf` can't represent these paths and we don't want users to
be exposed to `PathUri` yet, so this moves the public app-server API to
be expressed in terms of `ApiPathString`.

Stacked on #28165.

- change app-server v2 filesystem permission paths, including legacy
read/write roots, to `ApiPathString`
- localize API paths through `PathUri` when converting into the current
native core permission types
- make path-bearing permission conversions fallible and surface
localization failures instead of silently treating malformed grants as
ordinary denials
- propagate conversion failures through app-server and TUI approval
handling
- regenerate the app-server JSON and TypeScript schemas
- leave migration TODOs on native-path conversions so they can be
removed once core permission paths use `PathUri`
This commit is contained in:
Adam Perry @ OpenAI authored and GitHub committed 2026-06-15 19:25:54 -07:00
1 parent d959664420
commit ecfe174d5f
34 files changed
+546 -233

No files matched your search

+30 -17
View File
@@ -210,9 +210,9 @@ impl App {
&self,
thread_id: ThreadId,
request: &ServerRequest,
) -> Option<ThreadInteractiveRequest> {
) -> std::io::Result<Option<ThreadInteractiveRequest>> {
let thread_label = Some(self.thread_label(thread_id));
match request {
Ok(match request {
ServerRequest::CommandExecutionRequestApproval { params, .. } => {
let network_approval_context = params.network_approval_context.clone();
let additional_permissions = params.additional_permissions.clone();
@@ -305,18 +305,28 @@ impl App {
}
}
}
ServerRequest::PermissionsRequestApproval { params, .. } => Some(
ThreadInteractiveRequest::Approval(ApprovalRequest::Permissions {
thread_id,
thread_label,
call_id: params.item_id.clone(),
environment_id: params.environment_id.clone(),
reason: params.reason.clone(),
permissions: params.permissions.clone().into(),
}),
),
ServerRequest::PermissionsRequestApproval { params, .. } => {
// TODO(anp): Remove this native-path localization error path once core permission
// paths remain PathUri after crossing the app-server boundary.
let permissions = params.permissions.clone().try_into().map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("failed to localize requested filesystem paths: {err}"),
)
})?;
Some(ThreadInteractiveRequest::Approval(
ApprovalRequest::Permissions {
thread_id,
thread_label,
call_id: params.item_id.clone(),
environment_id: params.environment_id.clone(),
reason: params.reason.clone(),
permissions,
},
))
}
_ => None,
}
})
}
pub(super) fn push_thread_interactive_request(&mut self, request: ThreadInteractiveRequest) {
@@ -376,20 +386,23 @@ impl App {
requests
}
pub(super) async fn surface_pending_inactive_thread_interactive_requests(&mut self) {
pub(super) async fn surface_pending_inactive_thread_interactive_requests(
&mut self,
) -> Result<()> {
if self.active_side_parent_thread_id().is_some() {
return;
return Ok(());
}
let requests = self.pending_inactive_thread_requests().await;
for (thread_id, request) in requests {
if let Some(request) = self
.interactive_request_for_thread_request(thread_id, &request)
.await
.await?
{
self.push_thread_interactive_request(request);
}
}
Ok(())
}
pub(super) async fn submit_active_thread_op(
@@ -991,7 +1004,7 @@ impl App {
) -> Result<()> {
let inactive_interactive_request = if self.active_thread_id != Some(thread_id) {
self.interactive_request_for_thread_request(thread_id, &request)
.await
.await?
} else {
None
};