Something went wrong. Try again.
A collaborative coding-agent orchestrator for atproto radl.app
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154// One daemon instance: a config file, the data directory that holds its sessions, and the identity// that pair resolves to. This is the file that makes "several radiald on one machine" a thing an// operator can spell rather than a thing they have to remember.//// `config.ts` parses; this resolves. The separation matters because the data directory has a// PRECEDENCE (flag, environment, config file, default) and one of its rungs is resolved relative to// the config file it came from — neither of which is a property of the config's shape.
import { createHash } from 'node:crypto'import { dirname, isAbsolute, join, resolve } from 'node:path'import { FileSessionStore, defaultDataDirectory } from '@radial/atproto/node'import { CONFIG_FILENAME, defaultConfigPath, findConfigPath, loadConfig, type Environment, type RadialConfig,} from './config.js'
/** Which rung of the precedence decided the data directory — printed at startup, because the whole * failure this exists to prevent is an operator who believes a different one won. */export type DataDirSource = 'flag' | 'env' | 'config' | 'default'
export interface ResolvedDataDir { path: string source: DataDirSource /** Set when `$RADIAL_DATA_DIR` overrode a DIFFERENT `dataDir` in the config file: the stale export * in a shell that quietly wins over the file is the shadowing an operator cannot see otherwise. */ shadowed?: string}
/** Thrown when config discovery finds nothing. A named class so `cli.ts` can append its usage text * to this one case without pattern-matching on a message. */export class ConfigNotFoundError extends Error {}
/** * Where this instance's state lives, and why. * * Precedence: `--data-dir` → `$RADIAL_DATA_DIR` → `dataDir` in the config → the operator default. * The environment beats the file for the same reason `$RADIAL_CONFIG` does — an explicit invocation * beats a file — but it is reported (`shadowed`) rather than silent. * * A `dataDir` in the config is resolved against the DIRECTORY OF THE CONFIG, not the working * directory. That is the load-bearing choice: a cwd-relative `dataDir` would mean the same command * run from two directories runs under two identities, which is the failure being fixed. `--data-dir` * is resolved against the cwd, because that is what a flag typed in a shell means. */export function resolveDataDir(input: { flag?: string | undefined env?: Environment config?: RadialConfig | undefined configPath?: string | undefined cwd?: string}): ResolvedDataDir { const env = input.env ?? process.env const cwd = input.cwd ?? process.cwd() const fromConfig = input.config?.dataDir const configRelative = (value: string): string => isAbsolute(value) ? value : resolve(input.configPath ? dirname(input.configPath) : cwd, value)
if (input.flag) return { path: resolve(cwd, input.flag), source: 'flag' } if (env.RADIAL_DATA_DIR) { const path = resolve(cwd, env.RADIAL_DATA_DIR) const declared = fromConfig ? configRelative(fromConfig) : undefined return { path, source: 'env', ...(declared && declared !== path ? { shadowed: declared } : {}), } } if (fromConfig) return { path: configRelative(fromConfig), source: 'config' } let fallback: string try { fallback = defaultDataDirectory(env) } catch { // Same no-HOME escape hatch the run state directory has always had: a project-local directory // beats refusing to run at all in a minimal environment. fallback = join(cwd, '.radial-state') } return { path: fallback, source: 'default' }}
/** `radiald --data-dir ~/radial/b-state` in the operator's words, for a startup line. */export function describeDataDirSource(dataDir: ResolvedDataDir): string { switch (dataDir.source) { case 'flag': return 'from --data-dir' case 'env': return 'from $RADIAL_DATA_DIR' case 'config': return 'from "dataDir" in the config' case 'default': return 'the default' }}
/** * A short, stable id for this instance, derived from its run state directory. * * It exists because docker's namespace is machine-global while everything else here is per data * directory: two instances serving one space would otherwise derive the SAME container name from the * same request, colliding on `docker run --name` and killing each other's live containers during * startup orphan reconciliation. Derived rather than configured so an operator gains one without * editing anything, and printed at startup so it is identifiable in `docker ps`. */export function instanceId(stateDir: string): string { return createHash('sha256').update(resolve(stateDir)).digest('hex').slice(0, 6)}
export interface Instance { configPath: string config: RadialConfig dataDir: ResolvedDataDir /** Sessions for THIS instance: `<dataDir>/sessions.json`, never the process-wide default. */ sessions: FileSessionStore}
/** * The one call every command uses: find the config, resolve the data directory, and open the session * store that pair implies. `radiald run --config ~/radial/b.json` fully describes an instance when * that config carries a `dataDir` — which is the "easy" this whole change is for. */export async function loadInstance(args: { config?: string | undefined dataDir?: string | undefined env?: Environment cwd?: string}): Promise<Instance> { const env = args.env ?? process.env const cwd = args.cwd ?? process.cwd() const configPath = await findConfigPath(args.config, cwd, env) if (!configPath) { let fallback: string try { fallback = defaultConfigPath(env) } catch { fallback = `$HOME/.config/radial/${CONFIG_FILENAME}` } throw new ConfigNotFoundError( `No ${CONFIG_FILENAME} found in this directory or any parent, nor at ${fallback}`, ) } const config = await loadConfig(configPath) const dataDir = resolveDataDir({ ...(args.dataDir !== undefined ? { flag: args.dataDir } : {}), env, config, configPath, cwd, }) return { configPath, config, dataDir, sessions: new FileSessionStore(dataDir.path) }}