import type { LearningRefusal } from "@opengeni/sdk/learning-refusal"; import type { AllowanceExhaustedRefusal, HumanInputQuestion, HumanInputResponse, HumanInputSecretAnswerResult, MediaGenerationResult, ResourceRef, SessionStatus, Session, TimelineAnnotation, TimelineAnnotationSource, ToolAuthNeededPayload, ToolRef, ToolDisplayMetadata, } from "@opengeni/sdk"; /* ---------------------------------------------------------------------------- Timeline item types The projected, renderable shapes that `buildTimeline` folds a session's raw event log into. These are the data contract the renderer registry and every row component consume — the SINGLE SOURCE OF TRUTH used by both the live app and the component demo. -------------------------------------------------------------------------- */ /** The person who sent a message: their subject and the label frozen with it. */ export type MessageSender = { subjectId: string; /** What the deployment knew them as when the message was accepted, such as an email. */ label: string | null; }; export type TimelineAnnotationSourceDescriptor = Omit< TimelineAnnotationSource, "startOffset" | "endOffset" | "contextBefore" | "contextAfter" > & { text: string; }; export type UserMessageItem = { kind: "user-message"; id: string; /** * Renderer-only identity shared by a locally acknowledged message and the * durable event that replaces it. The canonical event id remains `id`; this * key only prevents the optimistic-to-durable handoff from remounting and * replaying the row entrance animation. */ reconciliationKey?: string | undefined; text: string; annotations?: TimelineAnnotation[] | undefined; annotationSource?: TimelineAnnotationSourceDescriptor | undefined; presentation?: { kind: "realtime_voice" | "realtime_voice_handoff"; /** Full bounded execution context sent with this visible voice request. */ context: string; }; /** Resources attached to this message (file uploads, repositories). */ resources: ResourceRef[]; /** Tools requested for the turn this message starts. */ tools: ToolRef[]; /** * The person who sent this message, as frozen when it was accepted. Absent * for messages from agents, services and schedules, and for older messages. */ sender?: MessageSender | undefined; occurredAt: string; /** Local-only delivery projection; absent for authoritative durable events. */ delivery?: | { state: "sending" | "queued" | "failed"; error?: string | undefined; onRetry?: (() => void) | undefined; onEdit?: (() => void) | undefined; onRemove?: (() => void) | undefined; } | undefined; }; export type HumanInputAnswerSummary = { questionId: string; label: string; prompt: string; values: string[]; /** A secret question's saved reference (never a value). */ secret?: HumanInputSecretAnswerResult | undefined; }; /** * One resolved structured question in the conversation. The live pending form * is rendered at the timeline tip by the host; after settlement this item keeps * both the agent's question and the human's answer visible as chat history. */ export type HumanInputItem = { kind: "human-input"; id: string; turnId: string | null; requestId: string; questions: HumanInputQuestion[]; response: HumanInputResponse; answers: HumanInputAnswerSummary[]; occurredAt: string; }; export type AgentMessageItem = { kind: "agent-message"; id: string; turnId: string | null; text: string; /** * Assistant channel: provider-declared, or `commentary` when the model asked * for tool work in the same response. Absent on legacy events and on * undeclared final messages. Known from the first delta for Responses models. */ phase?: "commentary" | "final_answer" | undefined; /** Still receiving deltas (no completed/turn-end seen yet). */ streaming: boolean; occurredAt: string; /** * When the first streamed text arrived, kept once completion moves * `occurredAt` to the completion time. Absent for a message that never * streamed. */ startedAt?: string | undefined; /** Available only after the canonical completed event lands. */ annotationSource?: TimelineAnnotationSourceDescriptor | undefined; }; export type ReasoningItem = { kind: "reasoning"; id: string; turnId: string | null; text: string; streaming: boolean; occurredAt: string; }; /** * Bounded delivery metadata carried by a projected tool-output event. The * canonical event remains unchanged; this fact only explains what the current * timeline surface could render and whether separately retained full evidence * exists. */ export type ToolCallTruncation = { truncated: true; surface: string; reason: string; omittedBytes: number | null; fullEvidence: { available: boolean; reason: string | null; }; }; /** * A tool output a step read delivered as a bounded preview: long strings keep * their head and tail (`structured`), or the output is the head and tail of * its text (`text`). The exact output is retained; `load` reads it, and the * row then re-renders with it. */ export type ToolOutputPreview = { shape: "structured" | "text"; originalBytes: number; previewBytes: number; /** `preview` until `load` is called; `error` when the exact read failed. */ state: "preview" | "loading" | "error"; /** Read the exact output. Absent when the timeline has no reader for it. */ load?: (() => void) | undefined; }; export type ToolCallItem = { kind: "tool-call"; id: string; turnId: string | null; callId: string | null; name: string; display?: ToolDisplayMetadata; arguments: unknown; output: unknown; truncation?: ToolCallTruncation | null; /** Set while `output` is a bounded preview of the exact output. */ outputPreview?: ToolOutputPreview | null | undefined; /** * The provider-native tool item (`agent.toolCall.created.payload.raw`). Carries * `type` (e.g. `apply_patch_call`, `computer_call`, `hosted_tool_call`) and the * tool-specific fields the per-tool renderers read (`operation`, `action`, * `providerData`, …). `undefined` for first-party MCP tools, which carry their * payload in `arguments`/`output` instead. */ raw: unknown; status: "running" | "complete" | "failed" | "cancelled"; occurredAt: string; /** Canonical textual projection of the settled tool output. */ annotationSource?: TimelineAnnotationSourceDescriptor | undefined; }; /** * An orchestration call against another session — the manager spawning or * messaging a worker. Rendered as a first-class "worker" row, not a generic * tool call. */ export type WorkerItem = { kind: "worker"; id: string; turnId: string | null; callId: string | null; action: "spawn" | "message"; /** The worker's initial message / the message sent to it, when parseable. */ prompt: string | null; /** * The title the manager gave a spawned worker (`session_create` `title`), * when present. Hosts can supply a live title for any session id; this is * the timeline's own fallback so a spawn names its agent even offline. */ title?: string | null | undefined; /** The target/spawned worker session id, when parseable from args/output. */ workerSessionId: string | null; /** Bounded structured failure retained from session_create/session_send_message. */ failure: { code: string; message: string } | null; status: "running" | "complete" | "failed" | "cancelled"; occurredAt: string; }; export type WorkerCompletionItem = { kind: "worker-completion"; id: string; turnId: string | null; occurredAt: string; childSessionId: string; childStatus: string; goalStatus: string | null; goalText: string | null; evidence: string | null; pausedReason: string | null; text: string; }; export type SandboxItem = { kind: "sandbox"; id: string; turnId: string | null; name: string; command: string | null; output: string; origin?: "created" | "restored" | "resumed" | null; status: "running" | "complete" | "failed" | "cancelled"; occurredAt: string; }; export type StartupPhase = | "queue" | "sandbox" | "rig" | "repository" | "files" | "tools" | "model_preparation" | "provider_first_byte"; /** A compact user-visible critical-path span reconstructed from durable events. */ export type StartupPhaseItem = { kind: "startup-phase"; id: string; turnId: string | null; phase: StartupPhase; /** Live presentation only: accepted work waiting for its first worker claim. */ dispatchWait?: Session["dispatchWait"]; /** Renderer-only elapsed anchor when acceptance preceded recorded startup spans. */ loadingStartedAt?: string | undefined; status: "running" | "complete" | "failed" | "cancelled"; startedAt: string; completedAt: string | null; durationMs: number | null; /** Sandbox origin and rig marker outcomes refine the settled label only. */ outcome: "created" | "restored" | "resumed" | "skipped" | null; blockedReason?: | "capture_in_progress" | "rotation_in_progress" | "provider_recovery_in_progress" | "compaction_required" | undefined; occurredAt: string; }; /** * A first-party workspace-memory write the agent made mid-turn — a `memory.saved` * (it committed a new preference / fact / procedure / decision / history) or a * `memory.corrected` (it updated or archived an existing one). A settled save is * ordinary progress, not an exceptional state, so it renders as a calm NEUTRAL * step on the rail (never accent/color). When the host app supplies an * `onMemoryClick` handler the row also deep-links to the record in its memory * pane; without one it is non-interactive rich content. */ export type KnowledgeItem = { kind: "knowledge"; id: string; turnId: string | null; status: "complete" | "failed"; outcome: "published" | "pending" | "rejected" | "archived" | "failed"; fileId?: string | undefined; filename?: string | undefined; entryId?: string | undefined; occurredAt: string; }; export type MemoryItem = { kind: "memory"; id: string; turnId: string | null; variant: "saved" | "corrected"; /** The memory's kind enum (`"preference" | "semantic" | …`); mapped to a human label at render. */ memoryKind: string; /** The memory text, ellipsized to ≤120 chars server-side. For a supersede this is the OLD text. */ preview: string; /** The save collapsed into an existing memory (no new row was written). Saved variant only. */ deduped?: boolean; /** The NEW text when a correction superseded the memory with a replacement; absent = updated-in-place or archived. */ replacementPreview?: string; /** * What a `memory.corrected` did: `"superseded"` (replaced by a new record, see * `replacementPreview`), `"updated"` (edited in place — the record lives on), or * `"archived"` (retired). Distinguishes updated-in-place from archived when there * is no replacement. Corrected variant only; read defensively (may be absent). */ action?: string; /** The saved / corrected memory's id — the deep-link target for a save. */ memoryId: string; /** The replacement memory's id when a correction produced one — the LIVE record the deep-link targets. */ replacementMemoryId?: string; occurredAt: string; }; export type FleetDecisionScoreItem = { candidateKey: string; eligible: boolean; rejectionReason: | "allocator_disabled" | "unavailable" | "cooling" | "quota_ceiling" | "overlay_isolation" | null; total: number; confidence: "unknown" | "low" | "medium" | "high"; }; /** * One production-vs-shadow placement explanation. Candidate keys are * event-local aliases only; no credential/account identity reaches this item. */ export type FleetDecisionItem = { kind: "fleet-decision"; id: string; turnId: string | null; policyVersion: "adaptive-shadow-v1"; actualOutcome: "selected" | "waiting" | "none"; actualCandidateKey: string | null; actualReason: | "lease_reused" | "pin" | "rotation" | "active" | "all_capped" | "allocator_disabled" | "none"; shadowOutcome: "selected" | "paced" | "none"; shadowCandidateKey: string | null; shadowReason: | "fenced_in_flight" | "fenced_candidate_missing" | "admission_paced" | "no_eligible_candidate" | "overlay_isolated_empty" | "best_score" | "affinity_best" | "hysteresis_hold"; comparison: "match" | "different_candidate" | "different_outcome" | "not_comparable_truncated"; confidence: "unknown" | "low" | "medium" | "high"; admissionOutcome: "admit" | "pace"; admissionReason: | "fenced_in_flight" | "pacing_disabled" | "capacity_unknown" | "capacity_available" | "work_conserving_borrow" | "manager_priority" | "standard_starvation_bound" | "capacity_saturated" | "emergency_fuse"; borrowedIdleCapacity: boolean; borrowedOverlayCapacity: boolean; strandedEligibleCount: number; candidateCount: number; truncatedCandidateCount: number; scoreRowsTruncatedCount: number; scores: FleetDecisionScoreItem[]; occurredAt: string; }; export type SessionStatusItem = { kind: "session-status"; id: string; status: SessionStatus; /** * The runtime refused to start the next turn (an admission block). Nothing * is asked of the person, so it reads as stuck, not "waiting on you". */ blocked?: true; /** Presentation-only evidence that this historical attention state resumed. */ resolvedAt?: string; occurredAt: string; }; export type GoalItem = { kind: "goal"; id: string; action: | "set" | "updated" | "completed" | "paused" | "resumed" | "cleared" | "held" | "continuation"; text: string | null; occurredAt: string; }; export type NoticeItem = { kind: "notice"; id: string; tone: "waiting" | "cancelled" | "failed" | "input"; text: string; /** An approval wait (`session.requiresAction`), independent of its display text. */ approval?: true; /** Presentation-only evidence that a historical approval wait resumed. */ resolvedAt?: string; /** * A model-capacity wait (no subscription capacity for the turn). Live, the * turn's work row carries it as `label` with `detail` as one quiet secondary * line; once `resolvedAt` is set it leaves no row, and its span is excluded * from the turn's worked duration. */ capacityWait?: { turnId: string | null; label: string; detail: string }; /** A preserved turn-end outcome, not a claim about current session state. */ recordedOutcome?: true; /** * Recorded agent wait only: delegated worker sessions that had not reported * back when the wait began. Best effort from the loaded history; absent when * none are known. */ waitingAgents?: number; /** Recorded agent wait only: when later input ended the wait. */ waitEndedAt?: string; /** Optional evidence kept inspectable without overwhelming the main rail. */ details?: { label: string; value: unknown }; action?: { label: string; url: string }; /** * A usage ceiling refused further work. `text` keeps the canonical sentence * for plain-text consumers; `MessageTimeline` renders this structured row * (customizable with `renderAllowanceExhausted` / `allowanceExhaustedLabels`). */ allowance?: AllowanceExhaustedRefusal; /** * The task's own usage limit stopped further model requests: a calm, * non-failure stop. Amounts are null when the marker carried none. * `MessageTimeline` renders it as a "Usage limit reached" row. */ sessionUsageLimit?: SessionUsageLimitStop; /** * A Skill, workspace instruction, or Knowledge change the agent tried to * save was refused. `text` keeps the plain sentence for plain-text * consumers; `MessageTimeline` renders it as a "Not saved" row with the * place where the change can still be made. */ learningRefusal?: LearningRefusal; occurredAt: string; }; /** What a timeline row knows about a task usage-limit stop. */ export type SessionUsageLimitStop = { rootSessionId: string | null; limitMicros: number | null; usedMicros: number | null; }; /** * First-class conversation-memory checkpoint. Stays outside collapsed turns so * auto-compaction cannot vanish behind a chevron. Chat bubbles are unchanged. */ export type ContextCompactionItem = { kind: "context-compaction"; id: string; turnId: string | null; phase: "started" | "compacted" | "skipped"; trigger: "auto" | "operator" | "proactive" | "overflow" | null; estimatedTokensBefore: number | null; estimatedTokensAfter: number | null; skipReason: string | null; /** * Present only for a `summarization_failed` skip where the provider * definitively rejected the compaction request. Content-free identifiers. */ providerRejection: ContextCompactionProviderRejection | null; /** * The same turn's recorded failure text, when that turn failed because this * compaction did. Diagnostics shown behind Details only. */ failureDetail?: string; /** Provider implementation id for debug disclosure only. */ implementation: string | null; occurredAt: string; }; export type ContextCompactionProviderRejection = { httpStatus: number; type: string | null; code: string | null; param: string | null; requestId: string | null; }; export type MachineInputMember = { id: string; kind: | "scheduled_occurrence" | "goal_continuation" | "background_command_result" | "session_wait_timeout" | "agent_message" | "agent_steer_instruction" | "child_terminal_result" | "media_generation_result" | "child_requires_action" | "child_requires_action_resolved" | "child_paused" | "child_waiting_capacity" | "child_progress"; classification: "success" | "failure" | "action_required" | "info"; sourceId: string; summary: string; result?: MediaGenerationResult | undefined; }; /** * One or more durable non-human inputs that joined the following agent turn. * This is communication, not a warning or a protocol-debug payload. */ export type MachineInputBatchItem = { kind: "machine-input-batch"; id: string; turnId: string | null; members: MachineInputMember[]; /** Readable-turn presentation keeps machine payload prose inside the disclosure. */ compact?: boolean; occurredAt: string; }; /** * A tool call hit a missing or lapsed connection. The broker reports that * condition as a tool error and the turn continues; reconnecting never resumes * or replays the original call. Carries the structured `tool.auth_needed` * payload so the renderer can draw a clean inline recovery affordance (provider * logo + one human line + a Connect/Reconnect button) and the app can start the * right flow (OAuth reconnect for the surviving connection, or credential * re-entry for an api-key one). The `reason` shapes the human copy but is never * shown raw. */ export type AuthNeededItem = { kind: "auth-needed"; id: string; turnId: string | null; /** The runtime surface that requested recovery, when the event is an MCP auth signal. */ serverId: string | null; /** Explicit recovery identity; never inferred from an opaque execution alias. */ canonicalServerId?: string | null; connectionSubjectScope?: "workspace" | "subject" | null; /** Durable event family that produced this notice. */ source?: "tool" | "credential" | "capability" | undefined; /** The connection's registrable domain, e.g. "linear.app". */ providerDomain: string; /** The lapsed connection to reconnect, when the row survived. */ connectionId: string | null; /** Host-owned bindings must never be routed into Opengeni's native reconnect flow. */ authoritySource?: ToolAuthNeededPayload["authoritySource"] | null | undefined; reason: ToolAuthNeededPayload["reason"] | null; /** Scopes the provider now needs; may inform the copy, never shown as a raw label. */ scopes: string[]; /** The OAuth `resource` (RFC 8707) the reconnect should target, when supplied. */ resource: string | null; /** The tool whose call triggered the reauth, for context. */ toolName: string | null; /** A pre-minted authorization URL, when the broker already produced one. */ authorizationUrl: string | null; /** Agent-selected catalog recommendation. The host still owns authorization. */ capability?: NonNullable | null | undefined; /** Human-reviewed custom integration proposal. Never treated as catalog authority. */ setupRequest?: ToolAuthNeededPayload["setupRequest"] | null | undefined; /** The Connected Machine the agent asked for, when the card names one. */ requestedSandboxId?: string | null | undefined; /** `chrome`: the Connected Machine card is for the person's own Chrome. */ purpose?: "chrome" | undefined; /** * A later Connected Machine card in this chat asks for the same thing, so * this one is shown as a short note instead of a second full card. */ askedAgainLater?: boolean | undefined; occurredAt: string; }; export type TurnOutcome = "complete" | "failed" | "cancelled"; export type TurnEndItem = { kind: "turn-end"; /** A durable Retry reopened this failed logical turn; its failure is history. */ resumedAt?: string; /** Keep an existing answer visible when a recorded input wait ends without final output. */ preserveWaitResponse?: true; /** The turn handed over to a Steer at a safe boundary instead of finishing. */ handedOver?: true; id: string; turnId: string | null; outcome: TurnOutcome; failureText: string | null; occurredAt: string; }; export type TimelineItem = ( | UserMessageItem | HumanInputItem | AgentMessageItem | ReasoningItem | ToolCallItem | WorkerItem | WorkerCompletionItem | SandboxItem | StartupPhaseItem | SessionStatusItem | GoalItem | NoticeItem | ContextCompactionItem | MachineInputBatchItem | AuthNeededItem | MemoryItem | KnowledgeItem | FleetDecisionItem | TurnEndItem | TurnStepsItem ) & { /** Durable identities, independent of renderer/reconciliation keys. */ sourceEvents?: readonly { eventId: string; sequence: number }[] | undefined; }; /** * Load state of a turn's steps (tool calls, their outputs, streamed reasoning * and command output). Conversation-first history reads only a turn's * conversation; its steps load when a reader opens the turn. * - `idle`: none loaded yet. * - `partial`: the newest steps are loaded; earlier ones remain. * - `complete`: every step is loaded. */ export type TurnStepsState = "idle" | "loading" | "partial" | "complete" | "error"; /** * Marks a turn whose steps are not all in the browser. It is not a step: the * readable grouping attaches it to the turn's work row (`work.steps`), which * keeps the row expandable and lets the opened row load the steps. */ export type TurnStepsItem = { kind: "turn-steps"; id: string; turnId: string; /** The turn's start, so a turn whose work is all hidden keeps its duration. */ occurredAt: string; state: TurnStepsState; /** Load the newest steps, or the next earlier page once some are loaded. */ load: () => void; /** * Keep this turn's loaded steps while it is open: the browser never evicts * steps a reader is looking at. Returns the release function. */ retain: () => () => void; }; /** * Activity items cluster between chat messages (reasoning, tools, workers, * sandbox, memory). Assistant prose is rendered separately. */ export type ActivityItem = | ReasoningItem | ToolCallItem | WorkerItem | SandboxItem | StartupPhaseItem | MemoryItem | KnowledgeItem | FleetDecisionItem | AgentMessageItem; export type TimelineGroup = | { kind: "item"; item: TimelineItem } | { kind: "activity"; id: string; items: ActivityItem[]; outcome?: TurnOutcome; failureText?: string; /** One turn's chronological work history, including progress prose after settlement. */ work?: { startedAt: string; endedAt?: string; responseStartedAt?: string; /** * A live wait. `untimed` marks a capacity wait, whose row shows no * running clock: that span is not work and ends on a quota reset or * a person's choice, not after a predictable time. */ waiting?: { label: string; since: string; detail?: string; untimed?: true; /** The wait needs the reader. */ waitingOn?: "you"; }; /** * Settled capacity-wait time inside this work span. It is not work: * the worked duration and the live working clock exclude it. */ pausedMs?: number; details: TimelineGroup[]; /** * While the turn is live, its progress notes stay readable above the * work row and are also listed in `details`. These are their item ids, * so an expanded work disclosure can fold the outside copies away. */ liveNoteIds?: string[]; /** * Set when this row folds several quiet wake-work-wait cycles (routine * input, work without a visible reply, a finished wait) into one row. * `summary` is the latest wait reason, shown under the collapsed row. */ cycles?: { count: number; summary?: string }; /** Steps of this turn that are not loaded yet (see TurnStepsItem). */ steps?: TurnStepsItem; }; } | { kind: "turn"; id: string; outcome: TurnOutcome; failureText?: string; startedAt: string; endedAt: string; groups: TimelineGroup[]; /** Adjacent compaction landmark just before this fold (secondary chip facet). */ contextCompactionCount?: number; };