mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
feat(desktop): mount private remote Stop controls and live receiver
Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
@@ -310,9 +310,10 @@ with a TypeScript lookup table or an id comparison in a component.
|
||||
|
||||
## Remote Desktop Stop
|
||||
|
||||
Native IPC accepts an owner-private, explicitly selected agent+Desktop Stop,
|
||||
not inferred agent location. The relay redelivers stored Stop duplicates without
|
||||
repeating relay side effects.
|
||||
Known Desktops exposes an owner-private, explicitly selected agent+Desktop Stop,
|
||||
not inferred agent location. The app-scoped receiver subscribes live only;
|
||||
reopening never replays commands. An explicit retry republishes the exact request;
|
||||
the relay redelivers stored Stop duplicates without repeating relay side effects.
|
||||
The receiver returns saved results or Unknown, never repeats a consumed Stop.
|
||||
Native owner-delegation and community checks
|
||||
precede durable admission and ordinary pair Stop. A delivery ACK is not success.
|
||||
|
||||
155
desktop/src/features/agents/ui/DesktopStopControl.test.mjs
Normal file
155
desktop/src/features/agents/ui/DesktopStopControl.test.mjs
Normal file
@@ -0,0 +1,155 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import React from "react";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import {
|
||||
DesktopStopControl,
|
||||
DesktopStopReceiver,
|
||||
} from "./DesktopStopControl.tsx";
|
||||
import { relayClient } from "../../../shared/api/relayClient.ts";
|
||||
|
||||
test("mounted Stop waits for a correlated result and retries identical bytes without replay", async () => {
|
||||
const dom = new JSDOM("<div id='root'></div>", {
|
||||
url: "https://desktop.test",
|
||||
});
|
||||
Object.assign(globalThis, {
|
||||
window: dom.window,
|
||||
document: dom.window.document,
|
||||
localStorage: dom.window.localStorage,
|
||||
IS_REACT_ACT_ENVIRONMENT: true,
|
||||
});
|
||||
const { createRoot } = await import("react-dom/client");
|
||||
const scope = { owner: "owner", community: "wss://one.example" };
|
||||
const request = { id: "request", kind: 50180, tags: [["d", "desktop"]] };
|
||||
const result = { id: "result", kind: 50181, tags: [["e", request.id]] };
|
||||
const client = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false, staleTime: Infinity } },
|
||||
});
|
||||
client.setQueryData(
|
||||
["relay-agents"],
|
||||
[
|
||||
{ pubkey: "agent", name: "Owned agent", ownerPubkey: "owner" },
|
||||
{ pubkey: "foreign", name: "Foreign agent", ownerPubkey: "other" },
|
||||
],
|
||||
);
|
||||
const original = {
|
||||
fetch: relayClient.fetchEvents,
|
||||
publish: relayClient.publishEvent,
|
||||
subscribe: relayClient.subscribeLive,
|
||||
};
|
||||
let live, release;
|
||||
let receiveCalls = 0,
|
||||
prepared = 0,
|
||||
closed = 0;
|
||||
const sent = [];
|
||||
window.__TAURI_INTERNALS__ = {
|
||||
invoke: async (command, args) => {
|
||||
assert.equal(args.owner, scope.owner);
|
||||
assert.equal(args.community, scope.community);
|
||||
if (command === "prepare_desktop_stop") {
|
||||
prepared++;
|
||||
assert.equal(args.desktop, "desktop");
|
||||
assert.equal(args.agent, "agent");
|
||||
return request;
|
||||
}
|
||||
if (command === "receive_desktop_stop") {
|
||||
receiveCalls++;
|
||||
return result;
|
||||
}
|
||||
assert.equal(command, "read_desktop_stop_results");
|
||||
return "stopped";
|
||||
},
|
||||
};
|
||||
relayClient.subscribeLive = async (filter, callback) => {
|
||||
assert.deepEqual(filter, {
|
||||
kinds: [50180],
|
||||
authors: [scope.owner],
|
||||
limit: 0,
|
||||
});
|
||||
live = callback;
|
||||
return () => {
|
||||
closed++;
|
||||
live = undefined;
|
||||
};
|
||||
};
|
||||
relayClient.publishEvent = async (event) => {
|
||||
sent.push(event);
|
||||
live?.(event);
|
||||
};
|
||||
relayClient.fetchEvents = async (filter) => {
|
||||
assert.deepEqual(filter, {
|
||||
kinds: [50181],
|
||||
authors: [scope.owner],
|
||||
"#e": [request.id],
|
||||
limit: 16,
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
release = () => resolve([result]);
|
||||
});
|
||||
};
|
||||
const root = createRoot(document.getElementById("root"));
|
||||
const render = (receiver) =>
|
||||
React.act(async () =>
|
||||
root.render(
|
||||
React.createElement(
|
||||
QueryClientProvider,
|
||||
{ client },
|
||||
React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
receiver
|
||||
? React.createElement(DesktopStopReceiver, { scope })
|
||||
: null,
|
||||
React.createElement(DesktopStopControl, {
|
||||
scope,
|
||||
desktop: { id: "desktop", name: "Workshop" },
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
const click = (text) =>
|
||||
React.act(async () =>
|
||||
[...document.querySelectorAll("button")]
|
||||
.find((b) => b.textContent === text)
|
||||
.click(),
|
||||
);
|
||||
try {
|
||||
await render(false);
|
||||
assert.doesNotMatch(document.body.textContent, /Foreign agent/);
|
||||
const select = document.querySelector("select");
|
||||
await React.act(async () => {
|
||||
select.value = "agent";
|
||||
select.dispatchEvent(new dom.window.Event("change", { bubbles: true }));
|
||||
});
|
||||
await click("Stop on Workshop");
|
||||
assert.equal(prepared, 1);
|
||||
assert.match(document.body.textContent, /Waiting for this Desktop/);
|
||||
assert.doesNotMatch(document.body.textContent, /Stop confirmed/);
|
||||
assert.equal(receiveCalls, 0, "absent receiver has not stopped anything");
|
||||
await React.act(async () => release());
|
||||
assert.match(document.body.textContent, /Stop confirmed by Workshop/);
|
||||
await render(true);
|
||||
assert.equal(receiveCalls, 0, "mount cannot replay stored Stop");
|
||||
// The mounted receiver returns a saved native result, while the sender
|
||||
// explicitly retries the exact prepared request rather than signing anew.
|
||||
relayClient.publishEvent = async (event) => {
|
||||
sent.push(event);
|
||||
if (event.kind === 50180) live?.(event);
|
||||
};
|
||||
await click("Retry same Stop");
|
||||
await React.act(async () => release());
|
||||
assert.equal(prepared, 1);
|
||||
assert.equal(receiveCalls, 1);
|
||||
assert.ok(sent.filter((e) => e.kind === 50180).every((e) => e === request));
|
||||
} finally {
|
||||
await React.act(async () => root.unmount());
|
||||
assert.equal(closed, 1);
|
||||
client.clear();
|
||||
relayClient.fetchEvents = original.fetch;
|
||||
relayClient.publishEvent = original.publish;
|
||||
relayClient.subscribeLive = original.subscribe;
|
||||
dom.window.close();
|
||||
}
|
||||
});
|
||||
170
desktop/src/features/agents/ui/DesktopStopControl.tsx
Normal file
170
desktop/src/features/agents/ui/DesktopStopControl.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useRelayAgentsQuery } from "../hooks";
|
||||
import {
|
||||
prepareStop,
|
||||
readStopOutcome,
|
||||
receiveStops,
|
||||
sendStop,
|
||||
} from "../desktopStop";
|
||||
import type { DesktopScope, DesktopRow } from "../desktopList";
|
||||
import type { RelayEvent } from "@/shared/api/types";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
|
||||
/** App-scoped live receiver; no historical requests are loaded on mount. */
|
||||
export function DesktopStopReceiver({ scope }: { scope: DesktopScope | null }) {
|
||||
const [error, setError] = useState("");
|
||||
const { owner, community } = scope ?? {};
|
||||
useEffect(() => {
|
||||
if (!owner || !community) return;
|
||||
let active = true;
|
||||
let close: (() => void) | undefined;
|
||||
setError("");
|
||||
void receiveStops({ owner, community }, () => active, setError)
|
||||
.then((unsubscribe) => {
|
||||
if (active) close = unsubscribe;
|
||||
else unsubscribe();
|
||||
})
|
||||
.catch(() => {
|
||||
if (active)
|
||||
setError("Remote Stop receiver is unavailable on this Desktop.");
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
close?.();
|
||||
};
|
||||
}, [owner, community]);
|
||||
return error ? (
|
||||
<p role="status" className="text-xs text-muted-foreground">
|
||||
{error}
|
||||
</p>
|
||||
) : null;
|
||||
}
|
||||
|
||||
/** Deliberately selects a host, not an inferred running location or presence. */
|
||||
export function DesktopStopControl({
|
||||
scope,
|
||||
desktop,
|
||||
}: {
|
||||
scope: DesktopScope;
|
||||
desktop: DesktopRow;
|
||||
}) {
|
||||
const agents = useRelayAgentsQuery();
|
||||
const owned = (agents.data ?? []).filter(
|
||||
(agent) => agent.ownerPubkey === scope.owner,
|
||||
);
|
||||
const [agent, setAgent] = useState("");
|
||||
const [request, setRequest] = useState<RelayEvent | null>(null);
|
||||
const [message, setMessage] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
const active = useRef(true);
|
||||
useEffect(() => {
|
||||
active.current = true;
|
||||
return () => {
|
||||
active.current = false;
|
||||
};
|
||||
}, []);
|
||||
const run = async (retry: boolean) => {
|
||||
setBusy(true);
|
||||
let current = retry ? request : null;
|
||||
try {
|
||||
current ??= await prepareStop(
|
||||
scope,
|
||||
desktop.id,
|
||||
agent,
|
||||
() => active.current,
|
||||
);
|
||||
if (!active.current) return;
|
||||
setRequest(current);
|
||||
setMessage("Stop requested. Waiting for this Desktop’s result…");
|
||||
try {
|
||||
await sendStop(scope, current, () => active.current);
|
||||
} catch {
|
||||
if (active.current)
|
||||
setMessage(
|
||||
"Delivery unconfirmed. Checking for this Desktop’s result…",
|
||||
);
|
||||
}
|
||||
for (let attempt = 0; attempt < 15 && active.current; attempt++) {
|
||||
const outcome = await readStopOutcome(
|
||||
scope,
|
||||
current,
|
||||
() => active.current,
|
||||
);
|
||||
if (!active.current) return;
|
||||
if (outcome === "stopped") {
|
||||
setMessage(`Stop confirmed by ${desktop.name}.`);
|
||||
return;
|
||||
}
|
||||
if (outcome === "failed") {
|
||||
setMessage(
|
||||
`Stop failed on ${desktop.name}. No success was confirmed.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
if (active.current)
|
||||
setMessage(
|
||||
"Stop unconfirmed. This Desktop may be unavailable; its agents may still be running.",
|
||||
);
|
||||
} catch {
|
||||
if (active.current)
|
||||
setMessage("Stop unconfirmed. No successful result could be read.");
|
||||
} finally {
|
||||
if (active.current) setBusy(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="mt-2 space-y-2">
|
||||
<label className="block text-xs">
|
||||
Agent to stop on {desktop.name}
|
||||
<select
|
||||
aria-label={`Agent to stop on ${desktop.name}`}
|
||||
className="ml-2 rounded border bg-background p-1 text-sm"
|
||||
value={agent}
|
||||
disabled={busy}
|
||||
onChange={(event) => {
|
||||
setAgent(event.target.value);
|
||||
setRequest(null);
|
||||
setMessage("");
|
||||
}}
|
||||
>
|
||||
<option value="">Choose your agent</option>
|
||||
{owned.map((item) => (
|
||||
<option key={item.pubkey} value={item.pubkey}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Stops only this agent on this Desktop in this community. This list does
|
||||
not establish where it is running.
|
||||
</p>
|
||||
{agents.isError && <p role="status">Your agent list is unavailable.</p>}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={!agent || busy}
|
||||
onClick={() => void run(false)}
|
||||
>
|
||||
Stop on {desktop.name}
|
||||
</Button>
|
||||
{request && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
disabled={busy}
|
||||
onClick={() => void run(true)}
|
||||
>
|
||||
Retry same Stop
|
||||
</Button>
|
||||
)}
|
||||
{message && (
|
||||
<p role="status" className="text-xs">
|
||||
{message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DesktopStopControl, DesktopStopReceiver } from "./DesktopStopControl";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useIdentityQuery } from "@/shared/api/hooks";
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
import { DesktopCapabilityDetails } from "./DesktopCapabilityDetails";
|
||||
|
||||
type View = {
|
||||
scope?: import("../desktopList").DesktopScope;
|
||||
capabilities?: DesktopCapabilities[];
|
||||
capabilityWarning?: string;
|
||||
list: DesktopList | null;
|
||||
@@ -78,7 +80,7 @@ export function DesktopListStartup() {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [refetch, pulse, report]);
|
||||
return null;
|
||||
return <DesktopStopReceiver scope={useDesktopScope()} />;
|
||||
}
|
||||
|
||||
export function KnownDesktops() {
|
||||
@@ -92,6 +94,7 @@ export function KnownDesktops() {
|
||||
}, []);
|
||||
return (
|
||||
<DesktopListView
|
||||
scope={useDesktopScope() ?? undefined}
|
||||
capabilities={capabilities.data?.rows}
|
||||
capabilityWarning={
|
||||
capabilities.isError
|
||||
@@ -122,6 +125,7 @@ export function KnownDesktops() {
|
||||
}
|
||||
|
||||
export function DesktopListView({
|
||||
scope,
|
||||
list,
|
||||
loading,
|
||||
error,
|
||||
@@ -184,6 +188,13 @@ export function DesktopListView({
|
||||
now,
|
||||
)}
|
||||
</div>
|
||||
{scope && (
|
||||
<DesktopStopControl
|
||||
key={`${scope.owner}:${scope.community}:${row.id}`}
|
||||
scope={scope}
|
||||
desktop={row}
|
||||
/>
|
||||
)}
|
||||
<DesktopCapabilityDetails
|
||||
report={capabilities?.find((item) => item.id === row.id)}
|
||||
now={now}
|
||||
|
||||
Reference in New Issue
Block a user