/** * Lifecycle events — PROTOCOL §9.1. * * `hello` (first on connect), `sync` (after agent.attach / on reconnect), `status` * (agent status changes). All are server→client events and never carry a correlation `id` * (§5.1). Reuses AgentStatus, Message, PermissionRequest, UiRequest — no duplication. */ import { Type, type Static } from '@sinclair/typebox'; import { AgentStatusSchema } from '@/contracts/agent-info'; import { MessageSchema } from '@/types/message'; import { PermissionRequestSchema, UiRequestSchema, } from '@/contracts/event'; /** The protocol version carried by `hello` (single source of truth, §7). */ export const PROTOCOL_VERSION = '0.1.0'; /** `hello` — emitted immediately on connect (§9.1). Client checks the version (§7). */ export const HelloSchema = Type.Object({ type: Type.Literal('hello'), protocol_version: Type.Literal(PROTOCOL_VERSION), server_version: Type.String(), capabilities: Type.Array(Type.String()), }); /** `sync` — agent bootstrap state, sent after `agent.attach` or direct `hello` (§9.1, §6.4). */ export const SyncSchema = Type.Object({ type: Type.Literal('sync'), /** Present on daemon connections; direct connections have one implicit agent (§5.2). */ agent_id: Type.Optional(Type.String()), status: AgentStatusSchema, /** The agent's authoritative active mode at the time of the snapshot. */ mode: Type.String(), /** * The attached runtime's secret-free capabilities (saga vr7kgx.6). Current servers always * emit it; optionality lets an older/malformed runtime degrade to no controls rather than * forcing the browser to infer support from the protocol version (§7). Projected from the * child runtime's `hello.capabilities` by the daemon; on a direct connection it mirrors the * child's own hello. The top-level daemon `hello` remains transport-scoped and does NOT * advertise per-runtime model support globally. */ capabilities: Type.Optional(Type.Array(Type.String())), /** * The attached runtime's authoritative generation epoch (saga vr7kgx.6 hardening). The * daemon increments it on every same-`agent_id` child-runtime replacement and includes * it in every daemon-authored `sync` (attach + post-replacement re-sync). A browser uses * it to fence stale catalogs/responses emitted by a superseded generation so an old * stream's late events cannot mutate current-generation state. Optional + additive: an * older/direct-connection `sync` omits it and a client treats absence as a stable * baseline (no fencing needed on a single-generation direct connection). */ runtime_epoch: Type.Optional(Type.Integer({ minimum: 0 })), live_window: Type.Object({ messages: Type.Array(MessageSchema), compaction_index: Type.Optional(Type.Integer()), }), pending_requests: Type.Array(Type.Union([PermissionRequestSchema, UiRequestSchema])), }); /** `status` — agent status change (§9.1). */ export const StatusEventSchema = Type.Object({ type: Type.Literal('status'), agent_id: Type.Optional(Type.String()), status: AgentStatusSchema, detail: Type.Optional(Type.String()), }); export const LifecycleEventSchema = Type.Union([ HelloSchema, SyncSchema, StatusEventSchema, ]); export type Hello = Static; export type Sync = Static; export type StatusEvent = Static; export type LifecycleEvent = Static;