Skip to content

Commit 57620c9

Browse files
committed
feat(acp): migrate system/file Tauri commands to ACP+
Move `get_home_dir`, `save_exported_session_file`, `path_exists`, `list_directory_entries`, `inspect_attachment_paths`, `list_files_for_mentions` and `read_image_attachment` from the goose2 Tauri shell into a new `goose::system_ops` module exposed over ACP+ via the `_goose/system/*` namespace. The desktop shell now opens save dialogs via the Tauri JS `@tauri-apps/plugin-dialog` plugin and persists the chosen file via a generic `_goose/system/write_file` ACP method. The Tauri shell drops its `base64`, `dirs`, `ignore`, and `mime_guess` dependencies along with the migrated commands, keeping the shell layer thin per the goose2 architecture rule. Existing system_ops behaviour is exercised through ported unit tests under `crates/goose/src/system_ops.rs`. Closes #8692 Co-Authored-By: Oz <oz-agent@warp.dev> Signed-off-by: Oz <oz-agent@warp.dev>
1 parent df302d7 commit 57620c9

19 files changed

Lines changed: 1133 additions & 221 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/goose-sdk/src/custom_requests.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,144 @@ pub struct ProviderInventoryEntryDto {
647647
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
648648
pub struct EmptyResponse {}
649649

650+
/// Resolve the current user's home directory.
651+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
652+
#[request(method = "_goose/system/home_dir", response = GetHomeDirResponse)]
653+
#[serde(rename_all = "camelCase")]
654+
pub struct GetHomeDirRequest {}
655+
656+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
657+
#[serde(rename_all = "camelCase")]
658+
pub struct GetHomeDirResponse {
659+
/// Absolute path to the user's home directory.
660+
pub path: String,
661+
}
662+
663+
/// Check whether a path exists on disk.
664+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
665+
#[request(method = "_goose/system/path_exists", response = PathExistsResponse)]
666+
#[serde(rename_all = "camelCase")]
667+
pub struct PathExistsRequest {
668+
pub path: String,
669+
}
670+
671+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
672+
#[serde(rename_all = "camelCase")]
673+
pub struct PathExistsResponse {
674+
pub exists: bool,
675+
}
676+
677+
/// A single filesystem entry surfaced to the desktop UI.
678+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
679+
#[serde(rename_all = "camelCase")]
680+
pub struct FileTreeEntryDto {
681+
pub name: String,
682+
pub path: String,
683+
/// `"file"` or `"directory"`.
684+
pub kind: String,
685+
}
686+
687+
/// List the immediate children of a directory.
688+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
689+
#[request(
690+
method = "_goose/system/list_directory_entries",
691+
response = ListDirectoryEntriesResponse
692+
)]
693+
#[serde(rename_all = "camelCase")]
694+
pub struct ListDirectoryEntriesRequest {
695+
pub path: String,
696+
}
697+
698+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
699+
#[serde(rename_all = "camelCase")]
700+
pub struct ListDirectoryEntriesResponse {
701+
pub entries: Vec<FileTreeEntryDto>,
702+
}
703+
704+
/// Metadata describing a single attachment path on disk.
705+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
706+
#[serde(rename_all = "camelCase")]
707+
pub struct AttachmentPathInfoDto {
708+
pub name: String,
709+
pub path: String,
710+
/// `"file"` or `"directory"`.
711+
pub kind: String,
712+
#[serde(default, skip_serializing_if = "Option::is_none")]
713+
pub mime_type: Option<String>,
714+
}
715+
716+
/// Inspect a batch of attachment paths. Missing entries are silently skipped.
717+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
718+
#[request(
719+
method = "_goose/system/inspect_attachment_paths",
720+
response = InspectAttachmentPathsResponse
721+
)]
722+
#[serde(rename_all = "camelCase")]
723+
pub struct InspectAttachmentPathsRequest {
724+
pub paths: Vec<String>,
725+
}
726+
727+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
728+
#[serde(rename_all = "camelCase")]
729+
pub struct InspectAttachmentPathsResponse {
730+
pub attachments: Vec<AttachmentPathInfoDto>,
731+
}
732+
733+
/// Walk one or more roots and return a sorted list of file paths suitable for
734+
/// `@`-mention pickers. Honours `.gitignore`, hidden files, and symlink escapes.
735+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
736+
#[request(
737+
method = "_goose/system/list_files_for_mentions",
738+
response = ListFilesForMentionsResponse
739+
)]
740+
#[serde(rename_all = "camelCase")]
741+
pub struct ListFilesForMentionsRequest {
742+
pub roots: Vec<String>,
743+
/// Maximum number of results to return. Clamped to 1..=5000; defaults to 1500.
744+
#[serde(default, skip_serializing_if = "Option::is_none")]
745+
pub max_results: Option<u32>,
746+
}
747+
748+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
749+
#[serde(rename_all = "camelCase")]
750+
pub struct ListFilesForMentionsResponse {
751+
pub files: Vec<String>,
752+
}
753+
754+
/// Read an image attachment from disk and return it as a base64 payload.
755+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
756+
#[request(
757+
method = "_goose/system/read_image_attachment",
758+
response = ReadImageAttachmentResponse
759+
)]
760+
#[serde(rename_all = "camelCase")]
761+
pub struct ReadImageAttachmentRequest {
762+
pub path: String,
763+
}
764+
765+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
766+
#[serde(rename_all = "camelCase")]
767+
pub struct ReadImageAttachmentResponse {
768+
/// Base64-encoded image bytes.
769+
pub base64: String,
770+
/// MIME type detected from the path's extension (always starts with `image/`).
771+
pub mime_type: String,
772+
}
773+
774+
/// Write a UTF-8 string to a path on disk, creating any missing parents.
775+
///
776+
/// The desktop shell uses this to persist content the user has chosen via a
777+
/// native file dialog (e.g. exported sessions). Tauri-backed file dialogs are
778+
/// still owned by the desktop shell; only the actual write is delegated to
779+
/// `goose serve`.
780+
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
781+
#[request(method = "_goose/system/write_file", response = EmptyResponse)]
782+
#[serde(rename_all = "camelCase")]
783+
pub struct WriteFileRequest {
784+
pub path: String,
785+
pub contents: String,
786+
}
787+
650788
/// List available local Whisper models with their download status.
651789
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
652790
#[request(

crates/goose/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ sec1 = { version = "0.7", default-features = false, features = ["der", "pkcs8"],
193193
goose-acp-macros = { path = "../goose-acp-macros" }
194194
tower-http = { workspace = true, features = ["cors"] }
195195
http-body-util = "0.1.3"
196+
mime_guess = "2"
196197

197198

198199
[target.'cfg(target_os = "windows")'.dependencies]

crates/goose/acp-meta.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,41 @@
194194
"method": "_goose/dictation/model/select",
195195
"requestType": "DictationModelSelectRequest",
196196
"responseType": "EmptyResponse"
197+
},
198+
{
199+
"method": "_goose/system/home_dir",
200+
"requestType": "GetHomeDirRequest",
201+
"responseType": "GetHomeDirResponse"
202+
},
203+
{
204+
"method": "_goose/system/path_exists",
205+
"requestType": "PathExistsRequest",
206+
"responseType": "PathExistsResponse"
207+
},
208+
{
209+
"method": "_goose/system/list_directory_entries",
210+
"requestType": "ListDirectoryEntriesRequest",
211+
"responseType": "ListDirectoryEntriesResponse"
212+
},
213+
{
214+
"method": "_goose/system/inspect_attachment_paths",
215+
"requestType": "InspectAttachmentPathsRequest",
216+
"responseType": "InspectAttachmentPathsResponse"
217+
},
218+
{
219+
"method": "_goose/system/list_files_for_mentions",
220+
"requestType": "ListFilesForMentionsRequest",
221+
"responseType": "ListFilesForMentionsResponse"
222+
},
223+
{
224+
"method": "_goose/system/read_image_attachment",
225+
"requestType": "ReadImageAttachmentRequest",
226+
"responseType": "ReadImageAttachmentResponse"
227+
},
228+
{
229+
"method": "_goose/system/write_file",
230+
"requestType": "WriteFileRequest",
231+
"responseType": "EmptyResponse"
197232
}
198233
]
199234
}

0 commit comments

Comments
 (0)