Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 81 additions & 4 deletions crates/goose/src/acp/server/new_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ impl GooseAcpAgent {
validate_absolute_cwd(&args.cwd)?;
let config = Config::global();
let project_id = meta_string(args.meta.as_ref(), "projectId")?;
let session_type = match meta_string(args.meta.as_ref(), "client")? {
Some(_) => SessionType::User,
None => SessionType::Acp,
};
let session_type = session_type_from_meta(args.meta.as_ref())?;
let current_mode: GooseMode = config.get_goose_mode().unwrap_or_default();
let recipe = self.resolve_recipe_from_meta(args.meta.as_ref()).await?;
let session_name = match recipe.as_ref() {
Expand Down Expand Up @@ -251,6 +248,30 @@ fn model_config_from_recipe_settings(
.internal_err_ctx("Failed to build model config from recipe settings")
}

fn session_type_from_meta(
meta: Option<&Meta>,
) -> Result<SessionType, agent_client_protocol::Error> {
if meta_bool(meta, "hidden")? {
return Ok(SessionType::Hidden);
Comment on lines +254 to +255

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve ACP recall scope for hidden sessions

When an external ACP client omits client but sends _meta.hidden: true, this collapses the session to SessionType::Hidden instead of retaining that it came from ACP. Chat Recall later scopes searches only by the persisted SessionType: Acp sessions search ACP history, while every other type searches User/Scheduled history (crates/goose/src/agents/platform_extensions/chatrecall.rs:103-106). In a hidden ACP session with Chat Recall enabled, the agent can therefore read desktop/legacy history and cannot recall ACP history, which regresses ACP session isolation. Consider preserving ACP provenance for hidden ACP sessions or updating the downstream scope logic before storing these sessions as plain Hidden.

Useful? React with 👍 / 👎.

@matt2e matt2e Jul 30, 2026

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.

I think this is a limitation of a single "type" per session in Goose's existing data model

}
Ok(match meta_string(meta, "client")? {
Some(_) => SessionType::User,
None => SessionType::Acp,
})
}

fn meta_bool(meta: Option<&Meta>, key: &str) -> Result<bool, agent_client_protocol::Error> {
let Some(value) = meta.and_then(|m| m.get(key)) else {
return Ok(false);
};
if value.is_null() {
return Ok(false);
}
value.as_bool().ok_or_else(|| {
agent_client_protocol::Error::invalid_params().data(format!("{key} must be a boolean"))
})
}

fn meta_goose_extensions(
meta: Option<&Meta>,
) -> Result<Option<Vec<GooseExtension>>, agent_client_protocol::Error> {
Expand All @@ -266,3 +287,59 @@ fn meta_goose_extensions(
agent_client_protocol::Error::invalid_params().data(format!("enabledExtensions: {e}"))
})
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

fn meta(value: serde_json::Value) -> Meta {
match value {
serde_json::Value::Object(map) => map,
other => panic!("expected object, got {other}"),
}
}

#[test]
fn hidden_meta_yields_hidden_session() {
let meta = meta(json!({ "hidden": true }));
assert_eq!(
session_type_from_meta(Some(&meta)).unwrap(),
SessionType::Hidden
);
}

#[test]
fn hidden_overrides_client() {
let meta = meta(json!({ "hidden": true, "client": "desktop" }));
assert_eq!(
session_type_from_meta(Some(&meta)).unwrap(),
SessionType::Hidden
);
}

#[test]
fn absent_hidden_preserves_acp() {
assert_eq!(session_type_from_meta(None).unwrap(), SessionType::Acp);
let meta = meta(json!({ "hidden": false }));
assert_eq!(
session_type_from_meta(Some(&meta)).unwrap(),
SessionType::Acp
);
}

#[test]
fn non_bool_hidden_is_rejected() {
let meta = meta(json!({ "hidden": "yes", "client": "desktop" }));
assert!(session_type_from_meta(Some(&meta)).is_err());
}

#[test]
fn client_meta_yields_user_session() {
let meta = meta(json!({ "client": "desktop" }));
assert_eq!(
session_type_from_meta(Some(&meta)).unwrap(),
SessionType::User
);
}
}
Loading