Something went wrong. Try again.
A collaborative coding-agent orchestrator for atproto radl.app
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647import assert from 'node:assert/strict'import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'import { tmpdir } from 'node:os'import { join } from 'node:path'import { it } from 'node:test'import { COLLECTIONS } from '../../core/dist/index.js'import { collectModelEnv, coverageAdvisories, describeActors, describeInstance, emptyRegistryError, forgeAdvisory, main, modelEnvNames, resetRunIndex, resolveModelEnvironment, resolveRunConfig, claimTimingAdvisory, routingForgeState,} from '../dist/cli.js'import { ForgeRegistry, TurnLedger, instanceId, loadInstance } from '../dist/index.js'
// `egress.start()` itself is Docker-only and is not exercised here (see docker-smoke.test.mjs,// which is env-gated behind RADIAL_DOCKER_TESTS). What *is* unit-testable without Docker is that// `resolveRunConfig` — the thing `runCommand` consults to decide whether to enforce egress at all// — defaults to enforcing it, per the "no bridge fallback" design decision (§13): an operator who// configures nothing gets the enforced sandbox, not silent open egress.
it('resolveRunConfig defaults merge poll timing and clamps invalid values', () => { const SPACE = 'at://did:plc:human/com.disnetdev.radial.space/space1' // Defaults: 60s base, 30min cap. const defaults = resolveRunConfig({ spaces: [SPACE] }) assert.equal(defaults.mergePollIntervalMs, 60_000) assert.equal(defaults.mergePollBackoffMaxMs, 30 * 60_000) // Zero/negative base interval is floored to 1ms (never busy-polls the forge). assert.equal(resolveRunConfig({ spaces: [SPACE], mergePollIntervalMs: 0 }).mergePollIntervalMs, 1) assert.equal(resolveRunConfig({ spaces: [SPACE], mergePollIntervalMs: -5 }).mergePollIntervalMs, 1) // A cap below the base interval is raised to the base, so the first delay can't exceed the cap. const clamped = resolveRunConfig({ spaces: [SPACE], mergePollIntervalMs: 120_000, mergePollBackoffMaxMs: 1_000, }) assert.equal(clamped.mergePollIntervalMs, 120_000) assert.equal(clamped.mergePollBackoffMaxMs, 120_000)})it('run.stateDir defaults under the resolved data directory, and an explicit one still wins', () => { // One setting separates everything an instance owns: point `dataDir` somewhere and the ledgers, // checkpoints and per-turn directories follow it, with no second thing to remember. const SPACE = 'at://did:plc:human/com.disnetdev.radial.space/space1' assert.equal(resolveRunConfig({ spaces: [SPACE] }, { dataDir: '/srv/radial/b' }).stateDir, '/srv/radial/b/run') assert.equal( resolveRunConfig({ spaces: [SPACE], stateDir: '/var/lib/radial-b' }, { dataDir: '/srv/radial/b' }).stateDir, '/var/lib/radial-b', ) // No data directory supplied: the historic default, so every existing call site means what it did. assert.ok(resolveRunConfig({ spaces: [SPACE] }).stateDir.endsWith('/run'))})
// The startup banner. Every failure this feature is about is an operator who believed a different// set of paths, so the wording is asserted rather than left to drift.const instanceFixture = (dataDir, run) => ({ configPath: '/home/op/radial/b.json', config: { identifier: 'agent-b.example', agents: { planner: { artifactTypes: ['plan'] } } }, dataDir, sessions: { path: join(dataDir.path, 'sessions.json') }, ...(run ? { run } : {}),})
it('describeInstance names every resolved path, and which rule chose the data directory', () => { const lines = describeInstance( instanceFixture({ path: '/home/op/radial/b-state', source: 'config' }), resolveRunConfig({ spaces: [] }, { dataDir: '/home/op/radial/b-state' }), ) assert.match(lines[0], /config: {3}\/home\/op\/radial\/b\.json/) assert.match(lines[1], /data: {5}\/home\/op\/radial\/b-state {3}\(from "dataDir" in the config\)/) assert.match(lines[2], /sessions: \/home\/op\/radial\/b-state\/sessions\.json/) assert.match(lines[3], /state: {4}\/home\/op\/radial\/b-state\/run/) // The instance id is printed because it is the segment that appears in `docker ps`. assert.ok(lines[3].includes(instanceId('/home/op/radial/b-state/run')))
// Each source names itself, so "why is it reading THAT file" is answerable from the log alone. const source = (dataDir) => describeInstance(instanceFixture(dataDir))[1] assert.match(source({ path: '/x', source: 'flag' }), /\(from --data-dir\)/) assert.match(source({ path: '/x', source: 'env' }), /\(from \$RADIAL_DATA_DIR\)/) assert.match(source({ path: '/x', source: 'default' }), /\(the default\)/)})
it('describeInstance warns about a shadowing $RADIAL_DATA_DIR and a relative run.stateDir', () => { const shadowed = describeInstance( instanceFixture({ path: '/env/state', source: 'env', shadowed: '/home/op/radial/b-state' }), ) assert.match(shadowed.at(-1), /\$RADIAL_DATA_DIR overrode "dataDir": \/home\/op\/radial\/b-state/)
// A relative `stateDir` stays cwd-relative (changing that would silently relocate the state of // every operator using the documented `".radial-state"`), so it is warned about instead. const relative = describeInstance( instanceFixture({ path: '/home/op/radial/b-state', source: 'config' }), resolveRunConfig({ spaces: [], stateDir: '.radial-state' }), ) assert.match(relative.at(-1), /"run\.stateDir" is relative/)
// Neither advisory fires for the ordinary case. const clean = describeInstance( instanceFixture({ path: '/home/op/radial/b-state', source: 'config' }), resolveRunConfig({ spaces: [] }, { dataDir: '/home/op/radial/b-state' }), ) assert.equal(clean.length, 4)})
it('the per-profile startup line carries the DID each profile is actually running as', () => { // Identical profile names on two instances is the supported case, so the profile name alone says // nothing; the DID is the fact a shared sessions.json used to get wrong in silence. const actor = (profile, did, handle, harness) => ({ profile, did, harness, session: { handle }, }) const lines = describeActors({ all: [ actor('planner', 'did:plc:bbb', 'agent-b.example', 'claude'), actor('implementer', 'did:plc:bbb', 'agent-b.example', 'claude'), actor('reviewer', 'did:plc:ccc', 'reviewer-b.example', 'pi'), ], }) assert.deepEqual(lines, [ ' planner did:plc:bbb agent-b.example claude', ' implementer did:plc:bbb agent-b.example claude', ' reviewer did:plc:ccc reviewer-b.example pi', ])})
it('the empty-registry error names the sessions file it read and what the config expected in it', () => { // "run radiald init" is no help when the question is *which* sessions file was read. const message = emptyRegistryError( instanceFixture({ path: '/home/op/radial/b-state', source: 'config' }), ) assert.match(message, /No initialized agent profiles found for \/home\/op\/radial\/b\.json/) assert.match(message, /in \/home\/op\/radial\/b-state\/sessions\.json/) assert.match(message, /config declares: planner/) assert.match(message, /radiald init --config \/home\/op\/radial\/b\.json/)})
it('resolveRunConfig defaults turnTransport by platform and preserves explicit overrides', () => { const run = { spaces: ['at://did:plc:human/com.disnetdev.radial.space/space1'] } assert.equal(resolveRunConfig(run, { platform: 'linux' }).turnTransport, 'unix') assert.equal(resolveRunConfig(run, { platform: 'darwin' }).turnTransport, 'tcp') assert.equal(resolveRunConfig(run, { platform: 'win32' }).turnTransport, 'tcp') assert.equal(resolveRunConfig({ ...run, turnTransport: 'auto' }, { platform: 'darwin' }).turnTransport, 'tcp') assert.equal(resolveRunConfig({ ...run, turnTransport: 'unix' }, { platform: 'darwin' }).turnTransport, 'unix') assert.equal(resolveRunConfig({ ...run, turnTransport: 'tcp' }, { platform: 'linux' }).turnTransport, 'tcp')})
it('resetRunIndex clears only derived sync databases and their SQLite sidecars', async () => { const stateDir = await mkdtemp(join(tmpdir(), 'radial-index-reset-')) try { for (const file of ['records.db', 'records.db-wal', 'records.db-shm', 'sync.db', 'sync.db-wal', 'sync.db-shm', 'ledger.db', 'check-ledger.db']) { await writeFile(join(stateDir, file), file) } await resetRunIndex(stateDir) for (const file of ['records.db', 'records.db-wal', 'records.db-shm', 'sync.db', 'sync.db-wal', 'sync.db-shm']) { await assert.rejects(readFile(join(stateDir, file)), /ENOENT/) } assert.equal(await readFile(join(stateDir, 'ledger.db'), 'utf8'), 'ledger.db') assert.equal(await readFile(join(stateDir, 'check-ledger.db'), 'utf8'), 'check-ledger.db') } finally { await rm(stateDir, { recursive: true, force: true }) }})
// FIX #12: an explicit `allowlist: []` (deny-all) is not the same as "unset", and `?? []`-style// fallbacks must not conflate them — `[]` is non-nullish, so `run.allowlist ?? [default]` already// preserves it correctly; this pins that down against a regression.
// FIX #11: run.memory threads through resolveRunConfig with no default applied at this layer// (turn.ts supplies the 4g floor when unset).it('resolveRunConfig threads run.memory through unchanged, and leaves it unset when not configured', () => { const withMemory = resolveRunConfig({ spaces: ['at://did:plc:human/com.disnetdev.radial.space/space1'], memory: '2g', }) assert.equal(withMemory.memory, '2g')
const withoutMemory = resolveRunConfig({ spaces: ['at://did:plc:human/com.disnetdev.radial.space/space1'] }) assert.equal('memory' in withoutMemory, false)})
it('resolveRunConfig applies check-runner defaults (checkImage falls back to the run image)', () => { const resolved = resolveRunConfig({ spaces: ['at://did:plc:human/com.disnetdev.radial.space/space1'], image: 'radial-turn:custom', }) assert.equal(resolved.checkConcurrency, 1) assert.equal(resolved.checkTimeoutMs, 900000) assert.equal(resolved.checkCooldownMs, 300000) assert.equal(resolved.checkRetryBound, 3) // checkImage defaults to the resolved run image when not set explicitly. assert.equal(resolved.checkImage, 'radial-turn:custom')})
it('warns, in the operator’s terms, when implementations run without a forge adapter', () => { const path = '/home/op/.config/radial/radial.json' const missing = forgeAdvisory({ adapter: false, configured: false, producesImplementation: true, configPath: path, }) // The consequence, not the mechanism: this is the setting that decides whether resolving a // review's findings updates the pull request under review or opens a second one. assert.match(missing, /no run\.forge configured/) assert.match(missing, /implementation v2 decides whether to continue its predecessor/) assert.match(missing, /closed without merging is invisible/) // And the part an operator cannot infer: with no forge nothing writes merge annotations, so the // merge check the fallback appears to make is one it can never actually make. assert.match(missing, /nothing writes merge annotations without a forge/) assert.ok(missing.includes(path))
// Configured but unauthenticated is the same consequence for a different reason. const unauthenticated = forgeAdvisory({ adapter: false, configured: true, producesImplementation: true, configPath: path, }) assert.match(unauthenticated, /GitHub authentication is unavailable/) assert.match(unauthenticated, /continue its predecessor/)
// Silence where there is nothing to warn about: a live adapter, or a daemon that produces no // implementations at all. assert.equal( forgeAdvisory({ adapter: true, configured: true, producesImplementation: true, configPath: path }), undefined, ) assert.equal( forgeAdvisory({ adapter: false, configured: false, producesImplementation: false, configPath: path }), undefined, )})
// ── registry coverage (§4/§8) ───────────────────────────────────────────────────────────────────// A hand-built index gives precise control over the two facts these advisories read: what the space// registry offers, and what the agent records in the space say they accept.
const CONFIG_PATH = '/home/op/.config/radial/radial.json'const SPACE = 'at://did:plc:human/com.disnetdev.radial.space/space1'const AGENT = 'did:plc:agent'
const typeRecord = (name) => ({ did: 'did:plc:human', uri: `at://did:plc:human/${COLLECTIONS.artifactType}/${name}`, cid: `cid-${name}`, value: { $type: COLLECTIONS.artifactType, name, scope: 'goal', createdAt: '2026-01-01T00:00:00Z' },})
const agentRecord = (profile, artifactTypes, did = AGENT) => ({ did, uri: `at://${did}/${COLLECTIONS.agent}/${profile}`, cid: `cid-${profile}`, value: { $type: COLLECTIONS.agent, handleName: profile, harness: 'claude', models: [], artifactTypes, createdAt: '2026-01-01T00:00:00Z' },})
const registry = (typeNames, agents) => ({ artifactTypes: typeNames.map(typeRecord), agents,})
const loaded = (profiles) => ({ all: profiles.map(([profile, artifactTypes, did = AGENT]) => ({ did, profile, artifactTypes })),})
it('names the registry types no agent in the space accepts, and how to cover them', () => { // The case an operator lands in by creating a space today with a radial.json written before the // system types were seeded: the registry offers five, the agent records accept two. const index = registry( ['plan', 'implementation', 'architecture', 'conventions', 'adr'], [agentRecord('planner', ['plan']), agentRecord('implementer', ['implementation'])], ) const actors = loaded([['planner', ['plan']], ['implementer', ['implementation']]]) const [advisory, ...rest] = coverageAdvisories(index, actors, { spaceUri: SPACE, configPath: CONFIG_PATH }) assert.deepEqual(rest, []) assert.match(advisory, /architecture, conventions, adr/) // The consequence, not the mechanism: this is what makes a request look like it is merely waiting. assert.match(advisory, /has no agent to assign and will sit open/) assert.ok(advisory.includes(CONFIG_PATH)) assert.ok(advisory.includes(SPACE)) assert.match(advisory, /radiald init planner --update/)})
it('counts another operator’s agents as coverage, and stays silent when everything is covered', () => { // An agent this daemon does not run is a perfectly good answer to "who can produce an adr?" — // warning about it would be this daemon reporting someone else's space as broken. const index = registry( ['plan', 'adr'], [agentRecord('planner', ['plan']), agentRecord('scribe', ['adr'], 'did:plc:other')], ) assert.deepEqual( coverageAdvisories(index, loaded([['planner', ['plan']]]), { spaceUri: SPACE, configPath: CONFIG_PATH }), [], ) // An empty registry has nothing to be uncovered, and neither has a space whose types are all met. assert.deepEqual(coverageAdvisories(registry([], []), loaded([]), { spaceUri: SPACE, configPath: CONFIG_PATH }), [])})
it('names a profile whose config has moved on from the record it published', () => { // The second way to arrive at an unassignable type: the config was edited and `radiald init` was // never re-run, so the space still offers the old list — and the config is not what a human reads. const index = registry(['plan', 'adr'], [agentRecord('planner', ['plan', 'adr'])]) const actors = loaded([['planner', ['plan', 'adr', 'conventions']]]) const advisories = coverageAdvisories(index, actors, { spaceUri: SPACE, configPath: CONFIG_PATH }) assert.equal(advisories.length, 1) assert.match(advisories[0], /profile "planner" publishes plan, adr but .* says plan, adr, conventions/) assert.match(advisories[0], /the published record is what an assignee menu offers/) assert.match(advisories[0], /radiald init planner --update/)
// Order within the list is not drift — the same set published in another order says the same thing. const reordered = coverageAdvisories( registry(['plan', 'adr'], [agentRecord('planner', ['adr', 'plan'])]), loaded([['planner', ['plan', 'adr']]]), { spaceUri: SPACE, configPath: CONFIG_PATH }, ) assert.deepEqual(reordered, [])
// A profile with no published record in this space (not a member here) is not drift either: there // is nothing to compare against, and `radiald init` is not what a missing grant needs. assert.deepEqual( coverageAdvisories(registry(['plan'], [agentRecord('planner', ['plan'])]), loaded([['planner', ['plan']], ['scribe', ['adr']]]), { spaceUri: SPACE, configPath: CONFIG_PATH, }), [], )})
// `radiald config init --data-dir` — the one-command scaffold for a SECOND instance. What it writes// has to be enough that `--config` alone selects the instance afterwards.it('config init writes a dataDir and prints follow-up commands that name the config', async () => { const dir = await mkdtemp(join(tmpdir(), 'radial-config-init-')) const lines = [] const log = console.log console.log = (line) => lines.push(String(line)) try { const configPath = join(dir, 'b.json') await main(['config', 'init', 'agent-b.example', '--config', configPath, '--data-dir', join(dir, 'b-state')]) const written = JSON.parse(await readFile(configPath, 'utf8')) assert.equal(written.dataDir, join(dir, 'b-state')) assert.equal(written.identifier, 'agent-b.example') // Both follow-ups carry `--config`: with several instances on a machine, an init that discovers // "the nearest radial.json" is exactly how the wrong one gets initialized. assert.ok(lines.some((line) => line.includes(`radiald init --config ${configPath} --password-stdin`))) assert.ok(lines.some((line) => line.includes(`radiald run --config ${configPath}`)))
// And what it wrote round-trips into a full instance: the sessions file follows the config. const instance = await loadInstance({ config: configPath, env: { HOME: dir }, cwd: '/' }) assert.equal(instance.sessions.path, join(dir, 'b-state', 'sessions.json')) assert.equal(instance.dataDir.source, 'config') } finally { console.log = log await rm(dir, { recursive: true, force: true }) }})
// `radiald turn list` — the command docs/plan.md sends an operator to before enabling replies. It// reads the same ledger `turn reset` writes, so the two agree about which rows exist.it('turn list prints the ledger and narrows to a state', async () => { const dir = await mkdtemp(join(tmpdir(), 'radial-turn-list-')) const lines = [] const log = console.log console.log = (line) => lines.push(String(line)) try { const configPath = join(dir, 'radial.json') await writeFile( configPath, JSON.stringify({ identifier: 'agent.example', agents: { planner: { artifactTypes: ['plan'] } }, run: { spaces: [], stateDir: dir }, }), ) const ledger = new TurnLedger(join(dir, 'ledger.db')) ledger.markAwaitingInput('at://did:plc:human/com.disnetdev.radial.artifactRequest/parked') ledger.markFulfilled('at://did:plc:human/com.disnetdev.radial.artifactRequest/done', { uri: 'at://a/b/c', cid: 'cid', }) ledger.close()
await main(['turn', 'list', '--config', configPath]) assert.equal(lines.filter((line) => line.includes('/parked')).length, 1) assert.equal(lines.filter((line) => line.includes('/done')).length, 1) assert.ok(lines.at(-1).startsWith('2 turns'))
lines.length = 0 await main(['turn', 'list', '--state', 'awaiting_input', '--config', configPath]) assert.equal(lines.filter((line) => line.includes('/parked')).length, 1) assert.equal(lines.filter((line) => line.includes('/done')).length, 0)
lines.length = 0 await main(['turn', 'list', '--state', 'gave_up', '--config', configPath]) assert.deepEqual(lines, ['no turns in state gave_up'])
// An unknown state is a typo, not an empty list: say so rather than print nothing. await assert.rejects( main(['turn', 'list', '--state', 'awaiting', '--config', configPath]), /Unknown turn state/, ) } finally { console.log = log await rm(dir, { recursive: true, force: true }) }})
it('routes a bare pull request URL back to the forge that owns it', async () => { // The merge poller is handed a URL with no project attached, so the routing has to be answerable // from the URL alone — which is what `ownsPullUrl` is for. const asked = [] const adapter = (kind, owns) => ({ kind, canObserve: true, matchesGitUrl: () => false, ownsPullUrl: owns, pullBelongsToProject: async () => true, projectIdentity: async () => kind, compareUrl: () => '', getPullRequestState: async (url) => { asked.push(`${kind}:${url}`) return { state: 'merged', mergedAt: '2026-07-05T00:00:00.000Z', headRef: '', headRepoFullName: '', baseRef: 'main' } }, }) const source = routingForgeState( new ForgeRegistry([ adapter('github', (url) => url.startsWith('https://github.com/')), adapter('tangled', (url) => url.startsWith('at://')), ]), ) assert.equal((await source.getPullRequestState('https://github.com/o/r/pull/7')).state, 'merged') assert.equal((await source.getPullRequestState('at://did:plc:agent/sh.tangled.repo.pull/3k')).state, 'merged') assert.deepEqual(asked, ['github:https://github.com/o/r/pull/7', 'tangled:at://did:plc:agent/sh.tangled.repo.pull/3k']) // An unroutable URL throws; the poller already treats a throw as "back off", never as an answer. await assert.rejects(() => source.getPullRequestState('https://gitlab.com/o/r/-/merge_requests/1'), /no configured forge observes/) // And an adapter that cannot observe is never asked. const unauthenticated = routingForgeState(new ForgeRegistry([{ ...adapter('github', () => true), canObserve: false }])) await assert.rejects(() => unauthenticated.getPullRequestState('https://github.com/o/r/pull/7'), /no configured forge observes/)})
// --- Startup model-credential planning ------------------------------------------------------
it('resolveRunConfig defaults run.modelEnv to an empty list', () => { const SPACE = 'at://did:plc:human/com.disnetdev.radial.space/space1' assert.deepEqual(resolveRunConfig({ spaces: [SPACE] }).modelEnv, []) assert.deepEqual(resolveRunConfig({ spaces: [SPACE], modelEnv: ['GROQ_API_KEY'] }).modelEnv, ['GROQ_API_KEY'])})
it('resolveRunConfig preserves explicit managed Codex auth', () => { const SPACE = 'at://did:plc:human/com.disnetdev.radial.space/space1' assert.deepEqual( resolveRunConfig({ spaces: [SPACE], codexAuth: { mode: 'chatgpt-session' } }).codexAuth, { mode: 'chatgpt-session' }, ) assert.equal(resolveRunConfig({ spaces: [SPACE] }).codexAuth, undefined)})
it('modelEnvNames unions what the loaded profiles need, without repeating a shared name', () => { const names = modelEnvNames( [{ harness: 'claude' }, { harness: 'pi' }, { harness: 'pi' }, { harness: 'codex' }], ['CLOUDFLARE_ACCOUNT_ID'], ) assert.equal(new Set(names).size, names.length) assert.ok(names.includes('ANTHROPIC_API_KEY')) // declared by two harnesses, listed once assert.ok(names.includes('CLAUDE_CODE_OAUTH_TOKEN')) assert.ok(names.includes('OPENAI_API_KEY')) // declared by pi assert.ok(names.includes('CODEX_API_KEY')) assert.ok(names.includes('CLOUDFLARE_ACCOUNT_ID'))})
it('collectModelEnv forwards only names the daemon actually holds, and never invents a value', () => { assert.deepEqual( collectModelEnv(['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'GROQ_API_KEY'], { OPENAI_API_KEY: 'sk-openai', GROQ_API_KEY: '', UNRELATED: 'x', }), { OPENAI_API_KEY: 'sk-openai' }, ) assert.deepEqual(collectModelEnv([], { OPENAI_API_KEY: 'sk-openai' }), {})})
it('resolveModelEnvironment refuses an unrunnable harness at startup, naming the profile', () => { // A throw out of runTurn is caught by the dispatcher and recorded as a crash, so discovering // this per-request would burn a ledger attempt and a cooldown each time instead of saying it // once, at launch. assert.throws( () => resolveModelEnvironment([{ profile: 'reviewer', harness: 'aider' }], [], { ANTHROPIC_API_KEY: 'sk' }), /profile "reviewer": Unknown harness "aider" \(supported: claude, pi, codex\)/, )})
it('resolveModelEnvironment accepts a codex profile on any one of the credentials codex reads', () => { for (const name of ['CODEX_API_KEY', 'CODEX_ACCESS_TOKEN']) { assert.deepEqual(resolveModelEnvironment([{ profile: 'impl', harness: 'codex' }], [], { [name]: 'value' }), { [name]: 'value', }) } assert.throws( () => resolveModelEnvironment([{ profile: 'impl', harness: 'codex' }], [], { ANTHROPIC_API_KEY: 'sk-ant' }), /profile "impl" runs harness "codex" but none of its provider credentials are set/, ) // OPENAI_API_KEY is not one of them: codex's exec auth chain reads CODEX_API_KEY and // CODEX_ACCESS_TOKEN only, so a daemon holding just an OpenAI key must refuse at startup rather // than start and fail every codex turn on auth inside the container. assert.throws( () => resolveModelEnvironment([{ profile: 'impl', harness: 'codex' }], [], { OPENAI_API_KEY: 'sk-openai' }), /profile "impl" runs harness "codex" but none of its provider credentials are set/, ) // It still forwards when an operator asks for it explicitly, alongside a credential that works. assert.deepEqual( resolveModelEnvironment([{ profile: 'impl', harness: 'codex' }], ['OPENAI_API_KEY'], { CODEX_API_KEY: 'sk-codex', OPENAI_API_KEY: 'sk-openai', }), { CODEX_API_KEY: 'sk-codex', OPENAI_API_KEY: 'sk-openai' }, )})
it('managed Codex auth replaces environment credentials and rejects ambiguous precedence', () => { const actors = [{ profile: 'impl', harness: 'codex' }] assert.deepEqual( resolveModelEnvironment(actors, [], {}, { managedCodexAuth: true }), {}, ) assert.throws( () => resolveModelEnvironment( actors, [], { CODEX_API_KEY: 'api-key' }, { managedCodexAuth: true }, ), /CODEX_API_KEY is also set.*Unset it/, ) assert.throws( () => resolveModelEnvironment( [{ profile: 'planner', harness: 'claude' }], [], { ANTHROPIC_API_KEY: 'key' }, { managedCodexAuth: true }, ), /no loaded profile uses harness "codex"/, )})
it('resolveModelEnvironment refuses a profile whose harness has no credential available', () => { assert.throws( () => resolveModelEnvironment([{ profile: 'planner', harness: 'claude' }], [], { OPENAI_API_KEY: 'sk-openai' }), /profile "planner" runs harness "claude" but none of its provider credentials are set/, ) // The refusal names what it would have accepted, and points at the escape hatch. assert.throws( () => resolveModelEnvironment([{ profile: 'planner', harness: 'claude' }], [], {}), /ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, or add the one you use to "run.modelEnv"/, )})
it('resolveModelEnvironment forwards each profile its own provider credentials, and the allowlist', () => { const modelEnv = resolveModelEnvironment( [ { profile: 'implementer', harness: 'claude' }, { profile: 'reviewer', harness: 'pi' }, ], ['CLOUDFLARE_ACCOUNT_ID'], { ANTHROPIC_API_KEY: 'sk-ant', OPENROUTER_API_KEY: 'sk-or', CLOUDFLARE_ACCOUNT_ID: 'acct-1234', // Present in the daemon's environment but on nobody's list: never forwarded. AWS_SECRET_ACCESS_KEY: 'not-forwarded', }, ) assert.deepEqual(modelEnv, { ANTHROPIC_API_KEY: 'sk-ant', OPENROUTER_API_KEY: 'sk-or', CLOUDFLARE_ACCOUNT_ID: 'acct-1234', })})
it('an operator-allowlisted name can be the only credential a profile has', () => { // run.modelEnv is what makes a provider (or gateway) no harness enumerates reachable at all. assert.deepEqual( resolveModelEnvironment([{ profile: 'reviewer', harness: 'pi' }], ['MY_GATEWAY_TOKEN'], { MY_GATEWAY_TOKEN: 'tok', }), { MY_GATEWAY_TOKEN: 'tok' }, )})
// --- Phase 7: claim timing --------------------------------------------------
it('resolveRunConfig defaults the claim timing and derives the renewal interval from the lease', () => { const SPACE = 'at://did:plc:human/com.disnetdev.radial.space/space1' assert.deepEqual(resolveRunConfig({ spaces: [SPACE] }).claims, { leaseMs: 600_000, renewIntervalMs: 200_000, confirmCycles: 1, maxOutstanding: 1, }) // Lengthening the lease alone still leaves three renewal attempts inside it. assert.equal(resolveRunConfig({ spaces: [SPACE], claims: { leaseMs: 1_200_000 } }).claims.renewIntervalMs, 400_000) // Shortening the lease alone below three times an EXPLICIT renewal interval is refused. assert.throws( () => resolveRunConfig({ spaces: [SPACE], claims: { renewIntervalMs: 500_000 } }), /at most a third/, ) // And so is a lease longer than the protocol allows a claim to declare — checked here as well as // at parse time, because a `ClaimTiming` can reach the manager without passing through a file. assert.throws( () => resolveRunConfig({ spaces: [SPACE], claims: { leaseMs: 2 * 60 * 60_000 } }), /protocol bound/, )})
it('resolveRunConfig defaults the jetstream backfill interval only when jetstream is configured', () => { const SPACE = 'at://did:plc:human/com.disnetdev.radial.space/space1' assert.equal(resolveRunConfig({ spaces: [SPACE] }).jetstream, undefined) assert.deepEqual( resolveRunConfig({ spaces: [SPACE], jetstream: { endpoint: 'wss://jetstream.test/subscribe' } }).jetstream, { endpoint: 'wss://jetstream.test/subscribe', backfillIntervalMs: 60_000 }, )})
it('claimTimingAdvisory refuses a lease that can lapse between two ingestion cycles', () => { const timing = { leaseMs: 600_000, renewIntervalMs: 200_000, confirmCycles: 1, maxOutstanding: 1 } assert.equal(claimTimingAdvisory(timing, 5_000), undefined) assert.equal(claimTimingAdvisory(timing, 150_000), undefined) // exactly four polls is fine const advisory = claimTimingAdvisory(timing, 200_000) assert.match(advisory, /at least four times the poll interval/) assert.match(advisory, /600000ms/) assert.match(advisory, /200000ms/)})