persistent-ai

@persistent-ai/fireflow-console (0.28.0)

Published 2026-08-17 13:00:33 +00:00 by ak

Installation

@persistent-ai:registry=
npm install @persistent-ai/fireflow-console@0.28.0
"@persistent-ai/fireflow-console": "0.28.0"

About this package

@persistent-ai/fireflow-console

The FireFlow Console Protocol — RFC 077.

A terminal session described as messages, not as an implementation. The test this package has to pass is that someone outside this repository can write a working client, or a working server, from it — without reading TerminalWorkflows.ts.

The constraint that defines it

Zero runtime dependencies beyond zod. No DBOS, no Redis, no Postgres, no React, and no dependency on fireflow-bash, fireflow-nodes or fireflow-types.

That is the point rather than an aesthetic: it is what makes this describable as a protocol instead of an extract of our server. Two places where it bit:

shape producer how it is declared here
ConsoleProgressPayload BashRunEvent in fireflow-bash { type: string, atMs: number }, structurally. Every BashRunEvent variant assigns to it; a consumer wanting the full union narrows on type, which it would have done anyway
AgUIEventLike AgUIEvent in fireflow-nodes Record<string, unknown> & { type: string, timestamp?: number } — the bare, envelope-free wire shape, declared rather than imported

Modules

session.ts identity, coordinate, SessionState, the ff://workspace@ref/path parse rule. The base module — imports nothing else in the package
events.ts the event union a client receives, and NoticeCode
commands.ts what a client sends, what comes back, and the session lifecycle
intents.ts the IntentFrame union — the frames a followed execution streams back
cursor.ts entry ids, ordering, resume, and the eviction marker
format.ts the three output projections, and the normative frame renderers
capabilities.ts what a client can read about the server it reached

session.ts declares intentKindSchema, which the ff grammar attaches to each command and the bash segmenter reads to decide whether an invocation needs workflow-level DBOS primitives. It lives in the base module so the dependency graph stays a DAG: everything else imports it, and it imports nothing.

What RFC 077 §18.3 left open, and what was decided

Gap 1 — IntentFrame enumerated. Three variants: exec-run (a followed execution's header and footer lines), exec-node (its per-node lines), and agui (one AG-UI event, per decision D2). The union is open — an unrecognised type parses as UnknownIntentFrame — but the fallback explicitly refuses the type names this version knows, so a malformed exec-run is a loud error rather than a silently unrenderable "unknown" frame. Run and node statuses are separate vocabularies: a run completed or did not (completed | failed | cancelled | timed-out), a node produced a value, threw, or was never reached (ok | error | skipped).

Gap 1 is closed for one producer. Only a flow execution has frames defined; nothing describes what a VFS watch or an MCP call would send, so the package cannot yet tell a client what to expect from those. intents.ts says so at the point where a reader would otherwise assume more.

Gap 3 — responses and session lifecycle. Extracted, not invented: every shape is lifted from the terminal router 076 shipped. fork is the exception — 076 §13 lists it and no server implements it, so the shape is specified and a server advertises sessions:fork only when it has it.

Gap 4 — cursor gaps. Run entries keep {seq + 1}-{index} unchanged. Entries that belong to the session rather than to a run — session-state — take {head + 1}-{1000000 + n}, a reserved band of the index component anchored to the run in progress or the last one to finish. Eviction is marked by a notice with code: 'transcript_gap', synthesized at read time; a notice rather than a new event kind because §15 says a conforming client drops an unknown kind, which would reproduce the exact silence the marker exists to break.

Two rules that point in opposite directions

Shapes a server sends are passthrough; shapes a client sends are strict. A v1 client parsing a v2 event or frame keeps the fields it does not understand, because §10 specifies --format=json as "full structure, stable keys" and a stripping parse would emit a lossy object into someone's jq. A v1 server parsing a client request strips, because ConsoleCommand must drop a client-supplied seq — otherwise a stale tab chooses where in the chain it lands. Input is narrowed, output is preserved.

Chunking is a protocol rule, so chunkText is in this package. text.slice(at, at + 32768) cuts UTF-16 code units, so an emoji straddling the boundary becomes two lone surrogates and the first serialisation to UTF-8 replaces both with U+FFFD — permanently, in the durable transcript. Every producer calls the same function rather than reimplementing the same bug.

Gap 2 — a handshake and protocol versioning — is deliberately out of scope and must not be built. There is exactly one client, shipped from this repository and deployed alongside the server, so a protocol bump is a coordinated deploy and a negotiation has nothing to negotiate with. ConsoleCapabilities already lets a client read what it reached, which is the half that pays for itself now. Fixing the other half before a second implementation exists to constrain it would be guessing at a shape.

Also not here

  • A wire transport. tRPC over WebSocket carries this today; SSE, a raw socket or an MCP tool would carry it equally well.
  • A terminal emulator protocol. No TTY, no pty, no ANSI negotiation, no window-size escapes, no job-control signals.
  • TerminalShellState and the prelude/epilogue capture. Those are facts about just-bash, so they stay in fireflow-bash/src/session. The split is by ownership, not by convenience.
  • Command-surface projections beyond frames. ff flow show, ff ps and the rest project their own structures and belong to RFC 080.

Dependencies

Dependencies

ID Version
zod ^3.25.76

Development Dependencies

ID Version
@persistent-ai/typescript-config 0.28.0
@types/node ^25.5.0
vitest ^4.1.0
Details
npm
2026-08-17 13:00:33 +00:00
1
BUSL-1.1
88 KiB
Assets (1)
Versions (5) View all
0.29.4 2026-08-18
0.29.1 2026-08-18
0.29.0 2026-08-18
0.28.0 2026-08-17
0.26.2 2026-08-10