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

+13 -7
View File
@@ -2433,7 +2433,7 @@ async fn side_defers_subagent_approval_overlay_until_side_exits() -> Result<()>
app.side_threads.remove(&side_thread_id);
app.active_thread_id = Some(main_thread_id);
app.surface_pending_inactive_thread_interactive_requests()
.await;
.await?;
assert_eq!(app.chat_widget.has_active_view(), true);
@@ -2462,8 +2462,8 @@ async fn inactive_thread_exec_approval_preserves_context() {
enabled: Some(true),
}),
file_system: Some(AdditionalFileSystemPermissions {
read: Some(vec![test_absolute_path("/tmp/read-only")]),
write: Some(vec![test_absolute_path("/tmp/write")]),
read: Some(vec![test_absolute_path("/tmp/read-only").into()]),
write: Some(vec![test_absolute_path("/tmp/write").into()]),
glob_scan_max_depth: None,
entries: None,
}),
@@ -2481,6 +2481,7 @@ async fn inactive_thread_exec_approval_preserves_context() {
})) = app
.interactive_request_for_thread_request(thread_id, &request)
.await
.expect("valid localized paths")
else {
panic!("expected exec approval request");
};
@@ -2499,8 +2500,8 @@ async fn inactive_thread_exec_approval_preserves_context() {
enabled: Some(true),
}),
file_system: Some(AdditionalFileSystemPermissions {
read: Some(vec![test_absolute_path("/tmp/read-only")]),
write: Some(vec![test_absolute_path("/tmp/write")]),
read: Some(vec![test_absolute_path("/tmp/read-only").into()]),
write: Some(vec![test_absolute_path("/tmp/write").into()]),
glob_scan_max_depth: None,
entries: None,
}),
@@ -2542,6 +2543,7 @@ async fn inactive_thread_exec_approval_splits_shell_wrapped_command() {
let Some(ThreadInteractiveRequest::Approval(ApprovalRequest::Exec { command, .. })) = app
.interactive_request_for_thread_request(thread_id, &request)
.await
.expect("valid localized paths")
else {
panic!("expected exec approval request");
};
@@ -2595,6 +2597,7 @@ async fn inactive_thread_file_change_approval_recovers_buffered_changes() {
let request = app
.interactive_request_for_thread_request(thread_id, &request)
.await
.expect("valid localized paths")
.expect("expected file change approval request");
let ThreadInteractiveRequest::Approval(ApprovalRequest::ApplyPatch {
@@ -2646,8 +2649,8 @@ async fn inactive_thread_permissions_approval_preserves_file_system_permissions(
enabled: Some(true),
}),
file_system: Some(AdditionalFileSystemPermissions {
read: Some(vec![test_absolute_path("/tmp/read-only")]),
write: Some(vec![test_absolute_path("/tmp/write")]),
read: Some(vec![test_absolute_path("/tmp/read-only").into()]),
write: Some(vec![test_absolute_path("/tmp/write").into()]),
glob_scan_max_depth: None,
entries: None,
}),
@@ -2662,6 +2665,7 @@ async fn inactive_thread_permissions_approval_preserves_file_system_permissions(
})) = app
.interactive_request_for_thread_request(thread_id, &request)
.await
.expect("valid localized paths")
else {
panic!("expected permissions approval request");
};
@@ -2703,6 +2707,7 @@ async fn inactive_thread_url_elicitation_routes_to_app_link() {
let Some(ThreadInteractiveRequest::AppLink(params)) = app
.interactive_request_for_thread_request(thread_id, &request)
.await
.expect("valid localized paths")
else {
panic!("expected app link request");
};
@@ -2742,6 +2747,7 @@ async fn inactive_thread_invalid_url_elicitation_is_declined() {
assert!(
app.interactive_request_for_thread_request(thread_id, &request)
.await
.expect("valid localized paths")
.is_none()
);
assert_matches!(