openclaw-agentchat 0.2.1 → 0.2.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/package.json +1 -1
- package/src/agentchat-client.ts +77 -3
- package/src/agentchat-protocol.ts +1 -0
- package/src/gateway.ts +17 -1
- package/src/state.ts +5 -1
package/package.json
CHANGED
package/src/agentchat-client.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
1
2
|
import type { ChatMessage, ClientOptions, MessageHandler } from "./agentchat-protocol";
|
|
2
3
|
|
|
3
4
|
export class AgentChatClient {
|
|
@@ -7,12 +8,19 @@ export class AgentChatClient {
|
|
|
7
8
|
private readonly messageHandlers: MessageHandler[] = [];
|
|
8
9
|
private connectResolve: (() => void) | null = null;
|
|
9
10
|
private connectReject: ((err: Error) => void) | null = null;
|
|
11
|
+
private lifecyclePromise: Promise<void> | null = null;
|
|
12
|
+
private lifecycleResolve: (() => void) | null = null;
|
|
13
|
+
private lifecycleReject: ((err: Error) => void) | null = null;
|
|
14
|
+
private transportError: Error | null = null;
|
|
15
|
+
private connectSettled = false;
|
|
16
|
+
private lifecycleSettled = false;
|
|
10
17
|
|
|
11
18
|
readonly url: string;
|
|
12
19
|
readonly agentId: string;
|
|
13
20
|
readonly token: string;
|
|
14
21
|
readonly capabilities: string[];
|
|
15
22
|
readonly heartbeatInterval: number;
|
|
23
|
+
readonly onDebug?: (event: string, meta?: Record<string, unknown>) => void;
|
|
16
24
|
|
|
17
25
|
constructor(options: ClientOptions) {
|
|
18
26
|
this.url = options.url;
|
|
@@ -20,18 +28,27 @@ export class AgentChatClient {
|
|
|
20
28
|
this.token = options.token ?? "dev-token";
|
|
21
29
|
this.capabilities = options.capabilities ?? [];
|
|
22
30
|
this.heartbeatInterval = options.heartbeatInterval ?? 30_000;
|
|
31
|
+
this.onDebug = options.onDebug;
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
connect(): Promise<void> {
|
|
26
35
|
return new Promise((resolve, reject) => {
|
|
36
|
+
this.connectSettled = false;
|
|
37
|
+
this.lifecycleSettled = false;
|
|
38
|
+
this.transportError = null;
|
|
27
39
|
this.connectResolve = resolve;
|
|
28
40
|
this.connectReject = reject;
|
|
41
|
+
this.lifecyclePromise = new Promise((lifecycleResolve, lifecycleReject) => {
|
|
42
|
+
this.lifecycleResolve = lifecycleResolve;
|
|
43
|
+
this.lifecycleReject = lifecycleReject;
|
|
44
|
+
});
|
|
29
45
|
|
|
46
|
+
this.debug("connect:start", { url: this.url, agentId: this.agentId });
|
|
30
47
|
this.ws = new WebSocket(this.url);
|
|
31
48
|
this.ws.onopen = () => this.handleOpen();
|
|
32
49
|
this.ws.onmessage = (event) => this.handleMessage(String(event.data));
|
|
33
50
|
this.ws.onclose = () => this.handleClose();
|
|
34
|
-
this.ws.onerror = () =>
|
|
51
|
+
this.ws.onerror = () => this.handleError(new Error("WebSocket error"));
|
|
35
52
|
});
|
|
36
53
|
}
|
|
37
54
|
|
|
@@ -45,6 +62,10 @@ export class AgentChatClient {
|
|
|
45
62
|
this.sessionId = null;
|
|
46
63
|
}
|
|
47
64
|
|
|
65
|
+
waitUntilClosed(): Promise<void> {
|
|
66
|
+
return this.lifecyclePromise ?? Promise.resolve();
|
|
67
|
+
}
|
|
68
|
+
|
|
48
69
|
sendMessage(channelId: string, content: string) {
|
|
49
70
|
this.send({
|
|
50
71
|
type: "message",
|
|
@@ -68,6 +89,7 @@ export class AgentChatClient {
|
|
|
68
89
|
}
|
|
69
90
|
|
|
70
91
|
private handleOpen() {
|
|
92
|
+
this.debug("socket:open");
|
|
71
93
|
this.send({
|
|
72
94
|
type: "auth",
|
|
73
95
|
agent_id: this.agentId,
|
|
@@ -76,14 +98,25 @@ export class AgentChatClient {
|
|
|
76
98
|
});
|
|
77
99
|
}
|
|
78
100
|
|
|
101
|
+
private handleError(error: Error) {
|
|
102
|
+
this.debug("socket:error", { error: error.message });
|
|
103
|
+
this.transportError = error;
|
|
104
|
+
if (!this.sessionId) {
|
|
105
|
+
this.rejectConnect(error);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.rejectLifecycle(error);
|
|
109
|
+
}
|
|
110
|
+
|
|
79
111
|
private handleMessage(raw: string) {
|
|
80
112
|
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
81
113
|
|
|
82
114
|
switch (data.type) {
|
|
83
115
|
case "auth_ok":
|
|
84
116
|
this.sessionId = String(data.session_id ?? "");
|
|
117
|
+
this.debug("auth:ok", { sessionId: this.sessionId });
|
|
85
118
|
this.startHeartbeat();
|
|
86
|
-
this.
|
|
119
|
+
this.resolveConnect();
|
|
87
120
|
break;
|
|
88
121
|
case "message":
|
|
89
122
|
for (const handler of this.messageHandlers) {
|
|
@@ -100,8 +133,9 @@ export class AgentChatClient {
|
|
|
100
133
|
}
|
|
101
134
|
break;
|
|
102
135
|
case "error":
|
|
136
|
+
this.debug("server:error", { message: String(data.message ?? "unknown error") });
|
|
103
137
|
if (this.connectReject && !this.sessionId) {
|
|
104
|
-
this.
|
|
138
|
+
this.rejectConnect(new Error(`Auth failed: ${String(data.message ?? "unknown error")}`));
|
|
105
139
|
}
|
|
106
140
|
break;
|
|
107
141
|
case "pong":
|
|
@@ -111,10 +145,22 @@ export class AgentChatClient {
|
|
|
111
145
|
}
|
|
112
146
|
|
|
113
147
|
private handleClose() {
|
|
148
|
+
this.debug("socket:close", { hadSession: Boolean(this.sessionId), transportError: this.transportError?.message ?? null });
|
|
114
149
|
if (this.heartbeatTimer) {
|
|
115
150
|
clearInterval(this.heartbeatTimer);
|
|
116
151
|
this.heartbeatTimer = null;
|
|
117
152
|
}
|
|
153
|
+
const closeError = this.transportError ?? new Error("WebSocket closed");
|
|
154
|
+
if (!this.sessionId) {
|
|
155
|
+
this.rejectConnect(closeError);
|
|
156
|
+
} else if (this.transportError) {
|
|
157
|
+
this.rejectLifecycle(this.transportError);
|
|
158
|
+
} else {
|
|
159
|
+
this.resolveLifecycle();
|
|
160
|
+
}
|
|
161
|
+
this.ws = null;
|
|
162
|
+
this.sessionId = null;
|
|
163
|
+
this.transportError = null;
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
private startHeartbeat() {
|
|
@@ -128,4 +174,32 @@ export class AgentChatClient {
|
|
|
128
174
|
this.ws.send(JSON.stringify(data));
|
|
129
175
|
}
|
|
130
176
|
}
|
|
177
|
+
|
|
178
|
+
private debug(event: string, meta?: Record<string, unknown>) {
|
|
179
|
+
this.onDebug?.(event, meta);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private resolveConnect() {
|
|
183
|
+
if (this.connectSettled) return;
|
|
184
|
+
this.connectSettled = true;
|
|
185
|
+
this.connectResolve?.();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private rejectConnect(error: Error) {
|
|
189
|
+
if (this.connectSettled) return;
|
|
190
|
+
this.connectSettled = true;
|
|
191
|
+
this.connectReject?.(error);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private resolveLifecycle() {
|
|
195
|
+
if (this.lifecycleSettled) return;
|
|
196
|
+
this.lifecycleSettled = true;
|
|
197
|
+
this.lifecycleResolve?.();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private rejectLifecycle(error: Error) {
|
|
201
|
+
if (this.lifecycleSettled) return;
|
|
202
|
+
this.lifecycleSettled = true;
|
|
203
|
+
this.lifecycleReject?.(error);
|
|
204
|
+
}
|
|
131
205
|
}
|
package/src/gateway.ts
CHANGED
|
@@ -166,7 +166,12 @@ export const agentChatGateway: AgentChatGatewayAdapter = {
|
|
|
166
166
|
if (pending) return pending;
|
|
167
167
|
|
|
168
168
|
const startPromise: PendingStart = (async () => {
|
|
169
|
-
const client = createGatewayClient(ctx.account)
|
|
169
|
+
const client = createGatewayClient(ctx.account, (event, meta) => {
|
|
170
|
+
log(ctx, "debug", `AgentChat client ${event}`, {
|
|
171
|
+
accountId: ctx.accountId,
|
|
172
|
+
...meta,
|
|
173
|
+
});
|
|
174
|
+
});
|
|
170
175
|
|
|
171
176
|
client.onMessage((message) => {
|
|
172
177
|
const selfId = ctx.account.agentId ?? ctx.account.accountId;
|
|
@@ -218,6 +223,17 @@ export const agentChatGateway: AgentChatGatewayAdapter = {
|
|
|
218
223
|
accountId: ctx.accountId,
|
|
219
224
|
channelId: ctx.account.defaultChannelId,
|
|
220
225
|
});
|
|
226
|
+
|
|
227
|
+
await client.waitUntilClosed();
|
|
228
|
+
deleteGatewayState(ctx.accountId);
|
|
229
|
+
setConnectedStatus(ctx, {
|
|
230
|
+
running: false,
|
|
231
|
+
connected: false,
|
|
232
|
+
lastStopAt: Date.now(),
|
|
233
|
+
});
|
|
234
|
+
log(ctx, "warn", "AgentChat gateway disconnected", {
|
|
235
|
+
accountId: ctx.accountId,
|
|
236
|
+
});
|
|
221
237
|
return state;
|
|
222
238
|
} catch (error) {
|
|
223
239
|
ctx.abortSignal.removeEventListener("abort", abortHandler);
|
package/src/state.ts
CHANGED
|
@@ -29,11 +29,15 @@ export function setMentionCursor(accountId: string, conversationId: string, time
|
|
|
29
29
|
mentionCursors.set(mentionCursorKey(accountId, conversationId), timestamp);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function createGatewayClient(
|
|
32
|
+
export function createGatewayClient(
|
|
33
|
+
account: AgentChatResolvedAccount,
|
|
34
|
+
onDebug?: (event: string, meta?: Record<string, unknown>) => void,
|
|
35
|
+
) {
|
|
33
36
|
return new AgentChatClient({
|
|
34
37
|
url: account.wsUrl,
|
|
35
38
|
agentId: account.agentId ?? account.accountId,
|
|
36
39
|
token: account.token,
|
|
37
40
|
capabilities: ["chat"],
|
|
41
|
+
onDebug,
|
|
38
42
|
});
|
|
39
43
|
}
|