@evgenyy/lessinbox-channel 0.1.8 → 0.1.10

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/README.md CHANGED
@@ -4,11 +4,13 @@ Openclaw channel plugin that sends and receives agent conversation state through
4
4
 
5
5
  ## What it does
6
6
 
7
- - Starts Lessinbox sessions (runs) when needed
7
+ - Keeps one continuous conversation per target (`channel:*` or `thread:*`)
8
+ - Starts new Lessinbox sessions (runs) only when needed
8
9
  - Posts assistant messages into Lessinbox threads
9
- - Maintains persistent `conversationId -> { threadId, runId }` mapping (`memory` or `redis`)
10
+ - Maintains persistent `conversationId -> { threadId, runId? }` mapping (`memory` or `redis`)
10
11
  - Opens workspace WebSocket stream and emits realtime inbound events (`message.created`, `interrupt.answered`, `run.state_changed`, etc.)
11
12
  - Exposes a channel plugin manifest + Lessinbox skill bundle
13
+ - Exposes guardrails middleware helpers for side-effect tooling (`executeLessinboxGuardedExecution`)
12
14
 
13
15
  ## Package contents
14
16
 
@@ -22,6 +24,7 @@ Openclaw channel plugin that sends and receives agent conversation state through
22
24
  {
23
25
  "channels": {
24
26
  "lessinbox": {
27
+ "enabled": true,
25
28
  "accounts": {
26
29
  "default": {
27
30
  "enabled": true,
@@ -34,7 +37,7 @@ Openclaw channel plugin that sends and receives agent conversation state through
34
37
  "redisPrefix": "lessinbox:openclaw",
35
38
  "workspaceStream": {
36
39
  "enabled": true,
37
- "wsUrl": "wss://lessinbox.example.com/v1/ws",
40
+ "wsUrl": "wss://lessinbox.example.com/v2/ws",
38
41
  "reconnectMs": 1500
39
42
  }
40
43
  }
@@ -56,14 +59,14 @@ If `threadId` and `runId` are missing, the plugin will create a new Lessinbox ru
56
59
 
57
60
  Target formats supported by the plugin:
58
61
 
59
- - `channel:<channelId>` - create/continue conversation for that channel
62
+ - `channel:<channelId>` - continue the latest thread for this channel (or create one if missing)
60
63
  - `thread:<threadId>` - create/continue conversation in that exact thread
61
64
  - raw channel id (`cml...` or `chn_...`) - treated as `channel:<id>`
62
65
  - account alias (`default`, etc.) - switches account when it matches configured account id
63
66
 
64
67
  ## Realtime inbound usage
65
68
 
66
- The package exports `subscribeToLessinboxEvents(...)` and also exposes `plugin.inbound.subscribe(...)`.
69
+ The package exports `subscribeToLessinboxEvents(...)` as a direct helper for custom integrations.
67
70
 
68
71
  Example:
69
72
 
@@ -79,7 +82,52 @@ const unsubscribe = subscribeToLessinboxEvents({
79
82
  })
80
83
  ```
81
84
 
82
- On terminal run states (`completed`, `failed`, `canceled`) the plugin clears stored run mapping automatically.
85
+ For Openclaw-native runtime lifecycle, inbound processing is wired through channel gateway hooks:
86
+
87
+ - `plugin.gateway.startAccount(...)` subscribes to Lessinbox workspace stream.
88
+ - `plugin.gateway.stopAccount(...)` unsubscribes and shuts down account listeners.
89
+ - Incoming `message.created` user messages are routed through Openclaw runtime reply dispatcher.
90
+
91
+ This is the preferred runtime path in v2.
92
+
93
+ On terminal run states (`completed`, `failed`, `canceled`) the plugin keeps thread mapping and clears only the run id, so the next outbound message starts a fresh run in the same thread.
94
+
95
+ ## Guardrails middleware usage
96
+
97
+ Use this helper when Openclaw is about to execute a side-effecting tool action (email, payments, external writes).
98
+
99
+ ```ts
100
+ import { executeLessinboxGuardedExecution } from "@evgenyy/lessinbox-channel"
101
+
102
+ await executeLessinboxGuardedExecution({
103
+ config: runtimeConfig,
104
+ accountId: "default",
105
+ scope: {
106
+ work_item_id: "wi_123",
107
+ run_id: "run_123",
108
+ thread_id: "thr_123"
109
+ },
110
+ tool: "email",
111
+ action: "send",
112
+ request: {
113
+ to: "user@example.com",
114
+ subject: "Status update"
115
+ },
116
+ side_effects: {
117
+ provider: "smtp"
118
+ },
119
+ onApprovalRequired: async ({ approval_request_id }) => {
120
+ // Optional: wait until someone approves this request in Lessinbox.
121
+ console.log("Approval required:", approval_request_id)
122
+ },
123
+ execute: async () => {
124
+ // your real side effect call
125
+ return { ok: true }
126
+ }
127
+ })
128
+ ```
129
+
130
+ This enforces Lessinbox policy decisions (`deny`, `require_simulation`, `require_approval`) and writes execution records before/after side effects.
83
131
 
84
132
  ## Build
85
133
 
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export interface LessinboxWorkspaceStreamConfig {
4
4
  reconnectMs?: number;
5
5
  }
6
6
  export interface LessinboxAccountConfig {
7
+ accountId?: string;
7
8
  enabled?: boolean;
8
9
  apiUrl: string;
9
10
  apiKey: string;
@@ -39,11 +40,223 @@ export interface InterruptCreateInput {
39
40
  deadline_at?: string;
40
41
  impact?: string;
41
42
  }
43
+ export type GuardrailsDecision = "allow" | "deny" | "require_approval" | "require_simulation";
44
+ export interface GuardrailsEvaluateInput {
45
+ tool: string;
46
+ action: string;
47
+ request?: unknown;
48
+ side_effects?: unknown;
49
+ metadata?: unknown;
50
+ action_hash?: string;
51
+ }
52
+ export interface GuardrailsEvaluateResponse {
53
+ decision: GuardrailsDecision;
54
+ reason: string;
55
+ policy_id: string;
56
+ policy_version: string;
57
+ requires_approval: boolean;
58
+ requires_simulation: boolean;
59
+ action_hash: string;
60
+ context_hash: string;
61
+ matched_rules: Array<{
62
+ policy_id: string;
63
+ policy_name?: string;
64
+ policy_version: string;
65
+ rule_name?: string;
66
+ decision: GuardrailsDecision;
67
+ reason?: string;
68
+ }>;
69
+ policy_decision_id?: string;
70
+ evaluated_at: string;
71
+ }
72
+ export interface SimulationCreateInput {
73
+ work_item_id?: string | null;
74
+ run_id?: string | null;
75
+ thread_id?: string | null;
76
+ mode?: "dry_run" | "read_only_probe" | "sandbox";
77
+ tool: string;
78
+ action: string;
79
+ request?: unknown;
80
+ side_effects?: unknown;
81
+ metadata?: unknown;
82
+ action_hash?: string;
83
+ }
84
+ export interface SimulationCreateResponse {
85
+ simulation_id: string;
86
+ work_item_id?: string;
87
+ run_id?: string;
88
+ thread_id?: string;
89
+ mode: string;
90
+ status: "completed";
91
+ action_hash: string;
92
+ input_hash: string;
93
+ simulation_hash: string;
94
+ guardrails_decision: GuardrailsDecision;
95
+ guardrails_reason: string;
96
+ plan: unknown;
97
+ predicted_risks: unknown;
98
+ predicted_costs: unknown;
99
+ created_at: string;
100
+ completed_at: string;
101
+ }
102
+ export type ApprovalStatus = "pending" | "approved" | "denied" | "canceled";
103
+ export interface ApprovalCreateInput {
104
+ work_item_id?: string | null;
105
+ run_id?: string | null;
106
+ thread_id?: string | null;
107
+ tool: string;
108
+ action: string;
109
+ request?: unknown;
110
+ side_effects?: unknown;
111
+ simulation_id?: string | null;
112
+ simulation_hash?: string | null;
113
+ reason?: string;
114
+ expires_at?: string;
115
+ metadata?: unknown;
116
+ action_hash?: string;
117
+ }
118
+ export interface ApprovalCreateResponse {
119
+ approval_request_id: string;
120
+ status: ApprovalStatus;
121
+ action_hash: string;
122
+ work_item_id?: string;
123
+ run_id?: string;
124
+ thread_id?: string;
125
+ simulation_id?: string;
126
+ simulation_hash?: string;
127
+ created_at: string;
128
+ expires_at?: string;
129
+ }
130
+ export interface ApprovalDecideInput {
131
+ decision: "approved" | "denied" | "canceled";
132
+ comment?: string;
133
+ metadata?: unknown;
134
+ }
135
+ export interface ApprovalDecideResponse {
136
+ approval_request_id: string;
137
+ status: ApprovalStatus;
138
+ action_hash: string;
139
+ decision_comment?: string;
140
+ decided_at: string;
141
+ }
142
+ export type ExecutionStatus = "planned" | "running" | "succeeded" | "failed" | "canceled" | "blocked" | "skipped";
143
+ export interface ExecutionCreateInput {
144
+ work_item_id?: string | null;
145
+ run_id?: string | null;
146
+ thread_id?: string | null;
147
+ actor_type?: "user" | "agent" | "system";
148
+ tool: string;
149
+ action: string;
150
+ request?: unknown;
151
+ response?: unknown;
152
+ side_effects?: unknown;
153
+ logs?: unknown;
154
+ status?: ExecutionStatus;
155
+ error?: unknown;
156
+ idempotency_key?: string | null;
157
+ started_at?: string;
158
+ ended_at?: string;
159
+ metadata?: unknown;
160
+ }
161
+ export interface ExecutionCreateResponse {
162
+ execution_id: string;
163
+ work_item_id?: string;
164
+ run_id?: string;
165
+ thread_id?: string;
166
+ status: ExecutionStatus;
167
+ created_at: string;
168
+ }
169
+ export interface LessinboxGuardedExecutionScope {
170
+ work_item_id?: string | null;
171
+ run_id?: string | null;
172
+ thread_id?: string | null;
173
+ }
174
+ export interface LessinboxGuardedExecutionInput<T> {
175
+ config: unknown;
176
+ accountId?: string;
177
+ scope?: LessinboxGuardedExecutionScope;
178
+ tool: string;
179
+ action: string;
180
+ request?: unknown;
181
+ side_effects?: unknown;
182
+ metadata?: Record<string, unknown>;
183
+ approval_reason?: string;
184
+ simulation_mode?: "dry_run" | "read_only_probe" | "sandbox";
185
+ onApprovalRequired?: (input: {
186
+ approval_request_id: string;
187
+ action_hash: string;
188
+ }) => void | Promise<void>;
189
+ execute: () => Promise<T>;
190
+ }
191
+ export interface LessinboxGuardedExecutionResult<T> {
192
+ action_hash: string;
193
+ guardrails: GuardrailsEvaluateResponse;
194
+ simulation?: SimulationCreateResponse;
195
+ approval?: ApprovalCreateResponse;
196
+ preflight_execution: ExecutionCreateResponse;
197
+ final_execution: ExecutionCreateResponse;
198
+ result: T;
199
+ }
42
200
  export interface LessinboxRunRef {
43
201
  threadId: string;
44
- runId: string;
202
+ runId?: string;
45
203
  status?: string;
46
204
  }
205
+ interface V2RunSnapshot {
206
+ id: string;
207
+ thread_id: string | null;
208
+ channel_id: string;
209
+ status: string;
210
+ }
211
+ interface V2RunGetResponse {
212
+ run: V2RunSnapshot;
213
+ }
214
+ interface V2ThreadRunSnapshot {
215
+ id: string;
216
+ status: string;
217
+ }
218
+ interface V2ThreadGetResponse {
219
+ id: string;
220
+ channel_id: string;
221
+ run: V2ThreadRunSnapshot | null;
222
+ session?: V2ThreadRunSnapshot | null;
223
+ }
224
+ interface V2ThreadSummary {
225
+ id: string;
226
+ channel_id: string;
227
+ run_id: string | null;
228
+ run_status: string | null;
229
+ session_id?: string | null;
230
+ session_status?: string | null;
231
+ }
232
+ interface V2ThreadListResponse {
233
+ threads: V2ThreadSummary[];
234
+ next_cursor: string | null;
235
+ }
236
+ interface V2MessageActor {
237
+ type?: string;
238
+ user_id?: string;
239
+ userId?: string;
240
+ agent_id?: string;
241
+ agentId?: string;
242
+ }
243
+ interface V2ThreadFeedMessage {
244
+ id: string;
245
+ run_id?: string | null;
246
+ session_id?: string | null;
247
+ kind?: string | null;
248
+ text?: string | null;
249
+ actor?: V2MessageActor;
250
+ created_at?: string;
251
+ }
252
+ interface V2ThreadFeedEntry {
253
+ type?: string;
254
+ message?: V2ThreadFeedMessage;
255
+ }
256
+ interface V2ThreadFeedResponse {
257
+ entries: V2ThreadFeedEntry[];
258
+ next_cursor: string | null;
259
+ }
47
260
  export type LessinboxWorkspaceEventKind = "event.appended" | "artifact.ready" | "checkpoint.created" | "checkpoint.resolved" | "run.status" | "thread.summary" | "message.created" | "interrupt.opened" | "interrupt.answered" | "run.state_changed";
48
261
  export interface LessinboxWorkspaceEvent {
49
262
  kind: string;
@@ -66,6 +279,12 @@ export interface SubscribeToLessinboxEventsInput {
66
279
  accountId?: string;
67
280
  onEvent: LessinboxInboundEventHandler;
68
281
  }
282
+ export declare function hashActionPayload(input: {
283
+ tool: string;
284
+ action: string;
285
+ request?: unknown;
286
+ side_effects?: unknown;
287
+ }): string;
69
288
  export declare function shutdownLessinboxPluginResources(): Promise<void>;
70
289
  export declare class LessinboxApi {
71
290
  private readonly apiUrl;
@@ -97,33 +316,76 @@ export declare class LessinboxApi {
97
316
  session_id?: string;
98
317
  status: string;
99
318
  }>;
319
+ evaluateGuardrails(input: GuardrailsEvaluateInput): Promise<GuardrailsEvaluateResponse>;
320
+ createSimulation(input: SimulationCreateInput): Promise<SimulationCreateResponse>;
321
+ createApproval(input: ApprovalCreateInput): Promise<ApprovalCreateResponse>;
322
+ decideApproval(approvalRequestId: string, input: ApprovalDecideInput): Promise<ApprovalDecideResponse>;
323
+ createExecution(input: ExecutionCreateInput): Promise<ExecutionCreateResponse>;
100
324
  completeRun(runId: string, summary?: string): Promise<void>;
101
325
  failRun(runId: string, error: {
102
326
  kind: string;
103
327
  message: string;
104
328
  details?: Record<string, unknown>;
105
329
  }): Promise<void>;
330
+ getRun(runId: string): Promise<V2RunGetResponse>;
331
+ getThread(threadId: string): Promise<V2ThreadGetResponse>;
106
332
  listThreads(input?: {
107
333
  bucket?: "needs_input" | "in_progress" | "done" | "all";
108
334
  channelId?: string;
109
335
  cursor?: string;
110
336
  limit?: number;
111
- }): Promise<{
112
- threads: unknown[];
113
- next_cursor: string | null;
114
- }>;
337
+ }): Promise<V2ThreadListResponse>;
115
338
  createWorkspaceWsToken(): Promise<{
116
339
  token: string;
117
340
  expires_at: string;
118
341
  }>;
342
+ getThreadFeed(input: {
343
+ threadId: string;
344
+ limit?: number;
345
+ cursor?: string;
346
+ }): Promise<V2ThreadFeedResponse>;
119
347
  private request;
120
348
  }
121
349
  export declare function resolveAccountConfig(config: unknown, accountId?: string): LessinboxAccountConfig;
122
- type ChannelPluginRuntimeAPI = {
350
+ export declare function createLessinboxApiFromConfig(input: {
351
+ config: unknown;
352
+ accountId?: string;
353
+ }): {
354
+ accountId: string;
355
+ account: LessinboxAccountConfig;
356
+ api: LessinboxApi;
357
+ };
358
+ export declare function executeLessinboxGuardedExecution<T>(input: LessinboxGuardedExecutionInput<T>): Promise<LessinboxGuardedExecutionResult<T>>;
359
+ type ChannelLogSink = {
360
+ debug?: (message: string) => void;
361
+ info?: (message: string) => void;
362
+ warn?: (message: string) => void;
363
+ error?: (message: string) => void;
364
+ };
365
+ type ChannelRuntimeState = Record<string, unknown>;
366
+ type ChannelGatewayContext = {
367
+ cfg: unknown;
368
+ accountId: string;
369
+ account: LessinboxAccountConfig;
370
+ runtime: unknown;
371
+ abortSignal: AbortSignal;
372
+ log?: ChannelLogSink;
373
+ getStatus: () => ChannelRuntimeState;
374
+ setStatus: (next: ChannelRuntimeState) => void;
375
+ };
376
+ type ChannelGatewayStartContext = ChannelGatewayContext;
377
+ type ChannelGatewayStopContext = ChannelGatewayContext;
378
+ type OpenclawPluginService = {
379
+ id: string;
380
+ start: (ctx: unknown) => void | Promise<void>;
381
+ stop?: (ctx: unknown) => void | Promise<void>;
382
+ };
383
+ type OpenclawPluginApi = {
384
+ runtime?: unknown;
123
385
  registerChannel: (input: {
124
386
  plugin: unknown;
125
- }) => void;
126
- onShutdown?: (handler: () => Promise<void> | void) => void;
387
+ } | unknown) => void;
388
+ registerService?: (service: OpenclawPluginService) => void;
127
389
  };
128
390
  type SendTextInput = {
129
391
  text: string;
@@ -139,18 +401,23 @@ type SendTextInput = {
139
401
  conversationId?: string;
140
402
  metadata?: Record<string, unknown>;
141
403
  };
142
- type SubscribeInput = {
143
- config?: unknown;
144
- accountId?: string;
145
- onEvent: LessinboxInboundEventHandler;
404
+ type SendMediaInput = Omit<SendTextInput, "text"> & {
405
+ text?: string;
406
+ mediaUrl?: string;
146
407
  };
147
408
  declare function sendTextToLessinbox(input: SendTextInput): Promise<{
148
409
  ok: boolean;
149
410
  threadId: string;
150
411
  runId: string;
151
412
  }>;
413
+ declare function sendMediaToLessinbox(input: SendMediaInput): Promise<{
414
+ ok: boolean;
415
+ threadId: string;
416
+ runId: string;
417
+ }>;
418
+ declare function startLessinboxGatewayAccount(ctx: ChannelGatewayStartContext): Promise<void>;
419
+ declare function stopLessinboxGatewayAccount(ctx: ChannelGatewayStopContext): Promise<void>;
152
420
  export declare function subscribeToLessinboxEvents(input: SubscribeToLessinboxEventsInput): () => void;
153
- declare function subscribeFromPlugin(input: SubscribeInput): () => void;
154
421
  export declare function createLessinboxPlugin(): {
155
422
  id: string;
156
423
  meta: {
@@ -163,18 +430,45 @@ export declare function createLessinboxPlugin(): {
163
430
  };
164
431
  capabilities: {
165
432
  chatTypes: string[];
433
+ threads: boolean;
434
+ media: boolean;
435
+ };
436
+ reload: {
437
+ configPrefixes: string[];
166
438
  };
167
439
  config: {
168
440
  listAccountIds: (cfg: unknown) => string[];
169
441
  resolveAccount: (cfg: unknown, accountId?: string) => LessinboxAccountConfig;
442
+ isConfigured: (account: LessinboxAccountConfig) => boolean;
443
+ describeAccount: (account: LessinboxAccountConfig) => {
444
+ accountId: string;
445
+ enabled: boolean;
446
+ configured: boolean;
447
+ workspaceId: string;
448
+ apiUrl: string;
449
+ };
170
450
  };
171
451
  outbound: {
172
452
  deliveryMode: string;
173
453
  sendText: typeof sendTextToLessinbox;
454
+ sendMedia: typeof sendMediaToLessinbox;
455
+ };
456
+ gateway: {
457
+ startAccount: typeof startLessinboxGatewayAccount;
458
+ stopAccount: typeof stopLessinboxGatewayAccount;
174
459
  };
175
- inbound: {
176
- subscribe: typeof subscribeFromPlugin;
460
+ status: {
461
+ defaultRuntime: {
462
+ accountId: string;
463
+ running: boolean;
464
+ connected: boolean;
465
+ lastConnectedAt: null;
466
+ lastDisconnect: null;
467
+ lastStartAt: null;
468
+ lastStopAt: null;
469
+ lastError: null;
470
+ };
177
471
  };
178
472
  };
179
- export default function register(api: ChannelPluginRuntimeAPI): void;
473
+ export default function register(api: OpenclawPluginApi): void;
180
474
  export {};