eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
423 lines (422 loc) • 18.6 kB
TypeScript
import { type AgentInfoResult, type ConnectionAuthorizationOutcome, type InputRequest, Client, ClientSession } from "#client/index.js";
import type { EveCliSetupStepEvent, EveCliSetupTerminalEvent } from "#cli/telemetry/index.js";
import type { OnboardingScreenEvent } from "./setup-commands.js";
import { inspectApplication } from "#services/inspect-application.js";
import { type SubagentView } from "./subagent-pump.js";
export type { SubagentRun, SubagentStepUpdate, SubagentToolUpdate, SubagentView, } from "./subagent-pump.js";
import { type DevBootProgressReporter } from "#internal/dev-boot-progress.js";
import { type PromptCommand, type PromptCommandSpec } from "./prompt-commands.js";
import { type RemoteConnectionController, type RemoteConnectionControllerOptions, type RemoteConnectionSnapshot } from "./remote-connection.js";
import type { DevelopmentCredentialGate } from "#services/dev-client/credential-gate.js";
import { type BootDetection } from "./setup-issues.js";
import type { RegistryPlannerContext } from "#setup/flows/registry.js";
import type { PlannerNavigation } from "#setup/prompter.js";
import type { SetupFlowRenderer } from "./setup-flow.js";
import type { TraceViewerRenderer } from "./traces/trace-viewer-session.js";
import type { RemoteDevelopmentTarget } from "./target.js";
import type { AssistantResponseStatsMode, LogDisplayMode, TerminalPartDisplayMode, TuiDisplayOptions } from "./types.js";
import { type TerminalInput, type TerminalOutput } from "./terminal-renderer.js";
import { type VercelStatusEffect, type VercelStatusSnapshot } from "./vercel-status.js";
import { type McpConnectionProbe } from "./mcp-connection-status.js";
import type { detectProjectIdentity } from "#setup/project-resolution.js";
import { getVercelAuthStatus } from "#setup/vercel-project.js";
import type { DevDiagnostics } from "../diagnostics.js";
import type { CommandLifecycle } from "../../shutdown.js";
export { parsePromptCommand, type PromptCommand } from "./prompt-commands.js";
export type AgentTUIStreamResult = {
events: AsyncIterable<AgentTUIStreamEvent> | ReadableStream<AgentTUIStreamEvent>;
abort?: () => void;
/**
* Requests cooperative server-side cancellation of the streaming turn
* (`/cancel`, Esc, or Ctrl+C; the keys steer when a message is queued). Unlike
* {@link abort} — which drops the client stream and forces a fresh session —
* the server settles the turn as `turn.cancelled` → `session.waiting`, so
* the stream reaches its boundary normally and the session keeps its context.
* Best-effort and idempotent; scoped to the turn the user observed when its id is known.
*/
cancel?: () => void;
turnState?: AgentTUITurnState;
};
export type AgentTUIStreamUsage = {
inputTokens?: number;
outputTokens?: number;
};
export type AgentTUIStreamEvent = {
type: "step-start";
} | {
type: "step-finish";
usage?: AgentTUIStreamUsage;
} | {
type: "assistant-delta";
id: string;
delta: string;
} | {
type: "assistant-complete";
id: string;
text?: string | null;
} | {
type: "reasoning-delta";
id: string;
delta: string;
} | {
type: "reasoning-complete";
id: string;
} | {
type: "tool-call-preparing";
toolCallId: string;
toolName: string;
} | {
type: "tool-call";
toolCallId: string;
toolName: string;
input: unknown;
} | {
type: "tool-approval-request";
approvalId: string;
toolCallId: string;
} | {
type: "tool-result";
toolCallId: string;
output: unknown;
} | {
type: "tool-error";
toolCallId: string;
errorText: string;
} | {
type: "tool-rejected";
toolCallId: string;
reason: string;
} | {
type: "error";
errorText: string;
hint?: string;
detail?: string;
} | {
type: "turn-cancelled";
} | {
type: "finish";
usage?: AgentTUIStreamUsage;
};
export type AgentTUITurnState = {
aborted?: boolean;
boundaryEvent?: "session.completed" | "session.failed" | "session.waiting";
pendingApprovals: AgentTUIToolApprovalRequest[];
pendingQuestions: InputRequest[];
sawSessionFailure: boolean;
/** Id of the streaming turn, once `turn.started` names it. Scopes cancels. */
turnId?: string;
/** True while a cooperative-cancel request loop is running for this turn. */
cancelInFlight?: boolean;
};
export type AgentTUISessionOptions = {
title?: string;
/**
* Text to seed the editable prompt buffer with before the user types.
* Set by the runner for the first prompt when `eve dev --input` is used.
*/
initialDraft?: string;
submittedPrompt?: string;
continueSession?: boolean;
tools?: TerminalPartDisplayMode;
reasoning?: TerminalPartDisplayMode;
subagents?: TerminalPartDisplayMode;
connectionAuth?: TerminalPartDisplayMode;
assistantResponseStats?: AssistantResponseStatsMode;
contextSize?: number;
};
export type AgentTUIToolApprovalRequest = {
approvalId: string;
toolCallId: string;
toolName: string;
title?: string;
input: unknown;
};
export type AgentTUIToolApprovalResponse = {
approved: boolean;
reason?: string;
};
export type AgentTUIInputOption = {
id: string;
label: string;
description?: string;
style?: "primary" | "danger" | "default";
};
export type AgentTUIInputQuestion = {
requestId: string;
prompt: string;
display: "select" | "text";
options?: ReadonlyArray<AgentTUIInputOption>;
allowFreeform?: boolean;
};
export type AgentTUIInputQuestionResponse = {
optionId?: string;
text?: string;
};
export type AgentTUIAgentHeader = {
name: string;
serverUrl: string;
info?: AgentInfoResult;
/** Message-of-the-day line shown below the startup card (local sessions only). */
tip?: string;
};
export type AgentTUIRenderer = {
/**
* Commits the startup card to the transcript before the first prompt and
* refreshes it after local dev artifact changes. Optional — renderers
* without a header simply skip it.
*/
renderAgentHeader?(header: AgentTUIAgentHeader): void;
/**
* Commits a single informational line to the transcript. Used for session
* recovery and slash-command results. Optional.
*/
renderNotice?(text: string): void;
/**
* Commits the session boundary (`┌── Session restarted, clear context.`)
* when a dead session is replaced mid-conversation. Optional; renderers
* without it get the plain notice.
*/
renderSessionBoundary?(): void;
/**
* Commits one development sandbox lifecycle line to the transcript.
* Optional so non-terminal renderers can ignore local prewarm progress.
*/
renderSandboxLog?(text: string): void;
renderSetupWarning?(text: string): void;
/** Clears the setup attention line once its issue is resolved. */
clearSetupWarning?(): void;
/** Commits the startup `/vc:login` invocation to the transcript. */
renderCommandInvocation?(text: string, status?: "failed"): void;
renderCommandResult?(text: string, tone?: "success" | "error"): void;
readonly setupFlow?: SetupFlowRenderer;
/**
* The renderer's full-screen local trace viewer, opened by `/traces`.
* The returned promise resolves when the user closes the viewer.
*/
readonly traceViewer?: TraceViewerRenderer;
readPrompt?(options?: AgentTUISessionOptions): Promise<string | undefined>;
/**
* Consumes the next prompt produced by mid-turn input: the Esc-popped
* steering message when one is staged, otherwise every message queued
* during the turn coalesced into one. The runner calls this at a clean
* turn boundary and submits the result as the next turn without reading
* the prompt. Optional — renderers without mid-turn input never queue.
*/
takeQueuedPrompt?(): string | undefined;
/**
* Reports the server session id backing the conversation — pushed by the
* runner once a send is accepted, and overwritten when a later session's
* turn is accepted. Deliberately sticky across `/reset` and interrupt
* recovery: the terminal renderer echoes the LAST session this TUI talked
* to in the parting line on exit, so an interrupted conversation (whose
* replacement session never ran a turn) can still be found again
* (`eve logs`, the session store). Optional.
*/
setSessionId?(sessionId: string): void;
readToolApproval?(request: AgentTUIToolApprovalRequest, options?: AgentTUISessionOptions): Promise<AgentTUIToolApprovalResponse>;
readInputQuestion?(question: AgentTUIInputQuestion, options?: AgentTUISessionOptions): Promise<AgentTUIInputQuestionResponse | undefined>;
renderStream(result: AgentTUIStreamResult, options?: AgentTUISessionOptions): Promise<void>;
/**
* Renders a server-initiated turn while `readPrompt` still owns input.
* Unlike `renderStream`, this must not replace the active key consumer or
* clear the user's draft.
*/
renderIdleStream?(result: AgentTUIStreamResult, options?: AgentTUISessionOptions): Promise<void>;
/**
* The renderer's whole subagent surface — sections, nested steps and
* tools, ghost sweeps, completion. One optional capability with required
* members: a renderer either has a subagent view or it doesn't, and a
* type-legal partial implementation (which would ghost placeholders or
* duplicate parent tool rows) cannot exist.
*/
readonly subagents?: SubagentView;
/**
* Out-of-band update for one MCP connection authorization lifecycle.
* Called by the runner as `authorization.*` events arrive.
* The renderer renders this as a persistent body section per
* connection that transitions through `required` → `pending` →
* one of the terminal `ConnectionAuthorizationOutcome` states.
*/
upsertConnectionAuth?(update: ConnectionAuthUpdate): void;
/**
* Sets the number of connections currently awaiting an OAuth
* callback. The renderer overrides its bottom status bar with a
* "waiting for connection authorization" hint while this is > 0,
* so the user understands the agent is parked, not hung.
*/
setConnectionAuthPendingCount?(count: number): void;
/**
* The log display mode currently in effect. Paired with
* {@link setLogDisplayMode}; both are absent on renderers that do not
* capture process output.
*/
logDisplayMode?(): LogDisplayMode;
/**
* Switches which captured log sources (stdout/stderr) the transcript
* shows. Captured output is buffered regardless of mode, so a change
* applies retroactively: hiding removes already-rendered log lines from
* the transcript and showing restores buffered ones at their original
* positions. Used by the `/loglevel` command.
*/
setLogDisplayMode?(mode: LogDisplayMode): void;
/**
* Commits any delayed local dev build errors immediately before dispatching
* a user prompt. Renderers without process-log capture ignore it.
*/
flushDelayedDevBuildErrors?(): void;
/**
* Sets the workspace-scoped Vercel segment of the persistent bottom
* status line. Pushed by the runner at startup and after Vercel-related
* setup outcomes. Renderers without a status line ignore it.
*/
setVercelStatus?(status: VercelStatusSnapshot): void;
/** Sets the remote deployment badge and its current connection/authentication state. */
setRemoteConnectionStatus?(status: RemoteConnectionSnapshot): void;
/**
* Clears the rendered transcript and resets per-conversation display
* state, leaving the UI interactive on a fresh screen. Used by the
* `/reset` command to start a new session with a clean slate.
*/
reset?(): void;
/**
* Tears down interactive mode and restores the terminal when the runner's
* lifecycle ends.
*/
shutdown?(): void;
/** Suspends an idle prompt so a server-initiated HITL request can own input. */
suspendPromptForInput?(): void;
requestInterrupt?(): void;
exitRequested?(): boolean;
};
export interface PromptCommandHandlerContext {
readonly renderer: AgentTUIRenderer;
readonly title: string;
/** Provider entry authorized by confirmed boot-time model-access evidence. */
readonly initialModelStep?: "provider";
readonly registryPlannerContext?: RegistryPlannerContext;
readonly onOnboardingScreen?: (input: OnboardingScreenEvent) => void;
/** Overrides the standalone command title inside a composed setup journey. */
readonly setupFlowTitle?: string;
/** Progress owned by an enclosing journey while this command runs. */
readonly setupFlowNavigation?: PlannerNavigation;
/** Live ChatGPT identity shown only inside model configuration UI. */
readonly chatGptAccountLabel?: string;
/**
* Leaves the current setup panel mounted for the next automatic onboarding
* command. The runner closes it if no next command can proceed.
*/
readonly keepSetupFlowOpen?: true;
readonly remoteConnection?: RemoteConnectionController;
readonly withExclusiveTerminal?: <T>(task: () => Promise<T>) => Promise<T>;
readonly disabledConnectionReasons?: Readonly<Record<string, string>>;
}
/** What one handled slash command leaves behind for the runner to apply. */
export interface PromptCommandOutcome {
/** Outcome line rendered under the echoed command; absent renders nothing. */
message?: string;
/** Promotes an outcome to a top-level status. */
tone?: "success" | "error";
/** Post-command work after setup settles. */
effect?: VercelStatusEffect | {
kind: "model-access-changed";
};
cancelled?: true;
}
export interface PromptCommandHandler {
handle(command: Extract<PromptCommand, {
type: "extension";
}>, context: PromptCommandHandlerContext): Promise<PromptCommandOutcome | undefined>;
}
type TuiStartup = {
readonly headerTip: string;
finish(): string;
};
export type EveTUIRunnerOptions = TuiDisplayOptions & {
session?: ClientSession;
/** Production TUI probe injected by the launcher; omitted in hermetic runners. */
probeMcpConnection?: McpConnectionProbe;
/**
* Optional client used to attach to child sessions for live subagent
* stream observation. When omitted, the TUI still shows the subagent
* section but cannot surface the subagent's reasoning / response /
* intermediate events — only the parent-stream `called` and
* `completed` transitions.
*/
client?: Client;
renderer?: AgentTUIRenderer;
screen?: TerminalOutput;
userInput?: TerminalInput;
/**
* Formats an error thrown while dispatching a turn (the initial
* `session.send()` POST — e.g. a transport failure or a Vercel
* Deployment Protection challenge) into the text rendered in the
* inline error region. Defaults to the error's message. Callers that
* know about transport-specific challenges (the `eve dev` glue) inject
* a richer formatter here.
*/
formatTransportError?: (error: unknown) => string;
/**
* Local `eve dev` server URL. When present, normal prompts refresh the
* runtime artifacts after HMR so the next prompt uses the latest authored
* artifacts while retaining its logical session.
*/
serverUrl?: string;
/** Absolute local application root; omitted for remote `--url` sessions. */
appRoot?: string;
/** Seeds the editable prompt buffer for the first prompt. */
initialInput?: string;
/** Explicit fresh-agent onboarding handoff from `eve init`. */
onboard?: boolean;
/** Reports timestamped steps and terminal result for fresh-agent onboarding. */
onOnboardingStep?: (input: EveCliSetupStepEvent) => void;
onOnboardingTerminal?: (input: EveCliSetupTerminalEvent) => void;
/** Handles non-core slash commands without adding feature branches to the runner. */
promptCommandHandler?: PromptCommandHandler;
/** Commands shown in discovery for this local or remote session. */
availablePromptCommands?: readonly PromptCommandSpec[];
/** Gives setup subprocesses exclusive terminal and development-host ownership. */
withExclusiveTerminal?: <T>(task: () => Promise<T>) => Promise<T>;
/** Remote target and mutable OIDC token source, when connected through `--url`. */
remote?: {
readonly target: RemoteDevelopmentTarget;
readonly credentials: DevelopmentCredentialGate;
readonly resolveOidcToken: NonNullable<RemoteConnectionControllerOptions["resolveOidcToken"]>;
readonly resolveDeployment: NonNullable<RemoteConnectionControllerOptions["resolveDeployment"]>;
};
/** Boot-time installation-state checks; defaults to the built-ins. */
bootDetections?: readonly BootDetection[];
/** Test seam for the status line's Vercel link probe; defaults to the real one. */
detectProjectIdentity?: typeof detectProjectIdentity;
/** Test seam for `/info`; defaults to the filesystem application inspector. */
inspectApplication?: typeof inspectApplication;
/** Test seam for the off-critical-path boot login probe; defaults to the real one. */
getVercelAuthStatus?: typeof getVercelAuthStatus;
/** Reports phases from this runner's initial local-dev connection. */
onBootProgress?: DevBootProgressReporter;
/** Parent-owned diagnostics recorder; omitted for remote and test renderers. */
diagnostics?: DevDiagnostics;
lifecycle?: CommandLifecycle;
/** Editing-only startup state retained until the final agent header is ready to paint. */
startup?: TuiStartup;
};
export declare class EveTUIRunner {
#private;
constructor(options: EveTUIRunnerOptions);
run(): Promise<void>;
}
/** Returns the registry address carried by a self-modification terminal handoff. */
export declare function registryHandoffAddress(toolName: string | undefined, output: unknown): string | undefined;
export type ConnectionAuthChallenge = {
url?: string;
userCode?: string;
expiresAt?: string;
instructions?: string;
};
export type ConnectionAuthState = "required" | "pending" | ConnectionAuthorizationOutcome;
export type ConnectionAuthUpdate = {
name: string;
description: string;
state: ConnectionAuthState;
challenge?: ConnectionAuthChallenge;
reason?: string;
};