import type { Static, TSchema } from "typebox"; import type { CorrectableSubmissionError } from "./submission-correctable-error.ts"; type HostContentPart = { type: "text"; text: string } | { type: "toolCall"; id: string; name: string; arguments?: unknown } | { type: string }; type HostMessage = { role: string; content?: unknown; toolName?: string; isError?: boolean; stopReason?: string }; type HostEventMessage = { role: string; content: readonly HostContentPart[]; toolName?: string; isError?: boolean; stopReason?: string }; type HostSessionEntry = { type: string; message?: HostMessage }; export type HostToolResult = { content: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }>; details: T; terminate?: boolean; }; /** Opaque host-owned identity persisted with a Role run. */ export type DurablePrincipal = object & { readonly __durablePrincipal?: never }; /** * Controlled post-admission failure classes (ADR 0052 / #107). Owner = host contract. * Closed set of typed facts only — never a fabricated "could not classify" label (#881). * When no typed confirmation exists, omit cause and keep the original diagnostic / error pointer. */ export type ControlledFailureCause = | "activation" | "provider" | "session" | "output" | "timeout"; /** Production-owned typed failure carried on a resolved turn result. */ export type RoleTurnKnownFailure = { /** Present only when a typed fact confirms the class; omitted when unknown (#881). */ readonly cause?: ControlledFailureCause; readonly identity?: { readonly name?: string; readonly code?: string | number; }; /** * Optional diagnostic already owned by a typed production field (e.g. session * assistant errorMessage). Settlement prefers this over child stderr selection. */ readonly diagnostic?: string; /** Secondary evidence attached to the same typed failure record. */ readonly details?: Readonly>; }; /** * Thrown activation failure with a production-owned typed cause. * Prefer this over ad-hoc Error property tags so settlement retains typed identity. * Final owner = host contract (#526); public-cli/pi/role-runtime all import here. */ export class ExplicitInternalActivationError extends Error { readonly knownCause: ControlledFailureCause; readonly failureCode?: string | number; constructor( message: string, options: { knownCause: ControlledFailureCause; code?: string | number; name?: string; cause?: unknown; }, ) { super( message, options.cause === undefined ? undefined : { cause: options.cause }, ); this.name = options.name ?? "ExplicitInternalActivationError"; this.knownCause = options.knownCause; if (options.code !== undefined) { this.failureCode = options.code; } } } /** Packaged method skill binding (zero/one/many). */ export type MethodBinding = { readonly kind: "skill"; readonly path: string; }; /** * Host-neutral closed role activation projection. * Adapter translates to host-specific flags; does not reverse-parse prompt prose. */ export type RoleTurnActivation = | { readonly role: "judge" } | { readonly role: "coder"; readonly phase: string; readonly taskPath: string; } | { readonly role: "fixer"; readonly phase: string; readonly packetPath: string; readonly prerequisitesPath?: string; } | { readonly role: "reviewer"; readonly baseRevision: string; readonly lens: "completeness" | "correctness"; readonly authorityRefs: readonly string[]; readonly ticketNumber?: number; } | { readonly role: "merger"; readonly inputPath: string } | { readonly role: "collector"; readonly repo: string; /** Bound PR when known at admission; omit when role will bind from materials. */ readonly pr?: string; readonly requestManifestPath?: string; /** Wait-window ms as decimal string (#678 D4); omit → package default 10 minutes. */ readonly waitMs?: string; } | { readonly role: "doctor"; readonly casePath: string } | { readonly role: "notary"; readonly sourceRun: string; /** Ticket from source-run admitted form rides activation → flag → role read surface (#635). */ readonly ticketNumber?: number; } | { readonly role: "countersign"; /** Admitted ticket on activation/admission/invocation (diarist; no private role flag). */ readonly ticketNumber?: number; } | { readonly role: "gleaner-left"; /** Required comparison-base revision for the unanchored merge-candidate diff. */ readonly baseRevision: string; } | { readonly role: "inspector"; readonly sourceRun?: string } | { readonly role: "gatekeeper" } | { readonly role: "navigator" } | { readonly role: "auditor" } | { readonly role: "diarist" } | { readonly role: "secretariat" }; export type RoleTurnContinuation = | { readonly kind: "initial"; readonly prompt: string } | { readonly kind: "resume"; readonly prompt: string }; /** Seat model consumed by the turn host (provider/model/thinking). */ export type RoleTurnModelConfig = { readonly provider: string; readonly model: string; readonly thinking?: string; }; /** * Typed cross-host resume handoff (#617 DK-4). * Present only when post-admission projects a real host switch. * priorNativeKind names the record family the paths belong to — Pi's own * session file, or sitian run records (ADR 0077) — so a consuming adapter * reads the handoff without knowing which host wrote it. * Target host reads those files itself; projector never copies bytes. */ export type RoleTurnHostTransition = { readonly priorNativeKind: "pi-native" | "sitian"; readonly priorNativePaths: readonly string[]; }; /** Gate review officers (台院 / 符宝郎 / 审刑院). Single authority for seat checks. */ export function isOfficerReviewSeat(role: string): boolean { return role === "notary" || role === "inspector" || role === "auditor"; } /** One main-session turn request over the host-neutral execution seam. */ export type RoleTurnRequest = { readonly principal: DurablePrincipal; readonly activation: RoleTurnActivation; readonly methods: readonly MethodBinding[]; readonly continuation: RoleTurnContinuation; readonly model?: RoleTurnModelConfig; readonly engine?: string; /** Labor-engine model id from the live seat table (#883); opaque pass-through. */ readonly engineModel?: string; readonly cwd: string; readonly home: string; readonly agentDir: string; readonly runDirectory: string; readonly correlationId?: string; readonly timeoutMs?: number; /** * Parent cancellation for a nested activation (role-inside-role public summons, * #675). The host terminates its child when this aborts; a public CLI process * has no parent to observe and leaves it absent. */ readonly signal?: AbortSignal; /** Set by post-admission only on a real host switch; never on same-host resume. */ readonly hostTransition?: RoleTurnHostTransition; /** * Court-turn attempt (#637 / #833): sole-final per attempt. Open court, summons, * and resume-with-message set this; bare resume without an open court omits it. */ readonly courtAttemptId?: string; /** * Public-invocation scope (#537): one ak-role call. Auto-resume reuses it; * explicit resume mints a new one. Owned by this shared Host envelope — not * courtAttemptId and not a detour sidecar file. */ readonly invocationScopeId?: string; /** * Selected host axis for this turn (#537 / ADR 0082). Projected from the * public-entry seat resolution — never re-read from invocation.json by tools. */ readonly host?: string; /** Station child role run (#840): omit automatic navigator attendance. */ readonly stationChild?: boolean; }; /** Turn result — only fields upper layers currently consume. */ export type RoleTurnResult = { readonly code: number | null; readonly stderr: string; readonly timedOut: boolean; readonly knownFailure?: RoleTurnKnownFailure; }; /** Host-neutral session custom-entry appender (Pi adapter provides the concrete codec). */ export type SessionCustomEntryAppender = ( authority: DurablePrincipalAuthority, principal: DurablePrincipal, customType: string, data: unknown, ) => Promise; /** Host-neutral main-session execution seam (S1b-2 / #526). */ export interface RoleTurnHost { executeTurn(request: RoleTurnRequest): Promise; } export type DurablePrincipalCoordinates = { readonly sessionDirectory: string; readonly sessionFile: string; }; export type NewDurablePrincipalRequest = { readonly cwd: string; readonly runId: string; readonly role: string; readonly home?: string; }; /** Host authority for issuing, checking, and temporarily decoding durable principals. */ export interface DurablePrincipalAuthority { issue(request: NewDurablePrincipalRequest): DurablePrincipal; /** * Host-owned seal of already-placed coordinates into a durable principal wire * object. Public layers must not forge opaque principal shapes (#636). */ seal(coordinates: DurablePrincipalCoordinates): DurablePrincipal; isAvailable(principal: DurablePrincipal): Promise; decode(principal: unknown): DurablePrincipalCoordinates; } type HostSessionManager = { getLeafEntry(): HostSessionEntry | undefined; getLeafId(): string | null | undefined; getEntries(): Iterable; getSessionDir(): string; getSessionFile(): string | undefined; getHeader?(): { readonly type: string; readonly id?: string } | null; setSessionFile?(path: string): void; appendCustomEntry?(customType: string, data?: unknown): unknown; }; /** Context supplied by a host for one activation and its interceptable events. */ export type HostContext = { cwd: string; mode: string; model: { readonly provider: string } | undefined; sessionManager: HostSessionManager; /** Per-turn admitted run directory (#879); never process-global env. */ runDirectory?: string; /** Per-turn court attempt (#879); never process-global env. */ courtAttemptId?: string; /** Public-invocation scope (#537); never process-global env. */ invocationScopeId?: string; /** Selected host axis (#537 / ADR 0082); never process-global env invent. */ host?: string; signal?: AbortSignal | undefined; ui?: { notify?(message: string, type?: "info" | "warning" | "error"): void }; transcript?(): string; abort(): void; }; /** Per-turn run directory; adapters must project any child-process identity. */ export function runDirectoryFromHostContext(context: HostContext): string | undefined { return typeof context.runDirectory === "string" && context.runDirectory.trim() !== "" ? context.runDirectory : undefined; } /** Per-turn court attempt; absence never inherits ambient process identity. */ export function courtAttemptIdFromHostContext(context: HostContext): string | undefined { return typeof context.courtAttemptId === "string" && context.courtAttemptId.trim() !== "" ? context.courtAttemptId : undefined; } export type HostToolDefinition = { name: string; label: string; description: string; promptSnippet?: string; parameters: S; execute( toolCallId: string, params: Static, signal: AbortSignal | undefined, update: ((result: HostToolResult) => void) | undefined, context: C, ): Promise>; /** * #641 chain② opt-in: when the output params carry the shared infrastructure * declaration but the seat can machine-verify a lawful normal completion, the * registration may bounce the submission as a correctable error instead of * failing the host. Return the correctable error to bounce, or undefined to * keep the shared host-failure path. */ bounceInfrastructureDeclaration?(params: unknown, toolCallId: string, context: C): CorrectableSubmissionError | undefined; }; type BeforeAgentStartEvent = { prompt: string; systemPrompt: string; systemPromptOptions: { skills?: readonly unknown[]; contextFiles?: readonly unknown[]; appendSystemPrompt?: string } }; type InputEvent = { text: string; images?: Array<{ type: "image"; data: string; mimeType: string }>; source?: string }; type ToolCallEvent = { toolName: string; toolCallId: string; input: Record }; type ToolResultEvent = { toolName: string; toolCallId: string; isError: boolean; content: HostToolResult["content"]; details: unknown }; type SessionStartEvent = { reason: string }; type ProviderResponseEvent = { status?: number }; type AgentEndEvent = { messages: readonly HostEventMessage[] }; /** Typed closure of exactly one assistant turn; calls come from the host event, not transcript inspection. */ type TurnEndEvent = { readonly turnIndex: number; readonly calls: readonly ToolExecutionEvent[] }; type ToolExecutionEvent = { toolName: string; toolCallId: string }; type ToolExecutionUpdateEvent = ToolExecutionEvent & { partialResult: unknown }; type ToolExecutionEndEvent = ToolExecutionEvent & { isError: boolean }; type HostEventMap = { before_agent_start: BeforeAgentStartEvent; input: InputEvent; tool_call: ToolCallEvent; tool_result: ToolResultEvent; session_start: SessionStartEvent; session_shutdown: Record; after_provider_response: ProviderResponseEvent; agent_end: AgentEndEvent; turn_end: TurnEndEvent; agent_settled: Record; tool_execution_start: ToolExecutionEvent; tool_execution_update: ToolExecutionUpdateEvent; tool_execution_end: ToolExecutionEndEvent; }; type HostInputResult = { action: "continue" } | { action: "transform"; text: string; images?: Array<{ type: "image"; data: string; mimeType: string }> } | { action: "handled" }; type HostEventResultMap = { /** * systemPrompt — model-facing prompt body (presentation bytes). * readingMaterial — optional typed material for the same agent-start turn. * Host adapters fold readingMaterial into the provider-visible system prompt * at the send boundary; it is not a test-only parallel face. */ before_agent_start: { systemPrompt?: string; readingMaterial?: unknown }; input: HostInputResult; tool_call: { block?: boolean; reason?: string; terminate?: boolean }; tool_result: { content?: HostToolResult["content"]; details?: unknown; isError?: boolean }; session_start: void; session_shutdown: void; after_provider_response: void; agent_end: void; turn_end: void; agent_settled: void; tool_execution_start: void; tool_execution_update: void; tool_execution_end: void; }; type HostEventHandler = (event: HostEventMap[K], ctx: HostContext) => HostEventResultMap[K] | void | Promise; export type HostEventRegistration = { [K in keyof HostEventMap]: [event: K, handler: HostEventHandler] }[keyof HostEventMap]; type HostGatekeeperSubject = { readonly kind: | "worker_completion" | "judge_draft" | "judge_compliance" | "countersign_verdict" | "secretariat_verdict"; }; /** Gatekeeper non-pass faces returned to parent (#836 includes transport_failure; never kill leg). */ type HostGatekeeperNonPass = { readonly status: "bounce" | "escalate" | "no_receipt" | "transport_failure" } & Record; export type HostSubmissionNonPass = | HostGatekeeperNonPass | { readonly code: "coder_skill_expansion_evidence_missing" }; export type HostGatekeeperActions = { failInfrastructure(error: unknown, context: HostContext, toolCallId?: string): never; /** Envelope-owned execute→tool_result bridge for any structured submission non-pass. */ bindSubmissionNonPass(toolCallId: string, result: HostSubmissionNonPass): void; }; export type HostSkillExpansionEvidence = Readonly<{ name: string; location: string; content: string; userMessage: string; }>; /** Host capability declaration (contract verb ④). */ export type HostCapabilityDeclaration = Readonly<{ skillExpansion(prompt: string): HostSkillExpansionEvidence | undefined; /** * Pi-only: recover the plain original request from a native skill turn text * (argv may already carry `/skill:` from the Pi adapter). Absent on * non-pi hosts so role body never parses Pi slash syntax (ADR 0082). */ skillOriginalRequest?(name: string, text: string): string; }>; /** Host-owned effects used by the shared activation envelope. */ export interface RoleEnvelopeHost { readonly host: RoleHost; appendEntry(customType: string, data?: unknown): void; sendMessage(message: { customType: string; content: string; display?: boolean; details?: unknown }, options: { triggerTurn: boolean; deliverAs?: "followUp" }): void | Promise; startKeepalive(context: HostContext): void; stopKeepalive(): void; } /** The activation surface consumed by package role factories. */ export interface RoleHost { readonly capabilities?: HostCapabilityDeclaration; /** Deliver a typed correctable rejection into the current durable session's model context. */ deliverSubmissionRejection?(rejection: { readonly kind: "correctable-rejection"; readonly code: string; readonly toolCallIds: readonly string[] }): void | Promise; registerFlag(name: string, definition: { description: string; type: "boolean" | "string"; default?: boolean | string }): void; getFlag(name: string): boolean | string | undefined; registerTool(tool: HostToolDefinition): void; getAllTools(): Array<{ name: string; sourceInfo?: { path?: string } }>; setActiveTools(names: string[]): void; getActiveTools(): string[]; /** * Shared gate envelope. On pass may return officer snapshot (receipt + nested * runId) for seat public-terminal projection (#969); callers may ignore it. */ requireGatekeeperPass?(options: { context: HostContext; subject: HostGatekeeperSubject; signal?: AbortSignal; hostActions: HostGatekeeperActions; toolCallId: string; submission?: unknown; }): Promise; on(event: "before_agent_start", handler: HostEventHandler<"before_agent_start">): void; on(event: "input", handler: HostEventHandler<"input">): void; on(event: "tool_call", handler: HostEventHandler<"tool_call">): void; on(event: "tool_result", handler: HostEventHandler<"tool_result">): void; on(event: "session_start", handler: HostEventHandler<"session_start">): void; on(event: "session_shutdown", handler: HostEventHandler<"session_shutdown">): void; on(event: "after_provider_response", handler: HostEventHandler<"after_provider_response">): void; on(event: "agent_end", handler: HostEventHandler<"agent_end">): void; on(event: "turn_end", handler: HostEventHandler<"turn_end">): void; on(event: "agent_settled", handler: HostEventHandler<"agent_settled">): void; on(event: "tool_execution_start", handler: HostEventHandler<"tool_execution_start">): void; on(event: "tool_execution_update", handler: HostEventHandler<"tool_execution_update">): void; on(event: "tool_execution_end", handler: HostEventHandler<"tool_execution_end">): void; getCommands?(): Array<{ name: string }>; } /** Institutional sub-session seats (#518 §1). */ export type InstitutionalSeat = | "gatekeeper" | "inspector" | "notary" | "auditor" | (string & {}); /** Non-secret host-neutral seat model selection. Single truth source is RoleTurnModelConfig. */ export type HostInstitutionalModelSelection = RoleTurnModelConfig; /** Usage statistics for institutional sub-session events and turns. */ export type HostSessionUsage = { readonly input: number; readonly output: number; readonly cacheRead: number; readonly cacheWrite: number; readonly totalTokens: number; readonly cost: { readonly input: number; readonly output: number; readonly cacheRead: number; readonly cacheWrite: number; readonly total: number; }; }; /** Closed stream event union consumed by institutional callers (#518 §1③). */ export type HostInstitutionalSessionEvent = | { readonly type: "message_end"; readonly role: "assistant" | "user" | string; readonly message?: unknown; readonly usage?: HostSessionUsage; } | { readonly type: "turn_end"; readonly stopReason?: string; } | { readonly type: "tool_call"; readonly toolCallId: string; readonly toolName: string; readonly args?: unknown; } | { readonly type: "tool_result"; readonly toolCallId: string; readonly toolName: string; readonly isError?: boolean; readonly details?: unknown; }; /** Terminal result of an institutional assistant turn (#518 §1③). */ export type HostAssistantTurnResult = { readonly text: string; readonly stopReason?: string; readonly errorMessage?: string; readonly usage?: HostSessionUsage; readonly messages?: readonly unknown[]; }; /** * Handle to an active institutional sub-session (#518 §1②). * Does not leak AgentSession, ModelRuntime, or Provider objects out of the adapter. */ export interface HostInstitutionalSessionHandle { readonly sessionFile?: string; readonly sessionId?: string; prompt(text: string): Promise; subscribe(listener: (event: HostInstitutionalSessionEvent) => void): () => void; abort(): void; close(): Promise; } /** Open options for an institutional sub-session (#518 §1①). */ export type HostInstitutionalSessionOptions = { readonly cwd: string; readonly selection: HostInstitutionalModelSelection; readonly systemPrompt: string; readonly tools?: readonly HostToolDefinition[]; readonly customTools?: readonly unknown[]; readonly noTools?: "all" | "builtin"; readonly toolsAllowlist?: readonly string[]; readonly agentDir?: string; readonly credentialScratchParent?: string; readonly signal?: AbortSignal; readonly idleRetry?: boolean; readonly sessionIdentity?: { readonly kind: string; readonly subject?: string; readonly parent?: { getSessionFile(): string | undefined }; }; readonly sessionManager?: unknown; }; /** Host-neutral institutional sub-session open seam. */ export interface InstitutionalSessionHost { openInstitutionalSession( options: HostInstitutionalSessionOptions, ): Promise; }