// What the side bar shows, kept current from the event stream. import * as vscode from "vscode"; import type { Agent, Brief, Briefs, Channel, InboxItem, Project } from "./spectra-api"; import { Bridge } from "./bridge"; import { projectForFolder } from "./pure"; export interface StreamEvent { seq: number; kind: string; actor: string; subject?: string | null; } export class Model implements vscode.Disposable { brief: Brief | undefined; agents: Agent[] = []; channels: Channel[] = []; projects: Project[] = []; inbox: InboxItem[] = []; briefs: Briefs = { overall: null, projects: [] }; error: string | undefined; private readonly changed = new vscode.EventEmitter(); readonly onDidChange = this.changed.event; private readonly events = new vscode.EventEmitter(); /** Every event from the stream, for open chats. */ readonly onEvent = this.events.event; private timer: NodeJS.Timeout | undefined; private stream: vscode.Disposable | undefined; constructor(private readonly bridge: Bridge) {} /** Start following the stream and fetch everything. */ start(): void { this.stream?.dispose(); this.stream = this.bridge.follow( "/api/v1/events?follow=1", (data) => { try { const e = JSON.parse(data) as StreamEvent; this.events.fire(e); } catch { return; } this.soon(); }, () => this.soon(), ); void this.refresh(); } stop(): void { this.stream?.dispose(); this.stream = undefined; } /** Refresh shortly, once for a burst of events. */ soon(): void { if (this.timer) clearTimeout(this.timer); this.timer = setTimeout(() => void this.refresh(), 300); } async refresh(): Promise { try { const [brief, agents, channels, projects, inbox, briefs] = await Promise.all([ this.bridge.request("GET", "/api/v1/brief"), this.bridge.request("GET", "/api/v1/agents"), this.bridge.request("GET", "/api/v1/channels"), this.bridge.request("GET", "/api/v1/projects"), this.bridge.request("GET", "/api/v1/inbox"), this.bridge.request("GET", "/api/v1/briefs"), ]); Object.assign(this, { brief, agents, channels, projects, inbox, briefs, error: undefined }); } catch (e) { this.error = (e as Error).message; } this.changed.fire(); } /** The project of the first workspace folder, if it is one. */ folderProject(): string | undefined { const f = vscode.workspace.workspaceFolders?.[0]; return f ? projectForFolder(this.projects, f.uri.fsPath) : undefined; } dispose(): void { this.stop(); this.changed.dispose(); this.events.dispose(); } }