UNPKG

paqad-ai

Version:

Spec-driven development framework — AI agents that think before they type

97 lines (89 loc) • 2.81 kB
import { subagentTitle } from './agents'; import { formatStopwatch, shortAge } from './cost'; import { IDLE_LIVE, type LiveState } from './live'; import type { Signal } from './palette'; import type { PaqadHudSnapshot, PaqadTone } from './types'; export const WAITING_FOR_ANSWER = 'Waiting for your answer'; export const SUBAGENT_ROW = 7; export const NO_RECORD = 'Not yet. paqad has no record for this session.'; export const SIGNOFF_ROW = 21; function checkedNote(snapshot: PaqadHudSnapshot, row: number, now: number): string | null { if (row !== SIGNOFF_ROW || snapshot.signoff?.state !== 'awaiting_sign_off') return null; const checked = Date.parse(snapshot.signoff.checked_at ?? ''); return Number.isFinite(checked) ? `checked ${shortAge(now - checked)} ago` : null; } export interface DesktopHeadline { row: number; signal: Signal; tone: PaqadTone; live: boolean; text: string; word: string | null; note: string | null; gap: string | null; clock: string | null; activity: string[]; } const TONE_SIGNAL: Readonly<Record<PaqadTone, Signal>> = { neutral: 'idle', working: 'accent', 'needs-you': 'attention', good: 'pass', warn: 'caution', bad: 'fail', }; function waitingRow(row: number, text: string): DesktopHeadline { return { row, signal: 'attention', tone: 'needs-you', live: false, text, word: WAITING_FOR_ANSWER, note: null, gap: null, clock: null, activity: [], }; } export function snapshotHeadline( snapshot: PaqadHudSnapshot, now: number, live: LiveState = IDLE_LIVE, ): DesktopHeadline { if (live.waitingOn === 'question') return waitingRow(3, WAITING_FOR_ANSWER); const head = snapshot.headline; const title = subagentTitle(live.agents?.running ?? []); if (title !== null && live.agents && head.tone !== 'needs-you') { const finished = live.agents.finished; return { row: SUBAGENT_ROW, signal: 'accent', tone: 'working', live: true, text: title, word: 'Working', note: finished > 0 ? `${finished} finished` : null, gap: head.gap ?? null, clock: null, activity: [...live.agents.lines], }; } const running = head.tone === 'working'; const from = head.clock_from ? Date.parse(head.clock_from) : Number.NaN; return { row: head.row ?? 0, signal: TONE_SIGNAL[head.tone], tone: head.tone, live: running, text: head.text || NO_RECORD, word: head.word ?? null, note: head.note ?? checkedNote(snapshot, head.row ?? 0, now), gap: head.gap ?? null, clock: running && Number.isFinite(from) ? formatStopwatch(now - from) : null, activity: [], }; } export function judgedHeadline(snapshot: PaqadHudSnapshot | null): boolean { return typeof snapshot?.headline.row === 'number'; }