import { type Static } from '@sinclair/typebox'; import { type AgentAcknowledgementView, type PutAgentAcknowledgementResponse, type ReplyAnswerView } from './api.js'; /** * Wire contract for Session Presence, Session Messages (Session Notes and * Answer Edits after the fenced answer), and the Delivery Attempts that hand * both answers and Session Messages into a running Agent Session. * * Everything here is additive: released CLIs and Companion Apps never call * these routes, and every existing response keeps its released shape. */ /** * Server Features a Companion App must see before advertising the capability * of the same name at registration, or before offering the action a feature * names. Clients ignore names they do not know. * * - `session_stopped_hold`: a stopped Agent Session (presence `out_of_reach` * with `accepts_messages: true`) accepts Session Notes and post-read Answer * Edits and holds them until it is reopened. * - `question_dismiss`: an open question can be dismissed * (`POST /api/v1/notifications/:request_id/dismiss`). * - `notification_content_batch`: the content of many requests in one * exchange (`POST /api/v1/notifications/content`). Without it, a Companion * App fetches each request's content on its own. */ export declare const SERVER_FEATURES: readonly ["session_notes", "answer_edits", "machine_approval", "session_stopped_hold", "question_dismiss", "notification_content_batch"]; export type ServerFeature = (typeof SERVER_FEATURES)[number]; /** `GET /api/v1/features` (user auth). A 404 means a server with none of them. */ export interface FeaturesResponse { features: ServerFeature[]; } /** * Mechanical evidence of whether an Agent Session's harness process is still * running. `unknown` means Notifai has no evidence either way and renders * exactly like a session without presence. */ export declare const SESSION_PRESENCE_STATES: readonly ["running", "out_of_reach", "ended", "unknown"]; export type SessionPresenceState = (typeof SESSION_PRESENCE_STATES)[number]; export declare const SESSION_ACTIVITIES: readonly ["working", "idle"]; export type SessionActivity = (typeof SESSION_ACTIVITIES)[number]; export declare const SessionActivitySchema: import("@sinclair/typebox").TUnion[]>; /** Effective presence, derived when read from the owner row and its lease. */ export type SessionPresenceView = { state: 'running'; activity: SessionActivity; /** True when new Session Notes and post-delivery Answer Edits are accepted. */ accepts_messages: boolean; last_seen_at: string; } | { /** * The lease lapsed without an end: the process may be alive but * unreachable. Also how a stopped Agent Session reads: its harness * stopped without the User closing it and may be reopened in place. */ state: 'out_of_reach'; last_seen_at: string; /** * Present and true only for a stopped Agent Session (server feature * `session_stopped_hold`): Session Notes and post-read Answer Edits are * accepted and held until it is reopened. Absent otherwise. */ accepts_messages?: true; } | { state: 'ended'; ended_at: string; } | { state: 'unknown'; }; /** Longest `wait_seconds` an attendance exchange may hold open. */ export declare const ATTENDANCE_MAX_WAIT_SECONDS = 25; /** Opaque, CLI-minted identity of one attendant incarnation. Never a PID. */ export declare const ATTENDANT_INCARNATION_PATTERN = "^inc_[A-Za-z0-9_-]{10,64}$"; /** Positive end evidence. Legacy unqualified ends may describe process failure. */ export declare const SESSION_END_REASONS: readonly ["continued", "closed"]; export type SessionEndReason = (typeof SESSION_END_REASONS)[number]; export declare const AttendanceRequest: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"running">; activity: import("@sinclair/typebox").TUnion[]>; accepts_messages: import("@sinclair/typebox").TBoolean; /** * `message_cursor` from the previous exchange. The server holds the * exchange only while no Session Message newer than it exists; absent * means answer at once with every claimable message. */ message_cursor: import("@sinclair/typebox").TOptional; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; }>, import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"ended">; end_reason: import("@sinclair/typebox").TOptional, import("@sinclair/typebox").TLiteral<"closed">]>>; successor_session_id: import("@sinclair/typebox").TOptional; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; }>, import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"withdrawn">; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; }>, import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"stopped">; /** As for `running`: the exchange is held while no newer held message exists. */ message_cursor: import("@sinclair/typebox").TOptional; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; }>]>; export type AttendanceRequestT = Static; /** A Session Message ready to be claimed and handed into the Agent Session. */ export type AttendanceMessage = { message_id: string; created_at: string; /** Account-preference snapshot taken when the message was accepted. */ agent_acknowledgement_text_required: boolean; } & ({ kind: 'note'; body: string; } | { kind: 'answer_edit'; request_id: string; /** The complete edited answer, one entry per answered question. */ answers: ReplyAnswerView[]; /** Human-readable rendering of the complete edited answer, assembled server-side. */ text: string; }); /** * Relative durations are measured from when the client sent the exchange, on * its own monotonic clock, so a wall-clock jump cannot extend them. */ export type AttendanceResponse = { /** This incarnation holds the lease. */ status: 'attending'; generation: number; lease_remaining_ms: number; message_cursor: string; /** Every Session Message for this Agent Session still waiting to be claimed. */ messages: AttendanceMessage[]; } | { /** Another incarnation holds a live lease; retry once it may have lapsed. */ status: 'owned'; retry_after_ms: number; } | { /** The server holds no lease for this incarnation; stop attending. */ status: 'withdrawn'; } | { /** * Answer to `stopped`: this incarnation still holds the stopped session * (it may send `ended`). `messages` are held for the session and cannot * be claimed until it is reopened and attended `running`. */ status: 'stopped'; generation: number; message_cursor: string; messages: AttendanceMessage[]; }; /** Most Agent Sessions one batched exchange may carry. */ export declare const ATTENDANCE_BATCH_MAX_SESSIONS = 100; /** * `POST /api/v1/agent-sessions/attendance?wait_seconds=N` (machine auth, CLI * capability `session_attendance`; offered when the server lists * `session_attendance_batch`). * * Each entry is exactly one per-session exchange, applied in order and with * the same lease and generation rules. One cursor covers the whole batch: the * server holds the exchange only while every entry is attending or already * stopped, none was acquired or stopped by this exchange, and no such session * has a claimable or held Session Message newer than the cursor. */ export declare const AttendanceBatchEntry: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"running">; activity: import("@sinclair/typebox").TUnion[]>; accepts_messages: import("@sinclair/typebox").TBoolean; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; session_id: import("@sinclair/typebox").TString; }>, import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"ended">; end_reason: import("@sinclair/typebox").TOptional, import("@sinclair/typebox").TLiteral<"closed">]>>; successor_session_id: import("@sinclair/typebox").TOptional; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; session_id: import("@sinclair/typebox").TString; }>, import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"withdrawn">; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; session_id: import("@sinclair/typebox").TString; }>, import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"stopped">; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; session_id: import("@sinclair/typebox").TString; }>]>; export type AttendanceBatchEntryT = Static; export declare const AttendanceBatchRequest: import("@sinclair/typebox").TObject<{ /** The previous batch's cursor; absent means answer at once with every claimable message. */ message_cursor: import("@sinclair/typebox").TOptional; sessions: import("@sinclair/typebox").TArray; activity: import("@sinclair/typebox").TUnion[]>; accepts_messages: import("@sinclair/typebox").TBoolean; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; session_id: import("@sinclair/typebox").TString; }>, import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"ended">; end_reason: import("@sinclair/typebox").TOptional, import("@sinclair/typebox").TLiteral<"closed">]>>; successor_session_id: import("@sinclair/typebox").TOptional; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; session_id: import("@sinclair/typebox").TString; }>, import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"withdrawn">; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; session_id: import("@sinclair/typebox").TString; }>, import("@sinclair/typebox").TObject<{ state: import("@sinclair/typebox").TLiteral<"stopped">; incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TOptional; session_id: import("@sinclair/typebox").TString; }>]>>; }>; export type AttendanceBatchRequestT = Static; /** * One entry's outcome, in request order. `unknown_session` replaces the * per-session 404; `unknown_successor` refuses a `continued` end whose * successor is not another Agent Session of this Account or has already * ended, changing nothing. With `session_continued_successor` an unseen * successor is created instead, and one that another machine owns or attends * is refused. The per-session route answers it with a 404 whose * `details.reason` is `unknown_successor`. */ export type AttendanceBatchResult = { session_id: string; } & (AttendanceResponse | { status: 'unknown_session'; } | { status: 'unknown_successor'; }); export interface AttendanceBatchResponse { /** The Account position the final snapshot observed; send it with the next batch. */ message_cursor: string; sessions: AttendanceBatchResult[]; } export declare const SESSION_MESSAGE_ID_PREFIX = "sm_"; export declare const SESSION_MESSAGE_ID_PATTERN = "^sm_[A-Za-z0-9_-]+$"; export declare const DELIVERY_ATTEMPT_ID_PATTERN = "^att_[A-Za-z0-9_-]+$"; /** * `POST /api/v1/agent-sessions/:session_id/delivery-attempts` (machine auth). * * Claims one hand-off: a Session Message, or a Notification Request's fenced * answer (the reply its `deliver` close selected). The claim is bound to the * generation the claimant holds; claims from a fenced generation are refused. * * The second form records, after the fact, that this machine already wrote a * selected answer into the session without holding a claim (the lease moved or * a claim was refused, and an answer is never withheld). It is stored as an * attempt whose outcome is `handed_off`, so the answer's Answer Edits follow * it in order. It is refused with `attempt_pending` while another attempt for * that answer has no outcome yet (temporary), and with `not_claimable` once one * reported anything but `released` (final). */ export declare const ClaimDeliveryAttemptRequest: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{ incarnation: import("@sinclair/typebox").TString; generation: import("@sinclair/typebox").TInteger; subject: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{ type: import("@sinclair/typebox").TLiteral<"session_message">; message_id: import("@sinclair/typebox").TString; }>, import("@sinclair/typebox").TObject<{ type: import("@sinclair/typebox").TLiteral<"answer">; request_id: import("@sinclair/typebox").TString; }>]>; /** * Answer Edits only, when the fenced answer's attempt is `unconfirmed`: * this machine proved that attempt's writer and its whole process group * are gone, so it can never write again. Deadline expiry alone is not proof. */ earlier_answer_writer_gone: import("@sinclair/typebox").TOptional>; }>, import("@sinclair/typebox").TObject<{ subject: import("@sinclair/typebox").TObject<{ type: import("@sinclair/typebox").TLiteral<"answer">; request_id: import("@sinclair/typebox").TString; }>; already_handed_off: import("@sinclair/typebox").TLiteral; }>]>; export type ClaimDeliveryAttemptRequestT = Static; export interface ClaimDeliveryAttemptResponse { attempt_id: string; /** * The claimant must not begin a harness write after this long, measured on * its own monotonic clock from when it sent the claim. Zero for an attempt * recorded after the fact. */ claim_remaining_ms: number; /** `handed_off` for an attempt recorded after the fact; absent for a claim. */ outcome?: 'handed_off'; } /** `details.reason` of a 409 `claim_refused`. */ export declare const DELIVERY_CLAIM_REFUSAL_REASONS: readonly ["generation_fenced", "not_claimable", "awaiting_earlier_answer", "awaiting_earlier_message", "attempt_pending"]; export type DeliveryClaimRefusalReason = (typeof DELIVERY_CLAIM_REFUSAL_REASONS)[number]; /** * - `handed_off`: the harness write completed. * - `unconfirmed`: a write may have happened but completion is unknown (crash * between write and journal mark). Never retried automatically. * - `released`: the claimant made no harness write and never will (its claim * deadline passed first); the subject becomes claimable again. */ export declare const DELIVERY_ATTEMPT_OUTCOMES: readonly ["handed_off", "unconfirmed", "released"]; export type DeliveryAttemptOutcome = (typeof DELIVERY_ATTEMPT_OUTCOMES)[number]; /** * `POST /api/v1/delivery-attempts/:attempt_id/report` (machine auth, the * attempt's machine). Accepted from a superseded incarnation too: attempts * outlive ownership. */ export declare const ReportDeliveryAttemptRequest: import("@sinclair/typebox").TObject<{ outcome: import("@sinclair/typebox").TUnion[]>; }>; export type ReportDeliveryAttemptRequestT = Static; export interface ReportDeliveryAttemptResponse { attempt_id: string; /** The stored outcome; a replay or a report after fencing returns what was kept. */ outcome: DeliveryAttemptOutcome; replayed: boolean; } export declare const SESSION_MESSAGE_KINDS: readonly ["note", "answer_edit"]; export type SessionMessageKind = (typeof SESSION_MESSAGE_KINDS)[number]; /** * `accepted → claimed → handed_off → acknowledged`, with terminal * `not_delivered` (never claimed) and `unconfirmed` (claimed, hand-off never * reported). An Agent Acknowledgement dominates any non-negative state and * never regresses. */ export declare const SESSION_MESSAGE_STATES: readonly ["accepted", "claimed", "handed_off", "acknowledged", "not_delivered", "unconfirmed"]; export type SessionMessageState = (typeof SESSION_MESSAGE_STATES)[number]; /** * Why a message was never claimed, or why a reply Answer Version was never * delivered. Clients render unknown reasons generically. */ export declare const SESSION_MESSAGE_NOT_DELIVERED_REASONS: readonly ["session_ended", "out_of_reach", "earlier_answer_unconfirmed"]; export type SessionMessageNotDeliveredReason = (typeof SESSION_MESSAGE_NOT_DELIVERED_REASONS)[number]; export declare const SESSION_NOTE_MAX_LENGTH = 4000; /** * `POST /api/v1/agent-sessions/:session_id/notes` (user auth, companion * capability `session_notes`). Accepted only while the Agent Session is * `running` with `accepts_messages`, or stopped (presence `out_of_reach` with * `accepts_messages`, held for 24 hours until it is reopened); otherwise 409 * `session_not_accepting_messages` and the app keeps the draft. */ export declare const CreateSessionNoteRequest: import("@sinclair/typebox").TObject<{ /** Allocated when the Send Delay countdown starts; makes retries idempotent. */ client_message_id: import("@sinclair/typebox").TString; device_id: import("@sinclair/typebox").TString; body: import("@sinclair/typebox").TString; }>; export type CreateSessionNoteRequestT = Static; export type SessionMessageView = { message_id: string; session_id: string; client_message_id: string; /** The installation that sent it; null once that installation was removed. */ device_id: string | null; state: SessionMessageState; not_delivered_reason: SessionMessageNotDeliveredReason | null; agent_acknowledgement_text_required: boolean; agent_acknowledgement: AgentAcknowledgementView | null; created_at: string; handed_off_at: string | null; /** Opaque change position; equal revisions mean an unchanged message. */ revision: string; } & ({ kind: 'note'; body: string; request_id: null; } | { kind: 'answer_edit'; request_id: string; answers: ReplyAnswerView[]; /** Human-readable rendering of the complete edited answer. */ text: string; }); export interface CreateSessionNoteResponse { /** True when `client_message_id` returned a previously accepted note. */ replayed: boolean; message: SessionMessageView; } /** * `GET /api/v1/session-messages?cursor=&limit=` (user auth). Pulled after a * `sync=session_messages` push and on foreground; pages cover every Session * Message whose revision advanced after `cursor`. */ export interface SessionMessagesPage { entries: SessionMessageView[]; /** Opaque, Account-bound position; save only after every entry is durable. */ next_cursor: string; has_more: boolean; } /** * `PUT /api/v1/session-messages/:message_id/agent-acknowledgement` (machine * auth). Accepted from the machine of any recorded attempt or the current * lease owner of the same Agent Session. Same body and bounds as a Notification * Request acknowledgement. */ export declare const PutSessionMessageAcknowledgementRequest: import("@sinclair/typebox").TObject<{ reply_seq: import("@sinclair/typebox").TOptional; session_id: import("@sinclair/typebox").TOptional; text: import("@sinclair/typebox").TOptional; }>; export type PutSessionMessageAcknowledgementRequestT = Static; export type PutSessionMessageAcknowledgementResponse = PutAgentAcknowledgementResponse; /** `GET /api/v1/session-messages/:message_id/agent-acknowledgement` (machine auth). */ export interface GetSessionMessageAcknowledgementResponse { message_id: string; agent_acknowledgement_text_required: boolean; agent_acknowledgement: AgentAcknowledgementView | null; } /** * `POST /api/v1/notifications/:request_id/answer-edits` (user auth, companion * capability `answer_edits`). `answers` holds only the edited questions; the * server merges them into the latest complete answer. * * While the reply window is open the edit is an ordinary correction reply * (latest wins). After a `deliver` close it becomes an `answer_edit` Session * Message and needs a `running` session with `accepts_messages`, or a stopped * one (held like a Session Note). Otherwise 409 * `answer_not_editable`; a stale `base_version` is 409 `answer_changed`. */ export declare const CreateAnswerEditRequest: import("@sinclair/typebox").TObject<{ /** Allocated when the Send Delay countdown starts; idempotent across both outcomes. */ client_edit_id: import("@sinclair/typebox").TString; device_id: import("@sinclair/typebox").TString; /** `version_id` of the latest Answer Version the edit was composed against. */ base_version: import("@sinclair/typebox").TString; answers: import("@sinclair/typebox").TArray>; text: import("@sinclair/typebox").TOptional; }>>; }>; export type CreateAnswerEditRequestT = Static; /** * Status of one Answer Version. Replies are `superseded` by a later reply, * `current` while they are the latest undelivered answer, or `delivered` when * a `deliver` close fenced them. A reply is `not_delivered` (reason * `session_ended`) when its Agent Session ended before the agent read it: the * answer was dropped and is never delivered later. Post-delivery edits carry * their Session Message state. */ export type AnswerVersionStatus = 'superseded' | 'current' | 'delivered' | 'presented' | SessionMessageState; export interface AnswerVersionView { origin: 'companion' | 'harness'; /** An explicit agent report does not attest which native UI control was used. */ provenance: 'agent-reported' | null; /** The exact version explicitly corrected, or null for an independent answer. */ base_version: string | null; /** `reply_id` for a reply, `message_id` for a post-delivery edit; `base_version` names it. */ version_id: string; /** `edit` when written through answer-edits, before or after delivery. */ source: 'reply' | 'edit'; status: AnswerVersionStatus; not_delivered_reason: SessionMessageNotDeliveredReason | null; /** The complete answer as of this version. */ answers: ReplyAnswerView[]; text: string; device_id: string | null; created_at: string; /** * This version's own acknowledgement: the request acknowledgement attaches * to the `delivered` version only, an edit's to that edit only. */ agent_acknowledgement: AgentAcknowledgementView | null; } export interface CreateAnswerEditResponse { /** True when `client_edit_id` returned a previously accepted edit. */ replayed: boolean; answer_version: AnswerVersionView; }