@yahaha-studio/kichi-forwarder 0.0.1-alpha.39 → 0.0.1-alpha.41
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/index.ts +29 -15
- package/package.json +1 -1
- package/src/service.ts +88 -13
- package/src/types.ts +11 -1
package/index.ts
CHANGED
|
@@ -53,15 +53,6 @@ const FIXED_HOOK_STATUSES: Record<string, ActionResult> = {
|
|
|
53
53
|
},
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
const MESSAGE_RECEIVED_BUBBLES = [
|
|
57
|
-
"Let me see...",
|
|
58
|
-
"Gotcha!",
|
|
59
|
-
"On it!",
|
|
60
|
-
"Hmm, interesting",
|
|
61
|
-
"Copy that",
|
|
62
|
-
"Reading...",
|
|
63
|
-
];
|
|
64
|
-
|
|
65
56
|
const MESSAGE_SENT_BUBBLES = [
|
|
66
57
|
"All set!",
|
|
67
58
|
"Sent.",
|
|
@@ -250,11 +241,12 @@ function syncFixedStatus(status: ActionResult): void {
|
|
|
250
241
|
});
|
|
251
242
|
}
|
|
252
243
|
|
|
253
|
-
async function handleMessageReceivedHook(): Promise<void> {
|
|
244
|
+
async function handleMessageReceivedHook(content: string): Promise<void> {
|
|
254
245
|
if (!service?.hasValidIdentity() || !service?.isConnected()) {
|
|
255
246
|
return;
|
|
256
247
|
}
|
|
257
|
-
|
|
248
|
+
const trimmed = content.length > 7 ? content.slice(0, 7) + "..." : content;
|
|
249
|
+
service.sendHookNotify("message_received", `"${trimmed}"`);
|
|
258
250
|
}
|
|
259
251
|
|
|
260
252
|
function handleMessageSentHook(): void {
|
|
@@ -284,8 +276,8 @@ function registerPluginHooks(api: OpenClawPluginApi): void {
|
|
|
284
276
|
}
|
|
285
277
|
});
|
|
286
278
|
|
|
287
|
-
api.on("message_received", async () => {
|
|
288
|
-
await handleMessageReceivedHook();
|
|
279
|
+
api.on("message_received", async (event) => {
|
|
280
|
+
await handleMessageReceivedHook(event.content);
|
|
289
281
|
});
|
|
290
282
|
|
|
291
283
|
api.on("message_sent", () => {
|
|
@@ -610,7 +602,18 @@ const plugin = {
|
|
|
610
602
|
return { success: false, error: tagsError };
|
|
611
603
|
}
|
|
612
604
|
const result = await service?.join(avatarId, botName, bio, tags ?? []);
|
|
613
|
-
|
|
605
|
+
if (!result) {
|
|
606
|
+
return { success: false, error: "Kichi service is not initialized" };
|
|
607
|
+
}
|
|
608
|
+
if (result.success) {
|
|
609
|
+
return { success: true, authKey: result.authKey };
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
success: false,
|
|
613
|
+
error: result.error,
|
|
614
|
+
...(result.errorCode ? { errorCode: result.errorCode } : {}),
|
|
615
|
+
...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
|
|
616
|
+
};
|
|
614
617
|
},
|
|
615
618
|
});
|
|
616
619
|
|
|
@@ -639,7 +642,18 @@ const plugin = {
|
|
|
639
642
|
parameters: { type: "object", properties: {} },
|
|
640
643
|
execute: async () => {
|
|
641
644
|
const result = await service?.leave();
|
|
642
|
-
|
|
645
|
+
if (!result) {
|
|
646
|
+
return { success: false, error: "Kichi service is not initialized" };
|
|
647
|
+
}
|
|
648
|
+
if (result.success) {
|
|
649
|
+
return { success: true };
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
success: false,
|
|
653
|
+
error: result.error,
|
|
654
|
+
...(result.errorCode ? { errorCode: result.errorCode } : {}),
|
|
655
|
+
...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
|
|
656
|
+
};
|
|
643
657
|
},
|
|
644
658
|
});
|
|
645
659
|
|
package/package.json
CHANGED
package/src/service.ts
CHANGED
|
@@ -11,11 +11,13 @@ import type {
|
|
|
11
11
|
CreateMusicAlbumPayload,
|
|
12
12
|
HookNotifyPayload,
|
|
13
13
|
HookNotifyType,
|
|
14
|
+
JoinAckPayload,
|
|
14
15
|
JoinPayload,
|
|
15
16
|
KichiConnectionStatus,
|
|
16
17
|
CreateNotesBoardNotePayload,
|
|
17
18
|
KichiForwarderConfig,
|
|
18
19
|
KichiIdentity,
|
|
20
|
+
LeaveAckPayload,
|
|
19
21
|
PoseType,
|
|
20
22
|
QueryStatusPayload,
|
|
21
23
|
QueryStatusResultPayload,
|
|
@@ -26,12 +28,32 @@ const IDENTITY_DIR = path.join(os.homedir(), ".openclaw", "kichi-world");
|
|
|
26
28
|
const IDENTITY_PATH = path.join(IDENTITY_DIR, "identity.json");
|
|
27
29
|
const MAX_NOTEBOARD_TEXT_LENGTH = 200;
|
|
28
30
|
|
|
31
|
+
type AckFailureResult = {
|
|
32
|
+
success: false;
|
|
33
|
+
error: string;
|
|
34
|
+
errorCode?: string;
|
|
35
|
+
errorMessage?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type JoinResult =
|
|
39
|
+
| {
|
|
40
|
+
success: true;
|
|
41
|
+
authKey: string;
|
|
42
|
+
}
|
|
43
|
+
| AckFailureResult;
|
|
44
|
+
|
|
45
|
+
export type LeaveResult =
|
|
46
|
+
| {
|
|
47
|
+
success: true;
|
|
48
|
+
}
|
|
49
|
+
| AckFailureResult;
|
|
50
|
+
|
|
29
51
|
export class KichiForwarderService {
|
|
30
52
|
private ws: WebSocket | null = null;
|
|
31
53
|
private stopped = false;
|
|
32
54
|
private reconnectTimeout: NodeJS.Timeout | null = null;
|
|
33
55
|
private identity: KichiIdentity | null = null;
|
|
34
|
-
private joinResolve: ((
|
|
56
|
+
private joinResolve: ((result: JoinResult) => void) | null = null;
|
|
35
57
|
private pendingRequests = new Map<
|
|
36
58
|
string,
|
|
37
59
|
{
|
|
@@ -64,7 +86,7 @@ export class KichiForwarderService {
|
|
|
64
86
|
botName: string,
|
|
65
87
|
bio: string,
|
|
66
88
|
tags: string[],
|
|
67
|
-
): Promise<
|
|
89
|
+
): Promise<JoinResult> {
|
|
68
90
|
return new Promise((resolve) => {
|
|
69
91
|
this.identity = { avatarId };
|
|
70
92
|
this.joinResolve = resolve;
|
|
@@ -75,7 +97,12 @@ export class KichiForwarderService {
|
|
|
75
97
|
} else {
|
|
76
98
|
this.ws?.once("open", sendJoin);
|
|
77
99
|
}
|
|
78
|
-
setTimeout(() => {
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
if (this.joinResolve) {
|
|
102
|
+
this.joinResolve = null;
|
|
103
|
+
resolve({ success: false, error: "Timed out waiting for join_ack" });
|
|
104
|
+
}
|
|
105
|
+
}, 10000);
|
|
79
106
|
});
|
|
80
107
|
}
|
|
81
108
|
|
|
@@ -102,27 +129,64 @@ export class KichiForwarderService {
|
|
|
102
129
|
}
|
|
103
130
|
|
|
104
131
|
private handleMessage(data: string): void {
|
|
132
|
+
this.logger.debug(`[kichi ws recv] ${data}`);
|
|
105
133
|
try {
|
|
106
134
|
const msg = JSON.parse(data);
|
|
107
135
|
this.tryResolvePendingRequest(msg);
|
|
108
|
-
if (msg.type === "join_ack"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
if (msg.type === "join_ack") {
|
|
137
|
+
const joinAck = msg as JoinAckPayload;
|
|
138
|
+
if (joinAck.success === false || !joinAck.authKey) {
|
|
139
|
+
const failure = this.buildAckFailure(joinAck, "Join failed");
|
|
140
|
+
this.logger.warn(`Join failed: ${failure.error}`);
|
|
141
|
+
this.joinResolve?.(failure);
|
|
142
|
+
this.joinResolve = null;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (this.identity) {
|
|
147
|
+
this.identity.authKey = joinAck.authKey;
|
|
148
|
+
this.saveIdentity();
|
|
149
|
+
this.logger.info(`Joined as ${this.identity.avatarId}`);
|
|
150
|
+
}
|
|
151
|
+
this.joinResolve?.({ success: true, authKey: joinAck.authKey });
|
|
113
152
|
this.joinResolve = null;
|
|
114
153
|
} else if (msg.type === "rejoin_failed" || msg.type === "auth_error") {
|
|
115
154
|
// AuthKey invalid/expired, clear it
|
|
116
155
|
this.logger.warn(`Auth failed: ${msg.reason || "unknown"}`);
|
|
117
156
|
this.clearAuthKey();
|
|
118
157
|
} else if (msg.type === "leave_ack") {
|
|
119
|
-
|
|
158
|
+
const leaveAck = msg as LeaveAckPayload;
|
|
159
|
+
if (leaveAck.success === false) {
|
|
160
|
+
const failure = this.buildAckFailure(leaveAck, "Leave failed");
|
|
161
|
+
this.logger.warn(`Leave failed: ${failure.error}`);
|
|
162
|
+
} else {
|
|
163
|
+
this.logger.info("Left Kichi world");
|
|
164
|
+
}
|
|
120
165
|
}
|
|
121
166
|
} catch (e) {
|
|
122
167
|
this.logger.warn(`Failed to parse message: ${e}`);
|
|
123
168
|
}
|
|
124
169
|
}
|
|
125
170
|
|
|
171
|
+
private buildAckFailure(
|
|
172
|
+
msg: { errorCode?: unknown; errorMessage?: unknown },
|
|
173
|
+
fallbackError: string,
|
|
174
|
+
): AckFailureResult {
|
|
175
|
+
const errorCode =
|
|
176
|
+
typeof msg.errorCode === "string" && msg.errorCode.trim().length > 0 ? msg.errorCode : undefined;
|
|
177
|
+
const errorMessage =
|
|
178
|
+
typeof msg.errorMessage === "string" && msg.errorMessage.trim().length > 0
|
|
179
|
+
? msg.errorMessage
|
|
180
|
+
: undefined;
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
error: errorMessage ?? (errorCode ? `${fallbackError} (${errorCode})` : fallbackError),
|
|
185
|
+
errorCode,
|
|
186
|
+
errorMessage,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
126
190
|
private tryResolvePendingRequest(msg: { type?: unknown; requestId?: unknown }): void {
|
|
127
191
|
const requestId = typeof msg.requestId === "string" ? msg.requestId : "";
|
|
128
192
|
if (!requestId) {
|
|
@@ -448,16 +512,24 @@ export class KichiForwarderService {
|
|
|
448
512
|
return "closed";
|
|
449
513
|
}
|
|
450
514
|
|
|
451
|
-
async leave(): Promise<
|
|
452
|
-
if (!this.identity?.avatarId || !this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN)
|
|
515
|
+
async leave(): Promise<LeaveResult> {
|
|
516
|
+
if (!this.identity?.avatarId || !this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) {
|
|
517
|
+
return { success: false, error: "Failed or not connected" };
|
|
518
|
+
}
|
|
519
|
+
|
|
453
520
|
return new Promise((resolve) => {
|
|
454
521
|
const handler = (data: WebSocket.Data) => {
|
|
455
522
|
try {
|
|
456
523
|
const msg = JSON.parse(data.toString());
|
|
457
524
|
if (msg.type === "leave_ack") {
|
|
458
525
|
this.ws?.off("message", handler);
|
|
526
|
+
const leaveAck = msg as LeaveAckPayload;
|
|
527
|
+
if (leaveAck.success === false) {
|
|
528
|
+
resolve(this.buildAckFailure(leaveAck, "Leave failed"));
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
459
531
|
this.clearAuthKey();
|
|
460
|
-
resolve(true);
|
|
532
|
+
resolve({ success: true });
|
|
461
533
|
}
|
|
462
534
|
} catch (e) {
|
|
463
535
|
this.logger.warn(`Failed to parse leave response: ${e}`);
|
|
@@ -467,7 +539,10 @@ export class KichiForwarderService {
|
|
|
467
539
|
this.ws!.send(
|
|
468
540
|
JSON.stringify({ type: "leave", avatarId: this.identity!.avatarId, authKey: this.identity!.authKey }),
|
|
469
541
|
);
|
|
470
|
-
setTimeout(() => {
|
|
542
|
+
setTimeout(() => {
|
|
543
|
+
this.ws?.off("message", handler);
|
|
544
|
+
resolve({ success: false, error: "Timed out waiting for leave_ack" });
|
|
545
|
+
}, 10000);
|
|
471
546
|
});
|
|
472
547
|
}
|
|
473
548
|
}
|
package/src/types.ts
CHANGED
|
@@ -69,7 +69,17 @@ export type JoinPayload = {
|
|
|
69
69
|
|
|
70
70
|
export type JoinAckPayload = {
|
|
71
71
|
type: "join_ack";
|
|
72
|
-
authKey
|
|
72
|
+
authKey?: string;
|
|
73
|
+
success?: boolean;
|
|
74
|
+
errorCode?: string;
|
|
75
|
+
errorMessage?: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type LeaveAckPayload = {
|
|
79
|
+
type: "leave_ack";
|
|
80
|
+
success?: boolean;
|
|
81
|
+
errorCode?: string;
|
|
82
|
+
errorMessage?: string;
|
|
73
83
|
};
|
|
74
84
|
|
|
75
85
|
export type LeavePayload = {
|