Something went wrong. Try again.
A collaborative coding-agent orchestrator for atproto radl.app
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371import { COLLECTIONS, materialize, MemoryRecordStore, tally, type MaterializedIndex,} from '@radial/core'import { FIXTURE_DIDS, fixtureSpace } from '@radial/core/fixture'import { describe, expect, it } from 'vitest'import { asks, asksByTarget, asksWithAgent, parkedAsks } from './asks.js'import { buildFixtureSpace } from './fixture.js'import { goalHref, systemHref, unitHref, unitsOf } from './space.js'import { agentDids, askBadges, askWithAgent, isAssigned, isAwaiting, isClaimed, isGoalView, isMoving, liveUnits,} from './units.js'import { reviewQueue, unjudged } from './verdicts.js'
// A `review` and an `answer` are the two things an agent works on that never become units. Everything// here is one rule seen from the sides the lists touch: an ask is with an agent when it is claimed, or// assigned to an active agent member; it is awaiting input when its turn asked something back; and it// is with a person otherwise. No row may be in two of those at once, and none of them is work.const space = buildFixtureSpace()
const IGNORED = 'Surface the records the fold ignored'const QUEUE = 'Review queue in the web UI'
const goal = (title: string) => { const found = space.index.goals.find((entry) => entry.target.value.title === title) if (!found) throw new Error(`fixture goal missing: ${title}`) return found}
const ask = (index: MaterializedIndex, rkey: string) => { const found = asks(index).find((row) => row.request.rkey === rkey) if (!found) throw new Error(`fixture ask missing: ${rkey}`) return found}
/** The daemon's auto-review of the g8 implementation: written by the reviewer, assigned to it. */const AUTO_REVIEW = '3lbr7ii8jj50k'/** Ana's review of the same version, asked of tim — a person. */const HUMAN_REVIEW = '3lbr7gg7hh50j'/** Tim's re-read of the architecture: open to any member, and claimed by the reviewer. */const CLAIMED_REVIEW = '3lbg3rq6ss60r'/** The open message ask in g1, assigned to the reviewer. */const MESSAGE_ASK = '3lbq2ans0002'
/** * The fixture as it reads once that ask's turn has asked a question back and stopped. * * Index surgery rather than new records, the way `replies.test.ts` builds its parked case: nothing * here is a state the fold could not produce — `computeAwaitingInput` puts a request's uri in this * list the moment its assignee posts an unanswered question — and the fixture deliberately does not * carry a fourth ask just to hold one. */function parkedOn(rkey: string): MaterializedIndex { const blocked = <T extends { openRequests: Array<{ rkey: string; uri: string }>; awaitingInput: string[] }>( target: T, ): T => { const held = target.openRequests.filter((request) => request.rkey === rkey) return held.length === 0 ? target : { ...target, awaitingInput: [...target.awaitingInput, ...held.map((request) => request.uri)] } } return { ...space.index, goals: space.index.goals.map(blocked), projects: space.index.projects.map(blocked), }}
describe('what an ask is with', () => { it('is with an agent when an agent was named, and stays out of the human queue', () => { const auto = ask(space.index, AUTO_REVIEW) expect(auto.kind).toBe('review') expect(auto.state).toBe('assigned') expect(auto.actor).toBe(FIXTURE_DIDS.reviewer) expect(askWithAgent(auto, agentDids(space.index))).toBe(true) expect(asksWithAgent(space.index).map((row) => row.request.rkey)).toContain(AUTO_REVIEW)
// The version it pins is the implementation's v1, so the row can open the drawer there rather // than following whatever lands next. expect(auto.pinned?.unit.type).toBe('implementation') expect(auto.pinned?.version.artifact.uri).toBe( unitsOf(space.index, goal(IGNORED)).find((unit) => unit.type === 'implementation')?.current ?.artifact.uri, )
// …and it is not owed by a person, so the signed-out queue does not claim it too. This is the // whole of the double-counting rule: one predicate, and the rail's badge is the sum of the // lists its pages draw. expect(reviewQueue(space.index).map((row) => row.unit.type)).toEqual(['implementation']) expect(reviewQueue(space.index)).toHaveLength(1) })
it('is with a person when a person was named, however many agents are also asked', () => { const human = ask(space.index, HUMAN_REVIEW) expect(human.actor).toBe(FIXTURE_DIDS.tim) expect(askWithAgent(human, agentDids(space.index))).toBe(false) expect(asksWithAgent(space.index).map((row) => row.request.rkey)).not.toContain(HUMAN_REVIEW) // Two asks on one version are two rows: this is the one in tim's queue, and the daemon's is in // nobody's — a queue is what a person is waiting to do, whoever is signed in. expect(reviewQueue(space.index, FIXTURE_DIDS.tim)).toHaveLength(1) expect(reviewQueue(space.index, FIXTURE_DIDS.reviewer)).toHaveLength(0) })
it('is with an agent when one holds a live lease on an unassigned ask, and says for how long', () => { const claimed = ask(space.index, CLAIMED_REVIEW) expect(claimed.state).toBe('claimed') // Nobody was named; the claim is what says an agent has it, and who. expect(claimed.request.value.assignee).toBeUndefined() expect(claimed.claim?.did).toBe(FIXTURE_DIDS.reviewer) expect(claimed.actor).toBe(FIXTURE_DIDS.reviewer) expect(askWithAgent(claimed, agentDids(space.index))).toBe(true)
// Project-scoped: an ask hangs off a system document as readily as off a goal's artifact. expect(claimed.target.target.uri).toBe(fixtureSpace().projects.radialNg) expect(claimed.pinned?.unit.type).toBe('architecture')
const badges = askBadges(claimed, { now: space.asOf }) expect(badges.map((badge) => badge.text)).toEqual(['claimed', 'architecture v3']) expect(badges[0]?.title).toBe('lease expires in 8 min') })
it('is with an agent when an agent was asked to reply', () => { const reply = ask(space.index, MESSAGE_ASK) expect(reply.kind).toBe('answer') expect(reply.state).toBe('assigned') expect(reply.actor).toBe(FIXTURE_DIDS.reviewer) expect(reply.pinned).toBeUndefined() expect(reply.subject?.value.body).toMatch(/stale-view guard/) expect(asksWithAgent(space.index).map((row) => row.request.rkey)).toContain(MESSAGE_ASK) })
it('is with nobody once its assignee is no longer a member', () => { // `agentDids` counts ACTIVE members only. An ask named to a removed agent still reads // `assigned` — the record says so — but nothing is going to run it, so it is not with an agent. // It stays in the signed-out queue, where a human can see it and retract it. const removed = new Set<string>() const auto = ask(space.index, AUTO_REVIEW) expect(askWithAgent(auto, removed)).toBe(false) const withoutReviewer: MaterializedIndex = { ...space.index, members: space.index.members.map((member) => member.did === FIXTURE_DIDS.reviewer ? { ...member, active: false } : member, ), } expect(agentDids(withoutReviewer).has(FIXTURE_DIDS.reviewer)).toBe(false) expect(asksWithAgent(withoutReviewer).map((row) => row.request.rkey)).not.toContain(AUTO_REVIEW) expect(reviewQueue(withoutReviewer).map((row) => row.unit.type)).toEqual([ 'implementation', 'implementation', ]) })})
describe('where a row goes', () => { it('opens a review on the version being judged, wherever that version lives', () => { // `AskRow` builds this: the base is the target's own page, and the version is carried so the // drawer opens on what is being read rather than following the tip of whatever lands next. const auto = ask(space.index, AUTO_REVIEW) if (!auto.pinned) throw new Error('fixture ask lost its version') const href = new URL( unitHref(goalHref(goal(IGNORED)), auto.pinned.unit, auto.pinned.version), 'https://radl.app', ) expect(href.pathname).toMatch(/^\/g\/[^/]+\/[^/]+$/) expect(href.searchParams.get('space')).toBe(space.uri) expect(href.searchParams.get('unit')).toBe(auto.pinned.unit.key) expect(href.searchParams.get('v')).toBe('1')
const claimed = ask(space.index, CLAIMED_REVIEW) const project = space.index.projects.find( (entry) => entry.target.uri === fixtureSpace().projects.radialNg, ) if (!claimed.pinned || !project) throw new Error('fixture ask lost its version') // Its third version, and the System page rather than a goal's. expect(claimed.pinned.version.version).toBe(3) const systemUnit = new URL( unitHref(systemHref(project), claimed.pinned.unit, claimed.pinned.version), 'https://radl.app', ) expect(systemUnit.pathname).toBe('/p/radial-ng/system') expect(systemUnit.searchParams.get('space')).toBe(space.uri) expect(systemUnit.searchParams.get('unit')).toBe(claimed.pinned.unit.key) expect(systemUnit.searchParams.get('v')).toBe('3') })
it('opens a reply in the thread, because that is where the box to answer it is', () => { // An answer ask pins no version, so `AskRow` falls back to the target's own page — the goal's // thread, where both the question and the composer are. const reply = ask(space.index, MESSAGE_ASK) expect(reply.pinned).toBeUndefined() expect(isGoalView(reply.target)).toBe(true) expect(goalHref({ uri: reply.target.target.uri }, space.uri)).toBe(goalHref(goal(QUEUE))) })})
describe('an ask whose turn asked a question back', () => { it('leaves the in-flight list for the awaiting one rather than for nowhere', () => { // The failure this guards: if asks join "With an agent" and parked ones are not added to // "Awaiting input", a review vanishes from every list the moment its agent asks something. expect(parkedAsks(space.index)).toEqual([])
const index = parkedOn(AUTO_REVIEW) const parked = ask(index, AUTO_REVIEW) expect(parked.state).toBe('awaiting') expect(parkedAsks(index).map((row) => row.request.rkey)).toEqual([AUTO_REVIEW]) expect(asksWithAgent(index).map((row) => row.request.rkey)).not.toContain(AUTO_REVIEW) // …and it does not fall into the review queue on the way past: "Waiting on you" is a group on // that same page, so a row in both would be drawn twice and counted twice. expect(reviewQueue(index)).toHaveLength(1) expect(askBadges(parked).map((badge) => badge.text)).toContain('awaiting you') })
it('does the same for a reply', () => { const index = parkedOn(MESSAGE_ASK) expect(parkedAsks(index).map((row) => row.request.rkey)).toEqual([MESSAGE_ASK]) expect(asksWithAgent(index).map((row) => row.request.rkey)).not.toContain(MESSAGE_ASK) })})
describe('an ask that has been answered', () => { it('is in no list at all', () => { // Both fulfillment rules, as the fixture already carries them: a review naming the request it // answers, and a reply threaded under the message its request pinned. Neither request is open, // so neither is an ask. const rkeys = asks(space.index).map((row) => row.request.rkey) // The g8 plan's review request… there is none; the g1 answer exchange completed. expect(rkeys).not.toContain('3lbq2ans0001') // Nothing in the space has more asks than the four the fixture writes. expect(rkeys).toEqual([AUTO_REVIEW, HUMAN_REVIEW, CLAIMED_REVIEW, MESSAGE_ASK]) })})
describe('shelving', () => { const shelved = (collection: string, rkey: string, subject: { uri: string; cid: string }) => { const fixture = fixtureSpace() const store = new MemoryRecordStore() for (const record of fixture.records) store.put(record) store.put({ did: FIXTURE_DIDS.tim, collection, rkey, uri: `at://${FIXTURE_DIDS.tim}/${collection}/${rkey}`, cid: `cid-${rkey}`, rev: '9999999999999', value: { $type: collection, ...(collection === COLLECTIONS.archiveGoal ? { goal: subject } : { project: subject }), archived: true, createdAt: fixture.asOf, }, } as never) return materialize(store, { spaceUri: fixture.spaceUri, asOf: fixture.asOf }) }
it('takes a goal’s asks out of every list, exactly as it takes its units', () => { const fixture = fixtureSpace() const record = fixture.records.find((entry) => entry.uri === fixture.goals.ignored) if (!record) throw new Error('fixture goal missing') const index = shelved(COLLECTIONS.archiveGoal, '3lba0archive001', { uri: record.uri, cid: record.cid, }) const rkeys = asks(index).map((row) => row.request.rkey) expect(rkeys).not.toContain(AUTO_REVIEW) expect(rkeys).not.toContain(HUMAN_REVIEW) expect(reviewQueue(index)).toEqual([]) expect(unjudged(index)).toEqual([]) })
it('takes a project’s asks with it, including the project-scoped ones', () => { const fixture = fixtureSpace() const record = fixture.records.find((entry) => entry.uri === fixture.projects.radialNg) if (!record) throw new Error('fixture project missing') const index = shelved(COLLECTIONS.archiveProject, '3lba0archive002', { uri: record.uri, cid: record.cid, }) // Archiving a project shelves its own system units and every goal under it, so all four asks go. expect(asks(index)).toEqual([]) expect(asksWithAgent(index)).toEqual([]) })})
describe('an ask is still not a unit', () => { it('adds no row and moves no pie, on either target it can hang off', () => { const ignored = unitsOf(space.index, goal(IGNORED)) expect(ignored.some((unit) => unit.type === 'review')).toBe(false) expect(ignored).toHaveLength(2) expect(tally(ignored)).toEqual({ total: 2, judged: 0 })
const queue = unitsOf(space.index, goal(QUEUE)) expect(queue.some((unit) => unit.type === 'answer')).toBe(false) expect(queue).toHaveLength(3) expect(tally(queue)).toEqual({ total: 3, judged: 1 })
// The project-scoped one is the same claim on the System page: nine documents, nine rows. const project = space.index.projects.find( (entry) => entry.target.uri === fixtureSpace().projects.radialNg, ) if (!project) throw new Error('fixture project missing') expect(unitsOf(space.index, project)).toHaveLength(9) })})
describe('the rail’s arithmetic', () => { it('counts exactly the rows “With an agent” draws', () => { // `Rail.svelte` adds `asksWithAgent` to the units `isMoving` picks out; the page draws that same // union, split into its two groups. The comment on that count is a constraint — the badge is the // sum of the lists it links to — so both halves are asserted against each other here, units // included: the two used to disagree, because the page split on `unit.state` while the badge // counted `isMoving`, and a claimed successor is only ever in one of those. const rows = asksWithAgent(space.index) const units = liveUnits(space.index) const counted = units.filter(({ unit }) => isMoving(unit)).length + rows.length const drawn = [ ...units.filter(({ unit }) => isClaimed(unit)), ...rows.filter((row) => row.state === 'claimed'), ...units.filter(({ unit }) => isAssigned(unit)), ...rows.filter((row) => row.state === 'assigned'), ] expect(drawn).toHaveLength(counted) expect(rows).toHaveLength(3) // And no row is in both halves of the split "For me"/"With an agent" rule. const agents = agentDids(space.index) for (const row of asks(space.index)) { const withAgent = askWithAgent(row, agents) const parked = row.state === 'awaiting' expect(withAgent && parked).toBe(false) } for (const { unit } of units) { expect(isMoving(unit) && isAwaiting(unit)).toBe(false) } })
it('groups by the target a per-goal badge reads', () => { const byTarget = asksByTarget(asks(space.index)) expect(byTarget.get(goal(IGNORED).target.uri)?.map((row) => row.request.rkey)).toEqual([ AUTO_REVIEW, HUMAN_REVIEW, ]) expect(byTarget.get(goal(QUEUE).target.uri)?.map((row) => row.request.rkey)).toEqual([ MESSAGE_ASK, ]) // A project-scoped ask never matches a goal uri, which is why the rail's amber-dot set needs no // guard for it. expect(byTarget.get(fixtureSpace().projects.radialNg)?.map((row) => row.request.rkey)).toEqual([ CLAIMED_REVIEW, ]) })
it('is oldest first, matching the queue it sits beside', () => { expect(asks(space.index).map((row) => row.request.value.createdAt)).toEqual([ '2026-07-25T11:13:00Z', '2026-07-25T11:40:00Z', '2026-07-25T13:48:00Z', '2026-07-25T14:09:20Z', ]) })})