@wishee-ai/openclaw-connector 1.0.0

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,89 @@
1
+ /**
2
+ * Manages the binding state (clawId + secret) for this OpenClaw instance.
3
+ * Stores binding info in ~/.openclaw/openclaw-connector/binding.json.
4
+ */
5
+ import crypto from "node:crypto";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ const BINDING_DIR = path.join(process.env.HOME || "/tmp", ".openclaw", "openclaw-connector");
9
+ const BINDING_FILE = path.join(BINDING_DIR, "binding.json");
10
+ const DEFAULT_BRIDGE_URL = "wss://ai-bridge.wishee.com.cn/ws";
11
+ const BIND_PAGE_BASE = "https://openclaw.coraai.com.cn/bind";
12
+ /**
13
+ * Load or create the binding state.
14
+ * Generates a new clawId + secret on first run.
15
+ */
16
+ export function loadOrCreateBinding(bridgeUrl) {
17
+ try {
18
+ if (fs.existsSync(BINDING_FILE)) {
19
+ const data = JSON.parse(fs.readFileSync(BINDING_FILE, "utf8"));
20
+ if (data?.clawId && data?.secret) {
21
+ // Allow overriding bridgeUrl from config
22
+ if (bridgeUrl)
23
+ data.bridgeUrl = bridgeUrl;
24
+ return data;
25
+ }
26
+ }
27
+ }
28
+ catch {
29
+ // regenerate
30
+ }
31
+ const state = {
32
+ clawId: crypto.randomUUID(),
33
+ secret: crypto.randomBytes(32).toString("hex"),
34
+ bridgeUrl: bridgeUrl || DEFAULT_BRIDGE_URL,
35
+ bound: false,
36
+ };
37
+ fs.mkdirSync(BINDING_DIR, { recursive: true });
38
+ fs.writeFileSync(BINDING_FILE, JSON.stringify(state, null, 2));
39
+ try {
40
+ fs.chmodSync(BINDING_FILE, 0o600);
41
+ }
42
+ catch {
43
+ // best-effort
44
+ }
45
+ return state;
46
+ }
47
+ /**
48
+ * Mark binding as completed in the local state file.
49
+ */
50
+ export function markBound(state) {
51
+ state.bound = true;
52
+ fs.mkdirSync(BINDING_DIR, { recursive: true });
53
+ fs.writeFileSync(BINDING_FILE, JSON.stringify(state, null, 2));
54
+ }
55
+ /**
56
+ * Build the binding URL for QR code display.
57
+ */
58
+ export function getBindUrl(state) {
59
+ const url = new URL(BIND_PAGE_BASE);
60
+ url.searchParams.set("clawId", state.clawId);
61
+ url.searchParams.set("secret", state.secret);
62
+ return url.toString();
63
+ }
64
+ /**
65
+ * Display QR code in terminal for binding.
66
+ */
67
+ export async function showBindingQR(state, logger) {
68
+ const url = getBindUrl(state);
69
+ logger.info("=== OpenClaw Connector Binding ===");
70
+ logger.info("Scan the QR code below to bind this OpenClaw instance:");
71
+ logger.info("");
72
+ try {
73
+ // qrcode-terminal is a CJS module
74
+ const qrcode = await import("qrcode-terminal");
75
+ const generate = qrcode.default?.generate || qrcode.generate;
76
+ generate(url, { small: true }, (qr) => {
77
+ for (const line of qr.split("\n")) {
78
+ logger.info(line);
79
+ }
80
+ });
81
+ }
82
+ catch {
83
+ logger.info("[QR code display failed — install qrcode-terminal]");
84
+ }
85
+ logger.info("");
86
+ logger.info(`Or open this URL: ${url}`);
87
+ logger.info("=================================");
88
+ }
89
+ //# sourceMappingURL=binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Outbound WebSocket client that connects this OpenClaw instance to the Cora bridge.
3
+ *
4
+ * Lifecycle:
5
+ * 1. Load/create binding state (clawId + secret)
6
+ * 2. If not bound, show QR code for user to scan
7
+ * 3. Connect to bridge via WebSocket
8
+ * 4. Send hello handshake → wait for welcome
9
+ * 5. Subscribe to agent events + transcript updates → forward to bridge
10
+ * 6. Handle RPC requests from bridge → dispatch to subagent API
11
+ */
12
+ import WebSocket from "ws";
13
+ import { loadOrCreateBinding, markBound, showBindingQR } from "./binding.js";
14
+ import { fetchTaskResult } from "./task-result.js";
15
+ // ── State ──────────────────────────────────────────────────────
16
+ let ws = null;
17
+ let closed = false;
18
+ let backoffMs = 1000;
19
+ let tickTimer = null;
20
+ let unsubAgentEvents = null;
21
+ let unsubTranscript = null;
22
+ let bindingState = null;
23
+ let pluginApi = null;
24
+ // ── Public API ─────────────────────────────────────────────────
25
+ export async function startConnector(api, ctx) {
26
+ pluginApi = api;
27
+ closed = false;
28
+ const bridgeUrl = api.pluginConfig?.bridgeUrl || undefined;
29
+ bindingState = loadOrCreateBinding(bridgeUrl);
30
+ ctx.logger.info(`[connector] clawId=${bindingState.clawId}, bound=${bindingState.bound}`);
31
+ if (!bindingState.bound) {
32
+ await showBindingQR(bindingState, ctx.logger);
33
+ }
34
+ // Subscribe to agent events
35
+ unsubAgentEvents = api.runtime.events.onAgentEvent((evt) => {
36
+ sendEvent({
37
+ type: "event",
38
+ event: "agent",
39
+ payload: {
40
+ runId: evt.runId,
41
+ stream: evt.stream,
42
+ data: evt.data,
43
+ ts: evt.ts,
44
+ seq: evt.seq,
45
+ sessionKey: evt.sessionKey,
46
+ },
47
+ });
48
+ // On lifecycle end, fetch task result after a short delay
49
+ if (evt.stream === "lifecycle" && evt.data?.phase === "end") {
50
+ const sessionKey = evt.sessionKey;
51
+ if (sessionKey) {
52
+ setTimeout(() => {
53
+ void fetchAndSendTaskResult(evt.runId, sessionKey, ctx.logger);
54
+ }, 500);
55
+ }
56
+ }
57
+ });
58
+ // Subscribe to transcript updates
59
+ unsubTranscript = api.runtime.events.onSessionTranscriptUpdate((evt) => {
60
+ sendEvent({
61
+ type: "event",
62
+ event: "transcript",
63
+ payload: {
64
+ sessionKey: evt.sessionKey,
65
+ message: evt.message,
66
+ messageId: evt.messageId,
67
+ },
68
+ });
69
+ });
70
+ // Start connection
71
+ connect(ctx.logger);
72
+ }
73
+ export function stopConnector() {
74
+ closed = true;
75
+ unsubAgentEvents?.();
76
+ unsubTranscript?.();
77
+ unsubAgentEvents = null;
78
+ unsubTranscript = null;
79
+ if (tickTimer) {
80
+ clearInterval(tickTimer);
81
+ tickTimer = null;
82
+ }
83
+ ws?.close();
84
+ ws = null;
85
+ pluginApi = null;
86
+ }
87
+ // ── Connection ─────────────────────────────────────────────────
88
+ function connect(logger) {
89
+ if (closed || !bindingState)
90
+ return;
91
+ const url = bindingState.bridgeUrl;
92
+ logger.info(`[connector] Connecting to bridge: ${url}`);
93
+ const socket = new WebSocket(url);
94
+ ws = socket;
95
+ socket.on("open", () => {
96
+ logger.info("[connector] WebSocket connected, sending hello...");
97
+ backoffMs = 1000;
98
+ const hello = {
99
+ type: "hello",
100
+ clawId: bindingState.clawId,
101
+ secret: bindingState.secret,
102
+ };
103
+ socket.send(JSON.stringify(hello));
104
+ });
105
+ socket.on("message", (data) => {
106
+ const text = typeof data === "string" ? data : data.toString("utf-8");
107
+ handleMessage(text, logger);
108
+ });
109
+ socket.on("close", (code, reason) => {
110
+ const reasonText = typeof reason === "string" ? reason : reason.toString("utf-8");
111
+ logger.warn(`[connector] Connection closed: code=${code}, reason=${reasonText}`);
112
+ ws = null;
113
+ if (code === 4001) {
114
+ logger.info("[connector] Not bound yet. Waiting for user to scan QR code...");
115
+ }
116
+ else if (code === 4003) {
117
+ logger.error("[connector] Authentication failed. Secret mismatch.");
118
+ }
119
+ scheduleReconnect(logger);
120
+ });
121
+ socket.on("error", (err) => {
122
+ logger.error(`[connector] WebSocket error: ${err.message}`);
123
+ });
124
+ }
125
+ function scheduleReconnect(logger) {
126
+ if (closed)
127
+ return;
128
+ const delay = backoffMs;
129
+ backoffMs = Math.min(backoffMs * 2, 30_000);
130
+ logger.info(`[connector] Reconnecting in ${delay}ms...`);
131
+ setTimeout(() => connect(logger), delay);
132
+ }
133
+ // ── Message handling ───────────────────────────────────────────
134
+ function handleMessage(raw, logger) {
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(raw);
138
+ }
139
+ catch {
140
+ return;
141
+ }
142
+ // Welcome frame (handshake accepted)
143
+ if (parsed.type === "welcome") {
144
+ const welcome = parsed;
145
+ logger.info(`[connector] Handshake accepted, userId=${welcome.userId}`);
146
+ if (bindingState && !bindingState.bound) {
147
+ markBound(bindingState);
148
+ logger.info("[connector] Binding confirmed and saved locally.");
149
+ }
150
+ // Start heartbeat
151
+ startHeartbeat();
152
+ return;
153
+ }
154
+ // RPC request from bridge
155
+ if (parsed.type === "req" && typeof parsed.id === "string" && typeof parsed.method === "string") {
156
+ const req = parsed;
157
+ void handleRpcRequest(req, logger);
158
+ return;
159
+ }
160
+ }
161
+ // ── RPC dispatch ───────────────────────────────────────────────
162
+ async function handleRpcRequest(req, logger) {
163
+ const { id, method, params } = req;
164
+ logger.info(`[connector] RPC request: method=${method}, id=${id}`);
165
+ try {
166
+ const subagent = pluginApi?.runtime.subagent;
167
+ if (!subagent) {
168
+ sendResponse(id, false, undefined, { message: "subagent not available" });
169
+ return;
170
+ }
171
+ let result;
172
+ switch (method) {
173
+ case "chat.send": {
174
+ const p = params;
175
+ result = await subagent.run({
176
+ sessionKey: p.sessionKey,
177
+ message: p.message,
178
+ deliver: p.deliver,
179
+ idempotencyKey: p.idempotencyKey,
180
+ });
181
+ break;
182
+ }
183
+ case "chat.history": {
184
+ const p = params;
185
+ result = await subagent.getSessionMessages({
186
+ sessionKey: p.sessionKey,
187
+ limit: p.limit,
188
+ });
189
+ break;
190
+ }
191
+ case "sessions.reset": {
192
+ const p = params;
193
+ await subagent.deleteSession({ sessionKey: p.sessionKey });
194
+ result = { ok: true };
195
+ break;
196
+ }
197
+ case "agent.wait": {
198
+ const p = params;
199
+ result = await subagent.waitForRun({
200
+ runId: p.runId,
201
+ timeoutMs: p.timeoutMs,
202
+ });
203
+ break;
204
+ }
205
+ default:
206
+ sendResponse(id, false, undefined, { message: `unknown method: ${method}` });
207
+ return;
208
+ }
209
+ sendResponse(id, true, result);
210
+ }
211
+ catch (err) {
212
+ const message = err instanceof Error ? err.message : String(err);
213
+ logger.error(`[connector] RPC error: method=${method}, error=${message}`);
214
+ sendResponse(id, false, undefined, { message });
215
+ }
216
+ }
217
+ // ── Sending ────────────────────────────────────────────────────
218
+ function sendResponse(id, ok, payload, error) {
219
+ const frame = { type: "res", id, ok };
220
+ if (payload !== undefined)
221
+ frame.payload = payload;
222
+ if (error)
223
+ frame.error = error;
224
+ sendRaw(frame);
225
+ }
226
+ function sendEvent(frame) {
227
+ sendRaw(frame);
228
+ }
229
+ function sendRaw(frame) {
230
+ if (ws?.readyState === WebSocket.OPEN) {
231
+ ws.send(JSON.stringify(frame));
232
+ }
233
+ }
234
+ function startHeartbeat() {
235
+ if (tickTimer)
236
+ clearInterval(tickTimer);
237
+ tickTimer = setInterval(() => {
238
+ sendEvent({ type: "event", event: "tick" });
239
+ }, 25_000);
240
+ }
241
+ // ── Task result helper ─────────────────────────────────────────
242
+ async function fetchAndSendTaskResult(runId, sessionKey, logger) {
243
+ const subagent = pluginApi?.runtime.subagent;
244
+ if (!subagent)
245
+ return;
246
+ const frame = await fetchTaskResult(subagent, runId, sessionKey, logger);
247
+ if (frame) {
248
+ sendEvent(frame);
249
+ logger.info(`[connector] Sent task-result for runId=${runId}`);
250
+ }
251
+ }
252
+ //# sourceMappingURL=connector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector.js","sourceRoot":"","sources":["../src/connector.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+ import { startConnector, stopConnector } from "./connector.js";
3
+ export default definePluginEntry({
4
+ id: "openclaw-connector",
5
+ name: "Cora OpenClaw Connector",
6
+ description: "Connects this OpenClaw instance to Cora bridge via reverse WebSocket",
7
+ register(api) {
8
+ api.registerService({
9
+ id: "openclaw-connector",
10
+ start: (ctx) => startConnector(api, ctx),
11
+ stop: () => stopConnector(),
12
+ });
13
+ },
14
+ });
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/D,eAAe,iBAAiB,CAAC;IAC/B,EAAE,EAAE,oBAAoB;IACxB,IAAI,EAAE,yBAAyB;IAC/B,WAAW,EAAE,sEAAsE;IACnF,QAAQ,CAAC,GAAG;QACV,GAAG,CAAC,eAAe,CAAC;YAClB,EAAE,EAAE,oBAAoB;YACxB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,GAAU,EAAE,GAAG,CAAC;YAC/C,IAAI,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE;SAC5B,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Wire protocol frames between the connector plugin and the bridge.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * After an agent lifecycle ends, fetch the session messages
3
+ * to extract the assistant's final reply text.
4
+ */
5
+ /**
6
+ * Fetch the latest assistant reply from a session after task completion.
7
+ * Returns a task-result event frame ready to send to bridge.
8
+ */
9
+ export async function fetchTaskResult(subagent, runId, sessionKey, logger) {
10
+ try {
11
+ const { messages } = await subagent.getSessionMessages({ sessionKey, limit: 5 });
12
+ // Find the last assistant message
13
+ let text = "";
14
+ for (let i = messages.length - 1; i >= 0; i--) {
15
+ const msg = messages[i];
16
+ if (msg?.role === "assistant") {
17
+ if (typeof msg.content === "string") {
18
+ text = msg.content;
19
+ }
20
+ else if (Array.isArray(msg.content)) {
21
+ text = msg.content
22
+ .map((c) => c.text ?? "")
23
+ .join("");
24
+ }
25
+ break;
26
+ }
27
+ }
28
+ if (!text) {
29
+ logger.info(`[task-result] No assistant reply found for runId=${runId}`);
30
+ return null;
31
+ }
32
+ return {
33
+ type: "event",
34
+ event: "task-result",
35
+ payload: { runId, sessionKey, text },
36
+ };
37
+ }
38
+ catch (err) {
39
+ logger.error(`[task-result] Failed to fetch session messages for runId=${runId}: ${err}`);
40
+ return null;
41
+ }
42
+ }
43
+ //# sourceMappingURL=task-result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-result.js","sourceRoot":"","sources":["../src/task-result.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAqB,EACrB,KAAa,EACb,UAAkB,EAClB,MAAqE;IAErE,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QAEjF,kCAAkC;QAClC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAyC,CAAC;YAChE,IAAI,GAAG,EAAE,IAAI,KAAK,WAAW,EAAE,CAAC;gBAC9B,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;oBACpC,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC;gBACrB,CAAC;qBAAM,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;oBACtC,IAAI,GAAI,GAAG,CAAC,OAAoC;yBAC7C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;yBACxB,IAAI,CAAC,EAAE,CAAC,CAAC;gBACd,CAAC;gBACD,MAAM;YACR,CAAC;QACH,CAAC;QAED,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,CAAC,oDAAoD,KAAK,EAAE,CAAC,CAAC;YACzE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO;YACL,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;SACrC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,4DAA4D,KAAK,KAAK,GAAG,EAAE,CAAC,CAAC;QAC1F,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "openclaw-connector",
3
+ "name": "Cora OpenClaw Connector",
4
+ "description": "Connects this OpenClaw instance to Cora bridge via reverse WebSocket",
5
+ "configSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "bridgeUrl": {
10
+ "type": "string",
11
+ "description": "Bridge WebSocket URL"
12
+ }
13
+ }
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@wishee-ai/openclaw-connector",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Connect OpenClaw to Cora bridge via reverse WebSocket",
6
+ "main": "dist/index.js",
7
+ "exports": { ".": { "import": "./dist/index.js" } },
8
+ "files": [ "dist", "openclaw.plugin.json" ],
9
+ "scripts": { "clean": "rm -rf dist", "prebuild": "npm run clean", "build": "tsc", "prepublishOnly": "npm run build" },
10
+ "license": "MIT",
11
+ "keywords": [ "openclaw", "cora", "connector", "bridge" ],
12
+ "dependencies": { "ws": "^8.18.0", "qrcode-terminal": "^0.12.0" },
13
+ "devDependencies": { "openclaw": "^2026.6.10", "@types/ws": "^8.5.0", "@types/qrcode-terminal": "^0.12.0", "@types/node": "^22.0.0", "typescript": "^5.7.0" },
14
+ "peerDependencies": { "openclaw": ">=2026.3.0" },
15
+ "peerDependenciesMeta": { "openclaw": { "optional": true } },
16
+ "openclaw": { "extensions": [ "./dist/index.js" ] }
17
+ }