buzz users presence misidentified who is online: the relay's /query
bridge intercepts presence filters and synthesizes relay-signed
kind:20001 events (bridge.rs synthesize_presence) with the subject in
the p tag, but cmd_get_presence mapped event.pubkey — the relay
keypair — so every row reported the relay signer's pubkey.
Extract the row mapping into map_presence_events, which reports the
p-tag subject when present and falls back to the event author
otherwise. The fallback keeps real user-signed kind:20001 updates
correct (build_presence_update emits no p tag — the author is the
subject there) and guards against a malformed bare ["p"] tag blanking
the pubkey.
Regression tests cover all three shapes: synthesized (p-tag subject
wins over the relay signer), user-signed (author reported), and
malformed p tag (author fallback).
Tested: cargo test -p buzz-cli --lib (149 passed, incl. the 3 new
tests), cargo clippy -p buzz-cli --all-targets -D warnings, cargo fmt
--check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
applyCommunity sent a `token` arg that the Rust apply_workspace command
stopped declaring when the relay moved to pure Nostr key auth ("Sprout
speaks Nostr. Nothing else.", #475). Tauri silently drops invoke args
the command doesn't declare, so the Community.token collected by the
add/edit dialogs was persisted to localStorage and never applied
anywhere — a secret-shaped field with no consumer. Resolve the arg
mismatch by deleting the plumbing rather than re-accepting the token:
- Stop sending `token` from applyCommunity and align its invoke args
with the Rust signature (relayUrl, nsec, reposDir).
- Remove the API Token field from the Add/Edit Community dialogs, and
drop token handling from useCommunities (update-result matrix,
dup-merge on add, reinit trigger) and the sidebar/switcher prop
types.
- Retire Community.token to a `@deprecated token?: never` marker and
strip the stale secret from persisted entries in loadCommunities
(extending the existing nsec-strip migration), so it cannot leak
from localStorage into future sessions.
- Regression tests: applyCommunity must send exactly the arg keys
apply_workspace declares (guards the silent-drop IPC contract), and
loadCommunities strips token/nsec and persists the cleaned list.
Tested: desktop pnpm test (2941 passed, incl. the 2 new regression
tests), tsc --noEmit, biome check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Make agents immune to the shared REPOS symlink re-pointing on workspace
switches (Phase 4 of lazy multi-workspace agents, building on the Phase
1-3 relay pinning, lazy activation, and UI scoping):
- Persist repos_dir per relay URL in a new .repos-dirs.json nest dotfile
(keys normalized like agent relay pins), written by every
apply_workspace alongside the single-value .repos-dir that keeps
driving the boot-time symlink resolve. The REPOS symlink itself keeps
following the active workspace as a human/tooling convention.
- At spawn, resolve the agent's own workspace entry (keyed by its
effective relay) to a canonical real path and hand it to the child as
BUZZ_REPOS_DIR, so a mid-task switch that re-points REPOS can no
longer land a workspace-A agent in workspace B's checkouts. A
configured-but-unresolvable entry fails the spawn closed (same
rationale as resolve_repos_at_boot); no entry falls back to the nest
REPOS path, preserving pre-map behavior.
- Adjust the nest AGENTS.md instructions (template v5) that referenced
REPOS/ relatively: agents are told to address checkouts through
$BUZZ_REPOS_DIR whenever it is set.
- Hash the raw per-relay map value into the spawn-config hash so a
repos-dir edit badges needsRestart while a pure workspace switch (and
filesystem state alone) cannot.
- Migrate the map entry in rebind_agent_relay when a community's relay
URL is edited, so rebound agents keep their repos-dir isolation.
- Split the grown repos.rs test module into repos/tests.rs (file-size
guard), mirroring nest/tests.rs and spawn_hash/tests.rs.
Tested: cargo test --manifest-path desktop/src-tauri/Cargo.toml (1433
passed, incl. 7 new repos-map/resolution tests and 2 new spawn-hash
tests), clippy --all-targets -D warnings, rustfmt --check, cargo check
--features mesh-llm.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Filter every managed-agent surface to the active community's relay so
concurrently running workspaces stop bleeding into each other's UI
(Phase 3 of lazy multi-workspace agents, building on the Phase 1 relay
pinning and Phase 2 lazy activation):
- Add agentRelayScope helpers: a frontend mirror of the backend relay
normalizer plus agentBelongsToRelay / partitionAgentsByRelay /
hasRunningAgentInCommunity. A blank pin follows the active community
(same defense-in-depth fallback as effective_agent_relay_url), and a
missing provider/community degrades to unscoped rather than blanking
every surface.
- Scope the useManagedAgentActions list — and the bulk stop, start/
stop/delete lookups, and presence derived from it — to the active
relay via a new lenient useActiveRelayUrl hook. Persona delete keeps
counting instances against the unscoped record set since deleting a
persona removes instances in every community.
- Gate the 5s managed-agents liveness poll on a running agent in this
community; agents running in other communities render no process
state here, so they no longer keep the poll alive.
- Hold auto-restart for agents pinned to other communities — their
working/observer signals are read from the active relay only, so a
pure workspace switch must never fire a restart — and re-check the
pin in the pre-fire re-fetch so a rebind cannot restart a foreign
agent.
- Scope mergeKnownAgentPubkeys' managed-agent source to the active
relay; the relay-agent (kind:10100) source stays unfiltered.
- Surface an "N agents running in other communities" line in the
agents header so concurrent background agents stay discoverable.
Tested: desktop pnpm test (2939 passed, incl. new agentRelayScope,
knownAgentPubkeys, and autoRestartPolicy unit tests), tsc --noEmit,
biome check, Playwright smoke project (537 passed, 5 failed; 2 of the
failures passed on rerun and the other 3 — channels intro-scroll,
video review mode, shared-compute empty state — reproduce on baseline
HEAD without this change, so all 5 are pre-existing flakes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Extract agent activation out of the one-shot boot restore into a
relay-filtered step that runs on every workspace apply (Phase 2 of lazy
multi-workspace agents, building on the Phase 1 relay pinning):
- Rename restore_managed_agents_on_launch to activate_workspace_agents,
taking the applied workspace relay and starting only local
start-on-launch agents pinned to that relay (normalized match; a blank
pin matches the visited workspace, mirroring the
effective_agent_relay_url defense-in-depth fallback).
- Call it from every apply_workspace instead of only the first — boot
restore becomes the session's first activation. The one-shot
managed_agent_restore_pending flag now gates only the mesh-llm Share
Compute restore.
- Track activated relays in AppState (activated_agent_relays) so each
workspace activates at most once per app session: bouncing A→B→A never
resurrects agents the user manually stopped in A. Nothing is stopped
on switch — each workspace's agents keep running against their own
relay.
- Gate the boot-time repos-dir/identity-recovery safety checks behind a
new session-wide managed_agent_activation_enabled flag, and run the
stale-process/orphan sweeps only on the session's first activation so
a later activation cannot reap a concurrent activation's
not-yet-tracked children.
Tested: cargo test --manifest-path desktop/src-tauri/Cargo.toml (1424
passed, incl. 7 new activation unit tests), clippy -D warnings, rustfmt,
cargo check --features mesh-llm.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Stop agent records from floating to whichever workspace is active — the
relay URL becomes the backend-side ownership key for agents (Phase 1 of
lazy multi-workspace agents):
- Stamp the resolved workspace relay onto records at create, on an
explicit relay edit that clears the field, and on snapshot/team
imports, instead of persisting blank.
- Migrate legacy blank-relay records on the first apply_workspace after
boot. Behavior-preserving: blank resolved to exactly that relay at
boot restore.
- Add a shared relay-URL normalizer (trailing slash, scheme/host case)
and hash the normalized resolved relay in the spawn-config hash so
cosmetic URL differences no longer trip the restart badge.
- Add a rebind_agent_relay command, invoked by updateCommunity when a
community's relay URL is edited, re-pinning records from the old URL
onto the new one so those agents don't orphan.
effective_agent_relay_url keeps its blank-to-workspace fallback as
defense-in-depth for records that escaped stamping. The pure record
mutations live in the new managed_agents::relay_pinning module.
Tested: cargo test --manifest-path desktop/src-tauri/Cargo.toml (1420
passed), clippy -D warnings, rustfmt, desktop pnpm test (2920 passed),
tsc, biome.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>