Loading files
c8ccfef9dThis file is pinned to the selected release. Download the original to open it in your design tool.
/**
* What the grids this computer is signed into can answer — read WITHOUT waking any of them.
*
* Every picker, the Models panel and the macOS Models menu ask here, and until issue 02 of
* grid-reads-without-waking each ask was a `grid models` spawn plus a SIGNED-IN read of the relay's
* model list. On the platform a signed-in read of a sleeping grid wakes it, so an open app kept every
* grid of the account awake all day — and the read that woke one usually came back empty. Now:
*
* - each grid is read through `gridReader.ts`, with no credential: an awake grid answers as before, a
* sleeping one says so at once (with the platform's record of what it served) and is not started;
* - what was read is kept as a PICTURE per grid (`gridPicture.ts`, persisted), so an answer is given at
* once and a sleeping grid shows its last known models instead of vanishing;
* - the account's OWN grid, whose status the owner can read, is read at most once per asleep episode.
*
* The credentialed model-list read is gone from every automatic path. What still wakes a grid is a
* person's act (issue 03): an explicit wake, moving an agent onto a sleeping grid, the first keystroke
* into a pane whose agent runs on one — each decided here, from the picture, and sent through
* `gridWake.ts`, the one module that reads a grid with its credential. Beside that, the picture says
* which rows only a computer of the account's that seems offline serves (`gridPresence.ts`): a label,
* never a removal.
*/
import { createHash, randomUUID } from 'node:crypto'
import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { env as config } from '../config/env.js'
import { gridCredentialsPath } from './gridCredentials.js'
import { signedInGridEmail } from './gridDerive.js'
import { gridJson } from './gridExec.js'
import {
emptyPicture, mergeAwake, idKey, parsePicture, provenStopped, sectionView, servedKey, servesAModel, unspelled, withAsleep,
withSpellings, withUnknown, withWindows, type GridPicture, type LocalRecord, type PictureState, type RowUnavailable,
type SectionView, type ServedHere,
} from './gridPicture.js'
import { ComputerPresence, computersIn, MACHINE_LIST_FRESH_MS } from './gridPresence.js'
import {
OWNER_ASLEEP_STATUS, readBase, readDiscoveryIds, readGridInfo, readOverview, readViaCli, type GridInfo, type GridRead,
type ReadNode,
} from './gridReader.js'
import type { GridLaunchOverride } from './gridLaunch.js'
import { PrewarmDebounce, wakeRead, type RelayAccess, type WakePurpose } from './gridWake.js'
import { readRunRecords } from './localModels.js'
export interface GridModel {
/** The id an engine is pointed at, in the exact case the grid serves it. */
id: string
/** Which machine answers it, or empty when the grid does not say. Display only. */
node: string
/** Every computer serving it seems offline — sent only to a client that asked for row state
* ([presentGridSections]); an older one reads it in `node` instead. */
unavailable?: RowUnavailable
}
/** How an explicit wake that did not show models ended: the grid did not come up in time, or came up
* with nobody serving anything. */
export type WakeOutcome = 'not_started' | 'nobody_serving'
/** One grid this computer is signed into, with what it serves — the picker's section. */
export interface GridSection {
/** The grid's name as `grid ls` prints it. */
name: string
/** `permissioned-public` is the account's own private grid; the others are shared. */
type: string
/** True for the account's private grid — the picker labels that one "Local". */
own: boolean
/** Its models: live while it is awake, the last known ones while it is not — never blanked. */
models: GridModel[]
/** Additive (an older app ignores these three): what the last read said, when the grid was last seen
* awake, and how old the list above is. */
state?: PictureState
seenAt?: string | null
lastKnownAge?: number | null
/** Additive (issue 03): the last explicit wake of this grid did not show models — while it stands. */
wakeOutcome?: WakeOutcome
}
/** What the Model Manager is told about the grid it runs on (`localModels.ts`'s `GridInventory`). */
export interface GridInventoryAnswer {
state: PictureState
nodes: Record<string, unknown>[]
/** The owner status as last read (memoised, never asked per tick); null when it could not be read. */
status: string | null
}
/** How long one read's answer stands before the next is made: the list is live while a grid is awake,
* and a sleeping grid changes only when something wakes it. A failure is retried sooner than sleep. */
const AWAKE_MEMO_MS = 15_000
const ASLEEP_MEMO_MS = 120_000
const FAILED_MEMO_MS = 30_000
/** `grid info` is a control-plane call: a member's grid address barely moves; the OWNER's status is the
* thing that says a grid fell asleep or woke, so it is asked more often, and less often while asleep. */
const ADDRESS_MEMO_MS = 10 * 60_000
const OWN_STATUS_AWAKE_MEMO_MS = 15_000
const OWN_STATUS_ASLEEP_MEMO_MS = 60_000
/** Several changes landing together (three grids refreshed at once) are one push, not three. */
const CHANGE_COALESCE_MS = 250
/** How long an ask waits for the FIRST read of a grid this daemon has never seen before answering with
* what it has. Kept well inside the app's 12s `grid_models_list` timeout, which the grid-name wait
* (`GRID_ATTACH_WAIT_MS`, 6s) shares: a read slower than this lands behind the answer and is pushed. */
const FIRST_READ_WAIT_MS = 4_000
/** An explicit wake re-reads the grid, without a credential, this often and for this long after its one
* credentialed read — long enough for a master to boot and its providers to report (issue 03). */
const WAKE_REREAD_MS = 3_000
const WAKE_WINDOW_MS = 45_000
/** How long a wake that showed no models keeps saying so. */
const WAKE_OUTCOME_STANDS_MS = 10 * 60_000
/** A grid seen awake this recently needs no prewarm: an agent moved onto it will find it up. */
const RECENTLY_AWAKE_MS = 60_000
interface GridRow { name: string; type: string; id: string }
/** The key a grid is tracked under when `grid ls` gave it no network id — its name, marked as such, so
* it can never collide with an id and never names a run-record directory. */
const NAME_ONLY_PREFIX = 'name:'
const nameOnlyRow = (name: string, type = ''): GridRow => ({ name, type, id: `${NAME_ONLY_PREFIX}${name}` })
/** Everything this daemon holds about one grid, keyed by its network id. */
interface Tracked {
id: string
/** The name `grid` knows it by — what goes into `grid info <name>` and the fallback's argv. */
name: string
/** The account's own grid. A property of the GRID, learnt from whoever last knew (the model list,
* the Model Manager), so an asker that does not know — a launch check — never changes how it is read. */
own: boolean
picture: GridPicture
loaded: boolean
/** Whether a picture existed before this process read anything (on disk, or read since). */
known: boolean
/** The picture as last written, so a read that changed nothing writes nothing. */
saved: string
readAt: number | null
readTtl: number
info: { at: number; value: GridInfo | null } | null
/** The own grid's one read of its current asleep episode has been made. */
episodeRead: boolean
/** What this daemon has watched a live run record serve here since the last awake read. */
seen: Set<string>
/** Ids discovery has already been asked to spell, so an id no provider spells is asked about once. */
spelled: Set<string>
/** The last awake overview's own node objects, for the Model Manager. Not persisted. */
rawNodes: Record<string, unknown>[]
pending: Promise<void> | null
/** An explicit wake running for this grid; a second ask joins it. */
waking: Promise<void> | null
/** How the last explicit wake ended when it showed no models, and when. */
wakeOutcome: { outcome: WakeOutcome; at: number } | null
/** What a picker was last told about this grid — what an agent's note is read from, with no I/O. */
lastView: GridView | null
}
/** One grid's section as this service builds it: the picture's view, and the wake it may be in. */
type GridView = Omit<SectionView, 'models'> & { models: GridModel[]; wakeOutcome?: WakeOutcome }
/** What a prewarm did: `fired` one credentialed read, or why it did not. */
export type PrewarmOutcome =
| 'fired' | 'debounced' | 'awake' | 'absent' | 'offline'
/** Keystroke only: the agent's grid is not one this daemon is tracking, or it is not asleep. */
| 'untracked' | 'not-asleep'
/** A grid as a launch names it: its network id (what it is tracked and persisted under) and its name. */
export interface GridRef { networkId: string; gridName: string }
/** Where an agent's inference goes (`gridAssignment.ts`) — all a prewarm or a note needs of it. */
export interface AgentGridTarget { baseUrl: string; model: string | null }
/** What is said about an agent already on a grid model (issue 03): that grid's state, and a note when its
* model will not answer — every computer serving it seems offline, or the latest list no longer has it. */
export interface GridNote { reason: 'offline' | 'not_served'; model: string; machine?: string }
export interface GridAnnotation { state: PictureState; note?: GridNote }
export interface GridModelsDeps {
now: () => number
/** Where pictures are kept (`<data dir>/grid-pictures`). */
dataDir: () => string
/** `~/.grid`, whose `run/engines/<grid id>/` holds this computer's run records. */
gridHome: () => string
/** The signed-in account — a node published under it is the account's own. */
email: () => string | null
/** [FIRST_READ_WAIT_MS], injectable so a test need not wait it out. */
firstReadWaitMs: number
/** The explicit wake's pause between re-reads — injectable, so a test's clock moves instead. */
sleep: (ms: number) => Promise<void>
/** Run `run` in `ms`, with nothing else to trigger it (an expiry); answers how to call it off. */
after: (ms: number, run: () => void) => () => void
}
const defaultDeps: GridModelsDeps = {
now: () => Date.now(),
dataDir: () => config.ADAPTER_DATA_DIR,
gridHome: () => dirname(gridCredentialsPath()),
email: () => signedInGridEmail(),
firstReadWaitMs: FIRST_READ_WAIT_MS,
sleep: (ms) => new Promise((resolve) => { setTimeout(resolve, ms).unref?.() }),
after: (ms, run) => {
const timer = setTimeout(run, ms)
timer.unref?.()
return () => clearTimeout(timer)
},
}
/**
* The pictures, the reads that keep them current, and the answers built from them.
*
* Nothing here runs on a timer: a grid is read only when something asks about it and its last answer
* has aged past its memo. So an app nobody looks at costs nothing, and an app somebody looks at costs a
* credential-less read every 15s at most per grid — and never a wake.
*/
export class GridModelsService {
private readonly deps: GridModelsDeps
private readonly tracked = new Map<string, Tracked>()
private readonly listeners = new Set<() => void>()
private readonly presence = new ComputerPresence()
private readonly debounce = new PrewarmDebounce()
private rows: GridRow[] | null = null
private changeTimer: NodeJS.Timeout | null = null
/** Called off by the next machine list: fires only when none came in time, and the labels lapse. */
private cancelStaleList: (() => void) | null = null
constructor(deps: Partial<GridModelsDeps> = {}) {
this.deps = { ...defaultDeps, ...deps }
}
/** Called (coalesced) whenever a read changed what a picker would be told. */
onChange(listener: () => void): () => void {
this.listeners.add(listener)
return () => { this.listeners.delete(listener) }
}
/** Resolves once every read already started has landed — for a test, and for a caller that must see
* the answer a read it triggered produced. */
async settled(): Promise<void> {
await Promise.all([...this.tracked.values()].flatMap((tracked) => [tracked.pending?.catch(() => {}), tracked.waking]))
}
/**
* Every picture this computer saved, taken back into memory with nothing read from any grid — at daemon
* start, so an agent's frame carries its grid's state and note, and a keystroke can start its grid, from
* the first moment: before any window has asked for the list, and when none ever does (a phone typing).
*/
async warm(): Promise<void> {
const folder = join(this.deps.dataDir(), PICTURES_DIR)
const names = await readdir(folder).catch(() => [] as string[])
await Promise.all(names.filter((name) => name.endsWith('.json')).map(async (name) => {
try {
const saved = JSON.parse(await readFile(join(folder, name), 'utf8')) as SavedPicture
if (typeof saved.networkId !== 'string' || !saved.networkId) return
const gridName = typeof saved.name === 'string' ? saved.name : ''
const tracked = await this.track({ id: saved.networkId, name: gridName, type: '' }, saved.own === true ? true : undefined)
await this.view(tracked)
} catch { /* not a picture this module wrote; the list will read that grid when asked */ }
}))
}
/** Every read is due again — for a caller that has just changed what a grid serves, or which grid is
* whose. The pictures stay: they are what is shown until the next read lands. */
forget(): void {
for (const tracked of this.tracked.values()) {
tracked.readAt = null
tracked.info = null
}
this.rows = null
}
/**
* Every grid this computer is signed into, own grid first, each with its models.
*
* Answered from the pictures at once. A grid whose answer has aged is read again in the background —
* one read per grid at a time — and a change is announced through [onChange]. Only a grid this daemon
* has never seen is waited for, and only for [FIRST_READ_WAIT_MS]: an answer made of nothing is worse
* than a short wait, and a timed-out ask is worse than both.
*/
async sections(ownGridName: string | null, opts: { refresh?: boolean; wake?: readonly string[] } = {}): Promise<GridSection[]> {
const rows = await this.gridRows(ownGridName)
const sections = await Promise.all(rows.map(async (row) => {
const tracked = await this.track(row, row.name === ownGridName)
// A person asked for this one to start: its wake is running before the answer is built, so the
// answer says "waking" — and a wake's own re-reads stand in for the refresh below.
if (opts.wake?.includes(row.name)) this.startWake(tracked)
if (opts.refresh !== false && !tracked.waking && this.due(tracked)) {
const refreshing = this.refresh(tracked)
if (!tracked.known) await this.within(refreshing, this.deps.firstReadWaitMs)
else void refreshing.catch(() => {})
}
return { name: row.name, type: row.type, own: tracked.own, ...await this.view(tracked) }
}))
sections.sort((a, b) => Number(b.own) - Number(a.own))
return sections
}
/** One grid's models, read now if its answer has aged — for a launch that must not start on a model
* nobody serves. Still never a waking read: a sleeping grid answers with what it last served. */
async models(gridName: string): Promise<GridModel[]> {
const tracked = await this.track(await this.rowFor(gridName))
if (this.due(tracked)) await this.refresh(tracked)
return (await this.view(tracked)).models
}
/** What the Model Manager needs to tell a running model from a stopped one, on the account's own grid. */
async inventory(gridName: string, force: boolean): Promise<GridInventoryAnswer> {
const tracked = await this.track(await this.rowFor(gridName), true)
if (force) tracked.readAt = null
if (this.due(tracked)) await this.refresh(tracked)
return {
state: tracked.picture.state,
nodes: tracked.picture.state === 'awake' ? tracked.rawNodes : [],
status: tracked.info?.value?.status ?? null,
}
}
private async rowFor(gridName: string): Promise<GridRow> {
return (await this.gridRows(null)).find((row) => row.name === gridName) ?? nameOnlyRow(gridName)
}
/** `grid ls --json` — a LOCAL registry read, no network. The last good answer stands in for a failed
* one; with none, the own grid alone, as before this module knew about shared grids. */
private async gridRows(ownGridName: string | null): Promise<GridRow[]> {
const { value } = await gridJson<Array<{ grid?: unknown; type?: unknown; id?: unknown }>>(['--remote', 'ls'])
if (Array.isArray(value)) {
this.rows = value
// A name goes into a `grid` argv (`info <grid>`, the fallback's `models <grid>`): one that reads
// as a flag, or carries a control character, is not one this daemon will pass along.
.filter((row) => typeof row?.grid === 'string' && /^[^-\x00-\x1f\x7f][^\x00-\x1f\x7f]*$/.test(row.grid.trim()))
.map((row) => {
const name = (row.grid as string).trim()
const type = typeof row.type === 'string' ? row.type : ''
return typeof row.id === 'string' && row.id.trim() ? { name, type, id: row.id.trim() } : nameOnlyRow(name, type)
})
}
if (this.rows) return this.rows
return ownGridName ? [nameOnlyRow(ownGridName, 'permissioned-public')] : []
}
/** The grid behind `row`, loaded from disk the first time. `own`, when the caller knows it, is recorded. */
private async track(row: GridRow, own?: boolean): Promise<Tracked> {
let tracked = this.tracked.get(row.id)
if (!tracked) {
tracked = {
id: row.id, name: row.name, own: false, picture: emptyPicture(), loaded: false, known: false, saved: '',
readAt: null, readTtl: 0, info: null, episodeRead: false, seen: new Set(), spelled: new Set(), rawNodes: [],
pending: null, waking: null, wakeOutcome: null, lastView: null,
}
this.tracked.set(row.id, tracked)
}
// A warmed grid may know only its id; the list's name is better than none.
if (row.name) tracked.name = row.name
if (own !== undefined) tracked.own = own
if (!tracked.loaded) {
tracked.loaded = true
await this.load(tracked)
}
return tracked
}
/** `work`, or nothing more than `ms` of it — what it produces meanwhile lands on its own. */
private async within(work: Promise<void>, ms: number): Promise<void> {
let timer: NodeJS.Timeout | undefined
await Promise.race([work.catch(() => {}), new Promise<void>((resolve) => { timer = setTimeout(resolve, ms) })])
clearTimeout(timer)
}
private due(tracked: Tracked): boolean {
const now = this.deps.now()
if (tracked.readAt === null || now - tracked.readAt >= tracked.readTtl) return true
// The owner's status is what says the own grid fell asleep or woke; it is checked on its own clock.
return tracked.own && (tracked.info === null || now - tracked.info.at >= this.statusTtl(tracked))
}
private statusTtl(tracked: Tracked): number {
if (!tracked.own) return ADDRESS_MEMO_MS
return tracked.info?.value?.status === OWNER_ASLEEP_STATUS ? OWN_STATUS_ASLEEP_MEMO_MS : OWN_STATUS_AWAKE_MEMO_MS
}
/** One refresh per grid at a time; every asker shares it. */
private refresh(tracked: Tracked): Promise<void> {
const pending = tracked.pending ?? this.read(tracked).then(() => {}).finally(() => { tracked.pending = null })
tracked.pending = pending
return pending
}
/**
* One credential-less read, applied. `waking`: an explicit wake's re-read, which goes to the grid even
* where an automatic look would not (the own grid past its one read per asleep episode). Answers the
* read it made, or null when the episode rule stood in for one.
*/
private async read(tracked: Tracked, waking = false): Promise<GridRead | null> {
const before = JSON.stringify(await this.signature(tracked))
const info = await this.info(tracked)
const asleepByStatus = tracked.own && info?.status === OWNER_ASLEEP_STATUS
let made: GridRead | null = null
// The own grid, while its status says asleep, is read exactly once per asleep episode — to fetch the
// platform's record of what it served — and then left alone until the status changes.
if (asleepByStatus && tracked.episodeRead && !waking) {
await this.apply(tracked, { kind: 'asleep', lastKnown: null }, null)
} else {
tracked.episodeRead = asleepByStatus
const base = readBase(info?.gridUrl ?? null)
made = base ? await readOverview(base) : { kind: 'unreachable' }
// Only when nothing answered at all — never to second-guess an answer the grid gave.
if (made.kind === 'unreachable') made = await readViaCli(tracked.name)
await this.apply(tracked, made, base)
}
await this.save(tracked)
tracked.known = true
if (JSON.stringify(await this.signature(tracked)) !== before) this.changed()
return made
}
private async info(tracked: Tracked): Promise<GridInfo | null> {
const now = this.deps.now()
if (tracked.info && now - tracked.info.at < this.statusTtl(tracked)) return tracked.info.value
// A failed ask keeps the last answer (the address is still the address), and is not re-asked at once.
const value = await readGridInfo(tracked.name) ?? tracked.info?.value ?? null
tracked.info = { at: now, value }
if (value?.status !== OWNER_ASLEEP_STATUS) tracked.episodeRead = false
return value
}
private async apply(tracked: Tracked, read: GridRead, base: string | null): Promise<void> {
const now = this.deps.now()
tracked.readAt = now
if (read.kind === 'asleep') {
tracked.picture = withAsleep(tracked.picture, read.lastKnown, now)
tracked.readTtl = ASLEEP_MEMO_MS
return
}
if (read.kind !== 'awake') {
tracked.picture = withUnknown(tracked.picture)
tracked.readTtl = FAILED_MEMO_MS
return
}
const here = await this.servedHere(tracked)
const email = this.deps.email()?.trim().toLowerCase() ?? ''
const isMine = (node: ReadNode): boolean => !!email && node.providerEmail?.trim().toLowerCase() === email
const previous = tracked.picture
let picture = mergeAwake(previous, read.nodes, now, isMine, (name, key) => provenStopped(previous, here, name, key))
picture = withWindows({ ...picture, caseMap: withSpellings(picture.caseMap, [...read.curatedIds, ...here.records.flatMap((r) => r.ids)]) }, read.windows)
const unknownIds = unspelled(picture, read.nodes).filter((key) => !tracked.spelled.has(key))
if (unknownIds.length && base) {
unknownIds.forEach((key) => tracked.spelled.add(key))
picture = { ...picture, caseMap: withSpellings(picture.caseMap, await readDiscoveryIds(base)) }
}
tracked.picture = picture
tracked.rawNodes = read.rawNodes
tracked.readTtl = AWAKE_MEMO_MS
// A wake that showed nothing stops saying so once the grid is found serving.
if (servesAModel(read.nodes)) tracked.wakeOutcome = null
// What this computer serves is re-learnt from what is live now: the grid has just said what it serves.
tracked.seen = new Set(liveKeys(here.records))
}
/**
* This computer's own records for the grid, and what they have been seen serving. Deliberately updates
* `seen` on EVERY look, answers included: "seen served since the last awake read" is only true if each
* look that found a live record counted.
*/
private async servedHere(tracked: Tracked): Promise<ServedHere> {
const records: LocalRecord[] = tracked.id.startsWith(NAME_ONLY_PREFIX) ? [] : await readRunRecords(this.deps.gridHome(), tracked.id)
for (const key of liveKeys(records)) tracked.seen.add(key)
return { records, seen: tracked.seen, own: tracked.own }
}
private async view(tracked: Tracked): Promise<GridView> {
const now = this.deps.now()
const view = sectionView(tracked.picture, await this.servedHere(tracked), now, (name) => this.presence.seemsOffline(name, now))
const outcome = tracked.wakeOutcome && now - tracked.wakeOutcome.at < WAKE_OUTCOME_STANDS_MS ? tracked.wakeOutcome.outcome : null
const shown: GridView = {
...view,
// A wake in flight is the state, whatever its re-reads have found so far.
state: tracked.waking ? 'waking' : view.state,
...(outcome && !tracked.waking ? { wakeOutcome: outcome } : {}),
}
tracked.lastView = shown
return shown
}
/** What a push is about: the rows (labels included), the state and a wake's outcome — not the ages,
* which move every second. */
private async signature(tracked: Tracked): Promise<unknown> {
const view = await this.view(tracked)
return { models: view.models, state: view.state, wakeOutcome: view.wakeOutcome }
}
/** Coalesced: the views are rebuilt first, so a listener reading an agent's note sees the change. */
private changed(): void {
if (this.changeTimer) return
this.changeTimer = setTimeout(() => {
this.changeTimer = null
void Promise.all([...this.tracked.values()].map((tracked) => this.view(tracked).catch(() => null))).then(() => {
for (const listener of this.listeners) {
try { listener() } catch { /* a listener's failure is its own */ }
}
})
}, CHANGE_COALESCE_MS)
this.changeTimer.unref?.()
}
// ── a person's act (issue 03) ─────────────────────────────────────────────────────────────────────
/** What was read about `tracked` is stale now: a person has just acted on it. The picture stays. */
private dropMemo(tracked: Tracked): void {
tracked.readAt = null
tracked.info = null
tracked.episodeRead = false
}
/**
* An explicit wake — "Show models", the viewer's "Wake now". Returns at once (the answer being built
* says "waking"); behind it, ONE credentialed read marked `(wake)`, then credential-less re-reads every
* [WAKE_REREAD_MS] for up to [WAKE_WINDOW_MS], merged like any read, until one finds a node serving.
* A second ask while it runs joins it.
*/
private startWake(tracked: Tracked): void {
if (tracked.waking) return
this.dropMemo(tracked)
tracked.wakeOutcome = null
// A prewarm right behind a wake would pay for the same boot twice.
this.debounce.mark(tracked.id, this.deps.now())
void this.credentialedRead(tracked, 'wake')
tracked.waking = this.wake(tracked).catch(() => {}).finally(() => {
tracked.waking = null
// The status said asleep when this began; whatever it says now is asked afresh.
tracked.info = null
this.changed()
})
this.changed()
}
private async wake(tracked: Tracked): Promise<void> {
let cameUp = false
for (let reread = 0; reread < WAKE_WINDOW_MS / WAKE_REREAD_MS; reread++) {
await this.deps.sleep(WAKE_REREAD_MS)
const read = await this.readNow(tracked)
if (read?.kind !== 'awake') continue
cameUp = true
if (servesAModel(read.nodes)) return
}
tracked.wakeOutcome = { outcome: cameUp ? 'nobody_serving' : 'not_started', at: this.deps.now() }
// It stops being said with no read to notice: tell the windows when it does.
this.deps.after(WAKE_OUTCOME_STANDS_MS, () => this.changed())
}
/** A wake's re-read: after any read already out, and never shared with the automatic path's memo. */
private async readNow(tracked: Tracked): Promise<GridRead | null> {
while (tracked.pending) await tracked.pending.catch(() => {})
let answered: GridRead | null = null
tracked.pending = this.read(tracked, true).then((read) => { answered = read }).finally(() => { tracked.pending = null })
await tracked.pending
return answered
}
/** One credentialed read marked `purpose`, detached — unless one went out for this grid in the last
* ten minutes. */
private sendPrewarm(tracked: Tracked, purpose: WakePurpose, now: number, access?: RelayAccess): PrewarmOutcome {
if (!this.debounce.allows(tracked.id, now)) return 'debounced'
this.debounce.mark(tracked.id, now)
void this.credentialedRead(tracked, purpose, access)
return 'fired'
}
/** The credentialed read itself ([wakeRead]). Its answer lists each model's context window, which the
* picture keeps for the next move onto that grid — the one thing it teaches that no free read did. */
private async credentialedRead(tracked: Tracked, purpose: WakePurpose, access?: RelayAccess): Promise<void> {
const name = tracked.name || (await this.gridRows(null)).find((row) => row.id === tracked.id)?.name
if (!name) return
const windows = await wakeRead(name, purpose, access)
if (!windows) return
tracked.picture = withWindows(tracked.picture, windows)
await this.save(tracked)
}
/**
* An agent was just moved onto `model` on `gridName`: start that grid while the pane restarts — when
* its picture says asleep or it was not seen awake in the last minute. Not when the model is not in the
* picture, and not when every computer serving it seems offline: a boot nobody can answer on is wasted.
* The move never waits on this, and is never refused by it.
*/
async retargetPrewarm(grid: GridRef, model: string, access?: RelayAccess): Promise<PrewarmOutcome> {
// By the id the move already resolved: no `grid ls` of its own behind a click.
const tracked = await this.track({ id: grid.networkId, name: grid.gridName, type: '' })
const now = this.deps.now()
const { state, seenAt } = tracked.picture
const view = await this.view(tracked)
this.dropMemo(tracked)
if (state !== 'asleep' && seenAt !== null && now - seenAt < RECENTLY_AWAKE_MS) return 'awake'
const row = servedRow(view.models, model)
if (!row) return 'absent'
if (row.unavailable) return 'offline'
return this.sendPrewarm(tracked, 'prewarm', now, access)
}
/**
* Someone typed into a terminal whose agent runs on `target`: when that grid's picture says asleep,
* start it while they type. Asked on EVERY input, so it answers from memory — the picture and the view
* a picker was last given — and the ten-minute debounce is checked first.
*/
async keystrokePrewarm(target: AgentGridTarget | null | undefined): Promise<PrewarmOutcome> {
const tracked = target ? this.trackedFor(target.baseUrl) : null
if (!tracked || !target) return 'untracked'
if (tracked.picture.state !== 'asleep' || tracked.waking) return 'not-asleep'
const now = this.deps.now()
if (!this.debounce.allows(tracked.id, now)) return 'debounced'
const rows = target.model ? tracked.lastView?.models : undefined
const row = rows && servedRow(rows, target.model!)
if (rows && !row) return 'absent'
if (row?.unavailable) return 'offline'
this.dropMemo(tracked)
return this.sendPrewarm(tracked, 'prewarm-key', now)
}
/** The grid an agent's inference goes to: the tracked grid whose id is a segment of its relay's path. */
private trackedFor(baseUrl: string): Tracked | null {
let segments: string[]
try { segments = new URL(baseUrl).pathname.split('/').filter(Boolean) } catch { return null }
return [...this.tracked.values()].find((tracked) => !tracked.id.startsWith(NAME_ONLY_PREFIX) && segments.includes(tracked.id)) ?? null
}
/**
* What an agent frame says about the agent's grid, from what a picker was last told — no I/O, so a
* frame costs nothing. Null for an agent on no grid this daemon is tracking.
*/
annotation(target: AgentGridTarget | null | undefined): GridAnnotation | null {
const tracked = target ? this.trackedFor(target.baseUrl) : null
const view = tracked?.lastView
if (!tracked || !view || !target) return null
if (!target.model || view.state === 'waking') return { state: view.state }
const row = servedRow(view.models, target.model)
if (row?.unavailable) return { state: view.state, note: { reason: 'offline', model: row.id, machine: row.unavailable.machine } }
// "No longer lists" needs a list: a grid never read says nothing about any model.
if (!row && tracked.picture.listAt !== null) return { state: view.state, note: { reason: 'not_served', model: target.model } }
return { state: view.state }
}
/** A machine list this daemon just read (`GET /api/machines`, or null when signed out): which of the
* account's other computers seem offline. Re-tells clients only when a verdict changed. */
observeMachines(body: unknown, localComputerId: string): void {
const list = computersIn(body, localComputerId) ?? { computers: [], guest: true }
if (this.presence.observe(list, this.deps.now())) this.changed()
// A list that stops coming (this computer lost the backend) stops labelling, with no read to notice.
this.cancelStaleList?.()
this.cancelStaleList = this.deps.after(MACHINE_LIST_FRESH_MS + 1_000, () => this.changed())
}
/** The context window `model` was last reported with on the grid tracked as `networkId`, read without a
* credential — what a launch tells the engine to compact inside. */
async contextWindow(grid: GridRef, model: string): Promise<number | undefined> {
const tracked = await this.track({ id: grid.networkId, name: grid.gridName, type: '' })
return tracked.picture.windows[idKey(model)]
}
private file(tracked: Tracked): string {
const name = createHash('sha256').update(tracked.id).digest('hex').slice(0, 24)
return join(this.deps.dataDir(), PICTURES_DIR, `${name}.json`)
}
private async load(tracked: Tracked): Promise<void> {
try {
const raw = JSON.parse(await readFile(this.file(tracked), 'utf8')) as { networkId?: unknown; picture?: unknown }
const picture = raw.networkId === tracked.id ? parsePicture(raw.picture) : null
if (!picture) return
tracked.picture = picture
tracked.known = true
tracked.saved = JSON.stringify(picture)
} catch { /* never read on this computer, or not a file this module wrote */ }
}
/** Owner-only, replaced atomically — a crash mid-write leaves the previous picture, never half of one. */
private async save(tracked: Tracked): Promise<void> {
const text = JSON.stringify(tracked.picture)
if (text === tracked.saved) return
try {
const file = this.file(tracked)
await mkdir(dirname(file), { recursive: true, mode: 0o700 })
const temp = `${file}.${randomUUID()}.tmp`
const saved: SavedPicture = { networkId: tracked.id, name: tracked.name, own: tracked.own, picture: tracked.picture }
await writeFile(temp, JSON.stringify(saved), { mode: 0o600 })
await rename(temp, file)
tracked.saved = text
} catch { /* the picture is still in memory; the next read tries again */ }
}
}
/** A picture as saved: which grid it is (its name and whether it is the account's own, so a daemon that
* just started can use it before `grid ls` is asked — both absent from a file written before issue 03). */
interface SavedPicture { networkId?: unknown; name?: unknown; own?: unknown; picture?: unknown }
/** Where pictures are kept, under the data directory. */
const PICTURES_DIR = 'grid-pictures'
/** The row a view offers for `model`, whatever its case. */
function servedRow(rows: readonly GridModel[], model: string): GridModel | undefined {
const key = idKey(model)
return rows.find((row) => idKey(row.id) === key)
}
function liveKeys(records: readonly LocalRecord[]): string[] {
return records
.filter((record) => record.pid !== null && record.alive)
.flatMap((record) => record.ids.map((id) => servedKey(record.name, idKey(id))))
}
/** The daemon's one service. Module-level like the memo it replaces, so every caller shares one picture. */
let service = new GridModelsService()
/** Live models on `gridName`, router excluded, as the picture has them after a read if one was due. */
export async function listGridModels(gridName: string | null): Promise<GridModel[]> {
if (!gridName?.trim()) return []
return service.models(gridName.trim())
}
/** The pictures this computer saved, back in memory ([GridModelsService.warm]) — at daemon start. */
export function warmGridModels(): Promise<void> {
return service.warm()
}
/** Every grid this computer is signed into, each with its models, own grid first. `wake` names the
* sections a person asked to start (issue 03's explicit wake). */
export function listAllGridModels(ownGridName: string | null, opts: { refresh?: boolean; wake?: readonly string[] } = {}): Promise<GridSection[]> {
return service.sections(ownGridName, opts)
}
/**
* The sections as a client that did or did not ask for row state reads them. One that did (`rowState`)
* gets each label as `unavailable` beside a plain `node`; an older one, which would draw neither, reads
* the label in the node text — "<computer> · seems offline" — and nothing else changes for it.
*/
export function presentGridSections(sections: GridSection[], opts: { rowState: boolean }): GridSection[] {
if (opts.rowState) return sections
return sections.map((section) => ({
...section,
models: section.models.map(({ unavailable, ...row }) => unavailable ? { ...row, node: `${unavailable.machine} · seems offline` } : row),
}))
}
/** An agent was just moved onto the grid model `launch` names — start that grid while the pane restarts,
* if it needs it ([GridModelsService.retargetPrewarm]), with the relay and credential the move resolved.
* A launch with no model (Auto) names nothing to check, and starts nothing. */
export async function retargetPrewarm(launch: GridLaunchOverride): Promise<PrewarmOutcome | null> {
if (!launch.model) return null
return service.retargetPrewarm({ networkId: launch.networkId, gridName: launch.networkName }, launch.model,
{ baseUrl: launch.baseUrl, apiKey: launch.apiKey })
}
/** Input reached a terminal whose agent runs on `target` ([GridModelsService.keystrokePrewarm]). */
export function keystrokePrewarm(target: AgentGridTarget | null | undefined): Promise<PrewarmOutcome> {
return service.keystrokePrewarm(target)
}
/** What an agent frame says about the grid the agent is on ([GridModelsService.annotation]). */
export function gridAnnotation(target: AgentGridTarget | null | undefined): GridAnnotation | null {
return service.annotation(target)
}
/** The context window `model` was last reported with on `grid`, read without a credential. */
export function gridContextWindow(grid: GridRef, model: string): Promise<number | undefined> {
return service.contextWindow(grid, model)
}
/** A machine list this daemon just read, or null when signed out ([GridModelsService.observeMachines]). */
export function observeMachineList(body: unknown, localComputerId: string): void {
service.observeMachines(body, localComputerId)
}
/** The Model Manager's inventory of the own grid (`LocalModels`' injected `inventory`). */
export function gridInventory(gridName: string, force: boolean): Promise<GridInventoryAnswer> {
return service.inventory(gridName, force)
}
/** Make every read due again — for a caller that has just changed what a grid serves. */
export function forgetGridModels(): void {
service.forget()
}
/** Be told (coalesced) when a background read changed what a picker would be told. */
export function onGridModelsChanged(listener: () => void): () => void {
return service.onChange(listener)
}
/** For tests: a fresh service, optionally with its clock and places injected. */
export function resetGridModels(deps: Partial<GridModelsDeps> = {}): GridModelsService {
service = new GridModelsService(deps)
return service
}