repositories / will
will
owned by admin
src/agent/ciwatch.ts
Rawimport { createLogger } from '../shared/log.ts'
import type { WakeQueue } from './queue-store.ts'
import type { AgentState } from './state.ts'
const log = createLogger({ component: 'ciwatch' })
/**
* Luci CI watch (spec AC16/AC23): poll pushed commits' runs and enqueue a
* ci_failure wake when a required run fails before deployment.
* Contract: GET {base}/api/v1/repos/{repo}/runs -> { runs: [...] }.
* The JSON endpoint is a required Luci extension (deploy/contract.md);
* polling is disabled until WILL_LUCI_BASE_URL is configured.
*/
export interface LuciRun {
id: string
revision: string
status: 'running' | 'success' | 'failed'
jobs?: { name: string; status: string }[]
}
export interface LuciClient {
listRuns(): Promise<LuciRun[]>
}
export function createLuciClient(baseUrl: string, repo: string): LuciClient {
return {
async listRuns() {
const res = await fetch(
`${baseUrl.replace(/\/$/, '')}/api/v1/repos/${encodeURIComponent(repo)}/runs`,
{
signal: AbortSignal.timeout(10_000),
},
)
if (!res.ok) throw new Error(`luci runs failed: ${res.status}`)
const body = (await res.json()) as { runs?: LuciRun[] }
return body.runs ?? []
},
}
}
/** Poll once; enqueue ci_failure wakes for newly-failed runs of tracked pushes. */
export async function pollCi(
client: LuciClient,
state: AgentState,
queue: WakeQueue,
): Promise<{ wakes: number }> {
const runs = await client.listRuns()
const tracked = new Set(state.data.pushedShas)
const handled = new Set(state.data.handledCiRuns)
let wakes = 0
for (const run of runs) {
if (run.status !== 'failed' || handled.has(run.id)) continue
if (!tracked.has(run.revision)) continue
await queue.enqueue('ci_failure', {
sha: run.revision,
job: run.jobs?.find((j) => j.status === 'failed')?.name ?? 'unknown',
})
wakes++
}
if (wakes > 0) {
const newHandled = runs.filter((r) => r.status === 'failed').map((r) => r.id)
await state.update((d) => {
d.handledCiRuns = [...new Set([...d.handledCiRuns, ...newHandled])].slice(-500)
})
}
log.debug('ci poll', { runs: runs.length, wakes })
return { wakes }
}