tinyrelaycommit

Commit 1b87a28f6ec6

browse files at this commit | history

1diff --git a/internal/webui/webmcp.js b/internal/webui/webmcp.js
2index 921568f..f118447 100644
3--- a/internal/webui/webmcp.js
4+++ b/internal/webui/webmcp.js
5@@ -145,6 +145,8 @@
6 object({agent: pubkey}, ["agent"]), reads, (input, signal) => query("browseagent", input, signal));
7 register("tiny.list_callbacks", "List event callbacks: id, owner, host, filter, paused state, failures and last delivery. Members and agents see their own; the owner and moderators see every callback. Uses your connected signer.",
8 object(), reads, (input, signal) => manage("listcallbacks", [], signal));
9+ register("tiny.list_join_requests", "List access requests from people who asked to join without an invite: pubkey, reason, time asked, status (pending, approved or denied), who decided and when. Pending requests come first. Uses your connected signer; owner or moderator.",
10+ object(), reads, (input, signal) => manage("listjoinrequests", [], signal));
11
12 function open(path) {
13 const url = new URL(local(path), location.href);
14@@ -232,6 +234,10 @@
15 object({agent: pubkey}, ["agent"]), "resumeagent", input => [input.agent]);
16 control("tiny.revoke_agent", "Revoke an agent's grant. The agent loses its role at once; only a new grant restores it.",
17 object({agent: pubkey}, ["agent"]), "revokeagent", input => [input.agent]);
18+ control("tiny.approve_join", "Approve an access request: the key becomes a member, the relay's member list and add-user record follow, and the request is marked approved. Owner or moderator.",
19+ object({pubkey}, ["pubkey"]), "approvejoin", input => [input.pubkey]);
20+ control("tiny.deny_join", "Deny an access request. The key stays outside the relay and may ask again. Owner or moderator.",
21+ object({pubkey}, ["pubkey"]), "denyjoin", input => [input.pubkey]);
22 control("tiny.pause_callback", "Pause an event callback by id. Deliveries stop until it is resumed. The callback's owner, the relay owner and moderators may do this.",
23 object({id}, ["id"]), "pausecallback", input => [input.id]);
24 control("tiny.resume_callback", "Resume a paused event callback by id and clear its failure count.",
25diff --git a/scripts/webmcp.test.mjs b/scripts/webmcp.test.mjs
26index 2461c3c..aa7009c 100644
27--- a/scripts/webmcp.test.mjs
28+++ b/scripts/webmcp.test.mjs
29@@ -238,6 +238,36 @@ test("agent tools list through the signer, read through the session and control
30 }
31 });
32
33+test("access request tools list through the signer and decide by pubkey", async () => {
34+ const calls = [];
35+ const asker = "b".repeat(64);
36+ const {tools} = await browser({path: "/r/work/manage/people", signedFetch: async (url, method, body) => {
37+ calls.push({url, method, body: JSON.parse(body)});
38+ const request = JSON.parse(body);
39+ if (request.method === "listjoinrequests") {
40+ return new Response(JSON.stringify({result: [{pubkey: asker, reason: "Met you at the meetup", requested_at: 1700000000, status: "pending", decided_by: "", decided_at: 0}]}));
41+ }
42+ return new Response(JSON.stringify({result: {pubkey: asker, status: request.method === "approvejoin" ? "approved" : "denied"}}));
43+ }});
44+ const list = tools.get("tiny.list_join_requests");
45+ assert.equal(list.annotations.readOnlyHint, true);
46+ const listed = await list.execute({});
47+ assert.equal(listed.structuredContent.result[0].pubkey, asker);
48+ assert.equal(listed.structuredContent.result[0].status, "pending");
49+ assert.equal(calls[0].url, "/r/work/manage/rpc");
50+ assert.equal(calls[0].method, "POST");
51+ assert.deepEqual(calls[0].body, {method: "listjoinrequests", params: []});
52+ for (const [name, method, status] of [["tiny.approve_join", "approvejoin", "approved"], ["tiny.deny_join", "denyjoin", "denied"]]) {
53+ const tool = tools.get(name);
54+ assert.equal(tool.annotations.consequentialHint, true);
55+ assert.deepEqual([...tool.inputSchema.required], ["pubkey"]);
56+ assert.equal(tool.inputSchema.properties.pubkey.pattern, "^[0-9a-f]{64}$");
57+ const result = await tool.execute({pubkey: asker});
58+ assert.deepEqual(calls.at(-1).body, {method, params: [asker]});
59+ assert.equal(result.structuredContent.result.status, status);
60+ }
61+});
62+
63 test("callback tools list through the signer and control by id", async () => {
64 const calls = [];
65 const {tools} = await browser({path: "/r/work/manage/agents", signedFetch: async (url, method, body) => {

About

ownerc01503e8035f
visibilitypublic
head1b87a28f6ec6

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs