oomi-ai 0.2.3 → 0.2.4
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/bin/oomi-ai.js +60 -59
- package/bin/sessionBridgeState.js +51 -0
- package/openclaw.extension.js +21 -9
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -2
package/bin/oomi-ai.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
7
|
import WebSocket from 'ws';
|
|
8
|
+
import { ensureSessionBridge, forwardFrameToSession, flushSessionQueue } from './sessionBridgeState.js';
|
|
8
9
|
|
|
9
10
|
const MARKER_START = '<oomi-agent-instructions>';
|
|
10
11
|
const MARKER_END = '</oomi-agent-instructions>';
|
|
@@ -790,6 +791,58 @@ async function startOpenclawBridge(flags) {
|
|
|
790
791
|
|
|
791
792
|
const brokerSocket = new WebSocket(wsUrl.toString());
|
|
792
793
|
let actionCableHeartbeat = null;
|
|
794
|
+
const setupGatewaySession = (sessionId, sessionBridge) => {
|
|
795
|
+
if (!sessionBridge || !sessionBridge.socket) return;
|
|
796
|
+
const gatewaySocket = sessionBridge.socket;
|
|
797
|
+
|
|
798
|
+
gatewaySocket.on('open', () => {
|
|
799
|
+
console.log(`[bridge] gateway.open ${sessionId}`);
|
|
800
|
+
flushSessionQueue(sessionBridge);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
gatewaySocket.on('message', (gatewayRaw) => {
|
|
804
|
+
const frame = typeof gatewayRaw === 'string' ? gatewayRaw : gatewayRaw.toString();
|
|
805
|
+
sendBrokerPayload(brokerSocket, { action: 'gateway_frame', type: 'gateway.frame', sessionId, frame });
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
gatewaySocket.on('close', (code, reason) => {
|
|
809
|
+
const reasonText = reason ? reason.toString() : '';
|
|
810
|
+
console.log(
|
|
811
|
+
`[bridge] gateway.close ${sessionId} code=${String(code)}${reasonText ? ` reason=${reasonText}` : ''}`
|
|
812
|
+
);
|
|
813
|
+
activeGatewaySockets.delete(sessionId);
|
|
814
|
+
sendBrokerPayload(brokerSocket, {
|
|
815
|
+
action: 'gateway_closed',
|
|
816
|
+
type: 'gateway.closed',
|
|
817
|
+
sessionId,
|
|
818
|
+
code,
|
|
819
|
+
reason: reasonText,
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
gatewaySocket.on('error', (err) => {
|
|
824
|
+
console.error(`[bridge] gateway.error ${sessionId}: ${String(err)}`);
|
|
825
|
+
sendBrokerPayload(brokerSocket, {
|
|
826
|
+
action: 'log',
|
|
827
|
+
type: 'log',
|
|
828
|
+
sessionId,
|
|
829
|
+
level: 'error',
|
|
830
|
+
message: `Gateway socket error (${sessionId}): ${String(err)}`,
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
const getOrCreateGatewaySession = (sessionId) => {
|
|
836
|
+
const existing = activeGatewaySockets.get(sessionId);
|
|
837
|
+
if (existing) return existing;
|
|
838
|
+
const sessionBridge = ensureSessionBridge({
|
|
839
|
+
sessions: activeGatewaySockets,
|
|
840
|
+
sessionId,
|
|
841
|
+
createSocket: () => new WebSocket(gateway.gatewayUrl),
|
|
842
|
+
});
|
|
843
|
+
if (sessionBridge) setupGatewaySession(sessionId, sessionBridge);
|
|
844
|
+
return sessionBridge;
|
|
845
|
+
};
|
|
793
846
|
|
|
794
847
|
brokerSocket.on('open', () => {
|
|
795
848
|
console.log('[bridge] Connected to managed broker.');
|
|
@@ -831,55 +884,9 @@ async function startOpenclawBridge(flags) {
|
|
|
831
884
|
|
|
832
885
|
if (payload.type === 'client.open') {
|
|
833
886
|
const sessionId = String(payload.sessionId || '').trim();
|
|
834
|
-
if (!sessionId
|
|
887
|
+
if (!sessionId) return;
|
|
835
888
|
console.log(`[bridge] client.open ${sessionId}`);
|
|
836
|
-
|
|
837
|
-
const sessionBridge = {
|
|
838
|
-
socket: gatewaySocket,
|
|
839
|
-
queue: [],
|
|
840
|
-
};
|
|
841
|
-
activeGatewaySockets.set(sessionId, sessionBridge);
|
|
842
|
-
|
|
843
|
-
gatewaySocket.on('open', () => {
|
|
844
|
-
console.log(`[bridge] gateway.open ${sessionId}`);
|
|
845
|
-
while (sessionBridge.queue.length > 0 && gatewaySocket.readyState === WebSocket.OPEN) {
|
|
846
|
-
const nextFrame = sessionBridge.queue.shift();
|
|
847
|
-
if (typeof nextFrame === 'string' && nextFrame) {
|
|
848
|
-
gatewaySocket.send(nextFrame);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
});
|
|
852
|
-
|
|
853
|
-
gatewaySocket.on('message', (gatewayRaw) => {
|
|
854
|
-
const frame = typeof gatewayRaw === 'string' ? gatewayRaw : gatewayRaw.toString();
|
|
855
|
-
sendBrokerPayload(brokerSocket, { action: 'gateway_frame', type: 'gateway.frame', sessionId, frame });
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
gatewaySocket.on('close', (code, reason) => {
|
|
859
|
-
const reasonText = reason ? reason.toString() : '';
|
|
860
|
-
console.log(
|
|
861
|
-
`[bridge] gateway.close ${sessionId} code=${String(code)}${reasonText ? ` reason=${reasonText}` : ''}`
|
|
862
|
-
);
|
|
863
|
-
activeGatewaySockets.delete(sessionId);
|
|
864
|
-
sendBrokerPayload(brokerSocket, {
|
|
865
|
-
action: 'gateway_closed',
|
|
866
|
-
type: 'gateway.closed',
|
|
867
|
-
sessionId,
|
|
868
|
-
code,
|
|
869
|
-
reason: reasonText,
|
|
870
|
-
});
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
gatewaySocket.on('error', (err) => {
|
|
874
|
-
console.error(`[bridge] gateway.error ${sessionId}: ${String(err)}`);
|
|
875
|
-
sendBrokerPayload(brokerSocket, {
|
|
876
|
-
action: 'log',
|
|
877
|
-
type: 'log',
|
|
878
|
-
sessionId,
|
|
879
|
-
level: 'error',
|
|
880
|
-
message: `Gateway socket error (${sessionId}): ${String(err)}`,
|
|
881
|
-
});
|
|
882
|
-
});
|
|
889
|
+
getOrCreateGatewaySession(sessionId);
|
|
883
890
|
return;
|
|
884
891
|
}
|
|
885
892
|
|
|
@@ -888,19 +895,13 @@ async function startOpenclawBridge(flags) {
|
|
|
888
895
|
const frame = typeof payload.frame === 'string' ? payload.frame : '';
|
|
889
896
|
if (!sessionId || !frame) return;
|
|
890
897
|
console.log(`[bridge] client.frame ${sessionId}`);
|
|
891
|
-
const sessionBridge =
|
|
892
|
-
if (!sessionBridge
|
|
893
|
-
console.log(`[bridge] client.frame dropped (no session) ${sessionId}`);
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
const gatewaySocket = sessionBridge.socket;
|
|
898
|
+
const sessionBridge = getOrCreateGatewaySession(sessionId);
|
|
899
|
+
if (!sessionBridge) return;
|
|
897
900
|
const frameWithAuth = injectGatewayAuth(frame, gateway);
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
} else if (gatewaySocket.readyState === WebSocket.CONNECTING) {
|
|
901
|
+
const result = forwardFrameToSession(sessionBridge, frameWithAuth);
|
|
902
|
+
if (result === 'queued') {
|
|
901
903
|
console.log(`[bridge] client.frame queued ${sessionId}`);
|
|
902
|
-
|
|
903
|
-
} else {
|
|
904
|
+
} else if (result === 'dropped') {
|
|
904
905
|
console.log(`[bridge] client.frame dropped (socket not open) ${sessionId}`);
|
|
905
906
|
}
|
|
906
907
|
return;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const WS_CONNECTING = 0;
|
|
2
|
+
const WS_OPEN = 1;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ensure session state exists so client frames can be buffered before client.open arrives.
|
|
6
|
+
*/
|
|
7
|
+
export function ensureSessionBridge({ sessions, sessionId, createSocket }) {
|
|
8
|
+
const id = String(sessionId || '').trim();
|
|
9
|
+
if (!id) return null;
|
|
10
|
+
|
|
11
|
+
const existing = sessions.get(id);
|
|
12
|
+
if (existing) return existing;
|
|
13
|
+
|
|
14
|
+
const socket = createSocket(id);
|
|
15
|
+
const next = { socket, queue: [] };
|
|
16
|
+
sessions.set(id, next);
|
|
17
|
+
return next;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Forward a frame to the gateway socket or queue it while connecting.
|
|
22
|
+
*/
|
|
23
|
+
export function forwardFrameToSession(sessionBridge, frameText) {
|
|
24
|
+
if (!sessionBridge || !sessionBridge.socket || typeof frameText !== 'string' || !frameText) {
|
|
25
|
+
return 'dropped';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { socket } = sessionBridge;
|
|
29
|
+
if (socket.readyState === WS_OPEN) {
|
|
30
|
+
socket.send(frameText);
|
|
31
|
+
return 'sent';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (socket.readyState === WS_CONNECTING) {
|
|
35
|
+
sessionBridge.queue.push(frameText);
|
|
36
|
+
return 'queued';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return 'dropped';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function flushSessionQueue(sessionBridge) {
|
|
43
|
+
if (!sessionBridge || !sessionBridge.socket) return;
|
|
44
|
+
const socket = sessionBridge.socket;
|
|
45
|
+
while (sessionBridge.queue.length > 0 && socket.readyState === WS_OPEN) {
|
|
46
|
+
const nextFrame = sessionBridge.queue.shift();
|
|
47
|
+
if (typeof nextFrame === 'string' && nextFrame) {
|
|
48
|
+
socket.send(nextFrame);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
package/openclaw.extension.js
CHANGED
|
@@ -154,7 +154,12 @@ async function postJson({ url, token, body, timeoutMs }) {
|
|
|
154
154
|
const oomiChannelPlugin = {
|
|
155
155
|
id: CHANNEL_ID,
|
|
156
156
|
meta: {
|
|
157
|
-
|
|
157
|
+
label: 'Oomi',
|
|
158
|
+
selectionLabel: 'Oomi (Managed)',
|
|
159
|
+
docsPath: '/channels/oomi',
|
|
160
|
+
docsLabel: 'oomi',
|
|
161
|
+
blurb: 'Managed channel transport for Oomi chat.',
|
|
162
|
+
aliases: ['oomi-ai'],
|
|
158
163
|
description: 'Managed Oomi channel plugin.',
|
|
159
164
|
},
|
|
160
165
|
capabilities: {
|
|
@@ -167,15 +172,22 @@ const oomiChannelPlugin = {
|
|
|
167
172
|
threads: true,
|
|
168
173
|
},
|
|
169
174
|
|
|
170
|
-
config
|
|
171
|
-
|
|
172
|
-
|
|
175
|
+
config: {
|
|
176
|
+
listAccountIds(cfg) {
|
|
177
|
+
const normalized = normalizeConfig(cfg);
|
|
178
|
+
return Object.entries(normalized.accounts)
|
|
179
|
+
.filter(([, account]) => account.enabled !== false)
|
|
180
|
+
.map(([accountId]) => accountId);
|
|
181
|
+
},
|
|
173
182
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
resolveAccount(cfg, accountId) {
|
|
184
|
+
const { accountId: resolvedAccountId, account } = resolveAccount(cfg, accountId);
|
|
185
|
+
if (!account) return null;
|
|
186
|
+
return {
|
|
187
|
+
id: resolvedAccountId,
|
|
188
|
+
...account,
|
|
189
|
+
};
|
|
190
|
+
},
|
|
179
191
|
},
|
|
180
192
|
|
|
181
193
|
outbound: {
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oomi-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Oomi CLI for OpenClaw setup",
|
|
5
5
|
"bin": {
|
|
6
6
|
"oomi": "bin/oomi-ai.js"
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"url": "https://github.com/crispcode-io/oomi/issues"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
|
-
"check": "node --check bin/oomi-ai.js"
|
|
49
|
+
"check": "node --check bin/oomi-ai.js",
|
|
50
|
+
"test": "node --test test/sessionBridgeState.test.mjs"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
53
|
"ws": "^8.19.0"
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"license": "MIT",
|
|
55
56
|
"files": [
|
|
56
57
|
"bin/oomi-ai.js",
|
|
58
|
+
"bin/sessionBridgeState.js",
|
|
57
59
|
"openclaw.plugin.json",
|
|
58
60
|
"openclaw.extension.js",
|
|
59
61
|
"agent_instructions.md",
|