~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs-agents

agents and atirc modules for atlibs: launch agents from a composition, chat on atirc; pages and the agents service

0
0
main
108 lines · 6.5 KB Raw History
import assert from "node:assert/strict";
import { test } from "node:test";

import { did, load, now } from "../../../atlibs/rust/src/harness.mjs";
import { runModule } from "../../../atlibs/javascript/src/run-module/index.js";

const url = new URL("../../target/wasm32-unknown-unknown/release/atirc.wasm", import.meta.url);
const { module, run, handle } = await load(url);
const start = (fields) => ({ did, now, ...fields, state: null, event: { type: "start" } });
// handle(), answering every call with answer(call) and recording { nsid, params, input, proxy }.
async function recorded(entry, input, answer) {
	const calls = [];
	const ran = await handle(entry, input, { respond: ({ nsid, params, input, proxy }) => (calls.push({ nsid, ...(params && { params }), ...(input && { input }), ...(proxy && { proxy }) }), answer()) });
	return { ...ran, calls };
}
const ENTRIES = ["read", "post", "irc_post"];
const PROXY = "did:web:irc.fedfork.com#atirc";
const channel = "#alice_bsky_social";
const message = (seq, text = `line ${seq}`) => ({ seq, channel, did, nick: "alice", text, ts: "2026-11-08T00:00:00Z", source: "xrpc" });

test("module has no imports and exports the ABI", () => {
	assert.equal(WebAssembly.Module.imports(module).length, 0);
	const names = WebAssembly.Module.exports(module).map((e) => e.name);
	for (const name of ["memory", "alloc", ...ENTRIES]) assert.ok(names.includes(name), name);
});

test("read defaults to the handle channel and pages through atirc via the proxy", async () => {
	const pages = [
		{ messages: Array.from({ length: 200 }, (_, i) => message(i + 1)), cursor: 200 },
		{ messages: [message(201), message(202)], cursor: 202 },
	];
	const ran = await recorded("read", { handle: "Alice.bsky.social", limit: 3 }, () => ({ ok: true, output: pages.shift() }));
	assert.deepEqual(ran.calls[0], { nsid: "chat.atirc.list", params: { channel, limit: "200", cursor: "0" }, proxy: PROXY });
	assert.equal(ran.calls[1].params.cursor, "200");
	assert.deepEqual(ran.result, { channel, messages: [message(200), message(201), message(202)], cursor: 202, more: false });
});

test("read stops after six full pages and says there is more", async () => {
	let seq = 0;
	const ran = await recorded("read", { channel: "#x", cursor: 7 }, () => ({ ok: true, output: { messages: Array.from({ length: 200 }, () => message(++seq)), cursor: seq } }));
	assert.equal(ran.calls.length, 6);
	assert.equal(ran.calls[0].params.cursor, "7");
	assert.equal(ran.result.more, true);
	assert.equal(ran.result.cursor, 1200);
	assert.equal(ran.result.messages.length, 50);
});

test("read needs a channel or handle, and a valid channel", () => {
	assert.match(run("read", start({})).error, /channel or handle/);
	assert.match(run("read", start({ channel: "no-hash" })).error, /channel must be/);
	assert.match(run("read", start({ channel: "#a b" })).error, /channel must be/);
});

test("post sends one proxied chat.atirc.post and returns the message", async () => {
	const ran = await recorded("post", { channel: "#atirc", text: "  hi  ", nick: "" }, () => ({ ok: true, output: message(9, "hi") }));
	assert.deepEqual(ran.calls, [{ nsid: "chat.atirc.post", input: { text: "hi", channel: "#atirc" }, proxy: PROXY }]);
	assert.deepEqual(ran.result, message(9, "hi"));
	assert.deepEqual(run("post", start({ text: "x" })).calls[0].input, { text: "x" });
});

test("post rejects empty text and surfaces a failed call", async () => {
	assert.match(run("post", start({ text: " " })).error, /text is required/);
	assert.match((await recorded("post", { text: "x" }, () => ({ ok: false, error: "Forbidden: channel is bound to another handle" }))).error, /Forbidden/);
});

// The host side of `proxy`: runModule validates it and hands it to call().
test("runModule passes proxy to call and rejects a malformed one", async () => {
	const ctx = { wasm: { run: async ({ entry, input }) => run(entry, input) } };
	const seen = [];
	const ran = await runModule(ctx, { module, calls: ["chat.atirc.post"] }, "post", { did, text: "hi" }, async (nsid, request) => {
		seen.push([nsid, request]);
		return message(1, "hi");
	});
	assert.deepEqual(seen.map(([nsid, { params, input, proxy }]) => [nsid, { params, input, proxy }]), [["chat.atirc.post", { params: undefined, input: { text: "hi" }, proxy: PROXY }]]);
	assert.equal(ran.result.seq, 1);
	const bad = { wasm: { run: async ({ input }) => (input.event.type === "start" ? { calls: [{ id: "x", nsid: "chat.atirc.post", proxy: "https://evil" }] } : { result: input.event, done: true }) } };
	const refused = await runModule(bad, { module }, "post", { did }, async () => ({}));
	assert.match(refused.result.error, /proxy must be did#service/);
});

const b64url = (o) => Buffer.from(JSON.stringify(o)).toString("base64url");
const token = `${b64url({ alg: "ES256" })}.${b64url({ endpoint: "endpointabc", handle: "alice.bsky.social", nick: "alice-bot" })}.sig`;

test("irc_post plans one iroh atirc/irc/1 exchange from the token's endpoint claim", async () => {
	const welcome = ":irc.fedfork.com 001 alice-bot :Welcome\r\n:alice-bot!alice-bot@x JOIN #alice_bsky_social\r\nERROR :Closing link\r\n";
	const ran = await recorded("irc_post", { token, text: "hi" }, () => ({ ok: true, output: { output: welcome } }));
	assert.deepEqual(ran.calls, [{
		nsid: "com.fedfork.exec.iroh.connect",
		input: {
			ticket: "endpointabc",
			alpn: "atirc/irc/1",
			send: `PASS ${token}\r\nNICK alice-bot\r\nUSER alice-bot 0 * :alice-bot\r\nJOIN #alice_bsky_social\r\nPRIVMSG #alice_bsky_social :hi\r\nQUIT :done\r\n`,
		},
	}]);
	assert.deepEqual(ran.result, { channel: "#alice_bsky_social", nick: "alice-bot", transport: "iroh", alpn: "atirc/irc/1" });
});

test("irc_post surfaces refusals and rejects bad input before any call", async () => {
	const reply = (output) => () => ({ ok: true, output: { output } });
	assert.match((await recorded("irc_post", { token, text: "hi" }, reply(":s 433 * alice-bot :Nickname is already in use\r\n"))).error, /refused: .*433/);
	assert.match((await recorded("irc_post", { token, text: "hi" }, reply("ERROR :bad token\r\n"))).error, /not registered: ERROR/);
	assert.match((await recorded("irc_post", { token, text: "hi", nick: "other" }, reply(":s 001 other :w\r\n"))).error, /did not join/);
	assert.match(run("irc_post", start({ text: "hi" })).error, /token/);
	assert.match(run("irc_post", start({ token: "nope", text: "hi" })).error, /JWT/);
	assert.match(run("irc_post", start({ token, text: "a\nQUIT" })).error, /one line/);
	assert.match(run("irc_post", start({ token, text: "hi", nick: "a b" })).error, /one word/);
	assert.equal(run("irc_post", start({ token, text: "hi", channel: "#x" })).state.channel, "#x");
});