@agentchatme/openclaw 0.6.11 → 0.6.13

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.
@@ -0,0 +1,759 @@
1
+ import { A as AgentchatChannelConfig } from './setup-entry-Dtj6vwDY.cjs';
2
+ export { a as AgentchatResolvedAccount, b as agentchatChannelEntry, c as agentchatPlugin, d as agentchatSetupEntry, c as agentchatSetupPlugin, b as default, p as parseChannelConfig } from './setup-entry-Dtj6vwDY.cjs';
3
+ export { hasAgentChatConfiguredState } from './configured-state.cjs';
4
+ import { WebSocket } from 'ws';
5
+ import 'openclaw/plugin-sdk/channel-core';
6
+ import 'zod';
7
+
8
+ interface Logger {
9
+ trace(obj: object, msg?: string): void;
10
+ debug(obj: object, msg?: string): void;
11
+ info(obj: object, msg?: string): void;
12
+ warn(obj: object, msg?: string): void;
13
+ error(obj: object, msg?: string): void;
14
+ child(bindings: Record<string, unknown>): Logger;
15
+ }
16
+
17
+ /**
18
+ * Optional Prometheus metrics for the channel plugin.
19
+ *
20
+ * Design: the gateway owns the `/metrics` endpoint. Plugins do not expose
21
+ * their own port. If the user passes a `prom-client` Registry via channel
22
+ * runtime context, we register our counters/gauges/histograms into it.
23
+ * Otherwise, we return a no-op recorder — zero overhead, zero config.
24
+ *
25
+ * Names are prefixed with `agentchat_channel_` for easy scraping.
26
+ */
27
+ interface MetricsRecorder {
28
+ /** Incremented on every successfully delivered inbound message. */
29
+ incInboundDelivered(labels: {
30
+ kind: InboundKind;
31
+ }): void;
32
+ /** Incremented on every successfully sent outbound message. */
33
+ incOutboundSent(labels: {
34
+ kind: OutboundKind;
35
+ }): void;
36
+ /** Incremented on every send failure, labeled by error class. */
37
+ incOutboundFailed(labels: {
38
+ errorClass: string;
39
+ }): void;
40
+ /** Incremented on every reconnect attempt. */
41
+ incReconnect(labels: {
42
+ reason: string;
43
+ }): void;
44
+ /** Observe end-to-end send latency (ms). */
45
+ observeSendLatency(ms: number): void;
46
+ /** Set the current connection state (cardinality bounded by state kinds). */
47
+ setConnectionState(kind: string): void;
48
+ /** Set the current in-flight send queue depth. */
49
+ setInFlightDepth(n: number): void;
50
+ }
51
+ type InboundKind = 'message' | 'group-message' | 'typing' | 'read' | 'presence' | 'rate-limit-warning';
52
+ type OutboundKind = 'message' | 'typing' | 'read';
53
+
54
+ /**
55
+ * Error taxonomy for the AgentChat channel plugin.
56
+ *
57
+ * Every error that crosses a pipeline boundary (inbound receive, outbound
58
+ * send, config validation, transport) must be classifiable as one of:
59
+ *
60
+ * terminal-auth — 401/403. API key invalid or revoked. Do not retry.
61
+ * Move state machine to AUTH_FAIL.
62
+ * terminal-user — 400/422 (client-side validation). Bug in this plugin
63
+ * or malformed outbound. Log at error level, drop,
64
+ * do not retry.
65
+ * retry-rate — 429. Retry after `retryAfterMs` (or jittered
66
+ * exponential backoff if header absent).
67
+ * retry-transient — 5xx, ECONNRESET, ETIMEDOUT, network flap.
68
+ * Exponential backoff + jitter. Open circuit after N.
69
+ * idempotent-replay — 409 on POST /v1/messages with duplicate
70
+ * client_msg_id. Treat as success, log at info.
71
+ * validation — Zod schema failed on inbound payload. Likely a
72
+ * server change. Log at error, drop, alert.
73
+ */
74
+ type ErrorClass = 'terminal-auth' | 'terminal-user' | 'retry-rate' | 'retry-transient' | 'idempotent-replay' | 'validation';
75
+ declare class AgentChatChannelError extends Error {
76
+ readonly class_: ErrorClass;
77
+ readonly retryAfterMs: number | undefined;
78
+ readonly statusCode: number | undefined;
79
+ constructor(class_: ErrorClass, message: string, options?: {
80
+ cause?: unknown;
81
+ retryAfterMs?: number;
82
+ statusCode?: number;
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Internal shared types for @agentchatme/openclaw.
88
+ *
89
+ * Kept intentionally small. Anything externally visible is exported from
90
+ * `./index.ts` or `./setup-entry.ts`. Types with a public SDK shape (channel
91
+ * plugin, setup plugin) live next to their respective entry files.
92
+ */
93
+ type UnixMillis = number;
94
+
95
+ /**
96
+ * Connection state machine for the AgentChat WebSocket.
97
+ *
98
+ * Pure functions only — no I/O, no timers. The runtime (P2) owns the socket
99
+ * and drives transitions by emitting events.
100
+ *
101
+ * States:
102
+ * DISCONNECTED — initial, and after a clean close
103
+ * CONNECTING — TCP/TLS handshake + WS upgrade in flight
104
+ * AUTHENTICATING — socket open, waiting for `hello.ok` from server
105
+ * READY — authenticated, receiving + sending normally
106
+ * DEGRADED — heartbeat missed or send buffer full; still connected but impaired
107
+ * DRAINING — SIGTERM received; flush in-flight sends, then close
108
+ * CLOSED — terminal after clean shutdown
109
+ * AUTH_FAIL — terminal after 401/403; operator action required (rotate key)
110
+ *
111
+ * Invariants:
112
+ * - From AUTH_FAIL, no event can move us back to CONNECTING without
113
+ * an explicit RECONFIGURED event (operator rotated key).
114
+ * - From CLOSED, only SHUTDOWN is a no-op; anything else is a bug.
115
+ * - DRAINING has a deadline (ms timestamp) after which we force-close.
116
+ */
117
+
118
+ type ConnectionState = {
119
+ readonly kind: 'DISCONNECTED';
120
+ } | {
121
+ readonly kind: 'CONNECTING';
122
+ readonly attempt: number;
123
+ readonly startedAt: UnixMillis;
124
+ } | {
125
+ readonly kind: 'AUTHENTICATING';
126
+ readonly startedAt: UnixMillis;
127
+ } | {
128
+ readonly kind: 'READY';
129
+ readonly connectedAt: UnixMillis;
130
+ } | {
131
+ readonly kind: 'DEGRADED';
132
+ readonly since: UnixMillis;
133
+ readonly reason: string;
134
+ } | {
135
+ readonly kind: 'DRAINING';
136
+ readonly since: UnixMillis;
137
+ readonly deadline: UnixMillis;
138
+ } | {
139
+ readonly kind: 'CLOSED';
140
+ } | {
141
+ readonly kind: 'AUTH_FAIL';
142
+ readonly reason: string;
143
+ };
144
+
145
+ /**
146
+ * AgentChat WebSocket client — state-machine-driven, production-grade.
147
+ *
148
+ * Responsibilities:
149
+ * - Own the WS transport and HELLO handshake lifecycle.
150
+ * - Drive the connection state machine (`./state-machine.ts`) by emitting
151
+ * events in response to socket/auth/heartbeat transitions.
152
+ * - Maintain native WS PING/PONG heartbeat at `ping.intervalMs`; declare
153
+ * DEGRADED on `ping.timeoutMs` miss, then force-reconnect.
154
+ * - Schedule reconnect with exponential backoff + ±`jitterRatio` jitter
155
+ * per `reconnect.*` config. Thundering-herd-safe across a fleet.
156
+ * - Emit typed events upwards (`inboundFrame`, `stateChanged`, `error`)
157
+ * so the normalizer (P3) and outbound adapter (P4) can consume them.
158
+ *
159
+ * Intentional design choices:
160
+ * - Uses the `ws` package directly (not `globalThis.WebSocket`) because
161
+ * OpenClaw runs on Node — `ws` exposes `.ping()` / `.pong()` / event
162
+ * hooks that native WebSocket omits. Every major agent-hosting target
163
+ * (Fly, Render, Vercel functions, Lambda) is Node.
164
+ * - Authenticates via HELLO frame (`{type: "hello", api_key}`), never via
165
+ * URL query string. Keeps the key out of access logs and Referers.
166
+ * - `RECONNECT_HARD_CAP_ATTEMPTS` guards against runaway reconnect loops
167
+ * after catastrophic network events; crosses into `AUTH_FAIL` so the
168
+ * operator sees a clear signal instead of silent retry forever.
169
+ * - Frame parsing is defensive: malformed JSON, unknown `type`, missing
170
+ * payload — all classified as `validation` errors (non-terminal for
171
+ * the connection, but dropped with a metric + log).
172
+ * - No message-layer ordering here. Per-conversation seq ordering + gap
173
+ * recovery belong to the SDK's `RealtimeClient` and the drain layer
174
+ * (P3 consumers). This module is strictly the transport.
175
+ */
176
+
177
+ /** One parsed inbound server frame. */
178
+ interface InboundFrame {
179
+ readonly type: string;
180
+ readonly payload: Record<string, unknown>;
181
+ readonly receivedAt: UnixMillis;
182
+ readonly raw: unknown;
183
+ }
184
+ /** One client-action frame the caller wants to push. */
185
+ interface OutboundFrame {
186
+ readonly type: string;
187
+ readonly payload: Record<string, unknown>;
188
+ readonly id?: string;
189
+ }
190
+ interface WsClientEvents {
191
+ stateChanged: (next: ConnectionState, prev: ConnectionState) => void;
192
+ inboundFrame: (frame: InboundFrame) => void;
193
+ error: (error: AgentChatChannelError) => void;
194
+ /** Fired once per `hello.ok` — useful for triggering a /v1/messages/sync drain upstream. */
195
+ authenticated: (at: UnixMillis) => void;
196
+ /** Fired after a clean close + CLOSED state (after stop() or terminal auth failure). */
197
+ closed: () => void;
198
+ }
199
+ interface WsClientOptions {
200
+ readonly config: AgentchatChannelConfig;
201
+ readonly logger: Logger;
202
+ readonly metrics: MetricsRecorder;
203
+ /** Clock source — override in tests. Default: `Date.now`. */
204
+ readonly now?: () => UnixMillis;
205
+ /** WebSocket constructor override — tests pass a mock. Default: `ws`. */
206
+ readonly webSocketCtor?: typeof WebSocket;
207
+ /** Random source for jitter — override in tests. Default: `Math.random`. */
208
+ readonly random?: () => number;
209
+ /** timer.setTimeout override for tests. */
210
+ readonly setTimeout?: (fn: () => void, ms: number) => NodeJS.Timeout;
211
+ readonly clearTimeout?: (h: NodeJS.Timeout) => void;
212
+ readonly setInterval?: (fn: () => void, ms: number) => NodeJS.Timeout;
213
+ readonly clearInterval?: (h: NodeJS.Timeout) => void;
214
+ }
215
+ type Listener<E extends keyof WsClientEvents> = WsClientEvents[E];
216
+ /**
217
+ * AgentChat channel WebSocket client.
218
+ *
219
+ * Lifecycle:
220
+ * 1. `start()` — DISCONNECTED → CONNECTING. Transport opens, HELLO is sent.
221
+ * 2. `hello.ok` → READY. Heartbeat timer starts. Upstream consumers begin.
222
+ * 3. On socket drop → DISCONNECTED → CONNECTING (with backoff).
223
+ * 4. `stop(deadlineMs)` → DRAINING. Wait for in-flight drains, then CLOSED.
224
+ *
225
+ * Thread-safety: this class is single-threaded. All callbacks fire on the
226
+ * Node event loop; callers should never invoke public methods from timer
227
+ * callbacks without being aware of reentrancy (e.g., a `stateChanged`
228
+ * listener that calls `send()` is fine, but `start()` inside `closed()`
229
+ * will re-enter the lifecycle — usually a bug).
230
+ */
231
+ declare class AgentchatWsClient {
232
+ private readonly config;
233
+ private readonly logger;
234
+ private readonly metrics;
235
+ private readonly now;
236
+ private readonly WebSocketCtor;
237
+ private readonly random;
238
+ private readonly _setTimeout;
239
+ private readonly _clearTimeout;
240
+ private readonly _setInterval;
241
+ private readonly _clearInterval;
242
+ private state;
243
+ private ws;
244
+ private attempt;
245
+ private helloAckTimer;
246
+ private pingTimer;
247
+ private pongTimer;
248
+ private reconnectTimer;
249
+ private drainTimer;
250
+ private readonly listeners;
251
+ constructor(options: WsClientOptions);
252
+ /** Current connection state. Read-only from the caller's perspective. */
253
+ getState(): ConnectionState;
254
+ /**
255
+ * Kick off connection. No-op if already connecting, authenticating, or
256
+ * ready. Throws on `AUTH_FAIL` — the caller must call `reconfigured()`
257
+ * after rotating the API key before retrying.
258
+ */
259
+ start(): void;
260
+ /**
261
+ * Push a client-action frame. Requires `canSend(state)` — returns false
262
+ * if the socket is not ready. Callers should queue and retry from the
263
+ * outbound adapter (P4) rather than drop here.
264
+ */
265
+ send(frame: OutboundFrame): boolean;
266
+ /**
267
+ * Request a graceful drain. The caller-supplied deadline is a Unix-ms
268
+ * timestamp; after it passes, the socket is force-closed regardless of
269
+ * remaining in-flight work. Safe to call from any state.
270
+ */
271
+ stop(deadline?: UnixMillis): void;
272
+ /**
273
+ * Signal that operator has rotated the API key. Transitions from
274
+ * AUTH_FAIL back to DISCONNECTED; the next `start()` call will attempt
275
+ * a fresh HELLO with the current config.
276
+ */
277
+ reconfigured(): void;
278
+ on<E extends keyof WsClientEvents>(event: E, handler: Listener<E>): () => void;
279
+ private openSocket;
280
+ private bindSocket;
281
+ private handleOpen;
282
+ private handleMessage;
283
+ private extractReason;
284
+ private extractPayload;
285
+ private handleHelloOk;
286
+ private handleAuthRejected;
287
+ private handleClose;
288
+ private closeSocket;
289
+ private startHeartbeat;
290
+ private stopHeartbeat;
291
+ private sendPing;
292
+ private handlePong;
293
+ private scheduleReconnect;
294
+ private computeReconnectDelay;
295
+ private scheduleDrainDeadline;
296
+ /**
297
+ * Called by upper layers when their pending work has fully drained, to
298
+ * let the WS client transition CLOSED earlier than the deadline.
299
+ */
300
+ drainCompleted(): void;
301
+ private applyEvent;
302
+ private emit;
303
+ private emitError;
304
+ }
305
+
306
+ /**
307
+ * Inbound normalizer — AgentChat server events → channel-neutral shape.
308
+ *
309
+ * Responsibilities:
310
+ * - Validate every server frame against a Zod schema. Unknown shapes
311
+ * surface as `validation` errors (the transport catches them and drops
312
+ * the frame without killing the connection).
313
+ * - Translate AgentChat-native payloads into a stable, plugin-internal
314
+ * `NormalizedInbound` shape. The OpenClaw channel binding (P7 runtime
315
+ * wiring) adapts this further to the gateway's `ChannelInboundMessage`
316
+ * contract.
317
+ * - Classify conversation kind (direct vs group) from the `dir_*`/`grp_*`
318
+ * prefix convention so consumers don't need to grep prefixes themselves.
319
+ *
320
+ * Why this module exists (rather than passing raw frames up):
321
+ * - The WS client is pure transport; it shouldn't know what
322
+ * `message.new` means semantically.
323
+ * - OpenClaw's ChannelInboundMessage has a specific shape (thread id,
324
+ * content parts, attachments, sender identity). Converting from
325
+ * AgentChat's wire format in one place is the only sane way to keep
326
+ * the mapping auditable.
327
+ * - Per-event Zod schemas make drift between plugin and server obvious
328
+ * and loud — a server field rename surfaces as a validation error
329
+ * caught in CI long before it goes to prod.
330
+ */
331
+
332
+ type ConversationKind = 'direct' | 'group';
333
+ interface NormalizedMessage {
334
+ readonly kind: 'message';
335
+ readonly conversationKind: ConversationKind;
336
+ readonly conversationId: string;
337
+ /** Sender handle. */
338
+ readonly sender: string;
339
+ readonly messageId: string;
340
+ readonly clientMsgId: string;
341
+ readonly seq: number;
342
+ readonly messageType: 'text' | 'structured' | 'file' | 'system';
343
+ readonly content: {
344
+ readonly text?: string;
345
+ readonly data?: Record<string, unknown>;
346
+ readonly attachmentId?: string;
347
+ };
348
+ readonly metadata: Record<string, unknown>;
349
+ /**
350
+ * Per-recipient delivery state. Since migration 011, per-recipient state
351
+ * lives on `message_deliveries`, not the message row — so fresh-send
352
+ * `message.new` envelopes have no status field. Drain-path (sync /
353
+ * undelivered) envelopes still include `'stored'`. `null` means the
354
+ * server didn't attach one; callers that need the authoritative state
355
+ * should query `GET /v1/messages/sync` or `/delivery-receipts`.
356
+ */
357
+ readonly status: 'stored' | 'delivered' | 'read' | null;
358
+ readonly createdAt: string;
359
+ readonly deliveredAt: string | null;
360
+ readonly readAt: string | null;
361
+ readonly receivedAt: UnixMillis;
362
+ }
363
+ interface NormalizedReadReceipt {
364
+ readonly kind: 'read-receipt';
365
+ readonly conversationKind: ConversationKind;
366
+ readonly conversationId: string;
367
+ readonly reader: string;
368
+ readonly throughSeq: number;
369
+ readonly at: string | null;
370
+ readonly receivedAt: UnixMillis;
371
+ }
372
+ interface NormalizedTyping {
373
+ readonly kind: 'typing';
374
+ readonly action: 'start' | 'stop';
375
+ readonly conversationKind: ConversationKind;
376
+ readonly conversationId: string;
377
+ readonly sender: string;
378
+ readonly receivedAt: UnixMillis;
379
+ }
380
+ interface NormalizedPresence {
381
+ readonly kind: 'presence';
382
+ readonly handle: string;
383
+ readonly status: 'online' | 'away' | 'offline';
384
+ readonly lastActiveAt: string | null;
385
+ readonly customStatus: string | null;
386
+ readonly receivedAt: UnixMillis;
387
+ }
388
+ interface NormalizedRateLimitWarning {
389
+ readonly kind: 'rate-limit-warning';
390
+ readonly endpoint: string | null;
391
+ readonly limit: number | null;
392
+ readonly remaining: number | null;
393
+ readonly resetAt: string | null;
394
+ readonly message: string | null;
395
+ readonly receivedAt: UnixMillis;
396
+ }
397
+ interface NormalizedGroupInvite {
398
+ readonly kind: 'group-invite';
399
+ readonly inviteId: string;
400
+ readonly groupId: string;
401
+ readonly groupName: string;
402
+ readonly groupDescription: string | null;
403
+ readonly groupAvatarUrl: string | null;
404
+ readonly groupMemberCount: number;
405
+ readonly inviterHandle: string;
406
+ readonly createdAt: string;
407
+ readonly receivedAt: UnixMillis;
408
+ }
409
+ interface NormalizedGroupDeleted {
410
+ readonly kind: 'group-deleted';
411
+ readonly groupId: string;
412
+ readonly deletedByHandle: string;
413
+ readonly deletedAt: string;
414
+ readonly receivedAt: UnixMillis;
415
+ }
416
+ /**
417
+ * Unknown server event — arrived over the wire but isn't in our explicit
418
+ * handler set. We still surface it (the gateway may want to log it or
419
+ * forward it), but with a `type` field so downstream can discriminate.
420
+ */
421
+ interface NormalizedUnknown {
422
+ readonly kind: 'unknown';
423
+ readonly type: string;
424
+ readonly payload: Record<string, unknown>;
425
+ readonly receivedAt: UnixMillis;
426
+ }
427
+ type NormalizedInbound = NormalizedMessage | NormalizedReadReceipt | NormalizedTyping | NormalizedPresence | NormalizedRateLimitWarning | NormalizedGroupInvite | NormalizedGroupDeleted | NormalizedUnknown;
428
+
429
+ /**
430
+ * Shape of a message record as returned by the AgentChat REST API.
431
+ *
432
+ * Mirrors the `Message` interface defined in the AgentChat TypeScript SDK
433
+ * (`packages/sdk-typescript/src/types/message.ts`). We keep a local copy so
434
+ * this plugin has no runtime dependency on the SDK — it's type-only at the
435
+ * source and the SDK isn't published to npm yet. If the server's message
436
+ * contract changes, update both files together; a drift is a schema
437
+ * mismatch the server will catch, but we'd rather fail type-check first.
438
+ */
439
+ type MessageType = 'text' | 'structured' | 'file' | 'system';
440
+ type MessageStatus = 'stored' | 'delivered' | 'read';
441
+ interface MessageContent {
442
+ text?: string;
443
+ data?: Record<string, unknown>;
444
+ attachment_id?: string;
445
+ }
446
+ interface Message {
447
+ id: string;
448
+ conversation_id: string;
449
+ sender: string;
450
+ client_msg_id: string;
451
+ seq: number;
452
+ type: MessageType;
453
+ content: MessageContent;
454
+ metadata: Record<string, unknown>;
455
+ status: MessageStatus;
456
+ created_at: string;
457
+ delivered_at: string | null;
458
+ read_at: string | null;
459
+ }
460
+
461
+ /**
462
+ * Outbound adapter — OpenClaw send → AgentChat `POST /v1/messages`.
463
+ *
464
+ * Pipeline (left to right, each step is independently observable):
465
+ * 1. Caller invokes `sendMessage(input)` with a channel-neutral shape.
466
+ * 2. We derive a stable `client_msg_id` (idempotency key). If the caller
467
+ * supplied one, we respect it; otherwise we mint a UUID.
468
+ * 3. Circuit-breaker precheck. Open circuit → fast-fail `retry-transient`.
469
+ * 4. `retryWithPolicy(...)` wraps the actual HTTP call.
470
+ * 5. On success, metrics/logs + return the `Message` the server minted.
471
+ * 6. On 409 (idempotent-replay), treat as success with the replay body.
472
+ * 7. On 429, honor `Retry-After` (seconds or HTTP-date) via the retry
473
+ * policy. Surface as `retry-rate`.
474
+ * 8. On 5xx / network flap, retry with jittered exponential backoff.
475
+ * 9. On 4xx other than 429/409, classify as `terminal-user` → no retry.
476
+ * 10. On 401/403 → `terminal-auth`. We signal upstream so the WS client
477
+ * moves to AUTH_FAIL and the operator gets paged.
478
+ *
479
+ * Backpressure:
480
+ * - An in-flight semaphore bounds concurrent sends to `outbound.maxInFlight`.
481
+ * - Over-threshold sends are enqueued and drained as capacity frees up.
482
+ * - Queue has its own hard cap (`10 * maxInFlight`); over-cap sends reject
483
+ * immediately with `retry-transient` so the caller can shed load rather
484
+ * than OOM.
485
+ */
486
+
487
+ type OutboundMessageInput = OutboundDirectMessage | OutboundGroupMessage;
488
+ interface OutboundMessageBase {
489
+ /**
490
+ * Client-generated idempotency key. Optional — we mint one if absent.
491
+ * Retries of the same logical send MUST reuse the same value or you'll
492
+ * get duplicates.
493
+ */
494
+ readonly clientMsgId?: string;
495
+ readonly type?: 'text' | 'structured' | 'file';
496
+ readonly content: {
497
+ readonly text?: string;
498
+ readonly data?: Record<string, unknown>;
499
+ readonly attachmentId?: string;
500
+ };
501
+ readonly metadata?: Record<string, unknown>;
502
+ /** Correlation id carried through logs and metrics. */
503
+ readonly correlationId?: string;
504
+ }
505
+ interface OutboundDirectMessage extends OutboundMessageBase {
506
+ readonly kind: 'direct';
507
+ /** Recipient handle (e.g. "alice"). */
508
+ readonly to: string;
509
+ }
510
+ interface OutboundGroupMessage extends OutboundMessageBase {
511
+ readonly kind: 'group';
512
+ /** `grp_*` conversation id. */
513
+ readonly conversationId: string;
514
+ }
515
+ interface OutboundBacklogWarning {
516
+ readonly recipientHandle: string;
517
+ readonly undeliveredCount: number;
518
+ }
519
+ interface SendResult {
520
+ readonly message: Message;
521
+ readonly backlogWarning: OutboundBacklogWarning | null;
522
+ readonly idempotentReplay: boolean;
523
+ readonly attempts: number;
524
+ readonly latencyMs: number;
525
+ readonly requestId: string | null;
526
+ }
527
+
528
+ /**
529
+ * AgentChat channel runtime — single lifecycle object that ties the WS
530
+ * transport (P2), inbound normalizer (P3), and outbound adapter (P4)
531
+ * together. This is the surface an OpenClaw channel binding consumes.
532
+ *
533
+ * Responsibilities:
534
+ * - Instantiate the WS client, hook its `inboundFrame` event through
535
+ * `normalizeInbound` and into user-supplied handlers.
536
+ * - Expose `sendMessage` that delegates to the OutboundAdapter.
537
+ * - Coordinate drain: `stop(deadline)` waits for outbound in-flight to
538
+ * clear (up to the deadline) before closing the WS.
539
+ * - Surface a single `getHealth()` snapshot combining connection state +
540
+ * outbound queue depth + circuit breaker state.
541
+ *
542
+ * Not in scope here:
543
+ * - Per-conversation seq ordering and gap recovery — the SDK's
544
+ * `RealtimeClient` owns that. For the channel plugin, we deliver
545
+ * inbound frames as they arrive; upper layers can call
546
+ * `GET /v1/messages/sync` for reconciliation if they require strict
547
+ * ordering across reconnects.
548
+ * - OpenClaw ChannelInboundMessage translation — that lives in the
549
+ * bridge module (consumed by the gateway), not here. Keeping this
550
+ * module decoupled means it can be smoke-tested without OpenClaw.
551
+ */
552
+
553
+ interface ChannelRuntimeHandlers {
554
+ /** Called for every successfully normalized inbound event. */
555
+ onInbound?: (event: NormalizedInbound) => void | Promise<void>;
556
+ /** Called whenever a frame fails validation. Default: log-and-drop. */
557
+ onValidationError?: (error: AgentChatChannelError, frame: InboundFrame) => void;
558
+ /** Called on state transitions — surface to gateway for health/UI. */
559
+ onStateChanged?: (next: ConnectionState, prev: ConnectionState) => void;
560
+ /** Called once per successful HELLO_OK (initial + every reconnect). */
561
+ onAuthenticated?: (at: UnixMillis) => void;
562
+ /** Called on every closed/unreachable situation worth observing. */
563
+ onError?: (error: AgentChatChannelError) => void;
564
+ /** Called when the server signals a backlog warning on an outbound send. */
565
+ onBacklogWarning?: (warning: OutboundBacklogWarning) => void;
566
+ }
567
+ interface ChannelRuntimeOptions {
568
+ readonly config: AgentchatChannelConfig;
569
+ readonly handlers?: ChannelRuntimeHandlers;
570
+ /** Pre-built logger — if absent, we construct one from `config.observability`. */
571
+ readonly logger?: Logger;
572
+ /** Pre-built metrics recorder — if absent, uses no-op. */
573
+ readonly metrics?: MetricsRecorder;
574
+ readonly fetch?: typeof fetch;
575
+ /** Provided by tests. */
576
+ readonly webSocketCtor?: ConstructorParameters<typeof AgentchatWsClient>[0]['webSocketCtor'];
577
+ readonly now?: () => UnixMillis;
578
+ readonly random?: () => number;
579
+ readonly sleep?: (ms: number) => Promise<void>;
580
+ }
581
+ interface HealthSnapshot {
582
+ readonly state: ConnectionState;
583
+ readonly authenticated: boolean;
584
+ readonly outbound: {
585
+ readonly inFlight: number;
586
+ readonly queued: number;
587
+ readonly circuitState: 'closed' | 'open' | 'half-open';
588
+ };
589
+ }
590
+ /**
591
+ * Orchestrates the full channel. Construct once per (channelId, accountId)
592
+ * pair at gateway startup; call `start()` to bring it up, `stop()` to
593
+ * drain. Dispose is folded into `stop()` — after it resolves, the
594
+ * instance is terminal.
595
+ */
596
+ declare class AgentchatChannelRuntime {
597
+ private readonly config;
598
+ private readonly handlers;
599
+ private readonly logger;
600
+ private readonly metrics;
601
+ private readonly ws;
602
+ private readonly outbound;
603
+ private readonly now;
604
+ private started;
605
+ private authenticated;
606
+ private stopPromise;
607
+ constructor(opts: ChannelRuntimeOptions);
608
+ /** Open the transport. Idempotent — subsequent calls are no-ops. */
609
+ start(): void;
610
+ /**
611
+ * Graceful shutdown. Waits for outbound in-flight to drain up to the
612
+ * deadline, then force-closes the WS. Returns a promise that resolves
613
+ * once the WS has emitted `closed`.
614
+ */
615
+ stop(deadlineMs?: UnixMillis): Promise<void>;
616
+ private pollUntilIdle;
617
+ /**
618
+ * Send an outbound message. Delegates to the OutboundAdapter; callers
619
+ * should handle `AgentChatChannelError` with class dispatch.
620
+ */
621
+ sendMessage(input: OutboundMessageInput): Promise<SendResult>;
622
+ /** Push a client-action frame over the WS (typing, read-ack, presence). */
623
+ sendWsAction(type: 'typing.start' | 'typing.stop' | 'message.read_ack' | 'presence.update', payload: Record<string, unknown>): boolean;
624
+ /**
625
+ * Operator has rotated the API key — signal the WS client so it can
626
+ * exit AUTH_FAIL. The caller is responsible for creating a new runtime
627
+ * with the updated config OR for calling this after config hot-reload.
628
+ */
629
+ reconfigured(): void;
630
+ getHealth(): HealthSnapshot;
631
+ private bindWsEvents;
632
+ private dispatchFrame;
633
+ private recordInboundMetric;
634
+ }
635
+
636
+ /**
637
+ * Account-config primitives for the AgentChat channel plugin.
638
+ *
639
+ * This module owns the smallest set of shared pieces that both the plugin
640
+ * definition (`channel.ts`) and the interactive setup wizard
641
+ * (`channel.wizard.ts`) need to read or mutate the `channels.agentchat.*`
642
+ * section of the OpenClaw config. Keeping them in a leaf file (no imports
643
+ * from other channel-* modules) breaks the channel.ts ↔ channel.wizard.ts
644
+ * import cycle — without it, the wizard literal's `channel:` field resolves
645
+ * to the temporal-dead-zone value of `AGENTCHAT_CHANNEL_ID` at module-init
646
+ * time, which bundlers emit as `undefined`.
647
+ *
648
+ * Invariants:
649
+ * - Flat form (`channels.agentchat.{apiKey,...}`) is the default-account
650
+ * layout when no `accounts` key is present. Once a named account is
651
+ * written, flat form is never used again for the default account.
652
+ * - The `enabled` flag is section-level (not per-account) and is kept out
653
+ * of the Zod-parsed account shape because the schema is `.strict()`.
654
+ */
655
+
656
+ declare const AGENTCHAT_CHANNEL_ID: "agentchat";
657
+ declare const AGENTCHAT_DEFAULT_ACCOUNT_ID = "default";
658
+
659
+ /**
660
+ * Minimal HTTP client used by the setup plugin (P7) to:
661
+ * - verify an API key is live before finalizing setup (`validateApiKey`)
662
+ * - drive the email-OTP self-registration flow for agents without a key yet
663
+ * (`registerAgentStart` → `registerAgentVerify`)
664
+ *
665
+ * Why this lives separately from `outbound.ts`:
666
+ * - `outbound.ts` is the hot-path message sender. It wants retries, a circuit
667
+ * breaker, metrics, and a long-lived `fetch`. Setup is one-shot, low-rate,
668
+ * user-interactive — a 200ms validation call doesn't need any of that.
669
+ * - Setup runs before the runtime exists; coupling it to `OutboundAdapter`
670
+ * (which expects a parsed `AgentchatChannelConfig`) would force an odd
671
+ * partial-config path during registration.
672
+ *
673
+ * The server endpoints this module targets are stable AgentChat REST calls:
674
+ * - `GET /v1/agents/me` → 200 OK when the key authenticates
675
+ * - `POST /v1/register` → 200 with `{ pending_id }`
676
+ * - `POST /v1/register/verify` → 201 with `{ agent, api_key }` on success
677
+ *
678
+ * All methods return strongly-typed result unions — setup UIs can `switch` on
679
+ * the discriminant without guessing at HTTP status codes.
680
+ */
681
+ /** Subset of the AgentChat agent row the setup surface needs to show the user. */
682
+ interface AgentchatAgentIdentity {
683
+ readonly handle: string;
684
+ readonly displayName: string | null;
685
+ readonly email: string;
686
+ readonly createdAt: string;
687
+ }
688
+ type ValidateApiKeyResult = {
689
+ readonly ok: true;
690
+ readonly agent: AgentchatAgentIdentity;
691
+ } | {
692
+ readonly ok: false;
693
+ /** High-level reason code the UI can map to a localized message. */
694
+ readonly reason: 'unauthorized' | 'forbidden' | 'deleted' | 'network-error' | 'unreachable' | 'server-error' | 'unexpected-shape';
695
+ readonly message: string;
696
+ readonly status?: number;
697
+ };
698
+ interface ValidateApiKeyOptions {
699
+ readonly apiBase?: string;
700
+ readonly fetch?: typeof fetch;
701
+ readonly timeoutMs?: number;
702
+ }
703
+ /**
704
+ * Probe the API key against `GET /v1/agents/me`. Returns a discriminated
705
+ * result — never throws on HTTP or network errors. The caller decides how
706
+ * to surface each failure mode to the user.
707
+ */
708
+ declare function validateApiKey(apiKey: string, opts?: ValidateApiKeyOptions): Promise<ValidateApiKeyResult>;
709
+ interface RegisterAgentStartInput {
710
+ readonly email: string;
711
+ readonly handle: string;
712
+ readonly displayName?: string;
713
+ readonly description?: string;
714
+ }
715
+ type RegisterStartResult = {
716
+ readonly ok: true;
717
+ readonly pendingId: string;
718
+ } | {
719
+ readonly ok: false;
720
+ readonly reason: 'invalid-handle' | 'handle-taken' | 'email-taken' | 'email-exhausted' | 'rate-limited' | 'otp-failed' | 'network-error' | 'server-error' | 'validation';
721
+ readonly message: string;
722
+ readonly status?: number;
723
+ readonly retryAfterSeconds?: number;
724
+ };
725
+ interface RegisterAgentVerifyInput {
726
+ readonly pendingId: string;
727
+ readonly code: string;
728
+ }
729
+ type RegisterVerifyResult = {
730
+ readonly ok: true;
731
+ readonly apiKey: string;
732
+ readonly agent: AgentchatAgentIdentity;
733
+ } | {
734
+ readonly ok: false;
735
+ readonly reason: 'expired' | 'invalid-code' | 'rate-limited' | 'handle-taken' | 'email-taken' | 'network-error' | 'server-error' | 'unexpected-shape' | 'validation';
736
+ readonly message: string;
737
+ readonly status?: number;
738
+ readonly retryAfterSeconds?: number;
739
+ };
740
+ interface RegisterOptions {
741
+ readonly apiBase?: string;
742
+ readonly fetch?: typeof fetch;
743
+ readonly timeoutMs?: number;
744
+ }
745
+ /**
746
+ * Kick off an email-OTP registration. Server caches the intent under a
747
+ * `pending_id` for 10 minutes and emails a 6-digit code to `email`.
748
+ */
749
+ declare function registerAgentStart(input: RegisterAgentStartInput, opts?: RegisterOptions): Promise<RegisterStartResult>;
750
+ /** Verify the OTP the user received by email and mint the API key. */
751
+ declare function registerAgentVerify(input: RegisterAgentVerifyInput, opts?: RegisterOptions): Promise<RegisterVerifyResult>;
752
+ /**
753
+ * Throw-flavored wrapper used by callers that prefer exception control flow
754
+ * (the setup plugin's `afterAccountConfigWritten` hook). Converts a failure
755
+ * result into an `AgentChatChannelError` with an appropriate class.
756
+ */
757
+ declare function assertApiKeyValid(apiKey: string, opts?: ValidateApiKeyOptions): Promise<AgentchatAgentIdentity>;
758
+
759
+ export { AGENTCHAT_CHANNEL_ID, AGENTCHAT_DEFAULT_ACCOUNT_ID, AgentChatChannelError, type AgentchatAgentIdentity, AgentchatChannelConfig, AgentchatChannelRuntime, type ChannelRuntimeHandlers, type ChannelRuntimeOptions, type ConnectionState, type ConversationKind, type ErrorClass, type HealthSnapshot, type Message, type MessageContent, type MessageStatus, type MessageType, type NormalizedGroupDeleted, type NormalizedGroupInvite, type NormalizedInbound, type NormalizedMessage, type NormalizedPresence, type NormalizedRateLimitWarning, type NormalizedReadReceipt, type NormalizedTyping, type NormalizedUnknown, type OutboundBacklogWarning, type OutboundDirectMessage, type OutboundGroupMessage, type OutboundMessageInput, type RegisterAgentStartInput, type RegisterAgentVerifyInput, type RegisterOptions, type RegisterStartResult, type RegisterVerifyResult, type SendResult, type ValidateApiKeyOptions, type ValidateApiKeyResult, assertApiKeyValid, registerAgentStart, registerAgentVerify, validateApiKey };