- commit
- 1b87a28f6ec644b3ace669ca4264061623b9506e
- parent
- 6d1dafb4feb3215a1661dee89c8ac814e703fb9e
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 6 | ++++++ |
| M | scripts/ | 30 | ++++++++++++++++++++++++++++++ |
@@ -145,6 +145,8 @@
145145 object({agent: pubkey}, ["agent"]), reads, (input, signal) => query("browseagent", input, signal));146146 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.",147147 object(), reads, (input, signal) => manage("listcallbacks", [], signal));148+ 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.",149+ object(), reads, (input, signal) => manage("listjoinrequests", [], signal));148150 149151 function open(path) {150152 const url = new URL(local(path), location.href);@@ -232,6 +234,10 @@
232234 object({agent: pubkey}, ["agent"]), "resumeagent", input => [input.agent]);233235 control("tiny.revoke_agent", "Revoke an agent's grant. The agent loses its role at once; only a new grant restores it.",234236 object({agent: pubkey}, ["agent"]), "revokeagent", input => [input.agent]);237+ 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.",238+ object({pubkey}, ["pubkey"]), "approvejoin", input => [input.pubkey]);239+ control("tiny.deny_join", "Deny an access request. The key stays outside the relay and may ask again. Owner or moderator.",240+ object({pubkey}, ["pubkey"]), "denyjoin", input => [input.pubkey]);235241 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.",236242 object({id}, ["id"]), "pausecallback", input => [input.id]);237243 control("tiny.resume_callback", "Resume a paused event callback by id and clear its failure count.",@@ -238,6 +238,36 @@ test("agent tools list through the signer, read through the session and control
238238 }239239 });240240 241+test("access request tools list through the signer and decide by pubkey", async () => {242+ const calls = [];243+ const asker = "b".repeat(64);244+ const {tools} = await browser({path: "/r/work/manage/people", signedFetch: async (url, method, body) => {245+ calls.push({url, method, body: JSON.parse(body)});246+ const request = JSON.parse(body);247+ if (request.method === "listjoinrequests") {248+ return new Response(JSON.stringify({result: [{pubkey: asker, reason: "Met you at the meetup", requested_at: 1700000000, status: "pending", decided_by: "", decided_at: 0}]}));249+ }250+ return new Response(JSON.stringify({result: {pubkey: asker, status: request.method === "approvejoin" ? "approved" : "denied"}}));251+ }});252+ const list = tools.get("tiny.list_join_requests");253+ assert.equal(list.annotations.readOnlyHint, true);254+ const listed = await list.execute({});255+ assert.equal(listed.structuredContent.result[0].pubkey, asker);256+ assert.equal(listed.structuredContent.result[0].status, "pending");257+ assert.equal(calls[0].url, "/r/work/manage/rpc");258+ assert.equal(calls[0].method, "POST");259+ assert.deepEqual(calls[0].body, {method: "listjoinrequests", params: []});260+ for (const [name, method, status] of [["tiny.approve_join", "approvejoin", "approved"], ["tiny.deny_join", "denyjoin", "denied"]]) {261+ const tool = tools.get(name);262+ assert.equal(tool.annotations.consequentialHint, true);263+ assert.deepEqual([...tool.inputSchema.required], ["pubkey"]);264+ assert.equal(tool.inputSchema.properties.pubkey.pattern, "^[0-9a-f]{64}$");265+ const result = await tool.execute({pubkey: asker});266+ assert.deepEqual(calls.at(-1).body, {method, params: [asker]});267+ assert.equal(result.structuredContent.result.status, status);268+ }269+});270+241271 test("callback tools list through the signer and control by id", async () => {242272 const calls = [];243273 const {tools} = await browser({path: "/r/work/manage/agents", signedFetch: async (url, method, body) => {