UNPKG

framework

Version:

The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.

70 lines 4.42 kB
import { resolveRunEventsPath } from '../store/index.js'; import { contextEventsSource, resolveProjectPath } from './context.js'; import { tailRunEvents } from './events-tail.js'; import { forwardStream, streamChannel } from './stream-channel.js'; // The live event stream behind the new dashboard (#405): the selected project's run, // read straight from the same `.the-framework/events.jsonl` the daemon writes. Each new // JSONL line becomes one `channel.send(event)`, so the file watcher maps 1:1 onto a // Telefunc Channel — serialization, type validation, and reconnect come for free. Runs, // docs, and the project log come over the read-model RPCs (reads.telefunc.ts). /** * The events file to tail, or undefined when the project is unknown. With a `runId` this is * that run's own log inside its worktree (#749): since #736 a run appends there, not to the * project root, so streaming the project path would follow a file nothing writes to. Once the * run has ended and its worktree is gone, the run's archived `<id>.jsonl` is that log (#1472) — * tailing the project root there would stream a foreign run's journal. */ async function resolveEventsPath(projectId, runId) { const cwd = await resolveProjectPath(projectId); return cwd ? resolveRunEventsPath(cwd, runId) : undefined; } /** * `onEvents(projectId, runId?)` returns a Channel that streams one live run: the client * `.listen()`s for `FrameworkEvent`s and `.close()`s to unsubscribe, which stops the tail. * An unknown project yields a channel that closes immediately (mirrors the read model's * empty results rather than throwing at the client). * * Pass the `runId` to follow that run's own log (#749). A project has several concurrent * runs since #736, each writing inside its worktree, so the run id is what makes the feed * that run's rather than a mix — and without it the feed for a worktree run is empty. * Omitting it keeps the pre-#736 behavior of tailing the project root. * * Two sources, chosen by the mount. An in-memory stream on the context wins: the relay's own run * (#426), or a run the daemon is relaying from a device (#1067). Otherwise the on-disk log. The * daemon sets a source that answers only for relayed runs, so an ordinary local run yields undefined * here and falls through to tailing the log, exactly as before. * * Only the on-disk tail sends the {@link StreamSync} marker: the in-memory sources have no * replay boundary to report, so a reconnecting client falls back to a grace deadline before * swapping its feed (see use-live-events in the dashboard). */ export async function onEvents(projectId, runId) { const stream = contextEventsSource()?.(projectId, runId); if (stream) { // An in-memory run: replay + follow it, mirroring how serveSSE consumes it. return streamChannel(send => forwardStream(stream, send)); } // Everywhere else: tail the run's on-disk events.jsonl (undefined path -> closed channel). // The relocating tail, because the journal moves mid-subscription: teardown copies it into the // archive and removes the worktree, and a fixed-path tail whose fs.watch missed the final // appends went silent without the run's `end`. On the move it re-resolves (the archive, #1472) // and carries its offset, so the feed gets exactly the lines the move would have swallowed. const path = await resolveEventsPath(projectId, runId); // The one place a run-scoped feed must NOT relocate to: the project-root journal, which is // resolveRunEventsPath's last-resort fallback once a Delete has removed worktree and archive // alike — it is another run's feed (#1472). A deleted session's tab goes quiet instead. The // initial attach stays permissive: a fallback run (non-git project) legitimately lives there. const rootJournal = runId === undefined ? undefined : await resolveEventsPath(projectId, undefined); let initial = true; return streamChannel(send => path ? tailRunEvents(async () => { const next = await resolveEventsPath(projectId, runId); if (initial) { initial = false; return next; } return rootJournal !== undefined && next === rootJournal ? undefined : next; }, send, () => send({ kind: 'stream-sync' })) : undefined); } //# sourceMappingURL=events.telefunc.js.map