@mirasoth/soothe-client 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
 
3
3
  /**
4
- * Custom error types for the Soothe client.
4
+ * Custom error types for the Soothe client (RFC-450 protocol-1).
5
5
  */
6
6
  /** Represents a WebSocket connection failure. */
7
7
  declare class ConnectionError extends Error {
@@ -10,12 +10,20 @@ declare class ConnectionError extends Error {
10
10
  readonly cause: Error;
11
11
  constructor(url: string, attempt: number, cause: Error);
12
12
  }
13
- /** Represents an error reported by the Soothe daemon. */
13
+ /**
14
+ * Represents an error reported by the Soothe daemon (RFC-450 §7).
15
+ *
16
+ * The daemon's structured error object carries a numeric `code` from the
17
+ * reserved ranges, a human-readable `message`, and optional `data`.
18
+ */
14
19
  declare class DaemonError extends Error {
15
- readonly code: string;
20
+ /** Numeric error code from the RFC-450 §7.3 registry. */
21
+ readonly code: number;
16
22
  /** The daemon's error message text. */
17
23
  readonly daemonMessage: string;
18
- constructor(code: string, message: string);
24
+ /** Optional machine-parseable error details. */
25
+ readonly data: unknown;
26
+ constructor(code: number, message: string, data?: unknown);
19
27
  }
20
28
  /** Represents a timeout waiting for a daemon response. */
21
29
  declare class TimeoutError extends Error {
@@ -23,12 +31,45 @@ declare class TimeoutError extends Error {
23
31
  readonly duration: string;
24
32
  constructor(operation: string, duration: string);
25
33
  }
34
+ /**
35
+ * Distinguishes clean vs unclean connection loss (RFC-450 §4, §8.3).
36
+ *
37
+ * A clean drop follows a `disconnect` notification (loops keep running
38
+ * server-side); an unclean drop is a read/write error or a missed pong
39
+ * (in-flight queries are cancelled). Pair with the Client's `'disconnected'`
40
+ * event: the cause is emitted exactly once when the connection drops.
41
+ */
42
+ declare enum DisconnectCause {
43
+ /** Abrupt loss: read/write error or missed pong (RFC-450 §8.3). */
44
+ Unclean = 0,
45
+ /** Graceful peer-initiated `disconnect` notification (RFC-450 §9.2). */
46
+ Clean = 1
47
+ }
48
+ /** Human-readable cause name for logging. */
49
+ declare function disconnectCauseName(cause: DisconnectCause): string;
50
+ /** Indicates a failed reconnection attempt sequence. */
51
+ declare class ReconnectError extends Error {
52
+ readonly url: string;
53
+ readonly attempts: number;
54
+ readonly cause: Error;
55
+ constructor(url: string, attempts: number, cause: Error);
56
+ }
57
+ /**
58
+ * Returned by `reattachAndProbe` when a loop accepts the reattach handshake
59
+ * but fails the `loop_get` liveness probe. Callers should fall back to a
60
+ * fresh `loop_new` bootstrap.
61
+ */
62
+ declare class StaleLoopError extends Error {
63
+ readonly loopID: string;
64
+ readonly cause?: Error;
65
+ constructor(loopID: string, cause?: Error);
66
+ }
26
67
 
27
68
  /**
28
69
  * Verbosity levels and tiers for event filtering.
29
70
  */
30
71
  /** User-configurable verbosity setting. */
31
- type VerbosityLevel = 'quiet' | 'normal' | 'debug';
72
+ type VerbosityLevel = "quiet" | "normal" | "debug";
32
73
  /** Minimum verbosity level at which content is visible. */
33
74
  declare enum VerbosityTier {
34
75
  /** Always visible (errors, assistant text, final reports) */
@@ -62,12 +103,20 @@ interface Config {
62
103
  reconnectDelay: number;
63
104
  /** Application-level heartbeat interval in ms */
64
105
  heartbeatInterval: number;
65
- /** Handshake: wait for daemon_ready in ms */
106
+ /** Handshake: wait for connection_ack (readiness "ready") in ms */
66
107
  daemonReadyTimeout: number;
67
108
  /** Bootstrap: wait for status with loop_id in ms */
68
109
  loopStatusTimeout: number;
69
- /** After loop_subscribe: wait for confirmation in ms */
110
+ /** After subscribe(method:"loop_events"): wait for confirmation in ms */
70
111
  subscriptionTimeout: number;
112
+ /** Max reconnection attempts on a mid-session drop (0 = infinite). */
113
+ reconnectMaxAttempts: number;
114
+ /** Initial backoff delay (ms) between reconnect attempts. */
115
+ reconnectInitialDelay: number;
116
+ /** Cap (ms) on exponential backoff delay between reconnect attempts. */
117
+ reconnectMaxDelay: number;
118
+ /** `loop_get` liveness probe timeout (ms) in reattachAndProbe. */
119
+ reattachProbeTimeout: number;
71
120
  }
72
121
  /** Returns default configuration. */
73
122
  declare function defaultConfig(): Config;
@@ -75,250 +124,260 @@ declare function defaultConfig(): Config;
75
124
  declare function loadConfigFromEnv(): Config;
76
125
 
77
126
  /**
78
- * Message types, encode/decode, NDJSON splitting, and factory functions
79
- * for the Soothe daemon wire protocol.
127
+ * Daemon ``loop_input`` intent_hint values (direct model turns, no agent graph).
80
128
  */
81
- interface BaseMessage {
82
- type: string;
83
- request_id?: string;
84
- }
85
- /** Loop-scoped user input. */
86
- interface LoopInputMessage extends BaseMessage {
87
- type: 'loop_input';
88
- loop_id: string;
89
- content: string;
90
- autonomous?: boolean;
91
- max_iterations?: number;
92
- preferred_subagent?: string;
93
- interactive?: boolean;
94
- model?: string;
95
- model_params?: Record<string, unknown>;
96
- attachments?: Record<string, unknown>[];
97
- }
98
- interface CommandMessage extends BaseMessage {
99
- type: 'command';
100
- cmd: string;
101
- }
102
- interface DaemonStatusMessage extends BaseMessage {
103
- type: 'daemon_status';
104
- }
105
- interface DaemonShutdownMessage extends BaseMessage {
106
- type: 'daemon_shutdown';
107
- }
108
- interface ConfigGetMessage extends BaseMessage {
109
- type: 'config_get';
110
- section: string;
111
- }
112
- interface LoopNewMessage extends BaseMessage {
113
- type: 'loop_new';
114
- /** Project directory; runner uses this path directly when set. */
115
- client_workspace?: string;
116
- /** Stable scope for persisted sandbox when client_workspace is unset. */
117
- client_workspace_id?: string;
118
- /** User segment under $SOOTHE_HOME/workspaces/ (empty → anonymous). */
119
- user_id?: string;
120
- /** When true, loop execution data is GC'd after idle period (workspace retained). */
121
- is_ephemeral?: boolean;
122
- /**
123
- * @deprecated Use `client_workspace`. Still accepted by the daemon as an alias.
124
- */
125
- workspace?: string;
129
+ /** Text-only chat completion via the configured ``default`` role. */
130
+ declare const INTENT_HINT_TEXT_COMPLETION: "text_completion";
131
+ /** Vision / image understanding via the configured ``image`` role (attachments required). */
132
+ declare const INTENT_HINT_IMAGE_TO_TEXT: "image_to_text";
133
+ /** OCR via the configured ``ocr`` role (attachments required). */
134
+ declare const INTENT_HINT_OCR: "ocr";
135
+ /** Embedding via the configured ``embedding`` role (text-only; JSON vector response). */
136
+ declare const INTENT_HINT_EMBED: "embed";
137
+ /** Supported daemon direct-model intent hints. */
138
+ type IntentHint = typeof INTENT_HINT_TEXT_COMPLETION | typeof INTENT_HINT_IMAGE_TO_TEXT | typeof INTENT_HINT_OCR | typeof INTENT_HINT_EMBED;
139
+ /** Legacy intent_hint values removed from the daemon wire contract. */
140
+ declare const REMOVED_INTENT_HINTS: readonly ["direct_llm", "quiz"];
141
+ type RemovedIntentHint = (typeof REMOVED_INTENT_HINTS)[number];
142
+ /**
143
+ * Wire ``loop_input.intent_hint``: direct model hints or agent-path pass-through
144
+ * (e.g. ``resume_clarification``, ``skill:foo``). Legacy ``direct_llm`` and
145
+ * ``quiz`` are rejected before send.
146
+ */
147
+ type LoopInputIntentHint = IntentHint | (string & {});
148
+ /** Returns an error message when ``hint`` is a removed legacy value; otherwise null. */
149
+ declare function validateLoopInputIntentHint(hint: string): string | null;
150
+ /** Phases emitted on ``mode=messages`` for user-visible loop assistant output. */
151
+ declare const LOOP_ASSISTANT_OUTPUT_PHASES: readonly ["goal_completion", "quiz", "autonomous_goal", "direct_model", "text_completion", "image_to_text", "ocr", "embed", "plan_direct"];
152
+ type LoopAssistantOutputPhase = (typeof LOOP_ASSISTANT_OUTPUT_PHASES)[number];
153
+ /** Default deliverable phases for triarch-style apps (direct hints + agent outputs). */
154
+ declare const DEFAULT_DELIVERABLE_PHASES: ReadonlySet<string>;
155
+
156
+ /**
157
+ * Protocol-1 wire envelope: message types, encode/decode, NDJSON splitting,
158
+ * and factory functions for the Soothe daemon (RFC-450).
159
+ *
160
+ * The unified `{proto, type, method, params, id}` envelope combines
161
+ * JSON-RPC 2.0's `method`/`params`/`id` structure with graphql-ws's `type`
162
+ * semantics for message class distinction.
163
+ */
164
+
165
+ /** Protocol version string (RFC-450 §8.1). */
166
+ declare const PROTO_VERSION = "1";
167
+ /** Default client capabilities declared in the connection_init handshake. */
168
+ declare const DEFAULT_CLIENT_CAPABILITIES: string[];
169
+ /** Client version reported in the connection_init handshake. */
170
+ declare const CLIENT_VERSION = "0.1.0";
171
+ type MessageType = "connection_init" | "connection_ack" | "request" | "response" | "notification" | "subscribe" | "next" | "error" | "complete" | "unsubscribe" | "ping" | "pong" | "receipt_response" | "disconnect" | "status";
172
+ /** Method names carried in the envelope `method` field (RFC-450 §9.2). */
173
+ type MethodName = "loop_list" | "loop_get" | "loop_tree" | "loop_prune" | "loop_delete" | "loop_new" | "loop_reattach" | "loop_detach" | "loop_input" | "loop_messages" | "loop_state_get" | "loop_state_update" | "loop_cards_fetch" | "loop_history_fetch" | "loop_events" | "autopilot_events" | "job_create" | "job_status" | "job_pause" | "job_resume" | "job_cancel" | "job_dag" | "job_guidance" | "daemon_status" | "daemon_shutdown" | "config_get" | "config_reload" | "skills_list" | "invoke_skill" | "models_list" | "mcp_status" | "auth" | "auth_refresh" | "slash_command" | "rpc_command" | "delivery_ack" | "cron_add" | "cron_list" | "cron_show" | "cron_cancel" | "disconnect";
174
+ /** Base fields shared by every protocol-1 message. */
175
+ interface BaseEnvelope {
176
+ proto: string;
177
+ type: MessageType;
126
178
  }
127
- /** Options for `loop_new` workspace fields. */
128
- interface LoopNewOptions {
129
- client_workspace?: string;
130
- client_workspace_id?: string;
131
- user_id?: string;
132
- is_ephemeral?: boolean;
133
- /** @deprecated Use `client_workspace`. */
134
- workspace?: string;
179
+ /** A client→server RPC request (expects a `response` correlated by `id`). */
180
+ interface RequestEnvelope extends BaseEnvelope {
181
+ type: "request";
182
+ method: MethodName;
183
+ params?: Record<string, unknown>;
184
+ id: string;
135
185
  }
136
- interface LoopSubscribeMessage extends BaseMessage {
137
- type: 'loop_subscribe';
138
- loop_id: string;
139
- verbosity: string;
140
- stream_delivery?: 'batch' | 'streaming';
186
+ /** A server→client RPC success response. */
187
+ interface ResponseEnvelope extends BaseEnvelope {
188
+ type: "response";
189
+ result?: Record<string, unknown>;
190
+ id?: string;
141
191
  }
142
- interface LoopDetachMessage extends BaseMessage {
143
- type: 'loop_detach';
144
- loop_id: string;
192
+ /** A fire-and-forget client→server notification (no `id`, no response). */
193
+ interface NotificationEnvelope extends BaseEnvelope {
194
+ type: "notification";
195
+ method: MethodName;
196
+ params?: Record<string, unknown>;
197
+ /** Optional receipt id for delivery confirmation (RFC-450 §5.7). */
198
+ receipt?: string;
145
199
  }
146
- interface LoopListMessage extends BaseMessage {
147
- type: 'loop_list';
148
- filter?: Record<string, unknown>;
149
- limit?: number;
200
+ /** Start a subscription stream (events arrive as `next`). */
201
+ interface SubscribeEnvelope extends BaseEnvelope {
202
+ type: "subscribe";
203
+ method: "loop_events" | "autopilot_events";
204
+ params?: {
205
+ loop_id?: string;
206
+ verbosity?: string;
207
+ /** Stream delivery mode: batch (coalesce until turn end), adaptive (small payloads passthrough), streaming (real-time chunks). */
208
+ stream_delivery?: "batch" | "adaptive" | "streaming";
209
+ /** Wire serialization tier: full (all fields), compact (essential fields only). */
210
+ wire_tier?: "full" | "compact";
211
+ } & Record<string, unknown>;
212
+ id: string;
150
213
  }
151
- interface LoopGetMessage extends BaseMessage {
152
- type: 'loop_get';
153
- loop_id: string;
154
- verbose?: boolean;
214
+ /** A stream event for an active subscription. */
215
+ interface NextEnvelope extends BaseEnvelope {
216
+ type: "next";
217
+ payload?: Record<string, unknown>;
218
+ id?: string;
155
219
  }
156
- interface LoopTreeMessage extends BaseMessage {
157
- type: 'loop_tree';
158
- loop_id: string;
159
- format?: string;
220
+ /** Structured error response (terminates the operation). */
221
+ interface ErrorEnvelope extends BaseEnvelope {
222
+ type: "error";
223
+ error: {
224
+ code: number;
225
+ message: string;
226
+ data?: unknown;
227
+ };
228
+ id?: string;
160
229
  }
161
- interface LoopPruneMessage extends BaseMessage {
162
- type: 'loop_prune';
163
- loop_id: string;
164
- retention_days?: number;
165
- dry_run?: boolean;
230
+ /** Explicit stream-completion signal. */
231
+ interface CompleteEnvelope extends BaseEnvelope {
232
+ type: "complete";
233
+ id?: string;
166
234
  }
167
- interface LoopDeleteMessage extends BaseMessage {
168
- type: 'loop_delete';
169
- loop_id: string;
235
+ /** Cancel a subscription by `id`. */
236
+ interface UnsubscribeEnvelope extends BaseEnvelope {
237
+ type: "unsubscribe";
238
+ id: string;
170
239
  }
171
- interface LoopReattachMessage extends BaseMessage {
172
- type: 'loop_reattach';
173
- loop_id: string;
240
+ /** Connection handshake (client→server, first message). */
241
+ interface ConnectionInitEnvelope extends BaseEnvelope {
242
+ type: "connection_init";
243
+ params?: {
244
+ client_version: string;
245
+ client_name?: string;
246
+ accept_proto?: string[];
247
+ capabilities?: string[];
248
+ };
174
249
  }
175
- interface SkillsListMessage extends BaseMessage {
176
- type: 'skills_list';
250
+ /** Connection handshake response (server→client). */
251
+ interface ConnectionAckEnvelope extends BaseEnvelope {
252
+ type: "connection_ack";
253
+ result?: {
254
+ server_version?: string;
255
+ protocol_version?: string;
256
+ capabilities?: string[];
257
+ readiness_state?: string;
258
+ heartbeat_interval_ms?: number;
259
+ };
177
260
  }
178
- interface ModelsListMessage extends BaseMessage {
179
- type: 'models_list';
261
+ /** Heartbeat ping (either direction). */
262
+ interface PingEnvelope extends BaseEnvelope {
263
+ type: "ping";
180
264
  }
181
- interface InvokeSkillMessage extends BaseMessage {
182
- type: 'invoke_skill';
183
- skill: string;
184
- args?: string;
265
+ /** Heartbeat pong response. */
266
+ interface PongEnvelope extends BaseEnvelope {
267
+ type: "pong";
185
268
  }
186
- interface DetachMessage extends BaseMessage {
187
- type: 'detach';
269
+ /** Delivery confirmation for a notification that carried a `receipt`. */
270
+ interface ReceiptResponseEnvelope extends BaseEnvelope {
271
+ type: "receipt_response";
272
+ receipt: string;
188
273
  }
189
- interface EventMessage extends BaseMessage {
190
- type: 'event';
191
- loop_id?: string;
192
- namespace: string;
193
- data: Record<string, unknown>;
194
- timestamp?: string;
274
+ /** Clean connection close (daemon keeps loops running). */
275
+ interface DisconnectEnvelope extends BaseEnvelope {
276
+ type: "disconnect";
195
277
  }
196
- interface StatusResponse extends BaseMessage {
197
- type: 'status';
198
- state: string;
278
+ /**
279
+ * A daemon status frame. `status` is a defined protocol-1 top-level type
280
+ * (RFC-450 §9.1): it passes through the daemon's legacy→`next` translator
281
+ * unchanged, so it is NOT wrapped in a `next` envelope.
282
+ */
283
+ interface StatusFrame extends BaseEnvelope {
284
+ type: "status";
285
+ state?: string;
199
286
  loop_id?: string;
200
- workspace: string;
287
+ workspace?: string;
201
288
  input_history?: string[];
202
289
  conversation_history?: unknown[];
290
+ [key: string]: unknown;
203
291
  }
204
- interface SubscriptionConfirmedResponse extends BaseMessage {
205
- type: 'subscription_confirmed';
206
- loop_id?: string;
207
- client_id: string;
208
- verbosity: string;
209
- }
210
- interface ErrorResponse extends BaseMessage {
211
- type: 'error';
212
- code: string;
213
- message: string;
214
- }
215
- interface DaemonReadyResponse extends BaseMessage {
216
- type: 'daemon_ready';
217
- state: string;
218
- message?: string;
219
- }
220
- interface DaemonStatusResponse extends BaseMessage {
221
- type: 'daemon_status_response';
222
- running: boolean;
223
- port_live: boolean;
224
- active_loops: number;
225
- }
226
- interface ShutdownAckResponse extends BaseMessage {
227
- type: 'shutdown_ack';
228
- status: string;
229
- }
230
- interface LoopNewResponse extends BaseMessage {
231
- type: 'loop_new_response';
292
+ /** Discriminated union of all decoded protocol-1 messages. */
293
+ type DecodedMessage = RequestEnvelope | ResponseEnvelope | NotificationEnvelope | SubscribeEnvelope | NextEnvelope | ErrorEnvelope | CompleteEnvelope | UnsubscribeEnvelope | ConnectionInitEnvelope | ConnectionAckEnvelope | PingEnvelope | PongEnvelope | ReceiptResponseEnvelope | DisconnectEnvelope | StatusFrame | Record<string, unknown>;
294
+ interface LoopInputParams {
232
295
  loop_id: string;
233
- success?: boolean;
234
- is_ephemeral?: boolean;
235
- }
236
- interface LoopSubscribeResponse extends BaseMessage {
237
- type: 'loop_subscribe_response';
238
- loop_id?: string;
239
- success: boolean;
240
- message?: string;
241
- }
242
- interface LoopDetachResponse extends BaseMessage {
243
- type: 'loop_detach_response';
244
- loop_id?: string;
245
- success: boolean;
246
- }
247
- interface LoopListResponse extends BaseMessage {
248
- type: 'loop_list_response';
249
- loops?: Record<string, unknown>[];
250
- total?: number;
251
- }
252
- interface LoopGetResponse extends BaseMessage {
253
- type: 'loop_get_response';
254
- loop?: Record<string, unknown>;
255
- }
256
- interface LoopTreeResponse extends BaseMessage {
257
- type: 'loop_tree_response';
258
- tree?: Record<string, unknown>;
259
- }
260
- interface LoopPruneResponse extends BaseMessage {
261
- type: 'loop_prune_response';
262
- result?: Record<string, unknown>;
263
- }
264
- interface LoopDeleteResponse extends BaseMessage {
265
- type: 'loop_delete_response';
266
- success: boolean;
267
- message?: string;
268
- }
269
- interface LoopReattachResponse extends BaseMessage {
270
- type: 'loop_reattach_response';
271
- loop_id?: string;
272
- success?: boolean;
273
- }
274
- interface HistoryReplayMessage extends BaseMessage {
275
- type: 'history_replay';
276
- loop_id?: string;
277
- events?: Record<string, unknown>[];
278
- total_events?: number;
279
- }
280
- interface HistoryReplayCompleteMessage extends BaseMessage {
281
- type: 'history_replay_complete';
282
- loop_id?: string;
283
- }
284
- interface ReplayCompleteMessage extends BaseMessage {
285
- type: 'replay_complete';
286
- loop_id?: string;
287
- event_count?: number;
288
- }
289
- interface LoopReattachedWireMessage extends BaseMessage {
290
- type: 'loop_reattached';
291
- loop_id?: string;
292
- timestamp?: string;
296
+ content: string;
297
+ autonomous?: boolean;
298
+ max_iterations?: number;
299
+ preferred_subagent?: string;
300
+ /** @deprecated Ignored by daemon; loop_input always uses non-interactive autonomous mode. */
301
+ interactive?: boolean;
302
+ model?: string;
303
+ model_params?: Record<string, unknown>;
304
+ attachments?: Record<string, unknown>[];
305
+ intent_hint?: LoopInputIntentHint;
306
+ response_schema?: Record<string, unknown>;
307
+ response_schema_name?: string;
308
+ response_schema_strict?: boolean;
309
+ clarification_mode?: string;
310
+ clarification_answer?: boolean;
311
+ clarification_answers?: string[];
293
312
  }
294
- interface SkillsListResponse extends BaseMessage {
295
- type: 'skills_list_response';
296
- skills?: Record<string, unknown>[];
313
+ /** Options for `loop_new` workspace fields. */
314
+ interface LoopNewOptions {
315
+ client_workspace?: string;
316
+ client_workspace_id?: string;
317
+ user_id?: string;
318
+ is_ephemeral?: boolean;
319
+ /** @deprecated Use `client_workspace`. */
320
+ workspace?: string;
297
321
  }
298
- interface ModelsListResponse extends BaseMessage {
299
- type: 'models_list_response';
300
- models?: Record<string, unknown>[];
322
+ /**
323
+ * Shape of a `next` payload when the daemon wraps a legacy free-form frame.
324
+ * The original frame type becomes `mode`; `data` carries the frame body with
325
+ * `loop_id` preserved.
326
+ */
327
+ interface StreamEventPayload {
328
+ namespace?: unknown;
329
+ mode?: string;
330
+ data?: Record<string, unknown>;
301
331
  }
302
- type DecodedMessage = LoopInputMessage | CommandMessage | DaemonStatusMessage | DaemonShutdownMessage | ConfigGetMessage | LoopNewMessage | LoopSubscribeMessage | LoopDetachMessage | LoopListMessage | LoopGetMessage | LoopTreeMessage | LoopPruneMessage | LoopDeleteMessage | LoopReattachMessage | SkillsListMessage | ModelsListMessage | InvokeSkillMessage | DetachMessage | EventMessage | StatusResponse | SubscriptionConfirmedResponse | ErrorResponse | DaemonReadyResponse | DaemonStatusResponse | ShutdownAckResponse | LoopNewResponse | LoopSubscribeResponse | LoopDetachResponse | LoopListResponse | LoopGetResponse | LoopTreeResponse | LoopPruneResponse | LoopDeleteResponse | LoopReattachResponse | HistoryReplayMessage | HistoryReplayCompleteMessage | ReplayCompleteMessage | LoopReattachedWireMessage | SkillsListResponse | ModelsListResponse | Record<string, unknown>;
303
- /** Encodes a message as JSON with newline delimiter. */
332
+ /** Encodes a message as JSON with a newline delimiter (NDJSON frame). */
304
333
  declare function encodeMessage(msg: unknown): string;
305
- /** Decodes a JSON message and returns a typed object. Unknown types return a raw map. */
334
+ /**
335
+ * Decodes a JSON message and returns a typed object. Unknown types return a
336
+ * raw map. The decoder is permissive: it accepts protocol-1 envelopes and
337
+ * surfaces `response.result` / `next.payload` for consumers that read them,
338
+ * but always returns the full envelope so callers can inspect `type`/`id`.
339
+ */
306
340
  declare function decodeMessage(data: string): DecodedMessage | null;
341
+ /** Builds a `request` envelope. */
342
+ declare function requestEnvelope(method: MethodName, params?: Record<string, unknown>, id?: string): RequestEnvelope;
343
+ /** Builds a `notification` envelope (no `id`). */
344
+ declare function notificationEnvelope(method: MethodName, params?: Record<string, unknown>): NotificationEnvelope;
345
+ /** Builds a `subscribe` envelope. */
346
+ declare function subscribeEnvelope(method: "loop_events" | "autopilot_events", params?: Record<string, unknown>, id?: string): SubscribeEnvelope;
347
+ /** Builds an `unsubscribe` envelope. */
348
+ declare function unsubscribeEnvelope(id: string): UnsubscribeEnvelope;
349
+ /** Builds a `connection_init` handshake envelope. */
350
+ declare function connectionInitEnvelope(opts?: {
351
+ client_version?: string;
352
+ client_name?: string;
353
+ accept_proto?: string[];
354
+ capabilities?: string[];
355
+ }): ConnectionInitEnvelope;
356
+ /** Builds a `ping` heartbeat envelope. */
357
+ declare function pingEnvelope(): PingEnvelope;
358
+ /** Builds a `pong` heartbeat envelope. */
359
+ declare function pongEnvelope(): PongEnvelope;
360
+ /** Builds a `disconnect` notification envelope. */
361
+ declare function disconnectEnvelope(): DisconnectEnvelope;
307
362
  /** Splits a single WebSocket text payload into individual JSON lines. */
308
363
  declare function splitWirePayload(data: string): string[];
309
364
  /**
310
- * Returns the AgentLoop id when present in a message.
311
- * Prefers loop_id field.
365
+ * Returns the StrangeLoop id when present in a message.
366
+ *
367
+ * Under protocol-1, loop-scoped stream events arrive as `next` envelopes
368
+ * whose `payload.data` carries the original frame (with `loop_id`). Raw
369
+ * `status` frames carry `loop_id` at the top level. This helper inspects
370
+ * both shapes.
312
371
  */
313
372
  declare function extractSootheLoopID(msg: unknown): [string, boolean];
314
- /** Generates a new UUID request ID. */
373
+ /** Generates a new UUID correlation ID (RFC-450 §5.2 `id`). */
315
374
  declare function newRequestID(): string;
316
- /** Creates a loop_input message with required fields. */
317
- declare function newLoopInputMessage(loopID: string, content: string): LoopInputMessage;
318
- /** Creates a loop_new message. */
319
- declare function newLoopNewMessage(opts?: LoopNewOptions | string): LoopNewMessage;
320
- /** Creates a loop_subscribe message. */
321
- declare function newLoopSubscribeMessage(loopID: string, verbosity: string, streamDelivery?: 'batch' | 'streaming'): LoopSubscribeMessage;
375
+ /** Creates a `loop_input` notification envelope. */
376
+ declare function newLoopInputMessage(loopID: string, content: string): NotificationEnvelope;
377
+ /** Creates a `loop_new` request envelope. */
378
+ declare function newLoopNewMessage(opts?: LoopNewOptions | string): RequestEnvelope;
379
+ /** Creates a `loop_events` subscribe envelope. */
380
+ declare function newLoopSubscribeMessage(loopID: string, verbosity: string, streamDelivery?: "batch" | "adaptive" | "streaming"): SubscribeEnvelope;
322
381
 
323
382
  /**
324
383
  * Client-facing event namespace constants for the Soothe daemon wire protocol.
@@ -339,18 +398,31 @@ declare const EventTacitusGatherSummary = "soothe.subagent.tacitus.gather.summar
339
398
  declare const EventTacitusCompleted = "soothe.subagent.tacitus.completed";
340
399
  declare const EventReplayComplete = "replay_complete";
341
400
  declare const EventLoopReattachedWire = "loop_reattached";
401
+ declare const EventCardReplayBegin = "card.replay_begin";
402
+ declare const EventCardCreated = "card.created";
403
+ declare const EventCardReplayEnd = "card.replay_end";
342
404
  declare const EventToolStarted = "soothe.tool.execution.started";
343
405
  declare const EventToolCompleted = "soothe.tool.execution.completed";
344
406
  declare const EventToolError = "soothe.tool.execution.error";
345
407
  declare const EventStreamToolCallUpdate = "soothe.stream.tool_call.update";
346
408
  declare const EventToolCallUpdatesBatch = "tool_call_updates_batch";
347
- declare const EventAgentLoopStarted = "soothe.cognition.agent_loop.started";
348
- declare const EventAgentLoopIterated = "soothe.cognition.agent_loop.iterated";
349
- declare const EventAgentLoopCompleted = "soothe.cognition.agent_loop.completed";
350
- declare const EventAgentLoopReasoned = "soothe.cognition.agent_loop.reasoned";
409
+ declare const EventStrangeLoopStarted = "soothe.cognition.strange_loop.started";
410
+ declare const EventStrangeLoopCompleted = "soothe.cognition.strange_loop.completed";
411
+ declare const EventStrangeLoopPlanDecision = "soothe.cognition.strange_loop.plan.decision";
412
+ declare const EventStrangeLoopReasoned = "soothe.cognition.strange_loop.reasoned";
413
+ declare const EventStrangeLoopStepStarted = "soothe.cognition.strange_loop.step.started";
414
+ declare const EventStrangeLoopStepQueued = "soothe.cognition.strange_loop.step.queued";
415
+ declare const EventStrangeLoopStepCompleted = "soothe.cognition.strange_loop.step.completed";
416
+ declare const EventStrangeLoopContextCompacted = "soothe.cognition.strange_loop.context.compacted";
351
417
  declare const EventMessageReceived = "soothe.protocol.message.received";
352
418
  declare const EventMessageSent = "soothe.protocol.message.sent";
353
419
  declare const EventFinalReport = "soothe.output.autonomous.final_report.reported";
420
+ declare const EventAutopilotGoalStatus = "soothe.autopilot.goal.status";
421
+ declare const EventAutopilotGoalProgress = "soothe.autopilot.goal.progress";
422
+ declare const EventAutopilotGoalCreated = "soothe.autopilot.goal.created";
423
+ declare const EventAutopilotGoalCompleted = "soothe.autopilot.goal.completed";
424
+ declare const EventAutopilotWorkerAssigned = "soothe.autopilot.worker.assigned";
425
+ declare const EventAutopilotWorkerUnassigned = "soothe.autopilot.worker.unassigned";
354
426
  declare const EventGeneralFailed = "soothe.error.general.failed";
355
427
  /** Splits a 4-segment event namespace into domain, component, and action. */
356
428
  declare function parseNamespace(ns: string): {
@@ -364,113 +436,365 @@ declare function classifyEventVerbosity(eventTypeOrNamespace: string): Verbosity
364
436
  declare function isCompletionEvent(eventType: string): boolean;
365
437
  /** Lifecycle subagent events (started/completed) for progress UI. */
366
438
  declare function isSubagentProgressEvent(eventType: string): boolean;
367
- /** Essential progress event types for minimal UI surfaces. */
368
- declare const ESSENTIAL_EVENT_TYPES: ReadonlySet<string>;
369
439
 
370
440
  /**
371
- * Client manages a WebSocket session with the Soothe daemon.
372
- * After close(), a new Client must be created to reconnect.
441
+ * Client manages a WebSocket session with the Soothe daemon (RFC-450 protocol-1).
442
+ *
443
+ * After close(), a new Client must be created to reconnect. The connection
444
+ * begins with a bidirectional connection_init/connection_ack handshake; no
445
+ * requests are accepted until the daemon reports readiness_state "ready".
373
446
  */
374
447
 
448
+ /** Input options for `sendInput` (loop_input). */
375
449
  interface InputOptions {
376
- /** Subscribed AgentLoop id (required for loop_input). */
450
+ /** Subscribed StrangeLoop id (required for loop_input). */
377
451
  loopID?: string;
378
452
  autonomous?: boolean;
379
453
  maxIterations?: number;
380
454
  subagent?: string;
455
+ /** @deprecated Ignored by daemon; loop_input always uses non-interactive autonomous mode. */
381
456
  interactive?: boolean;
382
457
  model?: string;
383
458
  modelParams?: Record<string, unknown>;
384
459
  attachments?: Record<string, unknown>[];
460
+ /** Daemon direct-model hint or agent-path pass-through (resume_clarification, skill:foo). */
461
+ intentHint?: LoopInputIntentHint;
462
+ /** JSON Schema for structured output (text_completion or image_to_text). */
463
+ responseSchema?: Record<string, unknown>;
464
+ /** Provider schema name for structured output. */
465
+ responseSchemaName?: string;
466
+ /** Strict mode for JSON schema (default true). */
467
+ responseSchemaStrict?: boolean;
468
+ /** Clarification relay mode ("auto" / "manual"). */
469
+ clarificationMode?: string;
470
+ /** Treat this input as answer to pending clarification interrupt. */
471
+ clarificationAnswer?: boolean;
472
+ /** Per-question answers for multi-question clarifications. */
473
+ clarificationAnswers?: string[];
385
474
  }
475
+ /** Capability set negotiated with the daemon. */
476
+ type NegotiatedCapabilities = ReadonlySet<string>;
386
477
  declare class Client extends EventEmitter {
387
478
  private url;
388
479
  private config;
389
480
  private ws;
390
481
  private messageBuffer;
391
482
  private resolvers;
483
+ private handshakeComplete;
484
+ private negotiatedCapabilities;
485
+ private protocolVersion;
486
+ private readinessState;
487
+ private heartbeatIntervalMs;
488
+ private heartbeatTimer;
489
+ private lastPongMonotonic;
490
+ private disconnFired;
491
+ private mux;
392
492
  constructor(url: string, config?: Config);
393
- /** Dials the Soothe daemon WebSocket. No WS-level ping/pong (RFC-0013). */
493
+ /**
494
+ * Dials the Soothe daemon WebSocket and completes the protocol-1 handshake
495
+ * (connection_init → connection_ack with readiness_state "ready").
496
+ */
394
497
  connect(): Promise<void>;
395
- /** Shuts down the WebSocket connection. */
498
+ /** Sends a `disconnect` notification and closes the WebSocket. */
396
499
  close(): void;
397
- /** Returns whether the client has an active WebSocket connection. */
500
+ /** Returns whether the client has an active, handshaked WebSocket connection. */
398
501
  isConnected(): boolean;
502
+ /**
503
+ * Returns whether the connection has dropped (the `'disconnected'` event has
504
+ * fired). Pair with the `'disconnected'` event for the signal. Use
505
+ * `disconnectCause()` to read the cause.
506
+ */
507
+ isDisconnected(): boolean;
508
+ /**
509
+ * Returns the cause of the most recent drop, or `null` if the connection has
510
+ * not dropped. Clean follows a `disconnect` notification (loops keep running
511
+ * server-side); unclean is a read/write error or missed pong.
512
+ */
513
+ disconnectCause(): DisconnectCause | null;
514
+ private _lastCause;
515
+ /**
516
+ * Delivers the disconnect cause exactly once via the `'disconnected'` event.
517
+ * Safe to call from any path; subsequent calls are no-ops. Listeners receive
518
+ * the cause as the event argument.
519
+ */
520
+ private _signalDisconnect;
521
+ /**
522
+ * Re-dials the daemon and re-handshakes after a connection drop (RFC-450
523
+ * §8.3). Does not re-establish loop subscriptions; follow with
524
+ * `reattachAndProbe()` to resume a loop session. The caller should invoke
525
+ * this after the `'disconnected'` event fires. Reuses the same Client,
526
+ * resetting the drop signal and multiplexer.
527
+ *
528
+ * Performs bounded-retry backoff using the configured reconnect knobs.
529
+ */
530
+ reconnect(): Promise<void>;
531
+ /**
532
+ * Resumes an existing loop after a reconnect: issues `loop_reattach`,
533
+ * re-subscribes to `loop_events`, then runs a `loop_get` liveness probe to
534
+ * detect stale loops that accept the handshake but silently drop input.
535
+ * Returns a `StaleLoopError` when the probe fails; callers should fall back
536
+ * to a fresh `loop_new` bootstrap.
537
+ *
538
+ * Per RFC-629: connection-level readiness is the handshake's readiness_state
539
+ * (+ daemon_status); loop_get is a loop-scoped probe only, not a readiness
540
+ * probe.
541
+ */
542
+ reattachAndProbe(loopID: string): Promise<void>;
543
+ /** Send connection_init and wait for connection_ack with readiness "ready". */
544
+ private _performHandshake;
545
+ private _startHeartbeat;
546
+ private _stopHeartbeat;
547
+ private _heartbeatTick;
548
+ private _sleep;
399
549
  /** Serializes msg as JSON and sends it as a WebSocket text frame. */
400
550
  sendMessage(msg: unknown): Promise<void>;
551
+ /** Low-level send that does not reject on a missing connection (best-effort). */
552
+ private _sendRaw;
401
553
  /** Returns an async iterable of decoded messages. Ends when connection closes. */
402
554
  receiveMessages(signal?: AbortSignal): AsyncGenerator<DecodedMessage>;
403
555
  /** Reads a single event from the daemon. Returns null on connection close. */
404
556
  readEvent(): Promise<Record<string, unknown> | null>;
405
- /** Reads a single event with a timeout. Returns null on timeout or connection close. */
557
+ /** Reads a single event with a timeout. Returns null on timeout or close. */
406
558
  readEventWithTimeout(timeout: number): Promise<Record<string, unknown> | null>;
407
- /** Sends user input to the daemon (loop_input; requires loopID). */
559
+ /**
560
+ * Reads the next frame directly from the live socket (via a resolver),
561
+ * bypassing `messageBuffer`. Used by RPC waits so that stream events
562
+ * previously buffered for `readEvent()`/`receiveMessages()` consumers are
563
+ * not re-cycled through the RPC wait loop (which would stall behind a
564
+ * continuous subscription stream). Non-RPC frames read here are pushed to
565
+ * `messageBuffer` for the stream readers.
566
+ */
567
+ private readLiveEventWithTimeout;
568
+ /**
569
+ * Sends a `request` envelope and waits for the matching `response` (or
570
+ * `error`) correlated by `id` (RFC-450 §5/§9). Returns the `result` object.
571
+ *
572
+ * Multiplexer-aware (RFC-629 constraint #1): registers a pending RPC wait
573
+ * keyed by the request id so that, even when a `receiveMessages()` reader
574
+ * is concurrently active, the matching `response`/`error` is routed to
575
+ * this caller instead of being discarded or buffered behind a stream.
576
+ * Non-matching frames are routed to their own waiters by the multiplexer
577
+ * or flow on to the resolver queue for stream readers.
578
+ */
579
+ requestResponse(method: MethodName, params: Record<string, unknown>, responseType?: string, timeout?: number): Promise<Record<string, unknown>>;
580
+ /**
581
+ * Races the multiplexer's RPC promise against a timeout and the connection
582
+ * drop signal. Resolves with the `result` on `response`; rejects with a
583
+ * `DaemonError` on `error`; rejects with a timeout/close error otherwise.
584
+ * The disconnect listener is always removed to avoid accumulating handlers.
585
+ */
586
+ private _raceRPC;
587
+ /**
588
+ * Sends a pre-built envelope (e.g. `unsubscribe`) that carries an `id` and
589
+ * waits for the matching `response`/`error`. Used for envelope types that
590
+ * are not `request` (e.g. `unsubscribe` → `autopilot_unsubscribe`) but still
591
+ * expect a correlated response from the daemon.
592
+ */
593
+ private _requestResponseForEnvelope;
594
+ /** Sends a fire-and-forget `notification` envelope (no response expected). */
595
+ notify(method: MethodName, params: Record<string, unknown>): Promise<void>;
596
+ /**
597
+ * Starts a subscription stream. Returns the subscription `id` for later
598
+ * correlation and `unsubscribe()`. Stream events arrive as `next` frames
599
+ * carrying the same `id`.
600
+ */
601
+ subscribe(method: "loop_events" | "autopilot_events", params: Record<string, unknown>, timeout?: number): Promise<string>;
602
+ /** Cancels an active subscription by id. */
603
+ unsubscribe(subscriptionId: string): Promise<void>;
604
+ /**
605
+ * Reads the next stream event from a subscription. For `next` frames the
606
+ * `payload` is returned; for `complete`/`error` the full envelope is
607
+ * returned so the caller can inspect termination.
608
+ */
609
+ next(): Promise<Record<string, unknown> | null>;
610
+ /** Sends user input to the daemon (loop_input notification; requires loopID). */
408
611
  sendInput(text: string, options?: InputOptions): Promise<void>;
409
- /** Sends a slash command to the daemon. */
612
+ /** Sends a slash command to the daemon (slash_command notification). */
410
613
  sendCommand(cmd: string): Promise<void>;
411
- /** Requests the daemon to create a new AgentLoop. */
614
+ /** Requests the daemon to create a new StrangeLoop and waits for the response. */
412
615
  sendLoopNew(opts?: LoopNewOptions | string): Promise<void>;
413
- /** Subscribes to events for a loop. */
414
- sendLoopSubscribe(loopID: string, verbosity: string, streamDelivery?: 'batch' | 'streaming'): Promise<void>;
415
- /** Detaches from a loop (keeps loop running). */
416
- sendLoopDetach(loopID: string, requestID?: string): Promise<void>;
417
- /** Notifies the daemon that this client is detaching. */
616
+ /** Subscribes to events for a loop (subscribe → loop_events). */
617
+ sendLoopSubscribe(loopID: string, verbosity: string, streamDelivery?: "batch" | "adaptive" | "streaming"): Promise<void>;
618
+ /** Detaches from a loop (unsubscribe by subscription id). */
619
+ sendLoopDetach(loopID: string): Promise<void>;
620
+ /** Notifies the daemon that this client is leaving (disconnect notification). */
418
621
  sendDetach(): Promise<void>;
419
- /** Sends the daemon_ready handshake message. */
420
- sendDaemonReady(): Promise<void>;
421
622
  /** Requests daemon status check. */
422
- sendDaemonStatus(requestID?: string): Promise<void>;
623
+ sendDaemonStatus(): Promise<void>;
423
624
  /** Requests daemon shutdown. */
424
- sendDaemonShutdown(requestID?: string): Promise<void>;
625
+ sendDaemonShutdown(): Promise<void>;
425
626
  /** Requests a config section from the daemon. */
426
- sendConfigGet(section: string, requestID?: string): Promise<void>;
427
- /** Requests the persisted loop list. */
428
- sendLoopList(filter?: Record<string, unknown>, limit?: number, requestID?: string): Promise<void>;
429
- /** Requests detailed loop metadata. */
430
- sendLoopGet(loopID: string, verbose?: boolean, requestID?: string): Promise<void>;
431
- /** Requests loop tree visualization. */
432
- sendLoopTree(loopID: string, format?: string, requestID?: string): Promise<void>;
433
- /** Requests pruning of old failed branches. */
434
- sendLoopPrune(loopID: string, retentionDays?: number, dryRun?: boolean, requestID?: string): Promise<void>;
435
- /** Requests loop deletion. */
436
- sendLoopDelete(loopID: string, requestID?: string): Promise<void>;
437
- /** Requests reattachment to a loop with history replay. */
438
- sendLoopReattach(loopID: string, requestID?: string): Promise<void>;
439
- /** Requests the skills catalog (RFC-400). */
440
- sendSkillsList(requestID?: string): Promise<void>;
441
- /** Requests the models catalog (RFC-400). */
442
- sendModelsList(requestID?: string): Promise<void>;
443
- /** Invokes a skill on the daemon (RFC-400). */
444
- sendInvokeSkill(skill: string, args?: string, requestID?: string): Promise<void>;
445
- /** Sends a request with a unique request_id and waits for a matching response. */
446
- requestResponse(payload: Record<string, unknown>, responseType: string, timeout: number): Promise<Record<string, unknown>>;
627
+ sendConfigGet(section: string): Promise<void>;
447
628
  /** Requests the skills catalog and waits for the response. */
448
629
  listSkills(timeout?: number): Promise<Record<string, unknown>>;
449
630
  /** Requests the models catalog and waits for the response. */
450
631
  listModels(timeout?: number): Promise<Record<string, unknown>>;
451
- /** Invokes a skill on the daemon host and receives echo (RFC-400). */
632
+ /** Invokes a skill on the daemon host and receives echo. */
452
633
  invokeSkill(skill: string, args?: string, timeout?: number): Promise<Record<string, unknown>>;
453
634
  /** Requests loop list and waits for response. */
454
- listLoops(timeout?: number): Promise<Record<string, unknown>>;
635
+ listLoops(timeout?: number, workspace?: string): Promise<Record<string, unknown>>;
455
636
  /** Requests loop details and waits for response. */
456
637
  getLoop(loopID: string, timeout?: number): Promise<Record<string, unknown>>;
457
638
  /** Requests loop tree and waits for response. */
458
639
  getLoopTree(loopID: string, timeout?: number): Promise<Record<string, unknown>>;
459
640
  /** Requests loop deletion and waits for response. */
460
641
  deleteLoop(loopID: string, timeout?: number): Promise<Record<string, unknown>>;
461
- /** Reads events until a daemon_ready with state == "ready". */
642
+ /** Requests persisted conversation/activity rows. */
643
+ sendLoopMessages(loopID: string, limit?: number, offset?: number, includeEvents?: boolean): Promise<void>;
644
+ /** Requests LangGraph checkpoint channel values. */
645
+ sendLoopStateGet(loopID: string): Promise<void>;
646
+ /** Applies partial checkpoint values. */
647
+ sendLoopStateUpdate(loopID: string, values: Record<string, unknown>, asNode?: string): Promise<void>;
648
+ /** Requests display card ledger snapshot. */
649
+ sendLoopCardsFetch(loopID: string): Promise<void>;
650
+ /** Requests the full loop history (RFC-631). */
651
+ sendLoopHistoryFetch(loopID: string): Promise<void>;
652
+ /** Requests MCP server status. */
653
+ sendMCPStatus(): Promise<void>;
654
+ /** Requests daemon config reload. */
655
+ sendConfigReload(): Promise<void>;
656
+ /** Submits credentials for daemon-side authentication. */
657
+ sendAuth(accessKey: string, secretKey: string): Promise<void>;
658
+ /** Refreshes the daemon-side auth token. */
659
+ sendAuthRefresh(refreshToken: string): Promise<void>;
660
+ /** Requests persisted messages and waits for response. */
661
+ getLoopMessages(loopID: string, limit?: number, offset?: number, includeEvents?: boolean, timeout?: number): Promise<Record<string, unknown>>;
662
+ /** Requests loop state and waits for response. */
663
+ getLoopState(loopID: string, timeout?: number): Promise<Record<string, unknown>>;
664
+ /** Updates loop state and waits for response. */
665
+ updateLoopState(loopID: string, values: Record<string, unknown>, asNode?: string, timeout?: number): Promise<Record<string, unknown>>;
666
+ /** Requests display cards and waits for response. */
667
+ fetchLoopCards(loopID: string, timeout?: number): Promise<Record<string, unknown>>;
668
+ /** Requests MCP status and waits for response. */
669
+ getMCPStatus(timeout?: number): Promise<Record<string, unknown>>;
670
+ /** Requests loop history and waits for response. */
671
+ fetchLoopHistory(loopID: string, timeout?: number): Promise<Record<string, unknown>>;
672
+ /** Requests daemon config reload and waits for response. */
673
+ reloadConfig(timeout?: number): Promise<Record<string, unknown>>;
674
+ /** Submits credentials for daemon-side authentication and waits for response. */
675
+ authenticate(accessKey: string, secretKey: string, timeout?: number): Promise<Record<string, unknown>>;
676
+ /** Refreshes the daemon-side auth token and waits for response. */
677
+ refreshAuthToken(refreshToken: string, timeout?: number): Promise<Record<string, unknown>>;
678
+ /** Creates an autopilot job and waits for the response. */
679
+ createJob(goal: string, verificationRules?: string, workspace?: string, timeout?: number): Promise<Record<string, unknown>>;
680
+ /** Queries job status and waits for the response. */
681
+ getJobStatus(jobId: string, timeout?: number): Promise<Record<string, unknown>>;
682
+ /** Pauses a running job. */
683
+ pauseJob(jobId: string, timeout?: number): Promise<Record<string, unknown>>;
684
+ /** Resumes a paused job. */
685
+ resumeJob(jobId: string, timeout?: number): Promise<Record<string, unknown>>;
686
+ /** Cancels a job. */
687
+ cancelJob(jobId: string, timeout?: number): Promise<Record<string, unknown>>;
688
+ /** Requests the DAG visualization for a job. */
689
+ getJobDag(jobId: string, timeout?: number): Promise<Record<string, unknown>>;
690
+ /** Sends guidance to a job or specific goal. */
691
+ sendJobGuidance(jobId: string, text: string, goalId?: string, timeout?: number): Promise<Record<string, unknown>>;
692
+ /** Subscribes to autopilot worker events. */
693
+ autopilotSubscribe(timeout?: number): Promise<string>;
694
+ /** Unsubscribes from autopilot worker events. */
695
+ autopilotUnsubscribe(timeout?: number): Promise<Record<string, unknown>>;
696
+ /** Creates a scheduled job from natural language. */
697
+ cronAdd(text: string, priority?: number, timeout?: number): Promise<Record<string, unknown>>;
698
+ /** Lists scheduled jobs. */
699
+ cronList(status?: string, timeout?: number): Promise<Record<string, unknown>>;
700
+ /** Shows a specific scheduled job. */
701
+ cronShow(jobId: string, timeout?: number): Promise<Record<string, unknown>>;
702
+ /** Cancels a scheduled job. */
703
+ cronCancel(jobId: string, timeout?: number): Promise<Record<string, unknown>>;
704
+ /**
705
+ * Waits for the connection_ack to report readiness (already done in
706
+ * connect(); kept for callers that reconnect manually). Resolves
707
+ * immediately if the handshake is already complete.
708
+ */
462
709
  waitForDaemonReady(timeout?: number): Promise<Record<string, unknown>>;
463
- /** Waits for subscription confirmation matching loop id. */
464
- waitForSubscriptionConfirmed(loopID: string, _verbosity: string, timeout?: number): Promise<void>;
465
710
  }
466
711
 
467
712
  /**
468
- * Convenience RPC helper functions for the Soothe client.
713
+ * Inbound-frame multiplexer for the protocol-1 client (RFC-629 constraint #1,
714
+ * RFC-450 §5.2/§5.5).
715
+ *
716
+ * Routes inbound protocol-1 frames to the correct waiter by `(type, id)`
717
+ * instead of discarding non-matching events. This makes the Client safe for
718
+ * concurrent RPCs and lets an active subscription stream coexist with RPC
719
+ * waits without starvation.
720
+ *
721
+ * Routing rules:
722
+ * - `response`/`error` with `id` in pending RPCs → pending RPC waiter
723
+ * - `next`/`complete` with `id` in pending subs → pending subscription waiter
724
+ * - `receipt_response` with `receipt` in receipts → receipt waiter
725
+ * - everything else → not consumed (flows to
726
+ * the application event
727
+ * stream / resolver queue)
728
+ *
729
+ * `ping`/`pong`/`connection_ack` are id-less lifecycle frames handled by the
730
+ * Client before reaching the multiplexer; the multiplexer leaves them
731
+ * un-consumed so the Client's existing handlers still see them.
732
+ *
733
+ * A frame routed to a waiter is consumed (returns `true`) and must NOT be
734
+ * forwarded to the resolver queue / event stream.
735
+ */
736
+ /**
737
+ * Multiplexer holds pending RPC, subscription, and receipt waiters keyed by
738
+ * their correlation id. The Client consults `route()` for each inbound frame
739
+ * before pushing it to the resolver queue.
740
+ */
741
+ declare class Multiplexer {
742
+ private rpcs;
743
+ private subs;
744
+ private receipts;
745
+ /**
746
+ * Installs a pending RPC wait keyed by `id`. Returns the pending call and an
747
+ * unregister function that MUST be called when the wait ends (success,
748
+ * timeout, or cancel) to avoid leaks. If a late response arrives after the
749
+ * caller has unregistered, it is dropped (log-and-drop) — no leak.
750
+ */
751
+ registerRPC(id: string): {
752
+ call: Promise<Record<string, unknown>>;
753
+ unregister: () => void;
754
+ };
755
+ /**
756
+ * Installs a pending subscription stream keyed by `id`. Returns the stream
757
+ * channel (an async-iterable-like push sink), a `done` signal, and an
758
+ * unregister function. The Client pushes `next`/`complete` frames via
759
+ * `push`; the application reads from the channel.
760
+ */
761
+ registerSubscription(id: string): {
762
+ push: (frame: Record<string, unknown>) => void;
763
+ done: Promise<void>;
764
+ unregister: () => void;
765
+ };
766
+ /**
767
+ * Installs a pending receipt wait keyed by `receipt`. Returns an unregister
768
+ * function.
769
+ */
770
+ registerReceipt(receipt: string): {
771
+ wait: Promise<Record<string, unknown>>;
772
+ unregister: () => void;
773
+ };
774
+ /**
775
+ * Wires a real sink for a registered subscription's `push`. Called by the
776
+ * Client right after `registerSubscription` to install the channel/queue the
777
+ * application reads from.
778
+ */
779
+ setSubscriptionSink(id: string, sink: (frame: Record<string, unknown>) => void): void;
780
+ /**
781
+ * Inspects one decoded frame, delivers it to a matching waiter if one
782
+ * exists, and returns `true` (consumed). Returns `false` for frames with no
783
+ * matching waiter — these flow on to the resolver queue / event stream.
784
+ * Safe to call from the message handler.
785
+ */
786
+ route(frame: Record<string, unknown>): boolean;
787
+ /** Reports whether an RPC waiter is registered for `id`. */
788
+ hasRPCWaiter(id: string): boolean;
789
+ }
790
+
791
+ /**
792
+ * Convenience RPC helper functions for the Soothe client (RFC-450 protocol-1).
469
793
  */
470
794
 
471
795
  /** Checks daemon status via RPC. */
472
796
  declare function checkDaemonStatus(client: Client, timeout?: number): Promise<Record<string, unknown>>;
473
- /** Performs a composite health check: connect + status RPC. */
797
+ /** Performs a composite health check: connect + handshake + status RPC. */
474
798
  declare function isDaemonLive(wsURL: string, timeout?: number): Promise<boolean>;
475
799
  /** Requests daemon shutdown via RPC. */
476
800
  declare function requestDaemonShutdown(client: Client, timeout?: number): Promise<void>;
@@ -478,20 +802,526 @@ declare function requestDaemonShutdown(client: Client, timeout?: number): Promis
478
802
  declare function fetchSkillsCatalog(client: Client, timeout?: number): Promise<Record<string, unknown>[]>;
479
803
  /** Fetches a daemon config section via RPC. */
480
804
  declare function fetchConfigSection(client: Client, section: string, timeout?: number): Promise<Record<string, unknown>>;
805
+ /** Requests daemon config reload via RPC. */
806
+ declare function requestDaemonConfigReload(client: Client, timeout?: number): Promise<Record<string, unknown>>;
807
+ /** Requests loop history (RFC-631) and waits for the response. */
808
+ declare function fetchLoopHistory(client: Client, loopID: string, timeout?: number): Promise<Record<string, unknown>>;
809
+ /** Submits credentials for daemon-side authentication and waits for the response. */
810
+ declare function authenticate(client: Client, accessKey: string, secretKey: string, timeout?: number): Promise<Record<string, unknown>>;
811
+ /** Refreshes the daemon-side auth token and waits for the response. */
812
+ declare function refreshAuthToken(client: Client, refreshToken: string, timeout?: number): Promise<Record<string, unknown>>;
481
813
 
482
814
  /**
483
- * Session bootstrap flows, wait helpers, and connect-with-retries.
815
+ * loop_new (or reuse id) subscribe(loop_events); returns the loop id.
816
+ * The protocol-1 handshake is assumed to have completed in `client.connect()`.
484
817
  */
485
-
486
- /** Daemon ready → loop_new (or reuse id) → loop_subscribe; returns loop id. */
487
818
  declare function bootstrapLoopSession(client: Client, resumeLoopId: string | null | undefined, config?: Config, loopNew?: LoopNewOptions): Promise<string>;
488
- /** Blocks until a daemon_ready message with state == "ready". */
819
+ /**
820
+ * Blocks until connection_ack reports readiness "ready". Resolves immediately
821
+ * if the handshake already completed during connect().
822
+ */
489
823
  declare function waitDaemonReady(client: Client, timeout: number): Promise<void>;
490
- /** Waits for a status message with a non-empty ``loop_id``. */
491
- declare function waitLoopStatusWithID(client: Client, timeout: number): Promise<StatusResponse>;
492
- /** Waits for subscription_confirmed or loop_subscribe_response matching loop id. */
824
+ /** Waits for a status message with a non-empty loop_id. */
825
+ declare function waitLoopStatusWithID(client: Client, timeout: number): Promise<Record<string, unknown>>;
826
+ /** Waits for a subscription confirmation `next` matching loop id. */
493
827
  declare function waitSubscriptionConfirmed(client: Client, wantLoopID: string, _wantVerbosity: string, timeout: number): Promise<void>;
494
828
  /** Attempts to connect to the Soothe daemon with bounded retries. */
495
829
  declare function connectWithRetries(client: Client, maxRetries?: number, retryDelay?: number): Promise<void>;
496
830
 
497
- export { type BaseMessage, Client, type CommandMessage, type Config, type ConfigGetMessage, ConnectionError, DaemonError, type DaemonReadyResponse, type DaemonShutdownMessage, type DaemonStatusMessage, type DaemonStatusResponse, type DecodedMessage, type DetachMessage, ESSENTIAL_EVENT_TYPES, type ErrorResponse, EventAgentLoopCompleted, EventAgentLoopIterated, EventAgentLoopReasoned, EventAgentLoopStarted, EventExploreCompleted, EventExploreMilestone, EventExploreStarted, EventExploreStepCompleted, EventFinalReport, EventGeneralFailed, EventLoopReattachedWire, type EventMessage, EventMessageReceived, EventMessageSent, EventPlanCreated, EventReplayComplete, EventStreamToolCallUpdate, EventTacitusCompleted, EventTacitusGatherSummary, EventTacitusStarted, EventToolCallUpdatesBatch, EventToolCompleted, EventToolError, EventToolStarted, type HistoryReplayCompleteMessage, type HistoryReplayMessage, type InputOptions, type InvokeSkillMessage, type LoopDeleteMessage, type LoopDeleteResponse, type LoopDetachMessage, type LoopDetachResponse, type LoopGetMessage, type LoopGetResponse, type LoopInputMessage, type LoopListMessage, type LoopListResponse, type LoopNewMessage, type LoopNewOptions, type LoopNewResponse, type LoopPruneMessage, type LoopPruneResponse, type LoopReattachMessage, type LoopReattachResponse, type LoopSubscribeMessage, type LoopSubscribeResponse, type LoopTreeMessage, type LoopTreeResponse, type ModelsListMessage, type ModelsListResponse, type ShutdownAckResponse, type SkillsListMessage, type SkillsListResponse, type StatusResponse, type SubscriptionConfirmedResponse, TimeoutError, type VerbosityLevel, VerbosityTier, bootstrapLoopSession, checkDaemonStatus, classifyEventVerbosity, connectWithRetries, decodeMessage, defaultConfig, encodeMessage, extractSootheLoopID, fetchConfigSection, fetchSkillsCatalog, isCompletionEvent, isDaemonLive, isSubagentProgressEvent, isValidVerbosityLevel, loadConfigFromEnv, newLoopInputMessage, newLoopNewMessage, newLoopSubscribeMessage, newRequestID, parseNamespace, requestDaemonShutdown, shouldShow, splitWirePayload, waitDaemonReady, waitLoopStatusWithID, waitSubscriptionConfirmed };
831
+ /**
832
+ * Persistence seam for appkit (RFC-629 Layer 1).
833
+ *
834
+ * SessionStore abstracts per-application storage: the session↔loop-id mapping
835
+ * that ConnectionPool consults to decide bootstrap vs reattach, and the
836
+ * message rows TurnRunner writes back when a turn completes. Applications
837
+ * implement this against their own store (Postgres, Redis, in-memory, …).
838
+ *
839
+ * Implementations must be safe for concurrent use.
840
+ */
841
+ /** Persisted mapping between an application session id and the daemon loop id. */
842
+ interface SessionEntry {
843
+ workspaceID: string;
844
+ sessionID: string;
845
+ loopID: string;
846
+ /** App-defined taxonomy (e.g. "primary" | "ephemeral"). */
847
+ sessionType: string;
848
+ /** Optional app key for ephemeral internal features. */
849
+ purpose?: string;
850
+ isActive: boolean;
851
+ resetCount: number;
852
+ lastUsedAt: number;
853
+ }
854
+ /** A persisted message row (assistant reply or error). */
855
+ interface SessionMessage {
856
+ id?: string;
857
+ /** "assistant" | "user" | "error". */
858
+ role: string;
859
+ content: string;
860
+ context?: unknown;
861
+ metadata?: Record<string, unknown>;
862
+ }
863
+ /**
864
+ * Persistence seam between appkit and the application's storage backend.
865
+ *
866
+ * ConnectionPool consults the store to decide whether to bootstrap a fresh
867
+ * loop (no loop id on file) or reattach to an existing one, and records the
868
+ * loop id once bootstrapped. TurnRunner persists the final assistant reply
869
+ * and error rows via appendMessage.
870
+ */
871
+ interface SessionStore {
872
+ /** Returns the persisted entry for sessionID, or null if no record exists. */
873
+ getSession(sessionID: string): Promise<SessionEntry | null>;
874
+ /** Persists a new session↔loop mapping. */
875
+ createSession(workspaceID: string, sessionID: string, loopID: string, sessionType: string): Promise<void>;
876
+ /** Stamps the session's last-used timestamp. */
877
+ updateLastUsed(sessionID: string): Promise<void>;
878
+ /** Bumps the reset counter (used to decide fresh bootstrap vs reattach). */
879
+ incrementResetCount(sessionID: string): Promise<void>;
880
+ /**
881
+ * Returns the daemon loop id for sessionID and whether one is on file.
882
+ * ok===false triggers a fresh loop_new bootstrap.
883
+ */
884
+ getLoopIDForSession(sessionID: string): Promise<{
885
+ loopID: string;
886
+ ok: boolean;
887
+ }>;
888
+ /** Writes a message row (assistant reply, error, etc.) for the session. */
889
+ appendMessage(sessionID: string, message: SessionMessage): Promise<void>;
890
+ }
891
+
892
+ /**
893
+ * SSE-style pub/sub fan-out for appkit (RFC-629 Layer 1).
894
+ *
895
+ * Generic, string-keyed pub/sub for SSE-style event delivery. The
896
+ * application-agnostic successor to a domain-keyed broadcaster: applications
897
+ * convert from their domain key type to `string` at their own boundary.
898
+ *
899
+ * Slow consumers do not stall the broadcaster: each subscriber has a bounded
900
+ * queue and overflowing events are dropped (drop-on-full).
901
+ */
902
+ /** One Server-Sent Event payload. The Type vocabulary is app-defined. */
903
+ interface SSEEvent {
904
+ type: string;
905
+ data: unknown;
906
+ }
907
+ /**
908
+ * SSEBroadcaster fans events out to all subscribers for a session id.
909
+ * Non-blocking: a full subscriber queue drops the event so one slow consumer
910
+ * cannot block the others.
911
+ */
912
+ declare class SSEBroadcaster {
913
+ private subscribers;
914
+ private nextSubID;
915
+ /** Creates an empty broadcaster. */
916
+ constructor();
917
+ /**
918
+ * Registers a new subscriber channel for a session id. Returns an async
919
+ * iterable the subscriber reads events from. Unsubscribe via
920
+ * `unsubscribe()` or `close()`.
921
+ */
922
+ subscribe(sessionID: string): {
923
+ iterable: AsyncIterable<SSEEvent>;
924
+ id: string;
925
+ };
926
+ /** Removes a subscriber by id and closes its iterable. Safe if unknown. */
927
+ unsubscribe(sessionID: string, subID: string): void;
928
+ /**
929
+ * Sends an event to all subscribers for a session id. Non-blocking: a full
930
+ * subscriber queue is skipped (drop-on-full) so one slow consumer cannot
931
+ * block the others.
932
+ */
933
+ broadcast(sessionID: string, event: SSEEvent): void;
934
+ /** Closes all subscribers for a session id and removes the entry. */
935
+ close(sessionID: string): void;
936
+ /** Closes every subscriber channel across all sessions. */
937
+ closeAll(): void;
938
+ }
939
+
940
+ /**
941
+ * Thinking-step extraction for appkit (RFC-629 Layer 1).
942
+ *
943
+ * Maps an allowlisted progress event to one structured UI line. Free-form
944
+ * streams (tokens, reports, reasoning) are excluded. Ported from the Go
945
+ * appkit's thinking_step with the allowlist made configurable.
946
+ */
947
+ /** Default thinking-step event allowlist (triarch's set). */
948
+ declare const DEFAULT_THINKING_STEP_EVENTS: ReadonlySet<string>;
949
+ /**
950
+ * Maps an allowlisted progress event to one structured UI line. Returns
951
+ * [line, true] for a recognized event; ["", false] otherwise. `allow` may be
952
+ * omitted to use the default allowlist.
953
+ */
954
+ declare function extractThinkingStep(eventType: string, data: Record<string, unknown> | null, allow?: ReadonlySet<string>): [string, boolean];
955
+
956
+ /**
957
+ * Event classifier for appkit (RFC-629 Layer 1).
958
+ *
959
+ * Maps a stream of decoded daemon events into deliverable/streaming/terminal
960
+ * outcomes, keyed on (namespace, mode, phase) per RFC-614/RFC-403
961
+ * (RFC-629 constraint #4). The app-agnostic successor to triarch's
962
+ * ProcessChatEvent, with the deliverable phase set promoted from hardcoded
963
+ * constants to configuration.
964
+ *
965
+ * Event shape: a protocol-1 `next` envelope carries
966
+ * `{type:"next", payload:{namespace, mode, data, loop_id}}`. The daemon
967
+ * wraps legacy free-form frames as `{payload:{namespace, mode:<orig type>,
968
+ * data:<orig frame>}}` (RFC-450 §9.3). The classifier inspects the payload's
969
+ * `mode`/`data`/`namespace` and the inner message's `phase`/`type`/`content`.
970
+ */
971
+
972
+ /** How a processed event should end the query loop. */
973
+ declare enum ChatEventTerminal {
974
+ /** Accumulate content; the query is still running. */
975
+ Continue = 0,
976
+ /** A user-visible final reply; persist it. */
977
+ DeliverableComplete = 1,
978
+ /** The query failed; persist an error. */
979
+ FailedComplete = 2
980
+ }
981
+ /** The structured outcome of classifying one daemon event. */
982
+ interface ChatEventResult {
983
+ content?: string;
984
+ /** User-visible progress line (not a final reply). */
985
+ thinkingStep?: string;
986
+ terminal: ChatEventTerminal;
987
+ /** soothe wire event type when terminal === DeliverableComplete. */
988
+ completionEvent?: string;
989
+ err?: Error;
990
+ }
991
+ /**
992
+ * Product-specific decisions an EventClassifier needs. The DeliverablePhases
993
+ * set is the key product knob: which message `phase` values count as
994
+ * user-facing deliverables (triarch uses quiz, goal_completion, direct_model, and
995
+ * direct intent_hint phases text_completion, image_to_text, ocr, embed;
996
+ * other apps pass their own).
997
+ */
998
+ interface ClassifierConfig {
999
+ /** Recognizes loop-tagged message phases that may end a query with
1000
+ * user-facing text. Required. */
1001
+ deliverablePhases: ReadonlySet<string>;
1002
+ /** Minimum trimmed rune count for a reply to be persisted as final
1003
+ * (avoids finishing on stub ACKs like "..."). Defaults to 8. */
1004
+ minDeliverableRunes?: number;
1005
+ /** Optional app override of the default thinking-step event allowlist. */
1006
+ thinkingStepEvents?: ReadonlySet<string>;
1007
+ }
1008
+ /** Maps a stream of decoded daemon events into deliverable/streaming/terminal outcomes. */
1009
+ declare class EventClassifier {
1010
+ private deliverablePhases;
1011
+ private minDeliverableRunes;
1012
+ private thinkingStepEvents?;
1013
+ constructor(cfg: ClassifierConfig);
1014
+ /**
1015
+ * Inspects one decoded event and returns its outcome. `accumulated` is the
1016
+ * running assistant text so far, used to pick the final reply when a
1017
+ * deliverable event arrives.
1018
+ */
1019
+ classify(msg: unknown, accumulated: string): ChatEventResult;
1020
+ /**
1021
+ * Reports whether a persisted completion_event is user-facing. Uses the
1022
+ * configured deliverable phase set; recognizes the protocol output namespace
1023
+ * and final_report component as deliverable.
1024
+ */
1025
+ isDeliverableCompletionEvent(eventType: string): boolean;
1026
+ isDeliverableLoopPhase(phase: string): boolean;
1027
+ private deliverableResult;
1028
+ private continueResult;
1029
+ private failedResult;
1030
+ /** Reports whether trimmed assistant text is long enough to persist as final. */
1031
+ isSubstantiveAssistantReply(content: string): boolean;
1032
+ /**
1033
+ * Picks the user-visible reply for a completed query. Only a deliverable
1034
+ * terminal result with a recognized completion event yields a final reply.
1035
+ */
1036
+ resolveDeliverableFinalContent(eventResult: ChatEventResult, _accumulated: string): [string, boolean];
1037
+ /** The event→outcome mapper, ported from triarch's ProcessChatEvent. */
1038
+ private processChatEvent;
1039
+ /** Classifies a `next` envelope by projecting its payload. */
1040
+ private classifyNextEnvelope;
1041
+ /**
1042
+ * Classifies an event payload by (namespace, mode, phase). `data` may be a
1043
+ * map or an array of messages (mode="messages").
1044
+ */
1045
+ private classifyEventPayload;
1046
+ /** Classifies a mode="messages" payload (array of message objects). */
1047
+ private classifyMessagesMode;
1048
+ /**
1049
+ * Extracts plain assistant text from mode="messages" events that carry a
1050
+ * terminal AIMessage without loop-tagged phase metadata (legacy direct_llm turns
1051
+ * before phase tagging; prefer deliverablePhases including text_completion).
1052
+ */
1053
+ private messagesModeAssistantContent;
1054
+ /** soothe output/responded events that carry user-facing final text. */
1055
+ private isFinalOutputEvent;
1056
+ }
1057
+
1058
+ /**
1059
+ * Single-flight query gate for appkit (RFC-629 Layer 1).
1060
+ *
1061
+ * Enforces single-flight query execution per session id and the
1062
+ * cancel-before-context ordering: when a query is cancelled, the daemon is
1063
+ * told to stop (command_request{command:"cancel"}) BEFORE the local abort
1064
+ * signal is cancelled, on a detached timeout so the caller's cancellation
1065
+ * cannot block the wire send.
1066
+ *
1067
+ * The app-agnostic successor to triarch's AcquireQuery/CancelQuery/
1068
+ * sendLoopCancelCommand.
1069
+ */
1070
+ /** Returned when a session already has an in-flight query. */
1071
+ declare class ErrQueryBusy extends Error {
1072
+ constructor();
1073
+ }
1074
+ /**
1075
+ * QueryGate enforces single-flight query execution per session id.
1076
+ */
1077
+ declare class QueryGate {
1078
+ private active;
1079
+ /** Constructs an empty gate. */
1080
+ constructor();
1081
+ /**
1082
+ * Reserves sessionID for one agent turn. Returns ErrQueryBusy if a query is
1083
+ * already in flight. `abort` is the AbortController for the query's timeout
1084
+ * context. `sendCancel` is the daemon-cancel sender; it is invoked from
1085
+ * `cancel()` on a detached 10s timeout.
1086
+ */
1087
+ acquire(sessionID: string, abort: AbortController, sendCancel: ((signal: AbortSignal) => Promise<void>) | null): void;
1088
+ /**
1089
+ * Cooperatively stops a running query for sessionID. Sends the daemon cancel
1090
+ * (on a detached 10s-timeout abort so caller cancellation cannot block the
1091
+ * wire send) BEFORE aborting the local context. Returns silently if no query
1092
+ * is in flight (intent already satisfied).
1093
+ */
1094
+ cancel(sessionID: string): Promise<void>;
1095
+ /**
1096
+ * Clears the gate for sessionID without sending a daemon cancel. Call when a
1097
+ * query completes normally (success or local failure) so the next turn can
1098
+ * acquire.
1099
+ */
1100
+ release(sessionID: string): void;
1101
+ /** Reports whether a query is in flight for sessionID. */
1102
+ isActive(sessionID: string): boolean;
1103
+ }
1104
+
1105
+ /**
1106
+ * ManagedClient — the subset of the core Client that appkit's ConnectionPool
1107
+ * and TurnRunner depend on (RFC-629 Layer 1).
1108
+ *
1109
+ * The concrete `Client` satisfies it; tests supply a fake. Defining it as an
1110
+ * interface lets appkit be unit-tested without a live WebSocket daemon.
1111
+ */
1112
+
1113
+ /**
1114
+ * ManagedClient is the subset of the core Client that appkit depends on.
1115
+ * Methods are async (TS) rather than channel-based (Go).
1116
+ */
1117
+ interface ManagedClient {
1118
+ /** Dials and handshakes. */
1119
+ connect(): Promise<void>;
1120
+ /** Re-dials after a drop. */
1121
+ reconnect(): Promise<void>;
1122
+ /** Resumes a loop by id and probes liveness. */
1123
+ reattachAndProbe(loopID: string): Promise<void>;
1124
+ /** Sends a fire-and-forget notification (e.g. loop_input). */
1125
+ sendMessage(msg: unknown): Promise<void>;
1126
+ /** Sends user input to the daemon (loop_input notification). */
1127
+ sendInput(text: string, options?: InputOptions): Promise<void>;
1128
+ /** Starts the read loop, returning the event stream. */
1129
+ receiveMessages(signal?: AbortSignal): AsyncGenerator<DecodedMessage>;
1130
+ /** Returns whether the connection has dropped. */
1131
+ isDisconnected(): boolean;
1132
+ /** Returns the drop cause, or null if not dropped. */
1133
+ disconnectCause(): DisconnectCause | null;
1134
+ /** Reports connection liveness. */
1135
+ isConnected(): boolean;
1136
+ /** Tears down the connection. */
1137
+ close(): void;
1138
+ }
1139
+ /**
1140
+ * Builds a fresh ManagedClient for a daemon URL and config. ConnectionPool
1141
+ * calls it per pooled connection. Applications may supply a custom factory
1142
+ * (e.g. wrapping Client with logging/metrics).
1143
+ */
1144
+ type ClientFactory = (url: string, config?: Config) => ManagedClient;
1145
+ /** Returns a ClientFactory that builds a core Client. */
1146
+ declare function defaultClientFactory(): ClientFactory;
1147
+ /**
1148
+ * Creates a new loop (loop_new + subscribe) on a connected client and returns
1149
+ * the new loop id. The default implementation calls bootstrapLoopSession;
1150
+ * apps may override it.
1151
+ */
1152
+ type BootstrapFunc = (client: ManagedClient, workspaceID: string, userID: string, config?: Config) => Promise<string>;
1153
+ /** Default bootstrap: loop_new + subscribe(loop_events). */
1154
+ declare function defaultBootstrapFunc(): BootstrapFunc;
1155
+
1156
+ /**
1157
+ * Per-session connection pool for appkit (RFC-629 Layer 1).
1158
+ *
1159
+ * Manages a pool of daemon connections, one active per session. Reuses an
1160
+ * active connection when still live, otherwise bootstraps a fresh loop
1161
+ * (loop_new + subscribe) or reattaches an existing one (loop_reattach +
1162
+ * subscribe + reattachAndProbe). Persistence of session↔loop mappings is
1163
+ * abstracted behind SessionStore.
1164
+ *
1165
+ * The app-agnostic successor to triarch's SoothePoolManager connection
1166
+ * mechanics.
1167
+ */
1168
+
1169
+ /** Returned when no free connection slot is available. */
1170
+ declare class ErrPoolExhausted extends Error {
1171
+ constructor();
1172
+ }
1173
+ /** Configures a ConnectionPool. Zero values use defaults. */
1174
+ interface PoolConfig {
1175
+ poolSize: number;
1176
+ queryTimeout: number;
1177
+ connectionTimeout: number;
1178
+ maxIdleTime: number;
1179
+ healthCheckInterval: number;
1180
+ }
1181
+ /** Returns env-overridable defaults (mirrors triarch). */
1182
+ declare function defaultPoolConfig(): PoolConfig;
1183
+ /** One connection slot in the pool. */
1184
+ declare class PooledConn {
1185
+ slotID: number;
1186
+ client: ManagedClient;
1187
+ eventStream: AsyncGenerator<DecodedMessage> | null;
1188
+ streamController: AbortController | null;
1189
+ sessionID: string;
1190
+ loopID: string;
1191
+ workspaceID: string;
1192
+ lastUsed: number;
1193
+ constructor(slotID: number, client: ManagedClient);
1194
+ /** Reports whether the underlying client signalled a drop. */
1195
+ isDisconnected(): boolean;
1196
+ isConnected(): boolean;
1197
+ getLoopID(): string;
1198
+ }
1199
+ /**
1200
+ * ConnectionPool manages a pool of daemon connections, one active per session.
1201
+ */
1202
+ declare class ConnectionPool {
1203
+ private cfg;
1204
+ private scfg;
1205
+ private factory;
1206
+ private bootstrap;
1207
+ private store;
1208
+ private pool;
1209
+ private activeSlots;
1210
+ private nextSlotID;
1211
+ private url;
1212
+ /**
1213
+ * Constructs a pool. `url` is the daemon WebSocket URL. If cfg is null,
1214
+ * defaultPoolConfig is used; if scfg is null, defaultConfig is used; nil
1215
+ * factory/bootstrap fall back to the defaults.
1216
+ */
1217
+ constructor(url: string, store: SessionStore, cfg?: PoolConfig | null, scfg?: Config | null, factory?: ClientFactory | null);
1218
+ /** Overrides the loop bootstrap function (useful for test fakes). */
1219
+ withBootstrap(f: BootstrapFunc): ConnectionPool;
1220
+ /**
1221
+ * Returns a live connection for sessionID, reusing an active slot or
1222
+ * bootstrapping/reattaching as needed. The caller must call `release()`
1223
+ * when done with the connection (a turn completes or the session is reset).
1224
+ */
1225
+ acquire(sessionID: string, workspaceID: string, userID: string, _signal?: AbortSignal): Promise<PooledConn>;
1226
+ /** Tears down the connection for sessionID and returns the slot. */
1227
+ release(sessionID: string): Promise<void>;
1228
+ /**
1229
+ * Tears down the connection for sessionID so the next acquire bootstraps
1230
+ * fresh. The store should archive the loop id so getLoopIDForSession returns
1231
+ * false next time.
1232
+ */
1233
+ resetSession(sessionID: string): Promise<void>;
1234
+ /** Gracefully shuts down all active connections. */
1235
+ stop(): void;
1236
+ /** Stats snapshot for observability. */
1237
+ stats(): {
1238
+ active: number;
1239
+ idle: number;
1240
+ };
1241
+ /** Bootstrap a fresh loop and start the reader. */
1242
+ private bootstrapNew;
1243
+ /** Reconnect + reattach an existing loop, then start the reader. */
1244
+ private resumeAndReattach;
1245
+ /** Starts a receiveMessages generator and stores the stream + controller. */
1246
+ private startReader;
1247
+ }
1248
+
1249
+ /**
1250
+ * Turn runner for appkit (RFC-629 Layer 1).
1251
+ *
1252
+ * Executes one query turn end-to-end: acquire a pooled connection, enforce
1253
+ * single-flight, send loop_input, consume the event stream, classify events,
1254
+ * resolve the deliverable, persist the reply, and broadcast completion.
1255
+ *
1256
+ * The app-agnostic successor to triarch's ExecuteQuery.
1257
+ */
1258
+
1259
+ /** Returned when a turn exceeds the configured timeout. */
1260
+ declare class ErrQueryTimeout extends Error {
1261
+ constructor();
1262
+ }
1263
+ /** Configures a TurnRunner. */
1264
+ interface TurnConfig {
1265
+ /** Per-turn deadline in ms. Defaults to 30m. */
1266
+ queryTimeout: number;
1267
+ }
1268
+ /** Carries optional daemon hints on a loop_input payload. */
1269
+ interface InputOpts {
1270
+ intentHint?: LoopInputIntentHint;
1271
+ preferredSubagent?: string;
1272
+ responseSchema?: Record<string, unknown>;
1273
+ responseSchemaName?: string;
1274
+ responseSchemaStrict?: boolean;
1275
+ }
1276
+ /** Optional attachment shape (IG-327: {mime_type, data(base64)}). */
1277
+ type Attachment = Record<string, unknown>;
1278
+ /**
1279
+ * Builds a loop_input payload with optional attachments. Apps build this from
1280
+ * their product modes (e.g. triarch's ask/agent/deep-research).
1281
+ */
1282
+ declare function inputMessageForLoop(text: string, loopID: string, attachments?: Attachment[], opts?: InputOpts): Record<string, unknown>;
1283
+ /** Completion hook signature. */
1284
+ type OnComplete = (sessionID: string, loopID: string, content: string, completionEvent: string, elapsedMs: number) => void;
1285
+ /** Error hook signature. */
1286
+ type OnError = (sessionID: string, loopID: string, err: Error) => void;
1287
+ /**
1288
+ * TurnRunner executes one query turn end-to-end.
1289
+ */
1290
+ declare class TurnRunner {
1291
+ private pool;
1292
+ private gate;
1293
+ private classifier;
1294
+ private store;
1295
+ private broadcaster;
1296
+ private cfg;
1297
+ private buildInput;
1298
+ private onComplete;
1299
+ private onError;
1300
+ /**
1301
+ * Constructs a TurnRunner. pool, gate, classifier, and store are required;
1302
+ * broadcaster may be null.
1303
+ */
1304
+ constructor(pool: ConnectionPool, gate: QueryGate, classifier: EventClassifier, store: SessionStore, broadcaster: SSEBroadcaster | null, cfg: TurnConfig);
1305
+ /** Overrides the loop_input payload builder. */
1306
+ withInputBuilder(f: typeof inputMessageForLoop): TurnRunner;
1307
+ /** Sets a completion hook (runs inline on success). */
1308
+ withOnComplete(f: OnComplete): TurnRunner;
1309
+ /** Sets an error hook (runs inline on failure). */
1310
+ withOnError(f: OnError): TurnRunner;
1311
+ /**
1312
+ * Runs one query turn. The response is broadcast via the SSE broadcaster and
1313
+ * persisted via the SessionStore; it is not returned to the caller (SSE
1314
+ * subscribers receive it). Resolves on success; rejects on failure
1315
+ * (ErrQueryTimeout, AbortError, or a daemon/processing error).
1316
+ */
1317
+ execute(sessionID: string, message: string, userID: string, workspaceID: string, attachments: Attachment[] | null, opts: InputOpts | null, signal?: AbortSignal): Promise<void>;
1318
+ /** Asks the daemon to cooperatively stop the loop runner on a detached signal. */
1319
+ private sendLoopCancel;
1320
+ private persistResponse;
1321
+ private persistFailed;
1322
+ private broadcastThinkingStep;
1323
+ private broadcastComplete;
1324
+ private broadcastError;
1325
+ }
1326
+
1327
+ export { type Attachment, type BaseEnvelope, type BootstrapFunc, CLIENT_VERSION, type ChatEventResult, ChatEventTerminal, type ClassifierConfig, Client, type ClientFactory, type CompleteEnvelope, type Config, type ConnectionAckEnvelope, ConnectionError, type ConnectionInitEnvelope, ConnectionPool, DEFAULT_CLIENT_CAPABILITIES, DEFAULT_DELIVERABLE_PHASES, DEFAULT_THINKING_STEP_EVENTS, DaemonError, type DecodedMessage, DisconnectCause, type DisconnectEnvelope, ErrPoolExhausted, ErrQueryBusy, ErrQueryTimeout, type ErrorEnvelope, EventAutopilotGoalCompleted, EventAutopilotGoalCreated, EventAutopilotGoalProgress, EventAutopilotGoalStatus, EventAutopilotWorkerAssigned, EventAutopilotWorkerUnassigned, EventCardCreated, EventCardReplayBegin, EventCardReplayEnd, EventClassifier, EventExploreCompleted, EventExploreMilestone, EventExploreStarted, EventExploreStepCompleted, EventFinalReport, EventGeneralFailed, EventLoopReattachedWire, EventMessageReceived, EventMessageSent, EventPlanCreated, EventReplayComplete, EventStrangeLoopCompleted, EventStrangeLoopContextCompacted, EventStrangeLoopPlanDecision, EventStrangeLoopReasoned, EventStrangeLoopStarted, EventStrangeLoopStepCompleted, EventStrangeLoopStepQueued, EventStrangeLoopStepStarted, EventStreamToolCallUpdate, EventTacitusCompleted, EventTacitusGatherSummary, EventTacitusStarted, EventToolCallUpdatesBatch, EventToolCompleted, EventToolError, EventToolStarted, INTENT_HINT_EMBED, INTENT_HINT_IMAGE_TO_TEXT, INTENT_HINT_OCR, INTENT_HINT_TEXT_COMPLETION, type InputOptions, type InputOpts, type IntentHint, LOOP_ASSISTANT_OUTPUT_PHASES, type LoopAssistantOutputPhase, type LoopInputIntentHint, type LoopInputParams, type LoopNewOptions, type ManagedClient, type MessageType, type MethodName, Multiplexer, type NegotiatedCapabilities, type NextEnvelope, type NotificationEnvelope, type OnComplete, type OnError, PROTO_VERSION, type PingEnvelope, type PongEnvelope, type PoolConfig, PooledConn, QueryGate, REMOVED_INTENT_HINTS, type ReceiptResponseEnvelope, ReconnectError, type RemovedIntentHint, type RequestEnvelope, type ResponseEnvelope, SSEBroadcaster, type SSEEvent, type SessionEntry, type SessionMessage, type SessionStore, StaleLoopError, type StatusFrame, type StreamEventPayload, type SubscribeEnvelope, TimeoutError, type TurnConfig, TurnRunner, type UnsubscribeEnvelope, type VerbosityLevel, VerbosityTier, authenticate, bootstrapLoopSession, checkDaemonStatus, classifyEventVerbosity, connectWithRetries, connectionInitEnvelope, decodeMessage, defaultBootstrapFunc, defaultClientFactory, defaultConfig, defaultPoolConfig, disconnectCauseName, disconnectEnvelope, encodeMessage, extractSootheLoopID, extractThinkingStep, fetchConfigSection, fetchLoopHistory, fetchSkillsCatalog, inputMessageForLoop, isCompletionEvent, isDaemonLive, isSubagentProgressEvent, isValidVerbosityLevel, loadConfigFromEnv, newLoopInputMessage, newLoopNewMessage, newLoopSubscribeMessage, newRequestID, notificationEnvelope, parseNamespace, pingEnvelope, pongEnvelope, refreshAuthToken, requestDaemonConfigReload, requestDaemonShutdown, requestEnvelope, shouldShow, splitWirePayload, subscribeEnvelope, unsubscribeEnvelope, validateLoopInputIntentHint, waitDaemonReady, waitLoopStatusWithID, waitSubscriptionConfirmed };