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 +86 -6
- package/dist/index.js +203 -6
- package/package.json +7 -2
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
|
|
5
|
-
* instances
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
6
|
-
* instances
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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.
|
|
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
|
|
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",
|