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
// node --test web/agents/agents-cubit.test.mjs
// The page cubit against a stubbed OAuth client (the session's PDS minting service auth) and a stubbed
// agents service that checks each token's aud and lxm and scopes launches to their caller.
import assert from "node:assert/strict";
import { registerHooks } from "node:module";
import { test } from "node:test";
registerHooks({
resolve(specifier, context, next) {
const lib = specifier.startsWith("atlibs/") && new URL(`../../../atlibs/javascript/src/${specifier.slice("atlibs/".length)}`, import.meta.url).href;
return next(lib || specifier, context);
},
});
const { AgentsCubit, DEFAULT_SERVICE, NSID, SCOPE } = await import("./agents-cubit.js");
const BOB = "did:plc:ocnuqjlzgohypj3zoznermms";
const SERVICE = "https://did-key-zdnaetest.xrpc.fedproxy.com";
const json = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
function memoryStorage() {
const m = new Map();
return { getItem: (k) => (m.has(k) ? m.get(k) : null), setItem: (k, v) => void m.set(k, String(v)), map: m };
}
// The PDS: getServiceAuth returns a token naming aud and lxm (the stub service reads them back).
function session(did = BOB) {
const minted = [];
const agent = {
did,
com: { atproto: { server: { getSession: async () => ({ success: true, data: { handle: "bob.test" } }) } } },
async fetchHandler(path) {
const [nsid, query] = path.replace("/xrpc/", "").split("?");
assert.equal(nsid, "com.atproto.server.getServiceAuth");
const { aud, lxm } = Object.fromEntries(new URLSearchParams(query));
minted.push({ aud, lxm });
return json(200, { token: `${did}|${aud}|${lxm}` });
},
};
const client = { init: async () => ({ session: {} }), signIn: async () => undefined, revoke: async () => undefined };
return { agent, client, minted };
}
// The service: verifies the token's aud/lxm, records caller, answers only the caller's launches.
function service() {
const launches = new Map();
const calls = [];
let n = 0;
const fetch = async (url, init) => {
const u = new URL(url);
const nsid = u.pathname.replace("/xrpc/", "");
const [caller, aud, lxm] = (init.headers.authorization ?? "").replace("Bearer ", "").split("|");
calls.push({ nsid, method: init.method, caller, body: init.body && JSON.parse(init.body) });
if (aud !== `did:web:${u.hostname}${u.port ? `%3A${u.port}` : ""}` || lxm !== nsid) return json(401, { error: "AuthenticationRequired", message: "bad token" });
const own = (rkey) => {
const l = launches.get(rkey);
if (!l) return json(400, { error: "InvalidRequest", message: "reading the launch record: RecordNotFound" });
if (l.caller !== caller) return json(403, { error: "Forbidden", message: `launch ${rkey} belongs to another caller` });
};
if (nsid === NSID.launch) {
const rkey = `3mzz${++n}`;
launches.set(rkey, { caller, status: "running", name: JSON.parse(init.body).name });
return json(200, { rkey, uri: `at://svc/com.fedfork.agents.launch/${rkey}`, status: "launching" });
}
if (nsid === NSID.get) {
const rkey = u.searchParams.get("rkey");
const denied = own(rkey);
if (denied) return denied;
return json(200, { rkey, uri: `at://svc/com.fedfork.agents.launch/${rkey}`, record: { ...launches.get(rkey), subhandle: `x-${rkey}.svc.test` } });
}
if (nsid === NSID.shutdown) {
const { rkey, delete: remove } = JSON.parse(init.body);
const denied = own(rkey);
if (denied) return denied;
if (remove) launches.delete(rkey);
else launches.get(rkey).status = "stopped";
return json(200, { rkey, status: "stopped", revoked: true, terminated: true, deleted: remove ? ["a", "b"] : [] });
}
return json(404, { error: "MethodNotImplemented" });
};
return { fetch, launches, calls };
}
async function page({ storage = memoryStorage(), svc = service(), did = BOB } = {}) {
const s = session(did);
const cubit = new AgentsCubit({ client: Promise.resolve(s.client), makeAgent: () => s.agent, fetch: svc.fetch, storage, service: SERVICE });
await cubit.init();
return { cubit, svc, storage, minted: s.minted };
}
test("the OAuth scope is only service auth for the three methods", () => {
assert.equal(SCOPE, "atproto rpc:com.fedfork.agents.launchAgent?aud=* rpc:com.fedfork.agents.getAgent?aud=* rpc:com.fedfork.agents.shutdownAgent?aud=*");
assert.match(DEFAULT_SERVICE, /^https:\/\/did-key-z[a-z0-9]+\.xrpc\.fedproxy\.com$/);
});
test("launch through the service with a PDS-minted token; the page lists it, reads it, shuts it down", async () => {
const { cubit, svc, minted } = await page();
assert.equal(cubit.state.session, "signed-in");
const launched = await cubit.launch({ bidders: "did:plc:4vcig7s43z3nyqrnepmcfzqt", name: "page", exp_s: "600" });
assert.equal(launched.status, "launching");
assert.deepEqual(svc.calls[0], { nsid: NSID.launch, method: "POST", caller: BOB, body: { bidders: "did:plc:4vcig7s43z3nyqrnepmcfzqt", name: "page", exp_s: 600 } });
assert.deepEqual(minted[0], { aud: "did:web:did-key-zdnaetest.xrpc.fedproxy.com", lxm: NSID.launch });
assert.deepEqual(cubit.state.agents.map((a) => [a.rkey, a.record.status]), [[launched.rkey, "running"]]);
assert.equal(cubit.state.busy.launch, null);
const status = await cubit.status(launched.rkey);
assert.equal(status.record.subhandle, `x-${launched.rkey}.svc.test`);
const down = await cubit.shutdown(launched.rkey, true);
assert.equal(down.status, "stopped");
assert.deepEqual(cubit.state.agents, [], "deleted on the service: forgotten here");
assert.ok(minted.every(({ aud }) => aud === "did:web:did-key-zdnaetest.xrpc.fedproxy.com"));
assert.deepEqual([...new Set(minted.map((m) => m.lxm))].sort(), Object.values(NSID).sort());
});
test("a reload lists what this browser launched for this account; another account's page sees none of it", async () => {
const storage = memoryStorage();
const svc = service();
const first = await page({ storage, svc });
const { rkey } = await first.cubit.launch({ name: "keep" });
const again = await page({ storage, svc });
assert.deepEqual(again.cubit.state.agents.map((a) => a.rkey), [rkey]);
const other = await page({ storage, svc, did: "did:plc:mallory0000000000000000" });
assert.deepEqual(other.cubit.state.agents, []);
const denied = await other.cubit.shutdown(rkey);
assert.match(denied.error, /^403 Forbidden: launch 3mzz1 belongs to another caller/);
assert.equal(svc.launches.get(rkey).status, "running");
});
test("service errors keep their status; setService needs https and switches the list", async () => {
const svc = service();
const { cubit } = await page({ svc });
const bad = await cubit.status("nope");
assert.match(bad.error, /^400 InvalidRequest: reading the launch record/);
await assert.rejects(() => cubit.setService("http://insecure.example"), /https origin/);
await cubit.launch({ name: "a" });
await cubit.setService("https://did-key-zdnaeother.xrpc.fedproxy.com/xrpc/whatever");
assert.equal(cubit.state.service, "https://did-key-zdnaeother.xrpc.fedproxy.com");
assert.deepEqual(cubit.state.agents, [], "launches are remembered per service");
});
// review 3a L5: the aud was did:web:<hostname>, the port dropped; serviceAud keeps it (%3A).
test("a service on a port gets tokens for did:web:<host>%3A<port>", async () => {
const { cubit, minted } = await page();
await cubit.setService("https://svc.example:8443");
const launched = await cubit.launch({ name: "p" });
assert.equal(launched.status, "launching", launched.error);
assert.deepEqual(minted.find((m) => m.lxm === NSID.launch && m.aud.startsWith("did:web:svc")), { aud: "did:web:svc.example%3A8443", lxm: NSID.launch });
});
test("signed out: no calls; logout keeps the service setting", async () => {
const svc = service();
const cubit = new AgentsCubit({ client: Promise.resolve({ init: async () => undefined }), makeAgent: () => null, fetch: svc.fetch, storage: memoryStorage(), service: SERVICE });
await cubit.init();
assert.equal(cubit.state.session, "signed-out");
assert.match((await cubit.launch({ name: "x" })).error, /needs a signed-in session/);
assert.equal(svc.calls.length, 0);
const { cubit: signedIn } = await page();
await signedIn.logout();
assert.deepEqual([signedIn.state.session, signedIn.state.service], ["signed-out", SERVICE]);
});