Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
4 changes: 3 additions & 1 deletion desktop/src-tauri/src/managed_agents/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,12 @@ pub async fn restore_managed_agents_on_launch(
// mid-turn session is not resumed by an
// eager child — and silently reintroduces
// N idle brains on every launch.
// Requested URL, not the canonical
// key: the child dials this.
spawn_agent_child(
app,
record,
&key.relay_url,
&relay_url,
true,
owner_hex_ref,
)
Expand Down
17 changes: 13 additions & 4 deletions desktop/src-tauri/src/managed_agents/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1692,9 +1692,17 @@ pub fn spawn_agent_child(
.map(|p| p.display().to_string())
.unwrap_or_else(|| effective_command.clone());

// The caller supplies the explicit canonical pair relay. This is the only
// relay this child may connect to, regardless of the record/workspace default.
let effective_relay_url = runtime_key.relay_url.clone();
// The caller supplies the explicit pair relay. This is the only relay this
// child may connect to, regardless of the record/workspace default.
//
// Dial the URL as requested, NOT `runtime_key.relay_url`. The key is an
// identity: `ManagedAgentRuntimeKey::new` canonicalizes it (folding loopback
// to 127.0.0.1) so `runtime_id()` stays stable, which is right for keying
// and wrong for addressing. The relay resolves a community from the literal
// request Host and fails closed on an unmapped one, so a deployment
// configured as `ws://localhost:PORT` rejects a canonicalized
// `ws://127.0.0.1:PORT` with a generic 404 and the harness never connects.
let effective_relay_url = relay_url.to_string();

// Augment PATH for DMG launches so child processes can find:
// - bundled CLI via ~/.local/bin symlink
Expand Down Expand Up @@ -2134,7 +2142,8 @@ pub fn start_managed_agent_process(
// Scalar PIDs are migration-only and never establish pair liveness.
record.runtime_pid = None;

let mut process = spawn_agent_child(app, record, &key.relay_url, false, owner_hex)?;
// Pass the requested URL, not the canonical key: the child dials this.
let mut process = spawn_agent_child(app, record, &relay_url, false, owner_hex)?;
let now = now_iso();
let receipt = super::ManagedAgentRuntimeReceipt {
key: key.clone(),
Expand Down
3 changes: 2 additions & 1 deletion desktop/src-tauri/src/managed_agents/runtime_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ fn start_pair(
.lock()
.ok()
.map(|keys| keys.public_key().to_hex());
let mut process = spawn_agent_child(&app, record, &key.relay_url, lazy, owner.as_deref())?;
// Pass the requested URL, not the canonical key: the child dials this.
let mut process = spawn_agent_child(&app, record, &relay_url, lazy, owner.as_deref())?;
Comment thread
ahmetkca marked this conversation as resolved.
let now = crate::util::now_iso();
let receipt = ManagedAgentRuntimeReceipt {
key: key.clone(),
Expand Down
37 changes: 37 additions & 0 deletions desktop/src-tauri/src/managed_agents/runtime_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,43 @@ impl ManagedAgentRuntimeKey {
}
}

#[cfg(test)]
mod tests {
use super::ManagedAgentRuntimeKey;

#[test]
fn runtime_key_relay_url_is_canonical_identity_not_a_dial_address() {
// `relay_url` here is canonicalized (loopback folds to 127.0.0.1) so
// `runtime_id()` is stable for one pair no matter how the operator spelled
// the host. That is correct for keying, and it is exactly why this field
// must never be handed to the harness as the URL to connect to: the relay
// resolves a community from the literal request Host and fails closed on
// an unmapped one, so a deployment configured as `ws://localhost:PORT`
// rejects `ws://127.0.0.1:PORT` with a generic 404. `spawn_agent_child`
// dials the URL as requested; see the comment there.
let pubkey = "a".repeat(64);
for spelling in [
"ws://localhost:3000",
"ws://127.0.0.1:3000",
"ws://[::1]:3000",
] {
let key = ManagedAgentRuntimeKey::new(pubkey.clone(), spelling)
.expect("loopback relay URL should be accepted");
assert_eq!(
key.relay_url, "ws://127.0.0.1:3000",
"spelling {spelling} should canonicalize"
);
}

// One identity across spellings, so the on-disk runtime path is stable.
let named = ManagedAgentRuntimeKey::new(pubkey.clone(), "ws://localhost:3000")
.expect("valid relay URL");
let numeric = ManagedAgentRuntimeKey::new(pubkey.clone(), "ws://127.0.0.1:3000")
.expect("valid relay URL");
assert_eq!(named.runtime_id(), numeric.runtime_id());
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ManagedAgentRuntimeLifecycle {
Expand Down