import type { AgentMessage } from '@earendil-works/pi-agent-core'; /** Test seams for prompt-scoped failures before session_start. The one-shot * variant records its first failure per node so the next cycling retry can * reach boot confirmation. */ export declare const FAIL_BEFORE_SESSION_START = "__FAIL_BEFORE_SESSION_START__"; export declare const FAIL_BEFORE_SESSION_START_ONCE = "__FAIL_BEFORE_SESSION_START_ONCE__"; /** Text marker that makes `session.prompt()` REJECT outright (no agent_start, * no turn) — the shape a broker whose model resolved but whose provider has * no installed credential produces ("No API key found for "). */ export declare const REJECT_PROMPT_CALL = "__REJECT_PROMPT_CALL__"; export declare class SessionManager { readonly sessionId: string; readonly sessionFile: string; readonly resumed: boolean; private constructor(); /** Fresh start: a brand-new session whose .jsonl lives under the node dir * (created empty if missing, like fake-pi-host). */ static create(_cwd: string): SessionManager; /** Spawn-time fork: create the fork's distinct fake session. The focused * broker tests do not replay source history, but they must reach the same * post-fork model/session construction path as the real engine. */ static forkFrom(_source: string, cwd: string): SessionManager; /** Resume: round-trip the .jsonl PATH the revive passed via `--session`. */ static open(path: string): SessionManager; getSessionId(): string; getSessionFile(): string; /** The directory containing the session file — the real SessionManager's * getSessionDir(). The broker prewarms the session-list cache with it at boot * (broker.ts runBroker), so the fixture must expose it or every broker boot * crashes before it binds view.sock. */ getSessionDir(): string; private leafId; /** Cycling support (crtr-cycle marker) — mirrors the real SDK's getLeafId/ * resetLeaf/appendCustomEntry just enough that `buildBrokerSession`'s cycling * branch (broker.ts) doesn't throw. The fake has no real cross-boot tree at * this point (FakeSession builds a fresh SessionTree every boot regardless), * so these are recording stubs, not a faithful branch model — * cycleAwareMessages already degrades to single-cycle passthrough for a * session manager without getEntries/getBranch (never implemented on the * fake), so multi-cycle REPLAY is out of scope for the fixture; only "does * not crash" is in scope. */ getLeafId(): string | null; resetLeaf(): void; appendCustomEntry(_customType: string, _data?: unknown): string; } type Handler = (ev: unknown, ctx: unknown) => void | Promise; interface BindExtensionsOpts { uiContext: BrokerUiContext; mode: string; shutdownHandler?: () => void; } /** The slice of the broker's ExtensionUIContext we exercise: the blocking dialog * (scenario 7) plus the three fire-and-forget display setters whose LAST VALUE * the broker retains and ships in `welcome.snapshot.display`. */ interface BrokerUiContext { confirm?: (title: string, message: string, opts?: { signal?: AbortSignal; timeout?: number; }) => Promise; setStatus?: (key: string, text?: string) => void; setWidget?: (key: string, lines?: string[], opts?: { placement?: 'aboveEditor' | 'belowEditor'; }) => void; setTitle?: (title: string) => void; } /** The slice of a loaded SDK extension we drive: its path + the handler map the * SDK's createExtensionAPI populated as the extension called pi.on(event, …) * during the loader's reload(). */ interface LoadedExtension { path: string; handlers: Map; } /** The shared extension runtime the SDK's loader built (createExtensionRuntime). * Its action methods start as `notInitialized` throwers; the REAL session's * bindCore swaps them for working impls bound to the live session — we replicate * the minimal slice (sendUserMessage/sendMessage/setSessionName) so the already- * registered handlers (e.g. the inbox-watcher) deliver instead of throwing. */ interface ExtRuntime { assertActive: () => void; sendUserMessage: (content: string, options?: { deliverAs?: string; }) => void; sendMessage: (message: unknown, options?: { deliverAs?: string; }) => void; setSessionName: (name: string) => void; [k: string]: unknown; } /** The minimal slice of the REAL DefaultResourceLoader the broker hands to * createAgentSession: getExtensions() returns the already-loaded (jiti) canvas * extensions + their shared runtime. */ interface ResourceLoader { getExtensions(): { extensions: LoadedExtension[]; errors?: unknown[]; runtime: ExtRuntime; }; } interface TreeEntry { type: 'message'; id: string; parentId: string | null; timestamp: string; message: AgentMessage; } interface SessionTreeNode { entry: TreeEntry; children: SessionTreeNode[]; } declare class FakeSession { private readonly sm; private readonly loader; private readonly dir; private readonly tree; private promptFailuresRemaining; private extensions; private readonly injected; private uiContext; private shutdownHandler; private streaming; private disposed; private eventSeq; private timer; /** Full-tier graceful-teardown seam: a real active turn that only settles * after abort(), so the broker's bounded abort wait is observable. */ private heldTurn; private readonly listeners; /** Idle watcher sends received while bindExtensions is still running. They * start naturally as soon as broker.ts subscribes, without a retry loop that * could keep a failed broker process alive forever. */ private readonly pendingIdlePrompts; private readonly messageLog; private turnEndLeafId; /** Testability seam: `hasPendingMessages()` reported to `agent_settled` * handlers. Real pi's `AgentSession.hasPendingMessages()` reflects its * steer+followUp queue; this fake has no such queue, so a test that needs to * reproduce a settle observed WITH pending sends (the real incident this * fixture otherwise cannot exercise) sets CRTR_FAKE_ENGINE_PENDING_AT_SETTLE=1 * before spawning the broker. Unset → false, matching the fixture's actual * (queueless) state. */ private readonly pendingAtSettle; private readonly modelRt; constructor(sm: SessionManager, loader: ResourceLoader | undefined, model?: FakeModel, modelRuntime?: FakeModelRuntime); get messages(): AgentMessage[]; get sessionManager(): { buildSessionContext: () => { messages: AgentMessage[]; thinkingLevel: string; model: null; }; getLeafId: () => string | null; getEntry: (id: string) => TreeEntry | undefined; getTree: () => SessionTreeNode[]; getUserMessagesForForking: () => Array<{ entryId: string; text: string; timestamp?: string; }>; getSessionId: () => string; getSessionFile: () => string; getSessionDir: () => string; }; get sessionId(): string; get sessionFile(): string | undefined; get model(): FakeModel | undefined; private currentModel; private currentThinkingLevel; get thinkingLevel(): string | undefined; setThinkingLevel(level: string): void; private recordModel; setModel(model: FakeModel): Promise; /** The broker's read-op picker data (buildListModelsData → registryOf(session)) * reads this. For a model-less boot it is the self-healing mask proxy — see * modelRt. */ get modelRuntime(): FakeModelRuntime; /** The `--models` scoped cycle set — none in these fixtures. */ get scopedModels(): Array<{ model: FakeModel; thinkingLevel?: string; }>; /** The slice of SettingsManager the list_models data builder touches. */ get settingsManager(): { getEnabledModels: () => string[] | null; }; get isStreaming(): boolean; getSessionStats(): Record; private syncTreeSnapshot; setSessionName(_name: string): void; prompt(text: string, _options?: unknown): Promise; steer(_text: string): Promise; followUp(_text: string): Promise; /** In-memory recording only — no proof file, matching the offline in-process * contract broker-sdk-wiring.test.ts's REAL-SDK tests already use. Purely * defensive: broker.ts's driveEngine now calls `session.sendCustomMessage` * on ANY frame whose inline memory-reference guidance resolves, so a fake- * engine-backed lifecycle test whose fixture prompt text happens to contain * a resolvable `/name` token must not crash on a missing method. */ readonly customMessages: Array<{ customType: string; content: unknown; display?: boolean; deliverAs?: string; }>; sendCustomMessage(message: { customType: string; content: unknown; display?: boolean; details?: unknown; }, options?: { deliverAs?: string; }): Promise; abort(): Promise; /** In-place tree rewind (broker `navigate_tree`). Mirrors the real SDK's * contract: the session file is unchanged, the live history is truncated, * and the navigated-to user message's text comes back as `editorText`. The * fake's "rewind" re-anchors the active branch without deleting the abandoned * tail so the welcome snapshot can prove the one-nudge property. */ navigateTree(targetId: string, _options?: unknown): Promise<{ editorText?: string; cancelled: boolean; }>; subscribe(listener: (event: unknown) => void): () => void; private startIdlePrompt; /** Fan one typed AgentSessionEvent out to every broker subscriber. A throwing * listener is recorded, never propagated (mirrors the broker's m7 try/catch). */ private emit; /** Emit a realistic streaming assistant turn on the subscribe channel: * agent_start → turn_start → message_start → message_update×N → * tool_execution_start/update/end → message_end → turn_end → agent_end. Drives * G1 (controller prompt relay), G3 (produce-while-detached, accrues messageLog), * and G8 (a fast event stream — `updates`/`padBytes` size the flood that sheds a * stalled viewer at the broker HWM). A per-update setImmediate yield lets a * fast viewer drain between frames so only the stalled one trips the HWM. */ private emitTurn; dispose(): void; bindExtensions(opts: BindExtensionsOpts): Promise; private recordInjected; private buildCtx; private recordEvent; private fire; private step; private dispatch; private runDialog; } interface FakeModel { provider: string; id: string; api?: string; } /** The fake `ModelRuntime` — the runtime-method subset broker.ts's * `registryOf` facade (and its model-less construction mask) actually reads. */ interface FakeModelRuntime { getModel: (provider: string, id: string) => FakeModel | undefined; getModels: () => FakeModel[]; getAvailableSnapshot: () => FakeModel[]; getAvailable: () => Promise; hasConfiguredAuth: (provider: string) => boolean; getError: () => string | undefined; refresh: () => Promise; } interface FakeServices { resourceLoader: ResourceLoader; modelRuntime: FakeModelRuntime; } export declare function createAgentSessionServices(options: { cwd: string; agentDir?: string; modelRuntime?: unknown; resourceLoaderOptions?: { additionalExtensionPaths?: string[]; appendSystemPrompt?: string[]; }; }): Promise; export declare function createAgentSessionFromServices(options: { services: FakeServices; sessionManager: SessionManager; model?: FakeModel; thinkingLevel?: string; }): Promise<{ session: FakeSession; }>; type FakeRuntimeFactory = (options: { cwd: string; agentDir: string; sessionManager: SessionManager; }) => Promise<{ session: FakeSession; services: FakeServices; }>; declare class FakeAgentSessionRuntime { private readonly createRuntime; private readonly cwd; private readonly agentDir; session: FakeSession; services: FakeServices; private rebindSession; constructor(createRuntime: FakeRuntimeFactory, cwd: string, agentDir: string, session: FakeSession, services: FakeServices); setRebindSession(rebindSession: (session: FakeSession) => Promise): void; newSession(): Promise<{ cancelled: boolean; }>; dispose(): Promise; } export declare function createAgentSessionRuntime(createRuntime: FakeRuntimeFactory, options: { cwd: string; agentDir: string; sessionManager: SessionManager; }): Promise; export declare const VERSION = "0.78.1"; export {};