Skip to content
Open
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion crates/api/src/bin/task_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ async fn main() -> anyhow::Result<()> {
config.agent.nearai_api_url.clone(),
system_configs_service as Arc<dyn services::system_configs::ports::SystemConfigsService>,
config.agent.channel_relay_url.clone(),
config.agent.non_tee_agent_url_pattern.clone(),
config.agent.crabshack_agent_url_pattern.clone(),
));

let vpc_auth_config = if config.vpc_auth.is_configured() {
Expand Down
2 changes: 1 addition & 1 deletion crates/api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ async fn main() -> anyhow::Result<()> {
system_configs_service.clone()
as Arc<dyn services::system_configs::ports::SystemConfigsService>,
config.agent.channel_relay_url.clone(),
config.agent.non_tee_agent_url_pattern.clone(),
config.agent.crabshack_agent_url_pattern.clone(),
));

// Initialize agent proxy service
Expand Down
25 changes: 13 additions & 12 deletions crates/api/src/routes/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ async fn get_instance_limit(
/// - Subscribed users: plan limit from subscription_plans config
/// - Unsubscribed users: no instances allowed (active subscription required)
///
/// In non-TEE mode, the user authenticates to compose-api with passkey credentials (`auth_secret` /
/// In crabshack mode, the user authenticates to compose-api with passkey credentials (`auth_secret` /
/// `backup_passphrase`) stored once per user (created on first use) and reused for later instances.
/// Those credentials are registered via non-TEE compose-api `/auth/register` and `/auth/login`.
/// Those credentials are registered via crabshack compose-api `/auth/register` and `/auth/login`.
///
/// Supports two response modes via content negotiation:
/// - Accept: text/event-stream → Returns SSE stream of lifecycle events
Expand Down Expand Up @@ -199,15 +199,16 @@ pub async fn create_instance(
.ok()
.flatten()
.unwrap_or_default();
let non_tee_infra = configs
let crabshack_infra = configs
.agent_hosting
.as_ref()
.and_then(|cfg| cfg.new_agent_with_non_tee_infra)
.unwrap_or(false);

// Refresh gateway cookie for non-TEE users (or users with existing non-TEE instances).
// Only refresh when non-TEE infrastructure is enabled to avoid redundant config/database work.
let gateway_cookie = if non_tee_infra {
// Refresh the gateway cookie only when crabshack infrastructure is enabled, to avoid redundant
// config/database work on the legacy_tee path. (setup_gateway_session_for_user itself no-ops
// when the user has no crabshack session to refresh.)
let gateway_cookie = if crabshack_infra {
app_state
.agent_service
.setup_gateway_session_for_user(user.user_id)
Expand All @@ -226,8 +227,8 @@ pub async fn create_instance(

if wants_stream {
// SSE streaming response
let rx = if non_tee_infra {
// Non-TEE mode: passkey flow against non-TEE compose-api
let rx = if crabshack_infra {
// crabshack mode: passkey flow against crabshack compose-api
app_state
.agent_service
.create_passkey_instance_streaming(
Expand All @@ -251,7 +252,7 @@ pub async fn create_instance(
ApiError::internal_server_error("Failed to start instance creation")
})?
} else {
// TEE mode: streaming flow against TEE compose-api
// legacy_tee mode: streaming flow against legacy_tee compose-api
app_state
.agent_service
.create_instance_from_agent_api_streaming(
Expand Down Expand Up @@ -320,8 +321,8 @@ pub async fn create_instance(
} else {
// Non-streaming fallback: Collect stream and return final instance as JSON
// Note: This blocks the request until instance creation completes
let instance = if non_tee_infra {
// Non-TEE mode: passkey flow against non-TEE compose-api
let instance = if crabshack_infra {
// crabshack mode: passkey flow against crabshack compose-api
let mut rx = app_state
.agent_service
.create_passkey_instance_streaming(
Expand Down Expand Up @@ -387,7 +388,7 @@ pub async fn create_instance(
ApiError::internal_server_error("Created instance not found in database")
})?
} else {
// TEE mode: TEE compose-api streaming flow; collect events
// legacy_tee mode: legacy_tee compose-api streaming flow; collect events
let mut rx = app_state
.agent_service
.create_instance_from_agent_api_streaming(
Expand Down
12 changes: 6 additions & 6 deletions crates/api/src/routes/oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,8 +972,8 @@ pub async fn oauth_callback(

tracing::debug!("Session token generated, length: {}", token.len());

// For non-TEE mode: set up gateway session (authenticate with compose-api)
// This allows users with existing non-TEE instances to access them immediately upon login
// For crabshack mode: set up gateway session (authenticate with compose-api)
// This lets users with existing crabshack instances access them immediately upon login
let gateway_cookie = app_state
.agent_service
.setup_gateway_session_for_user(session.user_id)
Expand Down Expand Up @@ -1297,8 +1297,8 @@ pub async fn mock_login(
session.session_id
);

// For non-TEE mode: set up gateway session (authenticate with compose-api)
// This allows users with existing non-TEE instances to access them immediately upon login
// For crabshack mode: set up gateway session (authenticate with compose-api)
// This lets users with existing crabshack instances access them immediately upon login
let gateway_cookie = app_state
.agent_service
.setup_gateway_session_for_user(user.id)
Expand Down Expand Up @@ -1418,8 +1418,8 @@ pub async fn near_auth(
is_new_user
);

// For non-TEE mode: set up gateway session (authenticate with compose-api)
// This allows users with existing non-TEE instances to access them immediately upon login
// For crabshack mode: set up gateway session (authenticate with compose-api)
// This lets users with existing crabshack instances access them immediately upon login
let gateway_cookie = app_state
.agent_service
.setup_gateway_session_for_user(session.user_id)
Expand Down
2 changes: 1 addition & 1 deletion crates/api/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ async fn create_test_server_and_db_inner(
system_configs_service.clone()
as Arc<dyn services::system_configs::ports::SystemConfigsService>,
config.agent.channel_relay_url.clone(),
config.agent.non_tee_agent_url_pattern.clone(),
config.agent.crabshack_agent_url_pattern.clone(),
));

// Initialize subscription service for testing
Expand Down
16 changes: 8 additions & 8 deletions crates/api/tests/upgrade_check_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,13 @@ async fn test_upgrade_completion_only_on_success() {
}

// ============================================================================
// NON-TEE UPGRADE AVAILABILITY (crabshack /images + /instances)
// CRABSHACK UPGRADE AVAILABILITY (crabshack /images + /instances)
// ============================================================================
//
// These scenarios are fully asserted in the `services` crate (wiremock + mock repository),
// because `AgentService::check_upgrade_available` lives there and integration tests here do not
// have access to `MockAgentRepository`. Run:
// cargo test -p services non_tee_check_upgrade
// cargo test -p services crabshack_check_upgrade
//
// Crabshack filter key comes from `service_type_for_crabshack` (see also
// `test_service_type_for_crabshack_transformation` below): by default DB `openclaw` maps to
Expand All @@ -320,14 +320,14 @@ async fn test_upgrade_completion_only_on_success() {
// `service_type: "openclaw-dind"` for that path.
//
// Covered service tests:
// - `non_tee_check_upgrade_legacy_openclaw_dind_filter_and_semver` — legacy `openclaw-dind` row +
// - `crabshack_check_upgrade_legacy_openclaw_dind_filter_and_semver` — legacy `openclaw-dind` row +
// crabshack allowlist filter + semver
// - `non_tee_check_upgrade_prerelease_same_numeric_max_picks_later_allowlist_entry` — rc vs release tie
// - `non_tee_check_upgrade_canonical_openclaw_images` — DB `openclaw` + crabshack `openclaw` images
// - `non_tee_check_upgrade_instance_404_blocks_upgrade` — unsynced instance / no upgrade
// - `non_tee_stable_only_filter_picks_highest_stable_not_prerelease` — mixed stable + pre-release allowlist,
// - `crabshack_check_upgrade_prerelease_same_numeric_max_picks_later_allowlist_entry` — rc vs release tie
// - `crabshack_check_upgrade_canonical_openclaw_images` — DB `openclaw` + crabshack `openclaw` images
// - `crabshack_check_upgrade_instance_404_blocks_upgrade` — unsynced instance / no upgrade
// - `crabshack_stable_only_filter_picks_highest_stable_not_prerelease` — mixed stable + pre-release allowlist,
// default `allow_prerelease_upgrades=false` picks highest stable
// - `non_tee_allow_prerelease_includes_prerelease_in_latest` — same allowlist with flag true picks pre-release
// - `crabshack_allow_prerelease_includes_prerelease_in_latest` — same allowlist with flag true picks pre-release
//
// `allow_prerelease_upgrades` behavior (stable-only vs including pre-releases when picking the newest
// versioned tag from crabshack) is intentionally exercised there with real `/images` mocks and
Expand Down
80 changes: 40 additions & 40 deletions crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ fn default_nearai_api_url() -> String {
.unwrap_or_else(|_| "https://private.near.ai/v1".to_string())
}

fn default_non_tee_agent_url() -> String {
fn default_crabshack_agent_url_pattern() -> String {
std::env::var("NON_TEE_AGENT_URL").unwrap_or_else(|_| "claws".to_string())
}

Expand All @@ -433,16 +433,17 @@ fn default_non_tee_agent_url() -> String {
pub struct AgentManager {
pub url: String,
pub token: String,
/// Whether this manager is non-TEE (true) or TEE (false)
/// Set at construction time based on infrastructure mode
/// Whether this manager is the crabshack (passkey/"claws") path (true) or the
/// legacy_tee (bearer) path (false). Set at construction time based on which
/// env var group the manager was loaded from.
#[serde(default)]
pub is_non_tee: bool,
pub is_crabshack: bool,
}

impl AgentManager {
/// Get the effective is_non_tee value based on explicit configuration
pub fn get_is_non_tee(&self) -> bool {
self.is_non_tee
/// Get the effective is_crabshack value based on explicit configuration
pub fn get_is_crabshack(&self) -> bool {
self.is_crabshack
}
}

Expand All @@ -451,7 +452,7 @@ impl std::fmt::Debug for AgentManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AgentManager")
.field("url", &self.url)
.field("is_non_tee", &self.get_is_non_tee())
.field("is_crabshack", &self.get_is_crabshack())
.field("token", &"[REDACTED]")
.finish()
}
Expand All @@ -475,10 +476,10 @@ pub struct AgentConfig {
/// signing secret instead of the former shared CHANNEL_RELAY_SIGNING_SECRET.
#[serde(default)]
pub channel_relay_url: Option<String>,
/// URL pattern to identify non-TEE compose-api endpoints for instance type detection
/// URL pattern to identify crabshack compose-api endpoints for instance type detection
/// Configurable via NON_TEE_AGENT_URL environment variable (defaults to "claws")
#[serde(default = "default_non_tee_agent_url")]
pub non_tee_agent_url_pattern: String,
#[serde(default = "default_crabshack_agent_url_pattern")]
pub crabshack_agent_url_pattern: String,
Comment thread
walnut-the-cat marked this conversation as resolved.
}

/// Split a comma-separated env var value into non-empty trimmed entries.
Expand All @@ -492,12 +493,11 @@ fn split_csv(value: &str) -> Vec<String> {

impl Default for AgentConfig {
fn default() -> Self {
// Load managers from both TEE and non-TEE configurations
// Load managers from both the legacy_tee and crabshack env var groups
// This supports mixed environments with both manager types available
// Manager type is determined dynamically from URL pattern, not from this setting
let mut managers: Vec<AgentManager> = Vec::new();

// Load TEE managers from AGENT_MANAGER_URLS_TEE
// Load legacy_tee managers from AGENT_MANAGER_URLS_TEE
if let Ok(urls_raw) = std::env::var("AGENT_MANAGER_URLS_TEE") {
let urls = split_csv(&urls_raw);
if !urls.is_empty() {
Expand All @@ -510,20 +510,20 @@ impl Default for AgentConfig {
tokens.len()
);
}
let tee_mgrs: Vec<AgentManager> = urls
let legacy_tee_mgrs: Vec<AgentManager> = urls
.into_iter()
.zip(tokens)
.map(|(url, token)| AgentManager {
url,
token,
is_non_tee: false,
is_crabshack: false,
})
.collect();
managers.extend(tee_mgrs);
managers.extend(legacy_tee_mgrs);
}
}

// Load non-TEE managers from AGENT_MANAGER_URLS
// Load crabshack (passkey/"claws") managers from AGENT_MANAGER_URLS
if let Ok(urls_raw) = std::env::var("AGENT_MANAGER_URLS") {
let urls = split_csv(&urls_raw);
if !urls.is_empty() {
Expand All @@ -536,29 +536,29 @@ impl Default for AgentConfig {
tokens.len()
);
}
let non_tee_mgrs: Vec<AgentManager> = urls
let crabshack_mgrs: Vec<AgentManager> = urls
.into_iter()
.zip(tokens)
.map(|(url, token)| AgentManager {
url,
token,
is_non_tee: true,
is_crabshack: true,
})
.collect();
managers.extend(non_tee_mgrs);
managers.extend(crabshack_mgrs);
}
}

// If no managers configured, fall back to legacy AGENT_API_BASE_URL
// This will be interpreted as TEE if no "claws" pattern in URL
// This is treated as a legacy_tee (bearer) manager
if managers.is_empty() {
let url = std::env::var("AGENT_API_BASE_URL")
.unwrap_or_else(|_| "https://api.agent.near.ai".to_string());
let token = std::env::var("AGENT_API_TOKEN").unwrap_or_default();
managers.push(AgentManager {
url,
token,
is_non_tee: false,
is_crabshack: false,
});
}

Expand All @@ -571,7 +571,7 @@ impl Default for AgentConfig {
.map(|url| url.trim_end_matches('/').to_string() + "/v1")
.unwrap_or_else(|_| "https://private.near.ai/v1".to_string()),
channel_relay_url: std::env::var("CHANNEL_RELAY_URL").ok(),
non_tee_agent_url_pattern: default_non_tee_agent_url(),
crabshack_agent_url_pattern: default_crabshack_agent_url_pattern(),
}
}
}
Expand Down Expand Up @@ -962,7 +962,7 @@ mod tests {
let mgr = AgentManager {
url: "https://test.com".to_string(),
token: "super-secret".to_string(),
is_non_tee: false,
is_crabshack: false,
};
let debug_output = format!("{:?}", mgr);
assert!(debug_output.contains("https://test.com"));
Expand Down Expand Up @@ -1200,39 +1200,39 @@ mod tests {
#[test]
#[serial]
fn test_agent_manager_type_detection_from_url() {
// Test that manager type uses explicit is_non_tee field (not URL pattern detection)
// Test that manager type uses the explicit is_crabshack field (not URL pattern detection)

// Manager with is_non_tee=true should be detected as non-TEE
let non_tee_mgr = AgentManager {
// Manager with is_crabshack=true should be detected as crabshack
let crabshack_mgr = AgentManager {
url: "https://api.openclaw-dev.near.ai".to_string(),
token: "token1".to_string(),
is_non_tee: true,
is_crabshack: true,
};
assert!(
non_tee_mgr.get_is_non_tee(),
"Manager with is_non_tee=true should be non-TEE"
crabshack_mgr.get_is_crabshack(),
"Manager with is_crabshack=true should be crabshack"
);

// Manager with is_non_tee=false should be detected as TEE
let tee_mgr = AgentManager {
// Manager with is_crabshack=false should be detected as legacy_tee
let legacy_tee_mgr = AgentManager {
url: "https://agents.example.com/api/crabshack".to_string(),
token: "token2".to_string(),
is_non_tee: false,
is_crabshack: false,
};
assert!(
!tee_mgr.get_is_non_tee(),
"Manager with is_non_tee=false should be TEE"
!legacy_tee_mgr.get_is_crabshack(),
"Manager with is_crabshack=false should be legacy_tee"
);

// Test that is_non_tee field is respected regardless of URL
// Test that is_crabshack field is respected regardless of URL
let compose_mgr = AgentManager {
url: "https://compose.example.com".to_string(),
token: "token3".to_string(),
is_non_tee: true,
is_crabshack: true,
};
assert!(
compose_mgr.get_is_non_tee(),
"Manager with is_non_tee=true should be non-TEE even with generic URL"
compose_mgr.get_is_crabshack(),
"Manager with is_crabshack=true should be crabshack even with generic URL"
);
}

Expand Down
Loading
Loading