@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 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
- service.sendHookNotify("message_received", pickRandomAction(MESSAGE_RECEIVED_BUBBLES));
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
- return result ? { success: true, authKey: result } : { success: false, error: "Failed" };
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
- return result ? { success: true } : { success: false, error: "Failed or not connected" };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yahaha-studio/kichi-forwarder",
3
- "version": "0.0.1-alpha.39",
3
+ "version": "0.0.1-alpha.41",
4
4
  "description": "Forward OpenClaw agent events to external WebSocket server for visualization",
5
5
  "type": "module",
6
6
  "main": "index.ts",
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: ((authKey: string) => void) | null = null;
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<string | null> {
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(() => { if (this.joinResolve) { this.joinResolve = null; resolve(null); } }, 10000);
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" && msg.authKey && this.identity) {
109
- this.identity.authKey = msg.authKey;
110
- this.saveIdentity();
111
- this.logger.info(`Joined as ${this.identity.avatarId}`);
112
- this.joinResolve?.(msg.authKey);
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
- this.logger.info("Left Kichi world");
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<boolean> {
452
- if (!this.identity?.avatarId || !this.identity?.authKey || this.ws?.readyState !== WebSocket.OPEN) return false;
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(() => { this.ws?.off("message", handler); resolve(false); }, 10000);
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: string;
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 = {