import type { DaemonRestartDTO, HealthDTO, StatusDTO } from './dto/health.js'; import type { ArtifactListDTO, ArtifactsQuery, ContextListDTO, CreateNodeRequest, ListNodesQuery, NodeDetailDTO, NodeSnapshotDTO, NodeSummaryDTO, TranscriptDTO, TranscriptQuery } from './dto/nodes.js'; import type { InterruptResultDTO, MessageResultDTO, SendMessageRequest } from './dto/messages.js'; import type { PushReportRequest, PushReportResultDTO, ReportDTO, ReportsQuery } from './dto/reports.js'; import type { CloseRequest, CloseResultDTO, PromoteRequest, RelaunchRootResultDTO, ReviveRequest, ReviveResultDTO, WaitRequest, YieldRequest } from './dto/lifecycle.js'; import type { SubscribeRequest, SubscriptionDTO } from './dto/subscriptions.js'; import type { FocusDTO, RegisterFocusRequest, SetFocusPaneRequest } from './dto/focus.js'; import { type ArmCronRequest, type CronDTO, type CronRunDTO, type CronScopeQuery, type CronShowDTO, type ListCronsQuery } from './dto/crons.js'; import type { NodeConfigPatch } from './dto/config.js'; import type { AttachEnsureRequest, AttachEnsureResultDTO } from './dto/attach.js'; import type { EnsureProfileRequest, ProfileDTO } from './dto/profiles.js'; import type { FilePeekDTO } from './dto/files.js'; import type { CredentialResultDTO, InstallCredentialRequest } from './dto/modelauth.js'; import type { CreateHumanBridgeRequest, HumanBridgeResultDTO, HumanConsultResultDTO, HumanDeliverResultDTO, HumanVisualResultDTO } from './dto/human.js'; import type { CancelInboxTicketRequest, CanceledTicketResultDTO, DeckTicketResultDTO, InboxDeckDTO, InboxListDTO, InboxTicketIdDTO, RespondInboxDeckRequest } from './dto/inbox.js'; import type { AttentionCountsDTO, AttentionDTO, DashboardDTO, DashboardQuery, HistoryGrepQuery, HistoryGrepResultDTO, HistoryReadQuery, HistoryReadResultDTO, HistorySearchQuery, HistorySearchResultDTO, PruneRequest, PruneResultDTO, RebuildIndexResultDTO, RosterDTO, SnapshotDTO } from './dto/canvas.js'; import type { CloseWorktreeResultDTO } from './dto/worktree.js'; export interface CrtrClientOptions { /** Unix socket path (default local transport). Exactly one of socketPath|baseUrl. */ socketPath?: string; /** `http(s)://host:port` for TCP/remote transport. */ baseUrl?: string; /** Extra headers (e.g. an edge auth token on TCP; crtrd ignores it). */ headers?: Record; /** Autostart on a cold socket (default true for socketPath, false for baseUrl). */ autostart?: boolean; /** Per-request timeout in ms (default 30_000). */ timeoutMs?: number; /** Injected daemon-start hook (spec §7.1). Called once on a cold socket when * autostart is on; after it resolves, the client polls `/healthz` and retries * the original request once. Absent → a cold socket throws `daemon_unavailable`. */ onColdSocket?: () => Promise; /** Injected cold-start diagnostic (issue #516). Called ONLY when the bounded * `/healthz` poll times out after `onColdSocket`, so the caller can attach * operator-useful context (e.g. a bounded tail of crtrd's stderr log) to the * `daemon_unavailable` error instead of it staying a bare message. Must * return synchronously and cheaply — it runs on the failure path, not the * happy path. A thrown/undefined result is treated as "no diagnostic". */ coldStartDiagnostic?: () => string | undefined; /** Bounded window (ms) to poll `/healthz` after `onColdSocket` resolves * before giving up with `daemon_unavailable`. Defaults to * `HEALTHZ_POLL_WINDOW_MS`. This must NOT silently drift from whatever * window actually governs "did the daemon start" (issue #508 follow-up): * `onColdSocket` is fire-and-forget, so this poll is the ONLY deadline that * determines whether the CLI reports success. A caller whose `onColdSocket` * hook triggers a differently-windowed startup verifier (e.g. the CLI's * `ensureDaemon`/`verifyDaemonStartup`) must pass that same window here so * a slow-but-valid cold start cannot pass the authoritative verifier while * this poll times out first. */ coldStartPollWindowMs?: number; } export declare class CrtrClient { private readonly socketPath?; private readonly baseUrl?; private readonly headers; private readonly autostart; private readonly timeoutMs; private readonly onColdSocket?; private readonly coldStartDiagnostic?; private readonly coldStartPollWindowMs; /** Guards against re-entering the autostart path more than once per client. */ private coldStartAttempted; constructor(opts: CrtrClientOptions); /** Construct a client bound to the default local socket with autostart on. Pass * `onColdSocket` to enable the daemon-spawn hook (spec §7.1); without it a cold * socket fails loud with `daemon_unavailable`. */ static forLocalSocket(opts?: Omit): CrtrClient; healthz(): Promise; status(): Promise; /** Ask the daemon to replace itself with a successor running the currently * selected runtime generation. Answers before the handover starts, so a * caller living inside a node the handover will tear down still gets a * settled result. */ restartDaemon(): Promise; createNode(req: CreateNodeRequest): Promise; listNodes(q?: ListNodesQuery): Promise; getNode(id: string): Promise; sendMessage(id: string, req: SendMessageRequest): Promise; /** First-class interrupt (the human Esc): cancels pending undelivered * human-send inbox entries, then aborts a live in-flight turn. NEVER * revives a dormant target. */ interruptNode(id: string): Promise; pushReport(id: string, req: PushReportRequest): Promise; forkNode(id: string): Promise; reviveNode(id: string, req?: ReviveRequest): Promise; relaunchRoot(id: string): Promise; closeNode(id: string, req?: CloseRequest): Promise; recycleNode(id: string): Promise; demoteNode(id: string): Promise; promoteNode(id: string, req: PromoteRequest): Promise; yieldNode(id: string, req: YieldRequest): Promise; waitNode(id: string, req: WaitRequest): Promise; patchConfig(id: string, patch: NodeConfigPatch): Promise; /** Land + close the node's managed git worktree (spec §6.2). Server-side * because it interleaves a canvas WRITE with a git land transaction and * crtrd is the repo host (same principle as spawnChild's creation git). */ closeWorktree(id: string): Promise; subscribe(id: string, req: SubscribeRequest): Promise; listFocuses(): Promise; focusOf(nodeId: string): Promise; focusByPane(pane: string): Promise; registerFocus(req: RegisterFocusRequest): Promise; setFocusPane(focusId: string, req: SetFocusPaneRequest): Promise; closeFocus(focusId: string): Promise; unsubscribe(id: string, target: string): Promise; /** Arm one cron (`POST /v1/crons`) — the server mints the cron_id. */ armCron(req: ArmCronRequest): Promise; /** Crons visible to the caller (`GET /v1/crons`). With `q.profile` that is * that profile's crons plus every global one; omit it only for a * canvas-home-wide provenance read ("which crons did node X arm"). */ listCrons(q?: ListCronsQuery): Promise; /** One cron with its run-log ring (`GET /v1/crons/:cronId`). */ showCron(cronId: string, q?: CronScopeQuery): Promise; /** Pause one cron (`POST /v1/crons/:cronId/pause`) — stops firing, keeps config+history. */ pauseCron(cronId: string, q?: CronScopeQuery): Promise; /** Resume one paused cron (`POST /v1/crons/:cronId/resume`). */ resumeCron(cronId: string, q?: CronScopeQuery): Promise; /** Run one cron NOW, out of band (`POST /v1/crons/:cronId/run`) — synchronous: * resolves with the settled run record after the subprocess closes. Does not * advance the schedule or consume a one-shot; never escalates. */ runCron(cronId: string, q?: CronScopeQuery): Promise; /** Cancel one cron (`DELETE /v1/crons/:cronId`, idempotent). */ cancelCron(cronId: string, q?: CronScopeQuery): Promise; ensureAttach(id: string, req?: AttachEnsureRequest): Promise; getReports(id: string, q?: ReportsQuery): Promise; getTranscript(id: string, q?: TranscriptQuery): Promise; getSnapshot(id: string): Promise; getArtifacts(id: string, q?: ArtifactsQuery): Promise; getContext(id: string): Promise; /** Read an absolute host path as UTF-8 (capped, `truncated` when clipped) for * the browser file-peek panel. */ peekFile(path: string): Promise; ensureProfile(name: string, req?: EnsureProfileRequest): Promise; listProfiles(): Promise; getProfile(name: string): Promise; /** Delete one profile by exact id or unique name (`DELETE /v1/profiles/:name`, * idempotent — a miss is success). */ deleteProfile(name: string): Promise; installCredential(provider: string, req: InstallCredentialRequest): Promise; /** Create a terminal `kind:'human'` bridge node with NO broker engine * (`spawnNode` server-side). Distinct from `createNode` (which launches a * broker) precisely because a human bridge must never have one. */ createHumanBridge(req: CreateHumanBridgeRequest): Promise; /** Run the registered humanloop completion handler server-side for one * `humanloop.completion/v1` event. crtrd re-verifies the full trust binding * before performing any canvas mutation. */ deliverHuman(event: unknown): Promise; /** Run the registered follow-up handler server-side for one * `humanloop.followup-request/v1` event. */ consultHuman(event: unknown): Promise; /** Run the registered visual handler server-side for one * `humanloop.visual-request-event/v1` event. */ visualHuman(event: unknown): Promise; /** Pending deck/review tickets across every available crouter-owned * humanloop root. */ listHumanInbox(): Promise; /** Read one pending deck by its opaque ticket id, with Markdown bodies * resolved inline. */ getHumanInboxDeck(ticketId: InboxTicketIdDTO): Promise; /** Submit ordered interaction responses for a pending deck. Single-assignment * server-side: a competing resolution races to `ticket_already_resolved`. */ respondHumanInboxDeck(ticketId: InboxTicketIdDTO, request: RespondInboxDeckRequest): Promise; /** Cancel a pending deck (terminal response, never deletion). */ cancelHumanInboxTicket(ticketId: InboxTicketIdDTO, request?: CancelInboxTicketRequest): Promise; /** Composed client-side from `GET /v1/nodes` + `GET /v1/status` (spec §6.3 — * the dashboard is absorbed into those two reads; there is no single route). * `generated_at` is the client-side capture instant of the composition. */ dashboard(q?: DashboardQuery): Promise; attention(): Promise; /** Per-node pending-ticket counts for a bounded viewer slice. */ attentionCounts(node_ids: string[]): Promise; /** Ranked/filtered content search over the per-cwd episodic corpus * (`crtr canvas history search`). Optional query: ranked when present, * recency browse when omitted. POST-bodied — the query carries arrays and * free text; the whole search executes server-side (spec §6.3). */ historySearch(q: HistorySearchQuery): Promise; /** Required-pattern line-hit search over the per-cwd episodic corpus * (`crtr canvas history grep`). Distinct stable schema from `historySearch` * — POST-bodied for the same reasons. */ historyGrep(q: HistoryGrepQuery): Promise; /** Resolve one `:` history ref to its full body * (`crtr canvas history read`). */ historyRead(q: HistoryReadQuery): Promise; /** The machine-readable browser canvas roster (`crtr canvas snapshot`) — * distinct from the per-node `getSnapshot`. */ canvasSnapshot(): Promise; /** The lean, set-based topology roster (`GET /v1/canvas/roster`) — exactly * two indexed queries server-side, no per-row enrichment. The recurring * poll target for attach/browser topology; use `canvasSnapshot` for the * enriched on-demand view. */ canvasRoster(): Promise; prune(req: PruneRequest): Promise; rebuildIndex(): Promise; /** Raw request for routes not yet method-wrapped. Applies the same * autostart + error-mapping semantics. */ request(method: string, path: string, body?: unknown): Promise; private nodePath; /** Validate a cron id before route construction — `routes.ts` interpolates * it raw, so a value carrying `/`, whitespace or `?` would corrupt the * request line rather than 404 cleanly. Mirrors `nodePath`. */ private cronPath; /** Validate an opaque inbox ticket id before route construction. A local * shape violation is a caller bug, not a server-rejectable request — throws * `TypeError` (matching the existing safe-segment discipline of a local * precondition, distinct from `nodePath`'s `ApiError` because that one IS a * request the server could plausibly receive and reject itself). */ private ticketId; private transport; private isColdSocketError; /** A connection torn down MID-request (Node's "socket hang up" / a broken * pipe) — as distinct from a refused connect, which means nothing is * listening. On the local socket that is `crtr sys daemon restart` doing its * generation handover: the daemon acks, then tears itself down and hands * over to a successor it spawned. The daemon IS coming back. */ private isHandoverHangup; /** Wait for the successor to answer `/healthz`, then replay the request when * replaying is safe. GET/HEAD are idempotent, so they retry transparently — * the handover stays invisible, which is the whole point of a restart that * resumes every node. A mutation may already have been applied server-side * before the socket dropped, so it fails with `daemon_restarting` (retry), * never `daemon_unavailable` ("start the daemon" is the wrong advice for a * daemon that is mid-handover). */ private rideOutHandover; /** Poll `/healthz` until the successor daemon answers, bounded by the * cold-start window. Tolerates both the pre-listen gap (cold socket) and a * second hang-up from a server still tearing down. */ private awaitHandover; /** Trigger the injected daemon-start hook, poll `/healthz`, then let the caller * retry once. Fail loud with `daemon_unavailable` when autostart is off, no * hook is wired, or the daemon never becomes reachable. */ private handleColdSocket; } /** Compose the cold-start `/healthz`-timeout `daemon_unavailable` message, * appending the injected diagnostic (issue #516) when one is present instead * of discarding the real startup failure behind a bare message. Exported (not * from the package's public `index.ts` surface, which is dependency-light by * design) purely so the regression test can assert the composition without * waiting out the real poll window. */ export declare function coldStartTimeoutMessage(diagnostic: string | undefined): string; /** Invoke the injected `coldStartDiagnostic` hook, treating a THROW the same * as an absent/undefined result — the contract `CrtrClientOptions` documents * ("a thrown/undefined result is treated as 'no diagnostic'"). Without this, * a broken hook would propagate and replace the typed `daemon_unavailable` * error the caller is entitled to. Exported alongside `coldStartTimeoutMessage` * for the same direct-unit-test reason. */ export declare function safeColdStartDiagnostic(hook: (() => string | undefined) | undefined): string | undefined;