clawdy 0.0.1 → 0.0.2

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,11 +1,18 @@
1
1
  /**
2
2
  * @clawdy/openclaw-channel
3
3
  *
4
- * OpenClaw channel plugin that enables outbound push messaging from OpenClaw
5
- * instances to the Clawdy Gateway. When OpenClaw wants to proactively send a
6
- * message (scheduled, cron, heartbeat, agent-initiated), this plugin POSTs to
7
- * the Clawdy Gateway's callback endpoint for delivery via web dashboard, Slack,
8
- * Telegram, or other configured channels.
4
+ * OpenClaw channel plugin that enables bidirectional messaging between OpenClaw
5
+ * instances and the Clawdy Gateway.
6
+ *
7
+ * **Outbound:** When OpenClaw wants to proactively send a message (scheduled,
8
+ * cron, heartbeat, agent-initiated), this plugin POSTs to the Clawdy Gateway's
9
+ * callback endpoint for delivery via web dashboard, Slack, Telegram, or other
10
+ * configured channels.
11
+ *
12
+ * **Inbound:** Exposes a `POST /clawdy/inbound` HTTP route on the OpenClaw
13
+ * gateway that accepts messages from the Clawdy Gateway and dispatches them
14
+ * through OpenClaw's standard agent pipeline (session management, agent
15
+ * routing, reply generation).
9
16
  *
10
17
  * ## Configuration (openclaw.json)
11
18
  *
@@ -34,18 +41,91 @@
34
41
  *
35
42
  * Using `:default` targets is recommended — the Clawdy Gateway resolves the
36
43
  * actual destination from the user's gateway link configuration in the database.
44
+ *
45
+ * ## Inbound Endpoint
46
+ *
47
+ * `POST /plugins/clawdy/inbound` (registered via `registerHttpRoute`)
48
+ *
49
+ * Request body:
50
+ * ```json
51
+ * {
52
+ * "text": "Hello from the dashboard",
53
+ * "senderId": "user_abc123",
54
+ * "agentId": "main", // optional, defaults to "main"
55
+ * "sessionKey": "clawdy-user_abc123" // optional, auto-generated if omitted
56
+ * }
57
+ * ```
58
+ *
59
+ * Headers:
60
+ * - `X-Proxy-Secret`: Must match `channels.clawdy.callbackSecret`
61
+ * - `Content-Type: application/json`
62
+ *
63
+ * Response:
64
+ * ```json
65
+ * { "ok": true, "response": "Agent reply text here" }
66
+ * ```
37
67
  */
68
+ /**
69
+ * HTTP route handler types (Node.js IncomingMessage/ServerResponse).
70
+ * Defined inline to avoid depending on @types/node.
71
+ */
72
+ interface HttpIncomingMessage {
73
+ method?: string;
74
+ headers: Record<string, string | string[] | undefined>;
75
+ on(event: string, listener: (...args: unknown[]) => void): void;
76
+ }
77
+ interface HttpServerResponse {
78
+ statusCode: number;
79
+ setHeader(name: string, value: string): void;
80
+ end(data?: string): void;
81
+ }
38
82
  /**
39
83
  * OpenClaw Plugin API — passed to the register function.
84
+ *
85
+ * Includes core registration methods and the runtime object that provides
86
+ * access to the channel reply dispatch pipeline.
40
87
  */
41
88
  interface OpenClawPluginApi {
42
89
  registerChannel(opts: {
43
90
  plugin: unknown;
44
91
  }): void;
92
+ registerHttpRoute?(opts: {
93
+ path: string;
94
+ handler: (req: HttpIncomingMessage, res: HttpServerResponse) => void | Promise<void>;
95
+ }): void;
96
+ /** Global OpenClaw configuration (openclaw.json). */
97
+ config: Record<string, unknown>;
98
+ /** Plugin runtime — provides channel reply dispatch utilities. */
99
+ runtime: {
100
+ channel: {
101
+ reply: {
102
+ finalizeInboundContext(ctx: Record<string, unknown>): Record<string, unknown>;
103
+ createReplyDispatcherWithTyping(opts: Record<string, unknown>): {
104
+ dispatcher: unknown;
105
+ replyOptions: unknown;
106
+ markDispatchIdle: () => void;
107
+ };
108
+ dispatchReplyFromConfig(opts: {
109
+ ctx: unknown;
110
+ cfg: unknown;
111
+ dispatcher: unknown;
112
+ replyOptions?: unknown;
113
+ }): Promise<void>;
114
+ resolveHumanDelayConfig(cfg: unknown, agentId?: string): unknown;
115
+ };
116
+ };
117
+ error?: (...args: unknown[]) => void;
118
+ };
119
+ /** Plugin logger. */
120
+ logger?: {
121
+ info(...args: unknown[]): void;
122
+ error(...args: unknown[]): void;
123
+ warn(...args: unknown[]): void;
124
+ };
45
125
  }
46
126
  /**
47
127
  * Entry point called by OpenClaw's plugin loader.
48
- * Registers the Clawdy channel plugin.
128
+ * Registers the Clawdy channel plugin and the inbound HTTP route.
49
129
  */
50
130
  export default function register(api: OpenClawPluginApi): void;
51
131
  export {};
package/dist/index.js CHANGED
@@ -2,11 +2,18 @@
2
2
  /**
3
3
  * @clawdy/openclaw-channel
4
4
  *
5
- * OpenClaw channel plugin that enables outbound push messaging from OpenClaw
6
- * instances to the Clawdy Gateway. When OpenClaw wants to proactively send a
7
- * message (scheduled, cron, heartbeat, agent-initiated), this plugin POSTs to
8
- * the Clawdy Gateway's callback endpoint for delivery via web dashboard, Slack,
9
- * Telegram, or other configured channels.
5
+ * OpenClaw channel plugin that enables bidirectional messaging between OpenClaw
6
+ * instances and the Clawdy Gateway.
7
+ *
8
+ * **Outbound:** When OpenClaw wants to proactively send a message (scheduled,
9
+ * cron, heartbeat, agent-initiated), this plugin POSTs to the Clawdy Gateway's
10
+ * callback endpoint for delivery via web dashboard, Slack, Telegram, or other
11
+ * configured channels.
12
+ *
13
+ * **Inbound:** Exposes a `POST /clawdy/inbound` HTTP route on the OpenClaw
14
+ * gateway that accepts messages from the Clawdy Gateway and dispatches them
15
+ * through OpenClaw's standard agent pipeline (session management, agent
16
+ * routing, reply generation).
10
17
  *
11
18
  * ## Configuration (openclaw.json)
12
19
  *
@@ -35,6 +42,29 @@
35
42
  *
36
43
  * Using `:default` targets is recommended — the Clawdy Gateway resolves the
37
44
  * actual destination from the user's gateway link configuration in the database.
45
+ *
46
+ * ## Inbound Endpoint
47
+ *
48
+ * `POST /plugins/clawdy/inbound` (registered via `registerHttpRoute`)
49
+ *
50
+ * Request body:
51
+ * ```json
52
+ * {
53
+ * "text": "Hello from the dashboard",
54
+ * "senderId": "user_abc123",
55
+ * "agentId": "main", // optional, defaults to "main"
56
+ * "sessionKey": "clawdy-user_abc123" // optional, auto-generated if omitted
57
+ * }
58
+ * ```
59
+ *
60
+ * Headers:
61
+ * - `X-Proxy-Secret`: Must match `channels.clawdy.callbackSecret`
62
+ * - `Content-Type: application/json`
63
+ *
64
+ * Response:
65
+ * ```json
66
+ * { "ok": true, "response": "Agent reply text here" }
67
+ * ```
38
68
  */
39
69
  Object.defineProperty(exports, "__esModule", { value: true });
40
70
  exports.default = register;
@@ -134,6 +164,35 @@ async function sendCallback(account, payload) {
134
164
  messageId: data.messageId ?? payload.messageId,
135
165
  };
136
166
  }
167
+ /**
168
+ * Read and parse JSON body from a Node.js IncomingMessage.
169
+ */
170
+ function readJsonBody(req) {
171
+ return new Promise((resolve, reject) => {
172
+ let body = "";
173
+ req.on("data", (...args) => {
174
+ const chunk = args[0];
175
+ body += typeof chunk === "string" ? chunk : String(chunk);
176
+ });
177
+ req.on("end", () => {
178
+ try {
179
+ resolve(JSON.parse(body));
180
+ }
181
+ catch (err) {
182
+ reject(new Error("Invalid JSON body"));
183
+ }
184
+ });
185
+ req.on("error", reject);
186
+ });
187
+ }
188
+ /**
189
+ * Send a JSON response on a Node.js ServerResponse.
190
+ */
191
+ function jsonResponse(res, statusCode, data) {
192
+ res.statusCode = statusCode;
193
+ res.setHeader("Content-Type", "application/json");
194
+ res.end(JSON.stringify(data));
195
+ }
137
196
  // ─── Channel Plugin ────────────────────────────────────────────────────────
138
197
  const clawdyPlugin = {
139
198
  id: "clawdy",
@@ -201,11 +260,149 @@ const clawdyPlugin = {
201
260
  },
202
261
  },
203
262
  };
263
+ // ─── Inbound Handler ───────────────────────────────────────────────────────
264
+ /**
265
+ * Create the inbound HTTP route handler.
266
+ *
267
+ * This handler accepts POST requests from the Clawdy Gateway, dispatches
268
+ * the message through OpenClaw's agent pipeline, and returns the agent's
269
+ * response text.
270
+ *
271
+ * The flow mirrors how other channel plugins (Telegram, Mattermost, etc.)
272
+ * handle inbound messages:
273
+ *
274
+ * 1. Parse & validate request (check X-Proxy-Secret header)
275
+ * 2. Build MsgContext (sender, channel, session, body)
276
+ * 3. Finalize context via `finalizeInboundContext`
277
+ * 4. Create reply dispatcher via `createReplyDispatcherWithTyping`
278
+ * 5. Dispatch through agent pipeline via `dispatchReplyFromConfig`
279
+ * 6. Collect response text and return to caller
280
+ */
281
+ function createInboundHandler(api) {
282
+ const core = api.runtime;
283
+ const cfg = api.config;
284
+ const log = api.logger ?? console;
285
+ return async (req, res) => {
286
+ // ── Method check ─────────────────────────────────────────────────
287
+ if (req.method !== "POST") {
288
+ jsonResponse(res, 405, { ok: false, error: "Method not allowed" });
289
+ return;
290
+ }
291
+ // ── Auth check ───────────────────────────────────────────────────
292
+ const account = resolveAccount(cfg);
293
+ if (!account.callbackSecret) {
294
+ jsonResponse(res, 500, {
295
+ ok: false,
296
+ error: "No callbackSecret configured in channels.clawdy",
297
+ });
298
+ return;
299
+ }
300
+ const proxySecret = req.headers["x-proxy-secret"] ?? "";
301
+ if (proxySecret !== account.callbackSecret) {
302
+ jsonResponse(res, 401, { ok: false, error: "Unauthorized" });
303
+ return;
304
+ }
305
+ // ── Parse body ───────────────────────────────────────────────────
306
+ let payload;
307
+ try {
308
+ const body = await readJsonBody(req);
309
+ if (!body.text || typeof body.text !== "string") {
310
+ jsonResponse(res, 400, { ok: false, error: "Missing required field: text" });
311
+ return;
312
+ }
313
+ if (!body.senderId || typeof body.senderId !== "string") {
314
+ jsonResponse(res, 400, { ok: false, error: "Missing required field: senderId" });
315
+ return;
316
+ }
317
+ payload = {
318
+ text: body.text,
319
+ senderId: body.senderId,
320
+ agentId: typeof body.agentId === "string" ? body.agentId : undefined,
321
+ sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : undefined,
322
+ };
323
+ }
324
+ catch {
325
+ jsonResponse(res, 400, { ok: false, error: "Invalid JSON body" });
326
+ return;
327
+ }
328
+ // ── Build & finalize message context ─────────────────────────────
329
+ const sessionKey = payload.sessionKey ?? `clawdy-${payload.senderId}`;
330
+ const from = `clawdy:${payload.senderId}`;
331
+ const to = "clawdy:default";
332
+ const ctxPayload = core.channel.reply.finalizeInboundContext({
333
+ Body: payload.text,
334
+ BodyForAgent: payload.text,
335
+ RawBody: payload.text,
336
+ CommandBody: payload.text,
337
+ From: from,
338
+ To: to,
339
+ SessionKey: sessionKey,
340
+ AccountId: "default",
341
+ ChatType: "direct",
342
+ SenderName: payload.senderId,
343
+ SenderId: payload.senderId,
344
+ Provider: "clawdy",
345
+ Surface: "clawdy",
346
+ MessageSid: crypto.randomUUID(),
347
+ Timestamp: Date.now(),
348
+ CommandAuthorized: true,
349
+ OriginatingChannel: "clawdy",
350
+ OriginatingTo: to,
351
+ });
352
+ // ── Create reply dispatcher ──────────────────────────────────────
353
+ // Collect all reply text chunks from the agent so we can return them.
354
+ const responseChunks = [];
355
+ const { dispatcher, replyOptions } = core.channel.reply.createReplyDispatcherWithTyping({
356
+ humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, payload.agentId),
357
+ deliver: async (replyPayload) => {
358
+ if (replyPayload.text) {
359
+ responseChunks.push(replyPayload.text);
360
+ }
361
+ },
362
+ onError: (err, info) => {
363
+ log.error(`[clawdy-channel] ${info.kind} reply failed:`, err);
364
+ },
365
+ });
366
+ // ── Dispatch through agent pipeline ──────────────────────────────
367
+ try {
368
+ await core.channel.reply.dispatchReplyFromConfig({
369
+ ctx: ctxPayload,
370
+ cfg,
371
+ dispatcher,
372
+ replyOptions,
373
+ });
374
+ const fullResponse = responseChunks.join("\n\n");
375
+ jsonResponse(res, 200, {
376
+ ok: true,
377
+ response: fullResponse,
378
+ });
379
+ }
380
+ catch (err) {
381
+ log.error("[clawdy-channel] Inbound dispatch failed:", err);
382
+ jsonResponse(res, 500, {
383
+ ok: false,
384
+ error: "Agent dispatch failed",
385
+ });
386
+ }
387
+ };
388
+ }
204
389
  // ─── Plugin Registration ───────────────────────────────────────────────────
205
390
  /**
206
391
  * Entry point called by OpenClaw's plugin loader.
207
- * Registers the Clawdy channel plugin.
392
+ * Registers the Clawdy channel plugin and the inbound HTTP route.
208
393
  */
209
394
  function register(api) {
210
395
  api.registerChannel({ plugin: clawdyPlugin });
396
+ // Register inbound HTTP route if the API supports it.
397
+ // This exposes POST /plugins/clawdy/inbound on the OpenClaw gateway.
398
+ if (api.registerHttpRoute) {
399
+ api.registerHttpRoute({
400
+ path: "/clawdy/inbound",
401
+ handler: createInboundHandler(api),
402
+ });
403
+ console.log("[clawdy-channel] Registered inbound HTTP route: /clawdy/inbound");
404
+ }
405
+ else {
406
+ console.log("[clawdy-channel] registerHttpRoute not available — inbound route not registered");
407
+ }
211
408
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdy",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "OpenClaw channel plugin for Clawdy — enables outbound push messaging from OpenClaw instances to the Clawdy Gateway",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,7 +13,7 @@
13
13
  "clean": "rm -rf dist",
14
14
  "prepublishOnly": "npm run clean && npm run build",
15
15
  "pack": "npm run clean && npm run build && npm pack",
16
- "publish:npm": "npm run clean && npm run build && npm publish --access public"
16
+ "publish:npm": "npm publish ./clawdy-0.0.2.tgz --access public"
17
17
  },
18
18
  "keywords": [
19
19
  "openclaw",
@@ -22,6 +22,11 @@
22
22
  "plugin",
23
23
  "gateway"
24
24
  ],
25
+ "openclaw": {
26
+ "extensions": [
27
+ "./dist/index.js"
28
+ ]
29
+ },
25
30
  "license": "MIT",
26
31
  "repository": {
27
32
  "type": "git",